Данная статья расскажет вам о том, как можно создавать свои плагины для популярного мультимедиа плеера Winamp.
Вступление.
В 2002 году я работал в одной компании системным администратором, и по долгу службы 80% времени мне приходилось находится в окружении серверов FreeBSD. У меня был еще в распоряжении был сервер с Windows на котором крутилась музыка чтобы не скучать, и было не удобно менять треки в плейлисте да и вообще работать с винампом (по некоторым причинам я не мог пользоваться такими вещами как Terminal Service, переключатели мониторов, и.т.п.), и я задался целью сделать управляющую программу для Винампа. Языком программирования был выбран VB, т.к. на этом языке я решения такой задачки не встречал и это мой любимый язык, также нужна была быстрота разработки.
Для программирования под API Winamp`а нам потребуется:
Правда есть небольшое ограничение, этим набором можно создавать только основные (gen_*) плагины.
Данный текст рассчитан на программистов уже имеющих опыт работы в VB с сетевыми приложениями и WinAPI.
Часть 1.
Пишем простую управляющую программу.
Для начала напишем простую программу (не плагин) для управления винампом. Например программу которая будет принимать команду на определенном TCP порту и транслировать ее Винампу.
Запускаем VB и создаем новый проект Standard EXE и добавляем в проект Microsoft Winsock Control 6.0, несколько API функций и констант, больше нам ничего не потребуется.
Вот декларации функций которые нам понадобятся:
- Private Declare Function
FindWindow Lib "user32" Alias "FindWindowA" ( _
ByVal lpClassName As String, _
ByVal lpWindowName As String _
) As Long
функция возвращает хендл на окно с заданным классом и/или строкой заголовка;
SendMessage
Lib "user32"
Alias "SendMessageA" ( _
ByVal hWnd As Long, _
ByVal wMsg As Long, _
ByVal wParam As Long, _
lParam As Any _
) As Long
функция посылает сообщение окну в указанным хендлом;
- Private Declare Sub
Sleep Lib "kernel32" ( _
ByVal dwMilliseconds As Long _
)
функция позволяющая процессу “уснуть” (система не выделяет процессу процессорного времени) на указанное число миллисекунд;
Размещаем эти функции в секции General, также нам потребуются следующие управляющие сообщения винампа (полный их список есть в SDK к нему) и системы:
‘system
- Private Const
WM_USER = &H400
- Private Const
WM_COMMAND = &H111
‘winamp
- Private Const
WM_Raise_Volume = 40058 'increase 1%
- Private Const
WM_Lower_Volume = 40059 'decrease 1%
- Private Const
WM_Close_Winamp = 40001
- Private Const
WM_Previous = 40044
- Private Const
WM_Next = 40048
- Private Const
WM_Play = 40045
- Private Const
WM_Pause_Unpause = 40046
- Private Const
WM_Stop = 40047
- Private Const
WM_Toggle_Shuffle = 40023
- Private Const
WA_SETVOLUME = 122
Также добавляем пару переменных уровня формы:
- Dim
Response As String
- Dim
Connections As Long
первая переменная потребуется для принятия строки от клиента, вторая для счета числа подключений. Внешними они сделаны по той причине, что они могут вам потребоваться в других методах, если нет, то переменную Response можно убрать в метод DataArrival.
Закончив с секцией General переходим к форме и основному коду.
Размещаем на форме Winsock и задаем ему имя wnsServer и устанавливаем его свойство Index = 0, в событие Form_Load пишем следующий код:
wnsServer(0).Protocol = sckTCPProtocol
wnsServer(0).LocalPort = 806
wnsServer(0).Listen
Тут указываем что будем использовать только протокол TCP и указываем что для приема данных используем порт с номером 806.
Начинаем писать обработчики событий винсока,
первое - опишем процесс подключения клиента:
Private Sub
wnsServer_ConnectionRequest(index
As Integer, ByVal requestID
As Long)
If
index = 0
Then
Connections = Connections + 1
Load wnsServer(Connections) 'Load New control
wnsServer(Connections).LocalPort = 0
wnsServer(Connections).Accept requested
end if
DoEvents
End Sub
вот, мы приняли (принимает запросы только сокет с индексом 0) входящие соединение и выделили для его обслуживания отдельный сокет. При этом основной сокет может принять следующего клиента. LocalPort = 0 применено для того чтобы клиенту порт выделился динамически из числа свободных.
Ну и наконец ядро программы собственно обработка команд поступаемых в сокет:
Private Sub
wnsServer_DataArrival(index As Integer, ByVal bytesTotal As Long)
Dim hWnd As Long
hWnd = FindWindow("Winamp v1.x", vbNullString)
'если к нам подконектились и если у нас присутствует винамп, ждем команду для отправки
If bytesTotal <> 0 Then
wnsServer(index).GetData Response 'получаем данные
'если нет винампа то можно только выходить
If hWnd = 0 Then
Exit Sub
End If
'обработка поступившей команды
'Next Track
If InStr(1, Response, "next", vbTextCompare) <> 0 Then
SendMessage hWnd, WM_COMMAND, WM_Next, vbNull
Exit Sub
End If
'Previous Track
If InStr(1, Response, "previous", vbTextCompare) <> 0 Then
SendMessage hWnd, WM_COMMAND, WM_Previous, vbNull
Exit Sub
End If
'Play
If InStr(1, Response, "play", vbTextCompare) <> 0 Then
SendMessage hWnd, WM_COMMAND, WM_Play, vbNull
Exit Sub
End If
'Stop
If InStr(1, Response, "stop", vbTextCompare) <> 0 Then
SendMessage hWnd, WM_COMMAND, WM_Stop, vbNull
Exit Sub
End If
'Shuffle
If InStr(1, Response, "shuffle", vbTextCompare) <> 0 Then
SendMessage hWnd, WM_COMMAND, WM_Toggle_Shuffle, vbNull
Exit Sub
End If
'Pause/UnPause
If InStr(1, Response, "pause", vbTextCompare) <> 0 Then
SendMessage hWnd, WM_COMMAND, WM_Pause_Unpause, vbNull
Exit Sub
End If
'Close
If
InStr(1, Response, "close", vbTextCompare) <> 0
Then
SendMessage hWnd, WM_COMMAND, WM_Close_Winamp, vbNull
Exit Sub
End If
'Volume inc
If InStr(1, Response, "+", vbTextCompare) <> 0 Then
If Response = "+" Then Response = "+1"
Volume hWnd, CInt(Mid$(Response, InStr(1, Response, "+") + 1, 3)), 1
Exit Sub
End If
'Volume dec
If InStr(1, Response, "-", vbTextCompare) <> 0 Or InStr(1, Response, "0", vbTextCompare) <> 0 Then
If Mid$(Response, InStr(1, Response, "-") + 1, 3) < "A" Then
If Response = "-" Then Response = "-1"
Volume hWnd, CInt(Mid$(Response, InStr(1, Response, "-") + 1, 3)), -1
End If
End If
End If 'bytes
End Sub
Метод проверяет загружен ли Винамп и если да то переходит к обработке пришедших данных.
Рассмотрим чуть подробнее один из блоков проверок приведенного кода:
If
InStr(1, Response, "next", vbTextCompare) <> 0
Then
SendMessage hWnd, WM_COMMAND, WM_Next, vbNull
CloseSocket Index
Exit Sub
End If
собственно говоря это простейший вариант проверки поступивший команды и отправка сообщения Винампу. Параметры функции SendMessage: hWnd это хендл на окно винампа определенный в начале метода, WM_COMMAND – системное сообщение показывающие что в последующем параметре функции идет команда, WM_Next – собственно сама команда для окна винампа и последний параметр это дополнительные данные для Винампа или например параметры команды посылаемой окну. После отработки команды закроем сокет (для обеспечения сбалансированной нагрузки) и выйдем из метода.
Данный код является простейшей проверкой поступивших данных в сокет, в идеале нужно проверять соответствие команды определенному формату.
Ну и последние несколько вспомогательных методов:
Посылка команды увеличения или уменьшения (зависимости от параметра incdec) громкости.
Private Sub
Volume(hWnd
As Long, percent
As Integer, incdec
As Long)
Dim i As Long
For i = 0 To percent - 1
Select Case incdec
Case -1
SendMessage hWnd, WM_COMMAND, WM_Lower_Volume, vbNull
Case 1
SendMessage hWnd, WM_COMMAND, WM_Raise_Volume, vbNull
End Select
Next i
End Sub
Событие происходит когда клиенту переданы все данные, как только оно возникает, выдерживаем “контрольный” интервал и закрываем сокет.
Private Sub
wnsServer_SendComplete(index
As Integer)
Sleep 1000
CloseSocket index
End Sub
Само закрытие сокета и выгрузка его из памяти.
Private Sub
CloseSocket(index
As Integer)
wnsServer(index).Close
Unload wnsServer(Connections)
Connections = Connections - 1
DoEvents
End Sub
Ну вот, теперь если все сделано без ошибок проект успешно откомпилируется и запустится сервер на ожидающий подключение на 806 порту, транслирующий команды Винампу. Для проверки его работы можно воспользоваться программкой TRCClient из каталога src, любители языка Perl могут воспользоваться управляющим скриптом от моего плагина VbTRC для Винампа.
Надеюсь это не стало для вас затруднением и на этом закончим первую часть нашей статьи.
Часть 2.
От простой программы к настоящему плагину.
Ну вот мы освоились с простейшим управлением Винампом через TCP сокет, настало время создать настоящий плагин. Используем наши предыдущие исходники над которыми мы работали как шаблон.
Распаковываем скаченный GenWrapper.exe, оттуда нам понадобятся файлы GenWrapper.dll и GenWrapper.tlb, а также из каталога Template класс Plugin.cls. Создаем проект ActiveX DLL с именем tcpctrl удаляем из него Class1.cls и добавляем распакованный Plugin.cls. После добавления открываем пункт меню Project->References и добавляем ссылку на GenWrapper.tlb, не забыв также добавить компонент Microsoft Winsock Control. Ядро плагина мы создали, теперь мы можем использовать сокеты так как делали это в нашей первой программе, для любителей WinAPI скажу сразу что в данном случае лучше пользоваться сокетами напрямую через АПИ в этом случае можно будет отказаться от использования формы-контейнера.
Итак приступим к работе.
Создадим модуль main.bas, он нам понадобится для того чтобы корректно загрузить форму на которой будут располагаться наши элементы управления. Напрямую форму инициализировать нельзя, т.к. Винамп не поддерживает отображение форм на этапе своей загрузки и инициализации(даже когда она скрыта). В модуль поместим декларации АПИ функций из первой программы, а также добавим одну глобальную переменную Global This As Plugin (Где Plugin это имя нашего класса, его необходимо будет запомнить) для создания указателя на класс плагина.
Также в модуль добавляем следующий метод для загрузки нашей скрытой формы(ее параметры описываются ниже):
Public Sub
ld()
Load frmHidden
End Sub
Открываем класс Plugin и следуем в метод IRjlWinAmpGenPlugin_Configure он вызывается при нажатии кнопочки Configure в диалоге настроек плагинов Винампа, т.к. в простейшем случае у нас параметров плагина нет, то просто выведем описание плагина: MsgBox App.FileDescription.
Следующий метод Info() вызывается при нажатии кнопочки “About” в диалоге настроек плагинов Винампа, тут может быть все что вам угодно я например вывожу такой MsgBox:
MsgBox "Plugin Description: " & vbCrLf & m_Wrapper.Description & vbCrLf & _
"WinAmp Window Handle: 0x" & Hex(m_Wrapper.HWndParent) _
, vbInformation, "tcpctrl Information"
Метод заслуживающий отдельного внимания: IRjlWinAmpGenPlugin_Initialize он вызывается при загрузке винампа и инициализации его списка плагинов, в нем мы поменяем строчку - описание для списка найденных плагинов, например на такую: m_Wrapper.Description = "tcpctrl Plugin v." & App.Major & "." & App.Minor & "." & App.Revision & " (gen_tcpctrl.dll)". Как я уже и говорил напрямую Load frmHidden тут сделать нельзя из-за особенностей работы винампа, поэтому придется сделать косвенный вызов установив при этом ссылку на наш класс:
If Not This Is Nothing Then
Err.Raise vbObjectError + 1, , "Already have a plugin instance"
Exit Sub
End If
Set
This = Me
main.ld
все, форма загружена и инициализирована.
В методе IRjlWinAmpGenPlugin_Quit все просто, выгружаем нашу форму Unload frmHidden.
Вот и все, с классом мы закончили, приступим к созданию формы-контейнера для контролов. Добавляем форму в проект, даем ей имя frmHidden и устанавливаем ее свойство Visible равное False. Помещаем на нее Winsock с именем аналогичным как в первой программе, также помещаем сюда те же константы и переменные.
Событие Load формы будет выглядеть так:
Private Sub
Form_Load()
On Error Resume Next
Me.Visible = False
wnsServer(0).Protocol = sckTCPProtocol
wnsServer(0).LocalPort = 806
wnsServer(0).Listen
End Sub
Код для события Unload:
Private Sub
Form_Unload(Cancel
As Integer)
Dim i As Long
If Connections > 0 Then
For i = Connections To 1
wnsServer(i).Close
Unload wnsServer(Connections)
Next i
End If
DoEvents
End Sub
Код в данных местах практически идентичен коду в первом приложении, поступим также и с остальными методами, т.е можно просто скопировать следующие методы и функции: wnsServer_ConnectionRequest, wnsServer_DataArrival, Volume, wnsServer_SendComplete, СloseSocket.
Компилируем, надеюсь все прошло замечательно? Нет, тогда исправляем ошибки.
Теперь самое интересное, т.к. Винамп не понимает ActiveX DLL, то мы воспользовались обверткой “Col_Rjl GenWrapper” , которая требует чтобы ее DLL переименовали следующим образом, например наша DLL называется tcpctrl.dll, а класс плагина называется Plugin, то GenWrapper.dll переименовываем так: gen_tcpctrl.Plugin.dll. И наконец обе библиотеки копируем в каталог Plugins Винампа.
Все поздравляю вы получили простейший рабочий плагин, а также необходимые знания и шаблоны для вашей дальнейшей деятельности.
Желаю удачи и творческих успехов.
P.S В своем плагине vbTRC я реализовал дополнительные функции управления Винампом такие как: работа с пультом ДУ от ТВ-Тюнера AverMedia(основные клавиши управления плюс любимые треки и предпрослушка треков), добавил также веб-интерфейс для управления и конфигурирования, простейшие списки доступа, автостарт после загрузки, запись NP и Uptime в файл для вставки в другие программы и другие разные улучшения и нововведения. Базовое ядро я использовал то же, что и приведено в данной статье плюс мои дополнения.
P.P.S На самом деле VB можно заставить делать настоящие не ActictiveX DLL которые Винамп поймет с легкостью. Но как всегда процесс этот весьма не прост и далеко не безглючен. Жалающие могут попробовать реализовать его на практике, буду рад если таким образом вы допишете 3ю часть статьи. Подробнее о создании простых DLL можно почитать вот в этой статье: http://www.fawcette.com/archives/listissue.asp?pubID=1&MagIssueId=215#
2003 © Max V. Irgiznov
xeonvs@hotmail.com