Трансляция проигрываемой в AIMP музыки в статус ВКонтакте

от
Java    vk api, ownlang, java native interface

А также:
  - Java Native Interface - взаимодействие Java c нативным кодом.
  - Создание Dll-библиотеки, использующей AIMP Remote API.
  - Создание модуля для OwnLang.
  - Взаимодействие с VK API.

Генерирование заголовочного файла для Си
Для взаимодействия Java с Си, нужно определиться с сигнатурой нативных методов и расположением классов. Предположим, что класс для взаимодействия с Dll-библиотекой будет иметь имя AIMP, находиться в пакете aimpremote и иметь метод currentTrack, который возвращает массив. Напишем такой класс.
  1. package aimpremote;
  2.  
  3. public final class AIMP {
  4.     public static native String[] currentTrack();
  5. }
Скомпилируем AIMP.java
javac aimpremote\AIMP.java

И воспользуемся утилитой для создания заголовочных файлов javah
javah aimpremote.AIMP

В результате получим файл aimpremote_AIMP.h с таким содержимым:
  1. /* DO NOT EDIT THIS FILE - it is machine generated */
  2. #include <jni.h>
  3. /* Header for class aimpremote_AIMP */
  4.  
  5. #ifndef _Included_aimpremote_AIMP
  6. #define _Included_aimpremote_AIMP
  7. #ifdef __cplusplus
  8. extern "C" {
  9. #endif
  10. /*
  11.  * Class:     aimpremote_AIMP
  12.  * Method:    currentTrack
  13.  * Signature: ()[Ljava/lang/String;
  14.  */
  15. JNIEXPORT jobjectArray JNICALL Java_aimpremote_AIMP_currentTrack
  16.   (JNIEnv *, jclass);
  17.  
  18. #ifdef __cplusplus
  19. }
  20. #endif
  21. #endif


Написание Dll-библиотеки
Создадим новый проект Win32 с именем LibAIMPRemote.
aimpremote_1.png
aimpremote_2.png
Добавим заголовочные файлы: сгенерированный aimpremote_AIMP.h и apiRemote.h из AIMP SDK (скачать можно с официального сайта).
aimpremote_3.png
Осталось в настройках проекта создать конфигурацию для компиляции 64-битной версии библиотеки и подключить заголовочные файлы из JDK.
aimpremote_4.png aimpremote_5.png

Теперь реализуем функцию currentTrack. Создадим массив из трёх элементов (исполнитель, название, альбом) и заполним его данными из MemoryMappedFile.
  1. #include "stdafx.h"
  2. #include "aimpremote_AIMP.h"
  3. #include "apiRemote.h"
  4.  
  5. #define WRITE_TO_STRING_ARRAY(i, x) \
  6.     memcpy(charBuffer, pBuff, (x) * 2); \
  7.     charBuffer[x] = '\0'; \
  8.     pBuff += (x); \
  9.     env->SetObjectArrayElement(info, (i), env->NewString(charBuffer, (x)));
  10.  
  11. JNIEXPORT jobjectArray JNICALL Java_aimpremote_AIMP_currentTrack
  12.   (JNIEnv *env, jclass jobj) {
  13.     HANDLE hAIMP = OpenFileMapping(FILE_MAP_READ, false, AIMPRemoteAccessClass);
  14.     if (!hAIMP) {
  15.         return NULL;
  16.     }
  17.  
  18.     jobjectArray info = env->NewObjectArray(3, env->FindClass("java/lang/String"), 0);
  19.  
  20.     PAIMPRemoteFileInfo aimpFileInfo = (PAIMPRemoteFileInfo)
  21.         MapViewOfFile(hAIMP, FILE_MAP_READ, 0, 0, AIMPRemoteAccessMapFileSize);
  22.  
  23.     if (aimpFileInfo) {
  24.         LPWSTR pBuff = (LPWSTR)((PBYTE)aimpFileInfo + sizeof(TAIMPRemoteFileInfo));
  25.         jchar charBuffer[256];
  26.  
  27.         WRITE_TO_STRING_ARRAY(2, aimpFileInfo->AlbumLength);
  28.         WRITE_TO_STRING_ARRAY(0, aimpFileInfo->ArtistLength);
  29.         pBuff += aimpFileInfo->DateLength
  30.             + aimpFileInfo->FileNameLength
  31.             + aimpFileInfo->GenreLength;
  32.         WRITE_TO_STRING_ARRAY(1, aimpFileInfo->TitleLength);
  33.     }
  34.  
  35.     UnmapViewOfFile(aimpFileInfo);
  36.  
  37.     return info;
  38. }
Если ругается на тип AIMPRemoteAccessClass, смените его в apiRemote.h на const LPCWSTR AIMPRemoteAccessClass = L"AIMP2_RemoteInfo";
Собрав проект, получим LibAIMPRemote.dll для x86 и x64.


Создание модуля для OwnLang
Сначала проверим работоспособность библиотеки, модифицировав класс AIMP.
  1. package aimpremote;
  2.  
  3. public final class AIMP {
  4.     static {
  5.         System.loadLibrary("LibAIMPRemote_x" + System.getProperty("sun.arch.data.model"));
  6.     }
  7.  
  8.     public static void main(String[] args) {
  9.         final String[] track = currentTrack();
  10.         System.out.println(track[0]);
  11.         System.out.println(track[1]);
  12.         System.out.println(track[2]);
  13.     }
  14.  
  15.     public static native String[] currentTrack();
  16. }
Для вызова нужной версии библиотеки пришлось переименовать Dll в LibAIMPRemote_x32.dll и LibAIMPRemote_x64.dll. Компилируем.
javac aimpremote\AIMP.java

Запустим с указанием пути к нативным библиотекам.
java -Djava.library.path=. aimpremote.AIMP

aimpremote_6.png

Работает! Теперь перейдём к созданию модуля. OwnLang берёт модули из пакета com.annimon.ownlang.lib.modules, поэтому создадим в этом пакете класс aimp, который реализует интерфейс Module и метод public void init(), в котором добавим одну единственную функцию currentTrack.
  1. package com.annimon.ownlang.lib.modules;
  2.  
  3. import aimpremote.AIMP;
  4. import com.annimon.ownlang.lib.ArrayValue;
  5. import com.annimon.ownlang.lib.Functions;
  6.  
  7. public final class aimp implements Module {
  8.  
  9.     @Override
  10.     public void init() {
  11.         Functions.set("currentTrack", args -> {
  12.             return ArrayValue.of(AIMP.currentTrack());
  13.         });
  14.     }
  15. }
Для компиляции этого класса нужен OwnLang.jar.
javac -cp .;OwnLang.jar com\annimon\ownlang\lib\modules\aimp.java

Для удобства, соберём классы модуля в jar-файл.
jar cvf aimp.jar aimpremote\AIMP.class com\annimon\ownlang\lib\modules\aimp.class

Запустим OwnLang в режиме REPL.
java -Djava.library.path=. -cp ".;aimp.jar;OwnLang.jar" com.annimon.ownlang.Main -r

aimpremote_7.png


Трансляция статуса ВКонтакте
Алгоритм работы такой: каждые 2-3 минуты получаем данные о проигрываемом треке. Ищем этот трек в ВК методом audio.search. Если аудиозапись найдена, обновляем статус методом audio.setBroadcast.
  1. use "std"
  2. use "http"
  3. use "json"
  4. use "functional"
  5. use "aimp"
  6.  
  7. access_token = "ваш токен"
  8.  
  9. def toParams(obj) {
  10.   str = ""
  11.   for k, v : obj
  12.     str += k + "=" + v + "&"
  13.   return str
  14. }
  15. def createRawUrl(method, params, token = "") = "https://api.vk.com/method/"+method+"?v=5.52&"+params+"access_token="+token
  16. def createUrl(method, params, token = "") = createRawUrl(method, toParams(params), token)
  17. def invoke(method, params, callback) = http(createUrl(method, params, access_token), callback)
  18.  
  19. def searchAudio(query, callback) = invoke("audio.search", {"q": query, "count": 1}, callback)
  20. def setBroadcast(audio) = invoke("audio.setBroadcast", {"audio": audio}, IDENTITY)
  21.  
  22. def update() {
  23.   extract(artist, title, ) = currentTrack()
  24.   if (length(artist) == 0 || length(title) == 0) return 0
  25.   println artist + " - " + title
  26.  
  27.   searchAudio(artist + " - " + title, def(r) {
  28.     data = jsondecode(r)
  29.     data = data.response
  30.     if (data.count == 0) return 0
  31.     audio = data.items[0]
  32.     setBroadcast("" + audio.owner_id + "_" + audio.id)
  33.   })
  34. }
  35.  
  36. thread(def() {
  37.   while(true) {
  38.     update()
  39.     sleep(2 * 60 * 1000)
  40.   }
  41. })

Для работы нужен токен с правами на изменение статуса и поиск музыки.

Запускаем программу
java -Djava.library.path=. -cp ".;aimp.jar;OwnLang.jar" com.annimon.ownlang.Main -f vk_audio_broadcast.own

  aimpremote_8.png



Скачать:
LibAIMPRemote.zip (VS 2012)
aimp_vk_status.zip
  • +12
  • views 6683