|

Introduction
This article shows a method of controlling WinAmp via a web browser.
Background
Sometime ago I was working at a place where all the developers worked in the same area away
from other people. It didn't take long before our main development server began to fill up with
quite a collection of MP3's. Once this happened the server then got connected to the phone
system and to a decent set of speakers. The only thing left to do was remotely control the
playlist. That's where this article comes in.
Using the code
WinAmp can be controlled using the Windows Messaging system. First you need to get a handle
to WinAmp, this is done using the FindWindow API. It turns out that all of the WomAmp
versions have the same class name: "Winamp v1.x".
Messages are sent in the form of:
int returnVal = SendMessage(hwndWinamp, WM_USER ,data, id);
In order for a Windows service to obtain the handle of the running WinAmp it needs to be
able to interact with the desktop. This is done by editing the service properties and checking
the 'Allow service to interact with desktop' button.
The web application communicates with the windows service via remoting. The class
winampcontroller is remoted between the web application and the windows service. In fact
you could do without the Windows service if you wanted to allow IIS to interact with the
desktop, but we didn't want to modify the properties of IIS to do this because of security
fears.
Since the application is written using the .NET framework, interop is used to call the
native Windows API, the following code snippet shows how to send simple commands to WinAmp.
public class winampcontroller : MarshalByRefObject
{
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr FindWindow(
[MarshalAs(UnmanagedType.LPTStr)] string lpClassName,
[MarshalAs(UnmanagedType.LPTStr)] string lpWindowName);
[StructLayout(LayoutKind.Sequential)]
struct COPYDATASTRUCT
{
public IntPtr dwData;
public int cbData;
[MarshalAs(UnmanagedType.LPStr)] public string lpData;
}
[DllImport("user32.dll", CharSet=CharSet.Auto)]
static extern int SendMessage(IntPtr hwnd, int wMsg, int wParam,
[In()] ref COPYDATASTRUCT lParam);
[DllImport("user32.dll", CharSet=CharSet.Auto)]
static extern int SendMessageA(IntPtr hwnd, int wMsg, int wParam,
int lParam);
const int WM_COMMAND = 0x111;
const int WM_USER = 1024;
const int WA_PLAY = 40045;
const int WA_STOP = 40047;
const int WA_PAUSE = 40046;
const int WA_PREVTRACK = 40044;
const int WA_NEXTTRACK = 40048;
const int WA_GETSTATUS = 104;
const int WA_PLAYLISTLEN = 124;
const int WA_SETVOLUME = 122;
const int WA_SETPLAYLISTPOS = 121;
const int WA_WRITEPLAYLIST = 120;
const int WA_VOLUMEUP = 40058;
const int WA_VOLUMEDOWN = 40059;
const int WA_CLEARPLAYLIST = 101;
const int WA_NOTHING = 0;
const int WA_RESTART = 135;
const int WA_REFRESHPLCACHE = 247;
public const int STOPPED = 0;
public const int PLAYING = 1;
public const int PAUSED = 3;
protected string m_windowName;
public winampcontroller()
{
try
{
m_windowName = ConfigurationSettings.AppSettings["WindowClassName"];
}
catch(NullReferenceException)
{
m_windowName = "Winamp v1.x";
}
}
public void Stop()
{
IntPtr hwnd = FindWindow(m_windowName, null);
SendMessageA(hwnd, WM_COMMAND, WA_STOP, WA_NOTHING);
}
public void Play()
{
IntPtr hwnd = FindWindow(m_windowName, null);
SendMessageA(hwnd, WM_COMMAND, WA_PLAY, WA_NOTHING);
}
public void Pause()
{
IntPtr hwnd = FindWindow(m_windowName, null);
SendMessageA(hwnd, WM_COMMAND, WA_PAUSE, WA_NOTHING);
}
}
WinAmp has pre-defined responses to different values of data, some of these are:
| 0 |
Retrieves the version of Winamp running. Version will be 0x20yx for 2.yx. |
| 100 |
Starts playback. |
| 101 |
Clears Winamp's playlist. |
| 102 |
Plays selected track. |
| 104 |
Returns the status of playback. If 'returnVal' is 1, Winamp is playing. If
'returnVal' is 3, Winamp is paused. Otherwise, playback is stopped. |
| 121 |
Sets the playlist position to the position specified in tracks in 'data'. |
| 122 |
Sets the volume to 'data', which can be between 0 (silent) and 255 (maximum). |
| 124 |
Returns length of the current playlist, in tracks. |
| 125 |
Returns the position in the current playlist, in tracks (requires Winamp 2.05+). |
| 129 |
Adds the specified file |
| 135 |
Restarts Winamp |
The windows service consists of a few lines of code outside of the code generated by Visual
Studio:
protected override void OnStart(string[] args)
{
int tcpChannelVal = 0;
try
{
tcpChannelVal = int.Parse(ConfigurationSettings.AppSettings["ChannelNum"]);
}
catch(Exception){
tcpChannelVal = 1096;
}
m_channel = new TcpChannel(tcpChannelVal);
ChannelServices.RegisterChannel(m_channel);
RemotingConfiguration.ApplicationName = "WinampController";
RemotingConfiguration.RegisterWellKnownServiceType(typeof(winampcontroller),
"WinampController",WellKnownObjectMode.Singleton);
}
protected override void OnStop()
{
ChannelServices.UnregisterChannel(m_channel);
}
To install the windows service, you need to use the InstallUtil program that comes with the
.NET Framework SDK or Visual Studio .NET
The web application's web.config files need to be changed to point to a directory
that contains MP3's
The whole application was put together in a few hours, us being programmers and not designers
means that the front end is not very pretty, but since it's only HTML it's easy to change.
In terms of features, this example controls a playlist of files in a directory. More
functionality and better error handling was destined for this project, but as usual not enough
time has meant that it will have to stay this way.
If anyone wants to create a better front end or add more features you are more than welcome
too, I would of course update the article with your name and contributions.
Points of Interest
When this code was originally written WinAmp was at version 2. Currently WinAmp is at
version 3, which does not provide the same methods for manipulating the player; however, I
did find this plugin which replicates the desired functions:
Winamp 2.x Plugin
Manager for Winamp 3
History
10/12/2002 - Article Created
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 30 (Total in Forum: 30) (Refresh) | FirstPrevNext |
|
 |
|
|
Hi,
I am a newbie to all of this. I want to do something similar to what you have done here, except not with WinAmp. But I started coding my own version before I ever saw this. I am using Windows Messages to contact an application which I wrote from an ASP.Net program. It works great in Visual Web Developer, but when I put it on the IIS, the Messages are no longer sent.
You say that you coded yours the way you did so that you wouldn't have to give IIS access to the desktop. Before I do something drastic and recode my WebForm to act like yours, I would like to see mine work. So can you please let me know how to give IIS permission to send Windows Messages? Also, how big a security risk is it to allow this?
Thanks
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Allowing IIS to interact with desktop:
Control Panel -> Administrative Tools -> Services -> World Wide Web Publishing Service -> right click -> Properties -> Log On -> Allow service to interact with desktop -> OK
Restart IIS.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
/* Code: C# * User: Stefan Vahldieck * Date: 31.01.2007 * Time: 22:05 */
using System; using System.Diagnostics; using System.Runtime.InteropServices;
namespace test { /// /// Description of WinAmpDemoSDK. /// public static class WinAmpDemoSDK { public static IntPtr hWnd = FindWindow("Winamp v1.x", null); public static Process WinAmpProcess = Process.GetProcessesByName("Winamp")[0];
[DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern IntPtr FindWindow( [MarshalAs(UnmanagedType.LPTStr)] string lpClassName, [MarshalAs(UnmanagedType.LPTStr)] string lpWindowName ); [DllImport("user32")] public static extern int GetWindowThreadProcessId( IntPtr hWnd, IntPtr ProcessId );
[ DllImport( "Kernel32.dll" ) ] public static extern bool ReadProcessMemory( IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, UInt32 nSize, ref UInt32 lpNumberOfBytesRead ); [DllImport( "Kernel32.dll", SetLastError = true ) ] public static extern IntPtr OpenProcess( UInt32 dwDesiredAccess, bool bInheritHandle, UInt32 dwProcessId ); [DllImport("KERNEL32.DLL")] public static extern int CloseHandle(int handle); [DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern int SendMessage( IntPtr hwnd, int wMsg, int wParam, uint lParam );
private static string readStringFromWinampMemory( int winampMemoryAddress ) { string str = ""; // Prozess zum lesen öffnen IntPtr handle = OpenProcess( 0x0010, false, (uint)WinAmpProcess.Id ); byte[] buff = new byte[500]; UInt32 ret = new UInt32(); IntPtr pos = new IntPtr( winampMemoryAddress ); if( ReadProcessMemory( handle, pos, buff, 500, ref ret ) ) { System.Text.Encoding encoding = System.Text.Encoding.Default; str = encoding.GetString( buff ); } return str; }
public static string[] getPlaylist() { int len = SendMessage( hWnd, (int)WA_IPC.WM_WA_IPC, 0, (uint)WA_IPC.IPC_GETLISTLENGTH ); string[] listNames = new string[len]; for( int i = 0; i { listNames[i] = readStringFromWinampMemory( SendMessage( hWnd, (int)WA_IPC.WM_WA_IPC, i, (uint)WA_IPC.IPC_GETPLAYLISTTITLE ) ); } return listNames; } } public enum WA_IPC { WM_WA_IPC = 1024,
// Only Needed constans where shown IPC_GETLISTLENGTH = 124,
IPC_SETPLAYLISTPOS = 121, IPC_GETLISTPOS = 125,
IPC_GETPLAYLISTFILE = 211, IPC_GETPLAYLISTTITLE = 212 }
}
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I put together a similar project in VB6 a while back to accomplish the same goal. I stumbled across this by accident, but now feel inclinded to upgrade mine to .NET. Thanks for sharing this demo. It's clean and easy to follow!
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
 |
|
|
This worked for me.
Put all 3 project directories in a single directory. Create a virtual web share off of the Winamp project folder calling it Winamp. Launch the solution file in Winamp Service directory.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
hello all, i am not sure if this forum is appropriate for my question, but heres hoping! i read the article and thought it might be ok to post here.
i am working on a client server architecture in C++. the server sterams an audio file to the client. now, this is the part i need help with:
i want the client to open up winamp and send the stream to winamp to play. there are a lot of things i need clarification on! where do i start?
in a nutshell, is it even possible to do this? ie, send a stream to winamp. i have read that it is... but i would really appreciate any advice on how to do this. would also appreciate any links you guys know of where i can read up on it. i totally don't know how to do this or where to start, so absolutely any introductory material would be welcome.
thanks a lot! i am a beginner, so please bear with me! i can send command and user messages now, but i dont' know how to sedn a stream to winamp and tell it to play it! drew.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Is there anyway to retrieve the actual file name for the currently playing file? So far, I've only found articles that allow for the retrieval of the name of the file, but that's from the ID3 tags. Aside from searching and reading all the ID3 tags in an entire music library, is there anyway to get the file name of the file?
Thanks, Chris
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Ah well, I can't even get the Findwindow function to work in asp.net let alone running this thing. Anyone successfully done this in VB for Asp.net? Can you mail me? Thank you very much.
|
| Sign In·View Thread·PermaLink | 1.00/5 (1 vote) |
|
|
|
 |
|
|
 |
|
|
Here is some working code 
HWND FindWindow( LPCTSTR lpClassName, LPCTSTR lpWindowName );
In C# .NET this function is referenced as follows:
[System.Runtime.InteropServices.DllImport("user32.dll", CharSet=System.Runtime.InteropServices.CharSet.Auto)] public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
And in VB .NET:
Private Declare Auto Function FindWindow Lib "user32" (ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
greetz Niels Penneman
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
[SocketException (0x274d): Det gick inte att göra en anslutning eftersom måldatorn aktivt nekade det] System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg) +264 System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type) +877 WinampController.winampcontroller.Status() in c:\work\winampcontroller\winampcontroller.cs:139 Winamp._default.BindCurrentSettings() in c:\work\website\www-ffc\winamp\default.aspx.cs:46 Winamp._default.Page_Load(Object sender, EventArgs e) in c:\work\website\www-ffc\winamp\default.aspx.cs:34 System.Web.UI.Control.OnLoad(EventArgs e) +67 System.Web.UI.Control.LoadRecursive() +35 System.Web.UI.Page.ProcessRequestMain() +731
Where is the problem??
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi!
First of all, thank you for your idea! It works fine for me, but two things are weird:
1. this piece of code does not work for me correctly: // try // { // m_windowName = ConfigurationSettings.AppSettings["WindowClassName"]; // } // catch(NullReferenceException) // { // m_windowName = "Winamp v1.x"; // } replacing it with // m_windowName = "Winamp v1.x"; it works perfectly !?
2. i created a DataGrid with ButtonColumns, but only ButtonColumns of the type "LinkButton" work. "PushButton" does not work (but looks better - any ideas?)
|
| Sign In·View Thread·PermaLink | 1.67/5 (3 votes) |
|
|
|
 |
|
|
Whenever I try to run this, I get:
Configuration Error Description: An error occurred during the processing of a configuration file required to service this request. Please review the specific error details below and modify your configuration file appropriately.
Parser Error Message: It is an error to use a section registered as allowDefinition='MachineToApplication' beyond application level. This error can be caused by a virtual directory not being configured as an application in IIS.
Source Error:
Line 39: "Passport" and "None" Line 40: --> Line 41: Line 42: Line 43: <!-- APPLICATION-LEVEL TRACE LOGGING
Source File: c:\inetpub\wwwroot\Winamp\web.config Line: 41
What am I not doing right??
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
Jesse3647 wrote: What am I not doing right??
This error can be caused by a virtual directory not being configured as an application in IIS. Just make the winamp directory an application in the IIS manager.
I rated this article 2 by mistake. It deserves more. I wanted to get to the second page... - vjedlicka 3:33 25 Nov '02
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I think the problem can be resolved if you modify your machine.config file. The service is looking for an app setting for the TCP port number. You must add the value to the cofig file. I had the same problem until I added this key. Once you do that, you should be able to start the service.
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
I've ported the code to VB.NET, but am having problems with the AppendToPlayList method. Winamp opens its 'Open File(s)' dialog, instead of adding the song to the playlist. I'm guessing that there's some truncation going on, since the dialog opens to the appropriate directory (e.g. if I pass "C:\mp3\Billy Bragg\Don't Try This At Home\Accident Waiting To Happen.mp3", the dialog opens to the "" folder).
I've included the appropriate code. Any suggestions on what I'm doing wrong would be appreciated.
C.
<DllImport("user32.dll", EntryPoint:="FindWindow", SetLastError:=True, CharSet:=CharSet.Auto)> _ Public Shared Function FindWindow(ByVal lpClassName As String, ByVal lpWindowName As String) As Integer ' End Function
<StructLayout(LayoutKind.Sequential)> _ Public Structure COPYDATASTRUCT Public dwData As Integer 'Specifies data to be passed to the receiving application Public cbData As Integer 'Specifies the size, in bytes, of the data pointed to by the lpData member <MarshalAs(UnmanagedType.LPStr)> Public lpData As String 'Pointer to data to be passed to the receiving application. This member can be NULL End Structure 'COPYDATASTRUCT
<DllImport("kernel32.dll", EntryPoint:="lstrcpy", SetLastError:=True, CharSet:=CharSet.Auto)> _ Public Shared Function lstrcpy(ByVal lpString1 As String, ByVal lpString2 As String) As Integer ' End Function
<DllImport("user32.dll", CharSet:=CharSet.Auto)> _ Public Shared Function SendMessage(ByVal InPtr As Integer, ByVal wMsg As Integer, ByVal wParam As Integer, ByVal lParam As COPYDATASTRUCT) As Integer ' End Function
<DllImport("user32.dll", CharSet:=CharSet.Auto)> _ Public Shared Function SendMessageA(ByVal IntPtr As Integer, ByVal wMsg As Integer, ByVal wParam As Integer, ByVal lParam As Integer) As Integer ' End Function Public Sub AppendToPlayList(ByVal filename As String) Dim hwnd As Integer = FindWindow(m_windowName, vbNullString)
Dim cds As COPYDATASTRUCT cds.dwData = 100 cds.lpData = lstrcpy(filename, filename) cds.cbData = Len(filename) + 1
Dim retval As Integer = SendMessage(hwnd, &H4A, 0&, cds) End Sub
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Just tested it and worked like a charm. Only my way to declare the functions is different. Check it out.
Private Declare Auto Function FindWindow Lib "user32" ( _ ByVal lpClassName As String, _ ByVal lpWindowName As String) As IntPtr
Private Declare Auto Function GetWindowText Lib "user32" ( _ ByVal hwnd As IntPtr, _ ByVal lpString As String, _ ByVal cch As Integer) As Integer
Private Declare Auto Function SendMessageA Lib "user32" ( _ ByVal hwnd As IntPtr, _ ByVal wMsg As Integer, _ ByVal wParam As Integer, _ ByVal lParam As Integer) As Integer
Private Declare Auto Function SendMessage Lib "user32" ( _ ByVal hwnd As IntPtr, _ ByVal wMsg As Integer, _ ByVal wParam As Integer, _ ByRef lParam As COPYDATASTRUCT) As Integer
_ Structure COPYDATASTRUCT Public dwData As IntPtr Public cbData As Integer Public lpData As String End Structure
Public Sub AppendToPlayList(ByVal filename As String) Dim cds As COPYDATASTRUCT cds.dwData = New System.IntPtr(100) cds.lpData = filename cds.cbData = filename.Length + 1 SendMessage(hwnd, &H4A, 0, cds) End Sub
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi Dan,
First of all, thanks for a great article / code sample ! I've tried to get it running at home, but haven't had any success at all. First, I installed the website and the service at my development server, but could not get winamp to play anything. Then I thought "well, the website doesn't seem to get the same WinampController as the service has (or something like that) - maybe I should try it locally first". And so I did - with the almost same result. I can open the website, add files to the "playlist", and press play. But nothing happens. Then I tried to add a file to my winamp instance manually, press play and then stop (on winamp) and then play on the web, that worked. So the message is sent to the winamp-instance, but the playlist-things aren't quite working. But thats fine, I'll find out why. However, that doesn't work on the server, and I just can't figure why.
I've been working quite a lot with VS6.0 (C++) but not very much with VS.NET - so maybe I'm missing some important project setting here. Anyway - here is my setup : One workspace (solution) with all three project in it. "References" in Winamp (the web) and in WinampService are OK - as far as I can tell - since the WinampController-reference "points" to the dll from the WinampController project. But are there any differences when moving the final files to the server ? Are paths coded into the .exe /web application, or do I have to install / register the WinampController.dll somehow ? I did install the service, of course.
Sigh... I can't see a way out. Frustrating that it works (kind of) locally - but not on the server where it should
Well, I hope you have some kind of advice, because this is really what I need. Thanks for doing all the hard work 
And finally - I after reading the article text several times, I still don't quite understand why the service is there. First I thought it would relay messages between the website (the controller, really) and the winamp instance, but that is done directly from the controller. Maybe I just don't understand what kind of object the winampcontroller really is.
Anyway, thanks in advance for any help or advice - and happy new year to you !
/Jan
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Now it works... all that was wrong was
public winampcontroller() { try { m_windowName = ConfigurationSettings.AppSettings["WindowClassName"]; } catch(NullReferenceException) { m_windowName = "Winamp v1.x"; } }
- which never left m_windowName with anything but null.
- this is NOT a signature !
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
System.Net.Sockets.SocketException: No connection could be made because the target machine actively refused it
Why this machine refuses to let me hear winamp? The code above is the one that I see in your program... This is the correction or only the proplem?
anyway...great idea
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
 |
|
|
Hi Dan:
Your article has nice idea,but whats the need of windows service here?Couldn't you directly use your wrapped class in your asp.net project?I think its useless here!
<html>Mazy</html>
"And the carpet needs a haircut, and the spotlight looks like a prison break And the telephone's out of cigarettes, and the balcony is on the make And the piano has been drinking, the piano has been drinking...not me...not me-Tom Waits
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Your right, you could do without the windows service (as i said in the article), but you would have to set IIS up to be able to interact with the desktop, we did not want to do that.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
General News Question Answer Joke Rant Admin
|