Powered by MSDN

US - English
NEW! Silverlight 5 is available Learn More

Looping MP3 or WMA without a gap RSS

7 replies

Last post Dec 03, 2008 08:13 PM by djprod

(0)
  • joshcomley

    joshcomley

    Member

    54 Points

    46 Posts

    Looping MP3 or WMA without a gap

    Aug 12, 2007 10:07 PM | LINK

    I want background music with a beat to back my game. It's only a short loop, different on each level.. but when it loops there is an evident gap. The solution I am using to create looping is the same one that a forum search returned, i.e. attaching a MediaEnded event, setting the start pos and playing again. If anyone has any pointers for gapless looping in Silverlight that would be much appreciated!

    Cheers!
    Josh

    mp3 wma looping silverlight media gap gapless

  • joshcomley

    joshcomley

    Member

    54 Points

    46 Posts

    Re: Looping MP3 or WMA without a gap

    Aug 12, 2007 10:10 PM | LINK

    Interestingly, I thought I'd try using WAV files to eliminate the gap, but then I get an AG_E_INVALID_FILE_FORMAT error! Silverlight supports wave files, so not sure what that is about...

  • joshcomley

    joshcomley

    Member

    54 Points

    46 Posts

    Re: Looping MP3 or WMA without a gap

    Aug 13, 2007 02:45 AM | LINK

    After trying lots of threading techniques and them some digging around it appears Silverlight does not yet have it's BackgroundWorker class ready, which makes it very difficult to initiate some UI changes from a separate thread (all UI changed must be done in the same thread that the UI was created in). I wanted to loop two sounds, without a gap between them. Achievable with just one loop, but two loops of differing lengths and you run out of HtmlTimer's scope (unless I'm missing something). So a thread kicks off, and a super quick HtmlTimer (i.e. set to 1) keeps checking for any updates to the _queue, and then performs them if necessary. Also, you may wonder why I create new MediaElements as opposed to restarting the old one's. It just performed a lot better this way round - which leads to garbage coleciton; I have done some in that I am removing the old MediaElements when they're done from the Children collection. This got rid of 98% of garbage building up on each loop, but there is still a small memory leak in this code, probably because the MediaElements are not destroyed from memory.

     

    A horrible, nasty, uncommented and unrefactored (it's too late for that sort of thing!) semi-solution to my problem (that doesn't get the two samples in perfect harmony, but darn close.. roll on BackgroundWorker):

     

          public partial class Page : Canvas
         
    {
                private Dictionary<MediaElement, int> _mediaElementPositions = new Dictionary<MediaElement, int>();
                private List<MediaElement> _loopedAudios = new List<MediaElement>();
                private List<int> _queue = new List<int>(); 

               
    public void Page_Loaded(object o, EventArgs e)
                {
                      // Required to initialize variables

                      InitializeComponent();

                      PlayLoopedSound("Loop1.wma", 0.5, 1);
                      PlayLoopedSound("Loop2.wma", 0.5, -1);
                }

                void PlayLoopedSound(string source, double volume, double balance)
                {
                      MediaElement me = new MediaElement();
                      _loopedAudios.Add(me);
                      me.Source = new Uri(source, UriKind.Relative);
                      me.Volume = volume;
                      me.Balance = balance;
                      me.MediaOpened += new EventHandler(SetToLoop);
                      Children.Add(me);
                      HtmlTimer timer = new HtmlTimer();
                      timer.Interval = 1;
                      timer.Tick += new EventHandler(timer_Tick);
                      timer.Enabled = true;
                      timer.Start();
                }

     
                void timer_Tick(object sender, EventArgs e)
                {
                      if (_queue.Count > 0)
                      {
                            CloneAndAddAudio(_loopedAudios[_queue[0]]);
                            _queue.RemoveAt(0);
                      }
                }

     
                void SetToLoop(object sender, EventArgs e)
                {
                    MediaElement me = sender as MediaElement;
                    // The 50 milliseconds removes the gap between loops
                    AudioLooper al = new AudioLooper((int)me.NaturalDuration.TimeSpan.TotalMilliseconds - 50, _loopedAudios.IndexOf(me));
                    al.AudioLooped += new AudioLoopedHandler(al_AudioLooped);
                    ThreadStart ts = new ThreadStart(al.Play);
                    Thread t = new Thread(ts);
                    t.Start();
                }

                void al_AudioLooped(object sender, AudioLoopedEventArgs e)
                {
                      _queue.Add(e.LoopedAudioIndex);
               

               
    private void CloneAndAddAudio(MediaElement me)
                {
                      MediaElement meNew = new MediaElement();
                      meNew.Source = me.Source;
                      meNew.Volume = me.Volume;
                      meNew.Balance = me.Balance;
                      _loopedAudios[_loopedAudios.IndexOf(me)] = meNew;
                      Children.Remove(me);
                      Children.Add(meNew);
                }
         

         
    public delegate void AudioLoopedHandler(object sender, AudioLoopedEventArgs e);

          public class AudioLoopedEventArgs : EventArgs
         
    {
                private int _loopedAudioIndex; 

               
    public int LoopedAudioIndex
                {
                      get { return _loopedAudioIndex; }
                      set { _loopedAudioIndex = value; }
                }
          }

          class AudioLooper
         
    {
                public event AudioLoopedHandler AudioLooped;
                private int _loopLength;
                private int _loopedAudioIndex; 

               
    public int LoopedAudioIndex
                {
                      get { return _loopedAudioIndex; }
                      set { _loopedAudioIndex = value; }
               

               
    public int LoopLength
                {
                      get { return _loopLength; }
                }

                public AudioLooper(int loopLength, int loopedAudioIndex)
                {
                      _loopedAudioIndex = loopedAudioIndex;
                      _loopLength = loopLength;
               

                public void Play()
                {
                      while (true)
                      {
                            Thread.Sleep(LoopLength);
                            AudioLoopedEventArgs e = new AudioLoopedEventArgs();
                            e.LoopedAudioIndex = LoopedAudioIndex;
                            OnAudioLooped(e);
                      }
               

                protected virtual void OnAudioLooped(AudioLoopedEventArgs e)
                {
                      AudioLooped(this, e);
                }
          }

  • Billy Porter

    Billy Porter

    Member

    46 Points

    23 Posts

    Re: Looping MP3 or WMA without a gap

    Dec 28, 2007 01:54 AM | LINK

    This can be achieved by adding Markers to your MediaElement. Add a marker on the time where you want to loop the file. When the marker is reached, set the position of the MediaElement to 0.

    Something like this:

    void mdeBackgroundMusic_MediaOpened(object sender, EventArgs e)
    {
    TimelineMarker marker = new TimelineMarker();
    marker.Time =
    TimeSpan.FromMilliseconds(TIME_OF_MP3_IN_MILLISECONDS);
    marker.Text =
    "foo"; // Dummy value requred
    marker.Type = "yada"; // Dummy value requred

    mdeBackgroundMusic.Markers.Add(marker);
    mdeBackgroundMusic.MarkerReached +=
    new TimelineMarkerEventHandler(mdeBackgroundMusic_MarkerReached);
    }

    void mdeBackgroundMusic_MarkerReached(object sender, TimelineMarkerEventArgs e)
    {
    mdeBackgroundMusic.Position =
    TimeSpan.FromMilliseconds(0);
    }

    HTH,
    Billy

  • GearWorld

    GearWorld

    Participant

    1661 Points

    2024 Posts

    Re: Looping MP3 or WMA without a gap

    Jan 18, 2008 11:12 PM | LINK

    Hi,

     Then is there any reason why my SilverLight app doesn't play wav file.  Its a valid wav file that Media Player 11 plays well.

    The same sound plays as .mp3 but .wav NOPE !

  • DDtMM

    DDtMM

    Member

    90 Points

    103 Posts

    Re: Looping MP3 or WMA without a gap

    Jun 03, 2008 06:25 PM | LINK

    Has anybody got Billy Porter's marker method to work without a gap?  I haven't.  If I use the MediaElement's duration as TIME_OF_MP3 then the MarkerVentHandler is never called.  I figured I'd subtract some milliseconds, which works, but I still get a gap.

    DDtMM
  • DDtMM

    DDtMM

    Member

    90 Points

    103 Posts

    Re: Looping MP3 or WMA without a gap

    Dec 01, 2008 08:10 PM | LINK

    One thing you can not do when looping sounds is use Natural Duration.  It is way off.  You have to figure out the duration with another program.  I used Audacity and zoomed in as far as possible and get a passable result.

    ... even then you can't rely on getting an accurate result because you don't know when exactly the thread that is responsible is going to go, and the point you wanted may have passed.

     So truely accurate looping is impossible.

    DDtMM
  • djprod

    djprod

    Member

    348 Points

    67 Posts

    Re: Looping MP3 or WMA without a gap

    Dec 03, 2008 08:13 PM | LINK

    As we all know by now, wav files aren't supported yet. The problem is that mp3 files add silence to the start of a file, and wma add silence to the end of a file, coupled with the fact that the time it takes for an audio file to restart or reposition is not constant. With this in mind it seems that when trying to do seamless looping with the SL Media Player, results will always be at best erratic. - My workaround was to us the "bgsound" HTML object, which DOES support wav files and looping.

    So with your WAV file at the ready, add the following line to your html Page:-

    <bgsound id="oBGSound" loop="-1" >

    the following xaml page gives you a button to start the loop, and a slider to control the volume :-

    THE XAML MARKUP-

    <UserControl x:Class="SilverlightApplication24.Page"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Canvas x:Name="LayoutRoot" Background="White">

    <Button Height="25" Width="70" Content="Play Loop" Click="playme"></Button>

    <TextBlock x:Name="volcount" Text="100" Canvas.Left="190" Canvas.Top="4" FontSize="12"></TextBlock>

    <Slider x:Name="volslid" Value="0" ValueChanged="setvol" Canvas.Top="4" Canvas.Left="80" Width="100" Minimum="-7000" Maximum="0"></Slider>

    </Canvas>

    </UserControl>

    END XAML MARKUP

    the page.xaml.cs gives you the functions:-

    page.xaml.cs MARKUP:-

    using System;

    using System.Windows.Browser;

    using System.Windows.Controls;

    namespace SilverlightApplication24

    {

    public partial class Page : UserControl

    {

    public Page()

    {

    InitializeComponent();

    }

    private void playme(object sender, EventArgs e)

    {

    HtmlPage.Document.GetElementById("oBGSound").SetProperty("src", "raw1.wav");

    }

    private void setvol(object sender, EventArgs e)

    {

    HtmlPage.Document.GetElementById("oBGSound").SetProperty("volume", volslid.Value);volcount.Text = "" + Math.Round((volslid.Value/70)+(100*1)/1);

    }

     

    }

    }

    More info on the bgsound object here :- http://msdn.microsoft.com/en-gb/library/ms535198(VS.85).aspx

    Hope this helps

    Regards, Marc.