views:

138

answers:

1

I want to play simultaneous multiply audio sources in Silverlight. So I've created a prototype in Silverlight 4 that should play a two mp3 files containing the same ticks sound with an intervall 1 second. So these files must be sounded as one sound if they will be played together with any whole second offsets (0 and 1, 0 and 2, 1 and 1 seconds, etc.)

I my prototype I use two MediaElement (me and me2) objects.

DateTime startTime;

private void Play_Clicked(object sender, RoutedEventArgs e) {
  me.SetSource(new FileStream(file1), FileMode.Open)));
  me2.SetSource(new FileStream(file2), FileMode.Open)));
  var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(1) };
  timer.Tick += RefreshData;
  timer.Start();
}

First file should be played at 00:00 sec. and the second in 00:02 second.

void RefreshData(object sender, EventArgs e) {
    if(me.CurrentState != MediaElementState.Playing) {
      startTime = DateTime.Now;
      me.Play();
      return;
    }

    var elapsed = DateTime.Now - startTime;
    if(me2.CurrentState != MediaElementState.Playing && 
      elapsed >= TimeSpan.FromSeconds(2)) {
      me2.Play();
      ((DispatcherTimer)sender).Stop();
    }
  }

The tracks played every time different and not simultaneous as they should (as one sound).

Addition: I've tested a code from the Bobby's answer.

private void Play_Clicked(object sender, RoutedEventArgs e) { 
  me.SetSource(new FileStream(file1), FileMode.Open))); 
  me2.SetSource(new FileStream(file2), FileMode.Open)));
  // This code plays well enough.
  // me.Play(); 
  // me2.Play();

  // But adding the 2 second offset using the timer, 
  // they play no simultaneous.
  var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2) }; 
  timer.Tick += (source, arg) => { 
    me2.Play(); 
    ((DispatcherTimer)source).Stop(); 
  };
  timer.Start();
}

Is it possible to play them together using only one MediaElement or any implementation of MediaStreamSource that can play multiply sources?

Addition 2: Debug the playing positions

Adding the debug information shows definitively that the me plays different compare to the timer's ticks

...
me2.Play();
System.Diagnostics.Debug.WriteLine(
  me.Position.TotalMilliseconds + " -> " + me2.Position.TotalMilliseconds);
// 1820 -> 0 (but not 2000 -> 0)

Addition3: Experience with markers

I have experienced with the Time in the TimeLineMarker and following code works well enough on my pc

me.Markers.Clear();
me.Markers.Add(new TimelineMarker { Time = TimeSpan.FromMilliseconds(1892) });
me.MarkerReached += (source, args) => {
                            me2.Play();
                            me.Markers.Clear();
                          };
me.Play();
A: 

Since you're using FileStreams to load the files, I assume you're not reading them over the web, but rather from the local file system.

You don't want to call Play() from the timer tick handlers if it's not necessary - ie for the first ME.

Also, you're running the timer every millisecond, not second (not sure if that's actually what you meant to say). If you want to kick of the second play 2 seconds after the first, then try calling me.Play() then create the timer to run on a 2-sec interval, and all it does is call me2.Play() and stop itself.

Also, in line with the comment above, to see if the behavior is reasonably deterministic (sufficiently synchronized), try running just this a few times to see if, in principle, they can play together well enough for you.

private void Play_Clicked(object sender, RoutedEventArgs e) {
  me.SetSource(new FileStream(file1), FileMode.Open)));
  me2.SetSource(new FileStream(file2), FileMode.Open)));
  me.Play();
  me2.Play();
}

.. Edit:

Timers are not guaranteed to execute exactly when the time interval occurs, but they are guaranteed to not execute before the time interval occurs. This is because DispatcherTimer operations are placed on the Dispatcher queue like other operations. When the DispatcherTimer operation executes is dependent on the other jobs in the queue and their priorities. (msdn : DispatcherTimer)

It can be very difficult to achieve perfect synchronization in multi-threaded contexts.. The DispatcherTimer is going to place the call to the handler in the UI Dispatcher when the interval ticks, but that doesn't mean it will get immediately invoked. The only thing that might be worth a shot at this point is to adjust the DispatcherPriority (e.g. to 'Send' (the highest)).

Do so with this constructor:

public DispatcherTimer(
    DispatcherPriority priority
)
Bobby
Yes, after starting two ME immediately they play together (not always 100% synchronized) but well enough for me.But if I add a timer they play not simultaneous:var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2) }; timer.Tick += (source, arg) => { me2.Play(); ((DispatcherTimer)source).Stop(); }; timer.Start();Is it possible to play them together using only one MediaElement?
Shurup
Not without combining the audio data ahead of time, so you're saying with the timer, the second stream doesn't starting playing 2 seconds after the first?
Bobby
Yes, either the timer ticks no exactly correct or the me2.play can't start immediately and has some delay. Maybe it would be possible to control the potion of the first me and when it plays the 00:02 second, start to play me2?
Shurup
I don't think that's the way to go - in general, it's going to be very hard to get things to sync-up precisely without getting to much lower-level code. See edit above..
Bobby
DispatcherPriority is internal in Silverlight 4
Shurup