Visual Basic, .NET, ASP, VBScript
 

   
 
Описание для автора не найдено
 
     
   
 

Данная статья расскажет вам о том, как можно создавать свои плагины для популярного мультимедиа плеера 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

функция возвращает хендл на окно с заданным классом и/или строкой заголовка;

  • Private Declare Function 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

 
     

   
   
     
  VBNet рекомендует