Autor Beitrag
Gausi
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 8535
Erhaltene Danke: 473

Windows 7, Windows 10
D7 PE, Delphi XE3 Prof, Delphi 10.3 CE
BeitragVerfasst: Do 22.09.05 15:23 
Die Playlist von Winamp einlesen
Bei der Veröffentlichung meiner mp3-Verwaltung wurde ich immerhin von 2 Usern angesprochen, wie ich dort die Playlist von Winamp einlese. Ich habe mich daher entschlossen, das hier für die Allgemeinheit hinzustellen.

Diese Funktionen wurden erfolgreich getestet mit Winamp v2.80. Mit Winamp 3.x könnte es Probleme geben - aber man ist sich wohl ziemlich einig, dass die 3er Version etwas vermurkst war. Ob das mit der neuen 5er klappt, kann ich nicht sagen.

Ich verdiene nicht 100% der Lorbeeren dafür. Das gröbste hat user profile iconcaty in diesem Beitrag schon vorher erledigt. Ich habe die eine Funktion dort nur erweitert und einige andere hinzugefügt.
Für weitere Informationen verweise ich auf www.winamp.com/nsdn/winamp/sdk/

Zunächst einmal brauchen wir zur besseren Lesbarkeit ein paar Konstanten
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
const   WINAMP_BUTTON1   : integer = 40044// previous title
        WINAMP_BUTTON2   : integer = 40045// play
        WINAMP_BUTTON3   : integer = 40046// pause
        WINAMP_BUTTON4   : integer = 40047// stop
        WINAMP_BUTTON5   : integer = 40048// next title
        WINAMP_VOLUMEUP  : integer = 40058// volume up
        WINAMP_VOLUMEDOWN: integer = 40059// volume down

        IPC_GETLISTLENGTH : integer = 124;
        IPC_GETLISTPOS :integer = 125;
        IPC_GETPLAYLISTFILE : integer = 211;
        IPC_GETPLAYLISTTITLE : integer = 212;

        IPC_GET_SHUFFLE : integer = 250;
        IPC_GET_REPEAT : integer = 251;

Wir brauchen nicht alle diese Konstanten zum Auslesen der Playlist, aber am Ende komme ich noch auf ein paar weitere Sachen zu sprechen, wofür man die brauchen kann.

Ein Hinweis zwischendurch: Gebraucht werden die Units Windows, Messages und SysUtils

Zuerst eine Funktion, die einen Eintrag aus der Playlist zurückliefert. Eingabeparameter ist der Index des gewünschten Stückes in der Playlist - Beginn der Zählung bei 0. Wird ein negativer Parameter übergeben, wird der aktuell gewählte Titel bestimmt:
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
function GetWinampTitel(data:Integer): String;
var hwndWinamp, TempHandle : THandle;
    dat2: array[0..500of Char;
    TrackPos: Integer;
    temp, MPointer: Cardinal;
begin
    // Prinzip ist fast immer dasselbe, daher nur einmal die Kommentare ;-)
    // Fenster suchen
    hwndWinamp:= FindWindow('Winamp v1.x',nil);
    if hwndWinamp= 0 then
    begin  // Kein Erfolg
        result:=''
    end else
    begin  // Erfolg - Winamp-Fenster gefunden
        if data<0 then
          // ermittle die aktuelle Position in der Playlist
          TrackPos:= SendMessage(hwndWinamp,WM_USER,0 , IPC_GETLISTPOS)
        else
          // sonst nehme die übergebene zahl als tracknummer
          TrackPos:=data;

        // Tja nu, halt ein paar Nachrichten an Winamp senden, um an die Daten zu kommen ;-)
        MPointer:= SendMessage(hwndWinamp,WM_USER,TrackPos , IPC_GETPLAYLISTTITLE);
        // Diese Info möchte Winamp nicht direkt rausrücken  - da muss man was tricksen ;-)
        GetWindowThreadProcessId(hwndWinamp,TempHandle);
        hwndWinamp:= OpenProcess(PROCESS_ALL_ACCESS,False,TempHandle);
        ReadProcessMemory(hwndWinamp, Pointer(MPointer), @dat2,500,temp);
        // warum ausgerechnet 500 kann ich nicht sagen. Habe das so übernommen.
        // Kann sein, dass auch kleinere Werte funktionieren.
        CloseHandle(hwndWinamp);
        Result:= string(dat2);
    end;
end;


Um die komplette Playlist auszulesen, benötigen wir natürlich noch eine Information darüber, wie lang die Liste ist:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
function GetWinampPlayListLength:integer;
var hwndWinamp:THandle;
begin
    hwndWinamp:= FindWindow('Winamp v1.x',nil);
    if hwndWinamp= 0 then
    begin
        result:=-1
    end else
    begin
        result := SendMessage(hwndWinamp,WM_USER,0 , IPC_GETLISTLENGTH);
    end;
end;


Bevor wir uns nun eine for-Schleife basteln, die uns die komplette Liste liefert, noch eine weitere Funktion. Was bei Winamp in der Liste steht, ist ja eigentlich gar nicht so interessant. Viel interessanter ist es, den Dateinamen des Listeneintrages zu erhalten. Das ist jetzt nicht mehr schwer - die Funktion ist fast identisch mit der Titel-Funktion - Wir senden nur eine andere Message an das Winamp-Fenster.
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
function GetWinampFilename(data:Integer): String;
var hwndWinamp, TempHandle : THandle;
    dat2: array[0..500of Char;
    TrackPos: Integer;
    temp, MPointer: Cardinal;
begin
    hwndWinamp:= FindWindow('Winamp v1.x',nil);
    if hwndWinamp= 0 then
    begin
        result:=''
    end else
    begin
        if data<0 then
          TrackPos:= SendMessage(hwndWinamp,WM_USER,0 , IPC_GETLISTPOS)
        else
          TrackPos:=data;

        MPointer:= SendMessage(hwndWinamp,WM_USER,TrackPos , IPC_GETPLAYLISTFILE);
        GetWindowThreadProcessId(hwndWinamp,TempHandle);
        hwndWinamp:= OpenProcess(PROCESS_ALL_ACCESS,False,TempHandle);
        ReadProcessMemory(hwndWinamp, Pointer(MPointer), @dat2,500,temp);
        CloseHandle(hwndWinamp);
        Result:= string(dat2);
    end;
end;


Jetzt können wir diese drei Funktionen z.B. so anwenden:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
procedure TForm1.Button1Click(Sender: TObject);
var i:integer;
begin
  for i:=0 to GetWinampPlayListLength-1 do
  begin
    listbox1.Items.Add(GetWinampFilename(i));
    listbox2.Items.Add(GetWinampTitel(i));
  end;
end;


So weit, so gut. Damit dürfte da Prinzip klar sein. Man kann aber z.B. auch nicht nur den aktuellen Titel bestimmen (GetWinampFilename(-1)), man kann auch den nächsten und vorigen Titel bestimmen. Dazu muss man berücksichtigen, ob Shuffle oder Repeat aktiviert ist. Shuffle macht eine Vorhersage des nächsten Titels unmöglich, bei Repeat wird vom Ende der Liste wieder an den Anfang gesprungen. Das Prinzip der Funktionen sollte klar sein: Man bestimmt die Position des aktuellen Titels, und bestimmt dann den Titel davor bzw. danach.
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
function GetWinampNextTitel:string;
var hwndWinamp, TempHandle : THandle;
    dat2: array[0..500of Char;
    TrackPos,maxpos: Integer;
    temp, MPointer: Cardinal;
begin
    hwndWinamp:= FindWindow('Winamp v1.x',nil);
    if hwndWinamp= 0 then
    begin
        result:=''
    end else
    begin
        // Wenn Shuffle aktiviert ist, kann keine Aussage über den nächsten Titel getroffen werden
        if SendMessage(hwndWinamp,WM_USER,0 , IPC_GET_SHUFFLE)=1 then
            result:='N/A [random]'
        else
        begin
            TrackPos := SendMessage(hwndWinamp,WM_USER,0 , IPC_GETLISTPOS);
            maxpos   := SendMessage(hwndWinamp,WM_USER,0 , IPC_GETLISTLENGTH);
            if maxpos-1 > Trackpos then inc(TrackPos) // noch nicht am Ende der Liste
            else begin
                // am Ende der Liste
                if SendMessage(hwndWinamp,WM_USER,0 , IPC_GET_REPEAT)=1
                // ist Repeat auf ON? - Dann liefere den ersten Titel in der Liste
                then TrackPos:=0 else
                begin
                    // andernfalls: Ende der Liste, kein Repeat - kein nächstes Lied
                    result:='N/A [no repeat]';
                    exit;
                end;
            end;
            MPointer:= SendMessage(hwndWinamp,WM_USER,TrackPos , IPC_GETPLAYLISTTITLE);
            GetWindowThreadProcessId(hwndWinamp,TempHandle);
            hwndWinamp:= OpenProcess(PROCESS_ALL_ACCESS,False,TempHandle);
            ReadProcessMemory(hwndWinamp, Pointer(MPointer), @dat2,500,temp);
            CloseHandle(hwndWinamp);
            // Überprüfen: Existiert nächstes File überhaupt?
            if fileexists(GetWinampFilename(Trackpos)) then
                Result := string(dat2)
            else
                Result := 'N/A [File not found]';
                // andere Möglichkeit: Die Liste weiter durchgehen, um den nächsten gültigen Titel zu erhalten.
        end;
    end;
end;

function GetWinampPrevTitel:string;
var hwndWinamp, TempHandle : THandle;
    dat2: array[0..500of Char;
    TrackPos,maxpos: Integer;
    temp, MPointer: Cardinal;
begin
    // Genau wie "Next", nur umgedreht ;-)
    hwndWinamp:= FindWindow('Winamp v1.x',nil);
    if hwndWinamp= 0 then
    begin
        result:=''
    end else
    begin
        if SendMessage(hwndWinamp,WM_USER,0 , IPC_GET_SHUFFLE)=1 then
            result:='N/A [random]'
        else
        begin
            TrackPos := SendMessage(hwndWinamp,WM_USER,0 , IPC_GETLISTPOS);
            maxpos   := SendMessage(hwndWinamp,WM_USER,0 , IPC_GETLISTLENGTH);
            if Trackpos > 0 then dec(TrackPos)
            else begin
                if SendMessage(hwndWinamp,WM_USER,0 , IPC_GET_REPEAT)=1
                then TrackPos:=maxpos-1 else
                begin
                    result:='N/A [no repeat]';
                    exit;
                end;
            end;
            MPointer:= SendMessage(hwndWinamp,WM_USER,TrackPos , IPC_GETPLAYLISTTITLE);
            GetWindowThreadProcessId(hwndWinamp,TempHandle);
            hwndWinamp:= OpenProcess(PROCESS_ALL_ACCESS,False,TempHandle);
            ReadProcessMemory(hwndWinamp, Pointer(MPointer), @dat2,500,temp);
            CloseHandle(hwndWinamp);
            if fileexists(GetWinampFilename(Trackpos)) then
                Result := string(dat2)
            else
                Result := 'N/A [File not found]';
        end;
    end;
end;

Und wenn man jetzt Winamp veranlassen möchte, den nächsten Titel auch abzuspielen, dann geht das so:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
procedure WinampPlayNext;
var hwndWinamp: THandle;
begin
    hwndWinamp:= FindWindow('Winamp v1.x',nil);
    if hwndWinamp<>0 then SendMessage(hwndWinamp,WM_COMMAND, WINAMP_BUTTON5, 0);
end;

Was passiert, wenn man WINAMP_BUTTON5 durch eine der anderen Button-Konstanten ersetzt, überlasse ich dem interessierten Leser zur Übung.

Viel Spass damit!

_________________
We are, we were and will not be.