Go
Not registered? Sign up!

Two Players in parallel?

Google Translate
20 posts | return to the Setup Problems forum | get the rss feed for this thread

Nov. 05, 2008Mustafa

Hello,

I am preparing a page for a seminar. I need to place the video of the speaker and his presentation as well. Sure, I want two videos to go in parallel. Both videos are streamed from Wowza Streaming Server.

Is there a way of synchronizing two videos?

thanks,
Mustafa

Nov. 05, 2008andersen

for limited synchronicity accuracy you can just use the javascript API...

for example source code please see the - http://www.jeroenwijering.com/?item=Javascript_API_Examples
such as - http://home5.inet.tele.dk/nyboe/flash/mediaplayer4/JW_API_xmpl_5-1-0-0.html

please note that the above examples all use swfobject v.2.1 - http://code.google.com/p/swfobject/

for examples using the v.3.x player and swfobject v.1.5 please see - http://home5.inet.tele.dk/nyboe/flash/mediaplayer/
such as - http://home5.inet.tele.dk/nyboe/flash/mediaplayer/controller.htm
or - http://home5.inet.tele.dk/nyboe/flash/mediaplayer/panorama.htm

but if you need acuracy in the millisecond range, you will need to merge the two videos into one - and use only one player...

Nov. 20, 2008Rod Hull

I am trying to do something similar. I have two FLV files that have were captured together at the same time (so are the same length), so all I need is to be able to get them to be played/controlled simultaneously.

I've got it working autostarting and playing two videos at once, but at the moment, each has its own control bar.

I want to be able to control both videos (pause/fwd/rwd) from the same single controlbar, so sending a pause will pause both streams at once, and similarly seeking back/forward will send each video to the exact same point...

Is this possible?

Nov. 20, 2008andersen

@Rod Hull - in theory, yes - that would be a thing touse the Javascript API for - see previous posting

but i doubt you will find the acuracy sufficient, as there will be a slight delay from starting one player until javascript notices and can start the other player - but maybe if you control both player from javascript in parallel it will then be accurate enough

Nov. 20, 2008kLink

1) hide the control bar on the secondary player.

2) use the JavaScript API

      [{( Reference: http://code.jeroenwijering.com/trac/wiki/FlashAPI )}]

to monitor the position of both players; anytime the position does not match, seek the secondary player to the primary player's position (maybe + 100~200ms to make up for the delay in seeking).

Don't expect perfect synchronization; ±200ms would be excellent.

Nov. 21, 2008Rod Hull

Well, I'm not a coder or web-designer by trade! I'm a Linux sysadmin who has to unfortunately dabble in web stuff now and again...

I've knocked something relatively crude up here - this is about the extent of my (quite frankly pathetic) web-design knowledge, unfortunately!

What I'd love to happen is whenever anything is done in the second video, the same is done to the first - seeking, play/pause etc. Similar to what I have with my button, but exclusively using the second player's control bar instead, and disabling any kind of control from the first player (so that even clicking on it won't do anything).

I just don't have any idea where to start! I'm going to take a look now at the ID Callback Control stuff to see if I can make any sense of it - I presume this would be the way forward?


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>

<title>Seminar Series</title><style type="text/css">
#video1 { position:absolute; left:64px; top:50px; width:320; height:240; background-color:#000000; }
#video2 { position:absolute; left:384px; top:50px; width:640; height:480; background-color:#000000; }
#logo { position:absolute; left:64px; top:290px; width:320; height:240; background-color:#000000; background-image: url(webstream.jpg) }
#playpause { position:absolute; left:174px; top:500px; width:50; height:50; }
</style>
<script type="text/javascript" src="swfobject.js"></script>
<script type="text/javascript">

function createPlayer(thePlaceholder, thePlayerId, theFile, theAutostart) {
var flashvars = {
file:theFile,
autostart:theAutostart,
controlbar:"none"
}
var params = {
allowfullscreen:"true",
allowscriptaccess:"always"
}
var attributes = {
id:thePlayerId,
name:thePlayerId
}
swfobject.embedSWF("player.swf", thePlaceholder, "320", "240", "9.0.115", false, flashvars, params, attributes);
}

function createPlayer2(thePlaceholder, thePlayerId, theFile, theAutostart) {
var flashvars = {
file:theFile,
autostart:theAutostart,
playlistsize:"100",
controlbar:"over"
}
var params = {
allowfullscreen:"true",
allowscriptaccess:"always"
}
var attributes = {
id:thePlayerId,
name:thePlayerId
}
swfobject.embedSWF("player.swf", thePlaceholder, "640", "480", "9.0.115", false, flashvars, params, attributes);
}


function init() {
createPlayer("placeholder1", "player1", "test_people.flv", false);
createPlayer2("placeholder2", "player2", "test_content.flv", false);
}
</script>
</head>

<body onload="init();">

<div id="video1">
<div id="placeholder1"></div>
</div>

<div id="video2">
<div id="placeholder2"></div>

</div>

<div id="logo">
<div id="placeholder3"></div>
</div>

<div id="playpause">
<input type="button" id="buttonId1" value="Play/Pause" onClick="player1.sendEvent('PLAY'),player2.sendEvent('PLAY')">
</div>

</body>
</html>

Nov. 21, 2008kLink

This should get you 99% of the way there. If the player1 is a bit jerky because it's getting corrective seeking too often, adjust the "fudge factor" a bit (see the code). I used the v4.2.90 player for testing.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

  <head>

    <title>Seminar Series</title>

    <style type="text/css">
      #playercontainer1
      {
        position:               absolute;
        left:                       64px;
        top:                       100px;
        width:                     320px;
        height:                    240px;
        background-color:        #000000;
        }

      #playercontainer2
      {
        position:               absolute;
        left:                      384px;
        top:                       100px;
        width:                     640px;
        height:                    540px;
        background-color:        #000000;
      }

      #logo
      {
        position:               absolute;
        left:                       64px;
        top:                       290px;
        width:                     320px;
        height:                    240px;
        background-color:        #000000;
        background-image:        url(webstream.jpg)
        };

      #playpause
      {
        position:               absolute;
        left:                      174px;
        top:                       550px;
        width:                      50px;
        height:                     50px;
      }
    </style>

    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/swfobject/2.1/swfobject.js"></script>

    <script type="text/javascript">
      var player         =  null;
      var currentTime1   =    -1;
      var currentTime2   =    -1;
      var currentState2  =  null;
      var previousState2 =  null;


      function playerReady(obj)
      {
        if(obj.id == 'playerId1')
        {
          player1 = gid(obj.id);
          player1.addModelListener('TIME',   'timeMonitor1');
        }

        if(obj.id == 'playerId2')
        {
          player2 = gid(obj.id);
          player2.addModelListener('TIME',   'timeMonitor2');
          player2.addModelListener('STATE',  'stateMonitor2');
        }
      };


      function timeMonitor1(obj)
      {
        currentTime1 = obj.position;
        if((currentTime1 < (currentTime2 - .2)) || (currentTime1 > (currentTime2 + .2)))
        {
          player1.sendEvent('SEEK', (currentTime2 - .1)); // fudge factor - adjust to prevent jerkiness in player1
        }
//gid('time').innerHTML = 'Time 1: ' + currentTime1 + '&nbsp;&nbsp;Time 2: ' + currentTime2;
      };


      function timeMonitor2(obj)
      {
        currentTime2 = obj.position;
      };


      function stateMonitor2(obj)
      {
        currentState2 = obj.newstate;
        if(currentState2 != previousState2)
        {
          switch(currentState2)
          {
            case 'PAUSED':
              player1.sendEvent('PLAY', 'false');
              break;    
            case 'PLAYING':
              player1.sendEvent('PLAY', 'true');
              break;
          };
        }
        previousState2 = currentState2;
//gid('state').innerHTML = 'State 2: ' + currentState2;
      };


      function gid(name)
      {
        return document.getElementById(name);
      };
    </script>

    <script type="text/javascript">
      var flashvars =
      {
        file:                 'test_people.flv',
        icons:                'false',
        displayclick:         'none',
        autostart:            'false',
        controlbar:           'none'
      };

      var params =
      {
        allowfullscreen:      'true',
        allowscriptaccess:    'always'
      };

      var attributes =
      {
        id:                   'playerId1',
        name:                 'playerId1'
      };

      swfobject.embedSWF('player.swf', 'player1', '320', '240', '9.0.124', false, flashvars, params, attributes);

      flashvars.file          = 'test_content.flv';
      flashvars.title         = 'Test Content';
      flashvars.description   = 'This is the controlling player.';
      flashvars.playlist      = 'bottom';
      flashvars.playlistsize  = '60';
      flashvars.controlbar    = 'over';
      attributes.id           = 'playerId2';
      attributes.name         = 'playerId2';

      swfobject.embedSWF('player.swf', 'player2', '640', '540', '9.0.124', false, flashvars, params, attributes);
    </script>

  </head>

  <body>

    <div id="playercontainer1" class="playercontainer1">
      <a id="player1" class="player" href="http://www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash">Get the Adobe Flash Player to see this video.</a>
    </div>
    <div id="playercontainer2" class="playercontainer2">
      <a id="player2" class="player" href="http://www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash">Get the Adobe Flash Player to see this video.</a>
    </div>
    <div id="logo">
      <div id="placeholder3">
      </div>
    </div>
    <div id="playpause">
      <button onclick="player1.sendEvent('PLAY');player2.sendEvent('PLAY')">Toggle PLAY/PAUSE</button>
    </div>
    <div id="time">
      Time:
    </div>
    <div id="state">
      State:
    <div>

  </body>

</html>

Nov. 24, 2008Rod Hull

Thanks a lot for the help, kLink.

Unfortunately the first player never starts (the toggle button does nothing and also there is no time/state data displayed. :(

It will start if you change the autostart var to true (obviously!), but there's still no time/state data being shown, and the controlling player doesn't seem to be controlling the first as well.

I'm using the latest version of the player (4.2.95) and swfobject 2.1 - the player I am using is obviously a slightly different revision to yours...not sure that would make that much difference?

Nov. 24, 2008kLink

@Rod,

Post a link to your test page and I'll have a look.

The JavaScript API only works online, so you have to be testing this online.

Nov. 24, 2008Rod Hull

Here's a copy of the page:
http://members.lycos.co.uk/rodhull/test.html

The video I'm using isn't the actual one I'll eventually use - this is just a test file I found on the web to try out...I can't put the real seminar content on here for copyright reasons...

Nov. 24, 2008kLink

@Rod,

http://members.lycos.co.uk/rodhull/test.html  returns:  "404 File not Found"

Nov. 24, 2008Rod Hull

! - Strange

It's definitely there - it's working for me...

Nov. 24, 2008kLink

@Rod,

Maybe I missed a character when I copied & pasted the URI.

Anyway, it works perfectly for me in IE6 & FF2.

The Time/State will show if you uncomment the two lines that begin "//gid...".

It's easier to see the time if you replace://gid('time').innerHTML = 'Time 1: ' + currentTime1 + '&nbsp;&nbsp;Time 2: ' + currentTime2;with:gid('time1').innerHTML = 'Time1: ' + currentTime1;
gid('time2').innerHTML = 'Time2: ' + currentTime2;
and replace: <div id="time">
Time:
</div>
with: <div id="time1">
Time1:
</div>
<div id="time2">
Time2:
</div>
so the itmes are stacked vertically.

The video in the small player is a bit jerky, so you will have to tweak the numbers here: if((currentTime1 < (currentTime2 - .2)) || (currentTime1 > (currentTime2 + .2)))
{
player1.sendEvent('SEEK', (currentTime2 - .1)); // fudge factor - adjust to prevent jerkiness in player1
}

The first line compares the position of the two players. If the small player's position is different by more than ±200ms compared to the large player, then the small player is scrubbed to match the large player's position minus 100ms. Depending on how your video is streamed and how it plays, you may need to increase the band to 300ms and adjust the position by a different factor, like:player1.sendEvent('SEEK', (currentTime2 - .2));or even:player1.sendEvent('SEEK', (currentTime2 + .1));

Nov. 24, 2008Rod Hull

Interesting - it must be a rendering/javascript quirk of FF3 which is why it doesn't work for me.

Here's what I see in FF3 running on Ubuntu 8.10:
http://img219.imageshack.us/img219/8766/screenflashtestba1.jpg

Opera 9 does the same though :(

It only ever plays the larger stream...the smaller one won't play, and the toggle button does nothing...strange...

It does work in IE under Wine for me and also in Safari 3.2 on OS X, but I need it to be made available to FF3 users ideally...a large proportion of people looking at this will be using Linux so won't be using IE or Safari...any ideas why it might be behaving so strangely under FF3?

EDIT:
Just checked and it works on FF3 under Windows and OS X - so it could well be either FF3 under Linux, or the Flash plugin version...frustrating!

EDIT2:
Under Ubuntu, FF3's Error Console declares upon pressing the toggle button that:

Error: player1 is not defined
Source File: http://members.lycos.co.uk/rodhull/test.html
Line: 1

Nov. 24, 2008kLink

There were some problems with getting the player name/id in Linux. Jeroen recently solved them.

See: http://www.jeroenwijering.com/?thread=14021

Try upgrading to v4.3.118 from here: http://code.jeroenwijering.com/trac/browser/trunk/as3/player.swf

Nov. 24, 2008Rod Hull

Nah - same thing :( It only works on Windows and Mac...

The error console still says that player1 is not defined...

Nov. 24, 2008kLink

@Rod,

Well, we can try explicitly getting the player's reference objects.

Replace this:
      function playerReady(obj)
      {
        if(obj.id == 'playerId1')
        {
          player1 = gid(obj.id);
          player1.addModelListener('TIME',   'timeMonitor1');
//alert('Player Ready(): ' + player1.id);
        }

        if(obj.id == 'playerId2')
        {
          player2 = gid(obj.id);
          player2.addModelListener('TIME',   'timeMonitor2');
          player2.addModelListener('STATE',  'stateMonitor2');
//alert('Player Ready(): ' + player2.id);
        }
      };

with this:
      function playerReady(obj)
      {
        setTimeout("getPlayers()", 250);
      };


      function  getPlayers()
      {
        player1 = gid('playerId1');
        player1.addModelListener('TIME',   'timeMonitor1');
alert('Player Ready(): ' + player1.id);

        player2 = gid('playerId2');
        player2.addModelListener('TIME',   'timeMonitor2');
        player2.addModelListener('STATE',  'stateMonitor2');
alert('Player Ready(): ' + player2.id);
      };


You should get four alerts. Two with "playerId1" and two with "playerId2".

If that works, comment out or delete the alerts.

Good Luck!

Nov. 25, 2008Rod Hull

Many thanks kLink for all your help.

The explicit function works perfectly.

I think I've got it at an acceptable level of sync. I ended up changing the check on ±200ms to 1 whole second instead which seemed to improve the smoothness a lot. This amount of sync is acceptable. When it goes live, each video is unique, and only the smaller one will have sound. The larger one is just a capture of a Powerpoint presentation..

I also had to put in another couple of control states for COMPLETED and IDLE in there to tell the small player to stop when the large one finishes (or the stop button is pressed) since without it on these occasions it was just repeatedly looping 100ms of the video on completion.

Nov. 25, 2008kLink

Looking good!

It's been my experience that it's really hard to keep two streams synchronized, but I'm a bit surprised that you had to go to a full second. Especially while the files are downloading, the CPU is quite busy downloading and displaying so the sync gets out of whack often. And, Flash doesn't have a fixed framerate, so that varies also.

Also nice to see that you were able to add the additional control states. I try to make the code simple and modular so it is easily understandable and you've confirmed that.

You're Welcome. Good Luck!

                                                                                     grin

Aug. 06, 2009desperate

Good stuff. I've been looking to do something like this for an upcoming event, with the exception that the format we are working with is in .wmv format. Any ideas?

Add a reaction

You can also return to the category or try this search for related threads.


 

Search the Forums

Go

Support

Support Here are some helpful links to learn more about the JW Player™:

Monetize Your Video

Monetize Your Video Earn money with ads from LongTail's AdSolution. Watch our demos and sign up now!

Why Buy a License?

Why Buy a License? If you don’t buy a commercial license, you cannot use a JW Player™ on (i) a site that has ads; (ii) a corporate site; or a (iii) CMS. Our licenses are very inexpensive, so what are you waiting for? Buy a license today.