How Do I Insert 'Now Playing' Info to my Posts on P2P?

Over the past few weeks, I received numerous e-mails from P2P contributors asking me how I was able to insert information in my signature at the Wrox P2P forum about the music I am listening to when I make the post. Some wondered if I had nothing to do at all, and had all the time in the world to insert this information manually. Others already had the idea I had automated this process and were interested in the technical solution of this, rather useless, feature.

In this article I'll explain where I got the idea for this automated signature, what techniques and concepts were involved, and where I found the information required to build the solution.

Introduction

For those of you who have no idea what this is all about, take a look at one on my posts at P2P, for example: http://p2p.wrox.com/topic.asp?TOPIC_ID=12455. At the bottom of my post, you'll see my signature, like this:

The signature of my post on P2P with the track info embedded

As you can see, I have added some information about the track I was listening to when I wrote the post, in this case Lonely World by Limp Bizkit. I can tell you, it's not a cheat. I was really listening to that track when I submitted the post to P2P. I also did not add the information manually as that would take way too much time. So how did I do it? It's quite a long story if you want to understand the technical nitty gritty but I'll explain all of it in the remainder of this article. I won't discuss the full code for the solution, but I'll focus on the important bits instead. At the end of this article, you can download the full source code for the applications that are described in this article.

It Started With The Idea

Some time ago Peter and I were discussing some possible uses (and misuses) of Web Services. We dreamt up a lot of useful and useless services. In the tradition of true Chindogu, the art of "Unuseless Inventions", we decided to create something both useful and useless. After some brainstorming, mental mapping and what more, we decided it would be cool and useless if there was a way to find out what track we were listening to in WinAmp. It's useless because it doesn't really add something to the quality of life. It's useful, because it would be a nice learning experience to get it working.

After this initial brainstorm, the idea started to grow. I tried to make an initial list with requirements, and decided that I needed:

  • an application that can read the information from WinAmp and make it available for other applications;
  • an application that would expose the track info through a public interface;
  • a way to automatically inject the track info at the end of every post I made at P2P.

I started with a search on Google to find out whether there was a API for WinAmp available that could be used from within a .NET application. Unfortunately, that wasn't the case. However, I soon ran into some other code that allowed me to look at the WinAmp Window and read its titlebar. From that point on, things were relatively easy. But, I am getting ahead of myself. Let's first take a look at the conceptual overview of this mission critical enterprise application:

Diagram with the conceptual overview of the application

I have numbered the important processes and concepts from 1 through 7 in this diagram. I'll briefly describe each step here and then go into them in more detail in the remainder of this article:

  1. When WinAmp is running, it displays the full file location of the track (and other data) in its Windows titlebar. This titlebar can be read by another application.
  2. A Windows Service runs on the same computer as WinAmp. This service continuously tries to poll the WinAmp window to see if it is running. If it is running, it reads the WinAmp titlebar and retrieves the full filename from it. With this filename, the Windows Service creates a MP3File object that is used to retrieve the ID3 information that is stored in the MP3 file. This information is then saved in a text document (3).
  3. The text document contains the full name of the MP3 track as it is displayed in WinAmp, the name of the track and the artist, the album name the track comes from and the number of the track on the album. A typical file looks like this:
    			C:\CDs\Massive Attack - 100th Window\03 - Everywhen.mp3
    AlbumName: 100th Window
    Artist Name: Massive Attack
    SongTitle: Everywhen
    Track Number: 3
  4. At the same time at the other side of the world (or, in fact, in the same office, but on a different machine) I am using Internet Explorer to post a message to the Wrox forum. I have customized my Internet Explorer so it has an additional menu item in the context menu you get when you right-click a text area. This menu item triggers a JavaScript WebService behavior. This behavior accesses my public Web Service, http://Imar.Spaanjaars.Com/WebServices/PublicContent.asmx, and calls one of its methods, NowPlayingGetStringP2P. This method returns a formatted string compatible with the P2P forums (that is, the [font] element is used and <strong> tags are replaced with [b] and [/b].
  5. To be able to serve the track info to the client, the Web Service looks at the Cache first (6). If the info exists in the cache (as a simple string) that information is returned. If the data is not present in the cache, it's read from the text file, and saved in the cache as a string. In both cases, the method returns the string with the track info to the client.
  6. The cache holds a copy of the data about the track that is currently playing in WinAmp. It can be deleted by the .NET run-time when it's cleaning up old stuff, or it can be explicitly invalidated when the text file is overwritten with a new version. When the cached object is destroyed, it will be recreated the next time the Windows Service is hit and reads the track info from the text file.
  7. The arrow for number 7 represents a CacheDepency between the text file with the track info and the cached information at the server. As soon as the text file is changed by the Windows Service (1) the object is removed from the cache, the Web Service (5) will read in the new info from the text file and again save it in the cache so it can be reused for subsequent requests to the service.

These seven items can be grouped together in three separate applications: a Windows Service that reads the track info, a Web Service that exposes the track information to the outside world and a JavaScript behavior that can access the Web Service form within client side code and append the track info to each post made at P2P. I will discuss each of these three applications in more detail. You'll find the full code for these applications at the end of this article.

The Windows Service

At first I didn't want to use a Windows Service at all. Instead, I wanted the Web Service to read the track information from the WinAmp window directly. However, I soon found out that ASP.NET and my WinAmp window would be running into two different "workspaces" or desktops. Effectively this means that code running within an ASP.NET application cannot read or see Window applications running on the server's desktop.

I decided to use a Windows Service instead. The advantage of a Windows Service is that it can be configured to start automatically when Windows starts, so the information the service provides is always available, without additional steps of starting the application manually. It's also rather easy to create a polling mechanism that checks the WinAmp window every now and then. All you need is a simple Timer object that fires at specific intervals. The final (and most important) benefit of a Window Service is that it can run in the same desktop as WinAmp, so the service can retrieve information from its titlebar.

The decision for a Windows Service was easily made. Fortunately, creating a Windows Service isn't that hard with Visual Studio .NET 2003. If you want to create your own Windows Service, I suggest the following articles:

The second article also discusses a way to install the service with a Windows Installer package. However, as soon as you have added the Installer class with the ServiceProcessInstaller and ServiceInstaller objects (explained in the articles), you can simply register your service with the following command line:

	InstallUtil.exe YourServiceName.exe

InstallUtil.exe is located in your Windows\Microsoft.NET\Framework\VersionNumber folder, so make sure you have that folder in your path when you execute the command from the command-line in the folder where your service (YourServiceName.exe) resides.

If you have set the StartType of the ServiceInstaller object to Automatic, the service will start as soon as Window starts. However, the first time after you have installed the service, you'll need to start it manually from the Services panel which you'll find under the Administrative Tools. The StartType setting will only determine what happens to your server when Window starts, but doesn't start the service the first time after it is installed.

With the skeleton for the service ready, it was time to make it do something useful. A common practice with a Windows Service is to add a Timer that fires at a specified interval. This allows you to perform specific actions regularly. A Timer is recommended over code that loops forever, checking the time and calling Sleep methods, as that can severely damage the performance of your system.

Besides the timer, I made a short list of other requirements I wanted the application to have:

  • The Timer interval should be configurable through an application configuration file.
  • If WinAmp is not running, the Timer interval should be multiplied by 10, to safe CPU cycles.
  • When a track is playing in WinAmp, the track info should be written to a text file.
  • While the same track is playing in WinAmp, the text file shouldn't be written again.

You won't find all the code for these requirements in great detail in this article, but the code downloads (at the end of this article) will show you how I implemented all these requirements.

The next step was actually getting the track info from WinAmp. After Googling a bit, I ran into this article: Retrieve the Winamp song title with .NET. I used the code from that article almost unmodified. The most important thing I did change was the length of the lpText variable from 100 to 400, so I could retrieve longer Windows titles.

The next step was to write some code for the Tick event for the timer. Whenever the timer fires, it should look at the WinAmp title, extract the file info and write it to the text file. I added the following code to the timer1_Tick event:

	private void timer1_Tick(object sender, System.Timers.ElapsedEventArgs e)
{
  try
  {
    // stop the timer, to avoid getting it called again while processing
    this.timer1.Stop();
    string newTrackName = GetSongTitle();
    if (newTrackName == "Error: Not running")
    {
      // Lower the polling frequency. Apparently, WinAmp is not running
      timer1.Interval = timerInterval * 10;
    }	
    else
    {
      // reset timer interval
      timer1.Interval = timerInterval;
    }

    if (newTrackName != currentTrackName)
    {
      // There has been a change in state, so write to the file");
      // Save full name of track in currentTrackName
      currentTrackName = newTrackName;
      string result = "";

      // Error or track name??
      if (newTrackName.Substring(0, 6).ToUpper() != "ERROR:")
      {
        try
        {
          if (File.Exists(newTrackName))
          {
            MP3File mp3File = ShellID3TagReader.ReadID3Tags(newTrackName);
            result = newTrackName;
            result += "\r\nAlbumName: " + mp3File.AlbumName;
            result += "\r\nArtist Name: " + mp3File.ArtistName;
            result += "\r\nSongTitle: " + mp3File.SongTitle;
            result += "\r\nTrack Number: " + mp3File.TrackNumber;
          }
          else
          {
            result = "ERROR: File does not exist: " + fullFileName;
          }
        }
        catch (Exception ex)
        {
          result = "ERROR: " + ex.ToString();
        }
      }
      else
      {
        // store the error returned from GetSongTitle
        // in the text file
        result = newTrackName;
      }
      WriteFile(result);
    }
  }
  catch{}
  finally
  {
    this.timer1.Start();
  }
}

Most of this code is pretty self explanatory. Basically, it gets the WinAmp title which holds the full path and name of the file that is currently playing. This filename is then used to retrieve the ID3 information from the MP3 file. I used another CodeProject article for the code that retrieves the ID3 information. With the class MP3File described in that article, it's easy to retrieve information about the track, like Artist, Track and Album name.
At the end of timer1_Tick, the detailed information about the track is written to the text file with the WriteFile method. This method deletes the WinAmp.txt file if necessary, and writes the contents of the track info string that is passed to it to the file.

For this code to work, I had to make a configuration change to WinAmp. On the Titles tab of the General Preferences section of the WinAmp Preferences dialog, I set Advanced title display format to %filename%. This way, only the full name and location of the file is displayed in the Windows titlebar for WinAmp.

With the MP3File class and the WriteFile method implemented, the Windows Service was ready. The final step was to compile the application into an exe and install it with the following command:

	InstallUtil.exe MyWinAmpService.exe

Because the service needs to interact with the desktop, I had to change the configuration for the service. On the Properties dialog for the service in the Service Manager there is a Log On tab. On that tab, there is a Allow service to interact with the desktop setting which needs to be enabled.
It's also possible to change this setting programmatically in the ProjectInstaller_AfterInstall method of the ProjectInstaller that is called when the service is installed.For more information about this, take a look at the article Install a Windows service the way YOU want to!

If you need to uninstall the service because you have made changes to the code and want to install it again, use the following command:

	InstallUtil.exe /U MyWinAmpService.exe

This removes the service from the services list, so you can recompile it in Visual Studio .NET.
If you install and uninstall the service a couple of times in succession it's possible that the service does not get removed from the Services list. This usually occurs when you uninstall the service with the Services manager still open. If that's the case, close the Services manager, repeat the uninstall command and then open the Services manager again. You'll find that the service has disappeared from the list and that you can now successfully reinstall it.

With the Windows Service in place, I had done most of the hard work. I could do pretty much anything I wanted with the text file containing the track info. So, the next logical step was to create a Web Service that exposes this information to the outside world.

The Web Service

Creating a Web Service is remarkably simple in the .NET world, especially when you're using Visual Studio .NET. For the WinAmp service, I performed the following steps:

  1. I added a Web Service to my project and called it PublicContent.asmx.
  2. I added a public method called NowPlayingGetStringP2P to the Web Service. This method returns the track info as a string.
  3. To make the method available as a Web Service, I decorated it with the WebMethod attribute. To avoid name collisions with other (overloaded) methods, I also added a MethodName to the attribute. Finally, to describe the method, I also added a Description to the WebMethod attribute. This description will be shown on the default documentation page that is served when people request the Web Service page. I ended up with the following code skeleton for the service:
    			[WebMethod (MessageName="NowPlayingGetStringP2P", 
      Description=@"Returns a formatted HTML string for use 
      in a post at p2p.wrox.com with information about the 
      track currently playing in WinAmp.")]
    public string NowPlayingGetStringP2P()
    {
    
    }
  4. For the method body, I added code that opens the text file, reads it line by line and assigns the values for each line to local variables. At the end, the separate pieces of track info are assembled into one long string that is returned by the method. It's quite a bit of code, but it 's not overly complicated, and has some comments embedded to describe the purpose of important parts:

    			public string NowPlayingGetStringP2P()
    {
    			string trackFileName = @"C:\WinAmp.txt";
    
      // Is the info present in the cache?
      if (System.Web.HttpContext.Current.Cache.Get("WinampTrackTitleString") != null)
      {
        return System.Web.HttpContext.Current.Cache.Get
            ("WinampTrackTitleString").ToString();
      }
      else
      {
        string result = "";
        // WinAmp file with track info found?
        if (File.Exists(trackFileName))
        {
          using (StreamReader sr = new StreamReader(trackFileName)) 
          {
            string tempLine = "";
            string artistName = "";
            string trackName = "";
            string albumName = "";
            int trackNumber = 0;
    
            tempLine = sr.ReadLine();
            if (tempLine != null)
            {
              if (tempLine.Length > 0)
              {
                // If the line doesn't start with ERROR, 
                // it contains the full path and file name
                if (tempLine.ToUpper().Substring(0, 6) != "ERROR:")
                {
                  // Read the next line that contains the Album Name
                  // in the format: AlbumName: 100th Window
                  tempLine = sr.ReadLine();
                  if (tempLine != null)
                  {
                    if (tempLine.Length > 0)
                    {
                      // Cut off AlbumName: 
                      albumName = tempLine.Substring(11);
                    }
                  }
              
                  // Read the next line that contains the Artist Name
                  // in the format: Artist Name: Massive Attack
                  tempLine = sr.ReadLine();
                  if (tempLine != null)
                  {
                    if (tempLine.Length > 0)
                    {
                      // Cut off Artist Name: 
                      artistName = tempLine.Substring(13);
                    }
                  }
    
                  // Read the next line that contains the Song Title
                  // in the format: SongTitle: Everywhen
                  tempLine = sr.ReadLine();
                  if (tempLine != null)
                  {
                    if (tempLine.Length > 0)
                    {
                      // Cut off SongTitle: 
                      trackName = tempLine.Substring(11);
                    }
                  }
    
                  // Read the next line that contains the Track Number
                  // in the format: Track Number: 3
                  tempLine = sr.ReadLine();
                  if (tempLine != null)
                  {
                    if (tempLine.Length > 0)
                    {
                      try
                      {
                        // Cut off Track Number: and convert to a number
                        string tempTrackNumber = tempLine.Substring
                            (14, tempLine.Length - 14);
                        trackNumber = Convert.ToInt32(tempTrackNumber);
                        if (trackNumber == 0)
                        {	
                          trackNumber = -1;
                        }
                      }
                      catch
                      {
                        trackNumber = -1;
                      }
                    }
                  }
    
                  if (trackName.Length == 0 || artistName.Length == 0)
                  {
                    result = "";
                  }
                  else
                  {
                    result = "[b]" + trackName + "[/b] by [b]" + artistName + "[/b]";
                    if (albumName.Length > 0)
                    {
                      if (trackNumber > 0)
                      {
                        result += " (Track " + trackNumber.ToString() + 
                          " from the album: [b]" + albumName + "[/b])";
                      }
                      else
                      {
                        result += " (From the album: [b]" + albumName + "[/b])";
                      }
                    }
                  }
                }
                else
                {
                  // Text file started with Error, so we return an empty string
    							result = "";
                }
              }
            }
          }
        }
        // Cache the information
        System.Web.HttpContext.Current.Cache.Insert
            ("WinampTrackTitleString", result, 
            new System.Web.Caching.CacheDependency(trackFileName));
        return result;
      }
    			}

This is enough to expose the track info to the outside world. From this point on, any application set up to access Web Services can access this service and retrieve the information as a string.
To make the information visible on my own Web site, I created a page called NowPlaying.aspx and added a reference to the Web Service by right-clicking my project in Visual Studio and choosing Add Web Reference... I set the reference to http://Imar.Spaanjaars.Com/WebServices/PublicContent.asmx and accepted the default Web reference name of com.spaanjaars.imar. (In the code download, I have used the Web Service located a localhost instead).
With the references set up, it's now a piece of cake to access the Web Service. I added the following code to the Page_Load method in the Code Behind of the NowPlaying page:

	com.spaanjaars.imar.PublicContent MyService = 
        new com.spaanjaars.imar.PublicContent(); 
  string track = MyService.NowPlayingGetStringP2P();
  if (track.Length > 0)
  {
    lblNowPlaying.Text = "Now playing: " + track;
  }
  else
  {
    lblNowPlaying.Text = "Currently, there is no track playing";
  }

When the NowPlaying page is hit, a new PublicContent object is created. By calling the NowPlayingGetStringP2P method, a string with the track info is returned.

In reality, I am calling a different method on the Web Service, called NowPlayingGetString. This method returns a string that uses <strong> tags, instead of the P2P forum [b] tags. Internally, it calls the NowPlayingGetStringP2P and replaces the P2P tags with real HTML tags.

This is a great example of the way VS.NET hides much of the complexity of Web Services for us. All we need to do is create an object and call one method. So, with only two lines of code, we can access the Web Service and get the return value from the method we choose to invoke. Behind the scenes, Visual Studio has created a proxy class for us that does all the hard work in accessing the Web Service. If you want to know more about accessing Web Services, be sure to check out the following articles:
 

If you recall the list with requirements from the beginning of this article, you'll know that at this point I have fulfilled the first two: I can retrieve and store the information from WinAmp, and I can expose this information through a public interface; a Web Service. The third requirement, inserting the information in my signature at P2P, seemed very difficult at first, but turned out pretty well.

Automatically Injecting the Track Info in my Signature

For this problem as well, Peter and I discussed a few possible methods. After some thinking out loud, we came to the conclusion we needed to hook into some kind of "post post" event in Internet Explorer. This event would fire just after the Post New Reply button gets clicked to send the message to P2P. It should work rather like the way AntiVirus programs like Symantec can inject code in the source of pages you request in the browser. However, instead of using GET when requesting pages, we needed something for POST when sending information back to the server. I Googled a bit for a possible solution, but nothing really useful came up.

But all of a sudden, I remembered a little nifty tool called ToggleBorders. This tool is great for debugging tables in your HTML pages. What I remembered from this tool is that it added a new menu item to the context menu of Internet Explorer that fires a custom HTML page. The download for the tool comes with that custom HTML page and an .inf file that allows you to create the custom menu item (more on that later).
One step closer to the solution. Now, if only I could access my Web Service from within that custom HTML page, I could retrieve the Now Playing info and add it to the <textarea> for the P2P post forum. Again after a bit of Googling, I ran into the article called Synchronize Your Databases with .NET Web Services by Alex Homer. Besides describing how to synchronize two databases through Web Services, this article also discusses a way to access a Web Service with JavaScript in Microsoft Explorer. Through the article I also found the pages at the MSDN site called: Using the WebService Behavior, About the WebService Behavior and WebService Behavior. These pages gave me valuable insight in how you can access Web Services with JavaScript, and are a must-read of you're interested in using Web Services from within client-side script in Internet Explorer.

After messing around a bit with the code presented in the articles and reference pages, I ended up with the following code for my JavaScript page:

	<html>
<head>
  <title>Get WinAmp Track Info From Web Service</title>
  <script type="text/javascript" defer="defer"> 
    var oXMLData;  // to hold reference to XML parser

    // Get a reference to the current (P2P) window
    var parentwin = external.menuArguments;
    var doc = parentwin.document;

    function init()
    {
      htcWService.useService("http://Imar.Spaanjaars.com/
            WebServices/PublicContent.asmx?WSDL","NowPlaying");
      iCallID = htcWService.NowPlaying.callService(dataLoaded, 
            "NowPlayingGetStringP2P");
      alert('Signature Inserted');
    }

    function dataLoaded(oResult)
    {
      if(oResult.error)
      {
        alert('Error code: ' + oResult.errorDetail.code);
        alert('Error string: ' + oResult.errorDetail.string);
      }
      else
      {
        oXMLData = new ActiveXObject('MSXML2.FreeThreadedDOMDocument');
        oXMLData.onreadystatechange = changeFunction;
        oXMLData.validateOnParse = true;
        oXMLData.async = false;
        oXMLData.loadXML(oResult.raw.xml);
      }
    }

    function changeFunction()
    {
      // check value of readyState property of XML parser
      // value 4 indicates loading complete
      if (oXMLData.readyState == 4)
      {
        if (oXMLData.parseError.errorCode != 0)
        {
          // there was an error while loading
          // so display message
        }
        else
        {
          var mySignature = '---------------------';
          mySignature += '------------------\r\n;
          mySignature += 'Imar Spaanjaars\r\n';
          mySignature += 'Everyone is unique, except for me.\r\n';
          var nowPlaying = '';
          if (oXMLData.text != '')
          {
            nowPlaying = '[size=1]While typing this post, I was ';
            nowPlaying += 'listening to: ';
            nowPlaying += oXMLData.text + '[/size=1]'
          }
          // Get the text area, and append the signature 
          // and the track info to its value
          doc.PostTopic.Message.value = 
                doc.PostTopic.Message.value + mySignature + nowPlaying;
        }
      }
    }
  </script>
</head>
<body onload="init();">
  <div id="htcWService" style="behavior:url(webservice.htc)" /><hr />
</body>
</html>

When this page is executed, the init method runs. This method sets up the Web Service behavior and calls the callService method. When the Web Service has returned its information, changeFunction is called. Inside this method, the string is retrieved from the service data and added to the nowPlaying string. At the end of the method, the signature text and the nowPlaying string are appended to the value of the Message text box, which is the text box where that holds the message in a P2P page.
Inside the custom HTML page, you can access the document from which the page was called with this syntax:

	var parentwin = external.menuArguments;
  var doc = parentwin.document;

The external.menuArguments returns a reference to the window that called the page. That window in turn exposes a document property that is a reference to the current document in the browser.
From here, you can access stuff inside the document like you normally would in an HTML page. So, doc.getElementById('Message') returns a reference to the text area called Message, and doc.PostTopic.Message.value would also return a reference to the same control

The final step was to find a way to call this page from within Internet Explorer. I used the ToggleBorders example as a starting point to create my own .inf file. I used the following file to register the custom HTML page with Internet Explorer and create a menu item that only shows up when you right-click HTML form elements:

	[Version]
Signature="$Chicago___FCKpd___12quot;
Provider="Imar Spaanjaars"

[DestinationDirs]
DefaultDestDir=10,"web"

[DefaultInstall]
AddReg=InsertSignature_AddReg
CopyFiles=InsertSignature_CopyFiles

[InsertSignature_CopyFiles]
InsertSignature.html
Webservice.htc

[InsertSignature_AddReg]
HKCU,"Software\Microsoft\Internet Explorer\MenuExt\Insert
     &Signature P2P"\,,0x00000000,"%10%\web\InsertSignature.html"
HKCU,"Software\Microsoft\Internet Explorer\MenuExt\Insert 
     &Signature P2P","contexts",65537,04,00,00,00

The InsertSignature_CopyFiles section determines the files that need to be "installed" (copied to the Windows\Web folder). For my little project, I needed to copy the custom HTML page, and the Web Service behavior file, Webservice.htc.

The InsertSignature_AddReg section determines the way the menu item is added to the context menu of Internet Explorer. The first line hooks up a menu item called Insert Signature P2P with the custom HTML page, InsertSignature.html. The 04 in the second line makes sure that the menu item is only visible when you right click an HTML form object, and will be invisible in all other situations.

The following pages and articles have more information about creating custom menu items:

When the .inf file was ready, I placed it in a sub folder together with Webservice.htc and InsertSignature.html, right-clicked the .inf file and chose Install. The two files were copied to the Windows\Web folder and the necessary information to create the context menu item was added to the registry. After I restarted Internet Explorer, I had the following custom menu available:

The Message textarea with the new context menu item visible

When the Insert Signature P2P, menu item is clicked, the InsertSignature.html is loaded and the code in the init method will run. The Web Service is accessed, the track info is retrieved and appended to the Message text area:

The Message textarea after the signature with track info has been inserted

While playing around with the Webservice behavior code in my custom HTML page, I ran into to some odd issues. According to the documentation, it is possible to access the Web Service synchronously. That is, the code blocks until the return value is received from the service. This works fine if you run the page stand-alone, either from a web server or from the local machine's disk. However, when the code is invoked from the custom menu item, it seems to start a new thread for accessing the Web service. In my code, that meant that as soon as callService was called, the init function ended, and the page went out of scope. Somehow, the Web Service never seemed to return the required information and append it to the message text area. I finally found a work around, by popping up a message box with the alert statement (the last line in the init method). As long as the dialog stays open, the custom HTML page stays in scope, and the Web Service can successfully return the track info and add it to the message. As soon as the signature is inserted, I can dismiss the dialog by clicking the OK button on the message box.

Summary

Like I admitted a couple of times in this article, the entire application is a bit useless. It doesn't add much value to add the track info to my posts at P2P.
However, trying to find out how to build the application turned out to be pretty useful. Knowing how to build and install a Windows Service, create a Web Service, access that service with the client side Webservice behavior and add custom menu items to Internet Explorer will prove to be useful in future projects I'll design and build. Hopefully, the concepts I have explained in this article will be useful to you too somewhere down your programming road.

Possible Extensions

The code presented in this article just scratches the surface of what is possible with Windows and Web Services in .NET. You could easily extend the ideas from this article. To get you started, here are a few ideas:

  • Create overloaded methods for the GetNowPlayingP2P method that returns information in different formats, like a DataSet, or formatted with real HTML tags.
  • Add code to the InsertSignature.html page so it recognizes which page it is in. This way, you can create a single signature page that can generate signatures for other forums and web pages (including your web-based e-mail program like Hotmail or Yahoo) as well.
  • Use the returned track info to retrieve further details from other Web Services. Maybe you can get details about the album or artist from Amazon or CDDB.

References

The following articles and Web pages have been useful in my quest for the final solution:

Windows Services

WinAmp and ID3 information

Web Services

Accessing a Web Service with Client-Side JavaScript

Custom Menus in Internet Explorer

Download Files


Where to Next?

Wonder where to go next? You can read existing comments below or you can post a comment yourself on this article .


Consider making a donation
Please consider making a donation using PayPal. Your donation helps me to pay the bills so I can keep running Imar.Spaanjaars.Com, providing fresh content as often as possible.



Feedback by Other Visitors of Imar.Spaanjaars.Com

On Wednesday, November 03, 2004 11:18:06 AM Ben said:
What I like is that you had an idea of something you wanted to do and then you set about finding out how to do it. If I was an employer I would employ people who can get to where they want when they don't know how, rather than people who know how to get there but can't actually get there.
On Saturday, January 08, 2005 3:49:56 AM Vadivel said:
The way you have visualized and implemented is amazing. I love the way you have presented the article as well. Way to go!

Best Regards
Vadivel

http://vadivel.thinkingms.com

Talk Back! Comment on Imar.Spaanjaars.Com

I am interested in what you have to say about this article. Feel free to post any comments, remarks or questions you may have about this article. The Talk Back feature is not meant for technical questions that are not directly related to this article. So, a post like "Hey, can you tell me how I can upload files to a MySQL database in PHP?" is likely to be removed. Also spam and unrealistic job offers will be deleted immediately.

When you post a comment, you have to provide your name and the comment. Your e-mail address is optional and you only need to provide it if you want me to contact you. It will not be displayed along with your comment. I got sick and tired of the comment spam I was receiving, so I have protected this page with a simple calculation exercise. This means that if you want to leave a comment, you'll need to complete the calculation before you hit the Post Comment button.

If you want to object to a comment made by another visitor, be sure to contact me and I'll look into it ASAP. Don't forget to mention the page link, or the QuickDocId of the document.

For more information about the Talk Back feature, check out this news item.