Пишем плеер для vk.com. Работа с API и BASS.DLL
Продолжаем работать с API сайта vk.com. Сегодня сделаем несколько рутинных задач, вроде вывода друзей в красивый список, вывода личного профиля и самое главное - начнём реализовывать проигрыватель музыки (ничего лучше я не придумал).
Часть 1. Парсинг личной информации.
Первым делом выведем личный профиль в более-менее красивом виде, что-то вроде этого:

Для вывода фото была написана отдельная процедура - getusersphoto(image: TImage), которая выводит фото юзера в компонент Image. Код процедуры:
Procedure getusersphoto (simage: TImage);
uses JPEG;
var
buf, s1: string;
i: integer;
image: tjpegimage;
ms: TMemoryStream;
begin
data.Text:= http.Get('https://api.vk.com/method/users.get?user_ids=USER_ID&fields=photo_max');
buf:= data.Text;
delete (buf, pos('.jpg', buf)+4, length(buf));
s1:= copy (buf, pos('"photo_max":"http:',buf)+13, length(buf));
for i := 1 to length(s1) do
begin
if s1[i] = '\' then delete(s1,pos(s1[i],s1),1);
end;
image:= tjpegimage.Create;
ms:= TMemoryStream.Create;
http.Get(s1, ms);
ms.Position := 0;
simage.AutoSize:= true;
image.LoadFromStream(ms);
simage.Picture.Graphic := image;
end;
В коде, думаю, всё понятно. Что может вызвать затруднение - помечено комментариями.
Для вывода имени, ID, статуса и прочего говнаих вещей, тупой парсинг одним словом, так же создана процедурка - procedure parsinfo;
Для работы процедуры нужна переменная типа info. Info - запись, содержащая следующие параметры:
type
info = record
name: string;
family: string;
pol: string;
city_index: string;
city: string;
sp: string;
id: string;
end;
Может кому-нибудь пригодиться код ниже, т.к. я тут парсю основные данные юзера, а процесс этот утомительный и скучный, так что пользуйтесь : )
var
tekinfo: info;
procedure TForm1.parsinfo;
var
buf, temp: String;
begin
data.Text:= http.Get('https://api.vk.com/method/users.get?user_ids='+userid.Text+'&fields=sex,city,has_mobile,relation');
buf:= Data.Text; //Хранитель времени и 1-ого запроса - переменная BUF
temp:= buf; //Переменная temp будет содержать копию 1-ого запроса, для парсинга из неё локальных данных
//id
temp:= buf;
delete (temp, pos(',"first_name"',temp), length(temp));
tekinfo.id:= copy(temp, pos('"uid":',temp)+6, length(temp));
//name
temp:= buf;
delete (temp, pos(',"last_name"',temp)-1, length(temp));
tekinfo.name:= copy (temp, pos('"first_name":"',temp)+14, length(temp));
//family
temp:= buf;
delete (temp, pos('","sex"',temp), length(temp));
tekinfo.family:= copy(temp, pos('"last_name":"',temp)+13, length(temp));
//city
temp:= buf;
delete (temp, pos('","has_mobile"',temp), length(temp));
tekinfo.city_index:= copy (temp, pos('"city":"',temp)+8, length(temp));
data.Text:= http.get('https://api.vk.com/method/getCities?api_id=ID_ПРИЛОЖЕНИЯ&'+md5(tekinfo.id+'api_id=ID_ПРИЛОЖЕНИЯ'+'cids='+tekinfo.city_index+'v=2.0')+'&v=2.0&cids='+tekinfo.city_index);
temp:= data.Text;
delete (temp, pos('"}]',temp), length(temp));
tekinfo.city:= copy (temp, pos('"name":"',temp)+8, length(temp));
//pol
temp:= buf;
delete (temp, pos(',"city"',temp), length(temp));
tekinfo.pol:= copy(temp, pos('"sex":',temp)+6, length(temp));
if tekinfo.pol = '1' then tekinfo.pol:= 'Женский' else tekinfo.pol:= 'Мужской';
//SP
temp:= buf;
delete (temp, (pos('","relation_partner"',temp)or pos('"}]}',temp)), length(temp));
tekinfo.sp:= copy(temp, pos('"relation":"',temp)+12, length(temp));
case strtoint(tekinfo.sp) of
1: tekinfo.sp:= 'не женат/не замужем';
2: tekinfo.sp:= 'есть друг/есть подруга';
3: tekinfo.sp:= 'помолвлен/помолвлена';
4: tekinfo.sp:= 'женат/замужем';
5: tekinfo.sp:= 'всё сложно';
6: tekinfo.sp:= 'в активном поиске';
7: tekinfo.sp:= 'влюблён/влюблена';
end;
form1.lbname.Caption:= tekinfo.name+' '+tekinfo.family;
form1.lbpol.Caption:= tekinfo.pol;
form1.lbgorod.Caption:= tekinfo.city;
form1.lbSP.Caption:= tekinfo.sp;
end;
Ничего сложно тут нет, обычный парсинг обычными строковыми функциями. Кто-то скажет: "Почему ты не использовал регулярки?" А потому что не люблю я их юзать, когда не так уж много нужно парсить. + лишние модули - не есть хорошо. Так что обошлось и без regExpr.
По коду - самое большое затруднение (да-да, даже в парсинге появились трудности) вызвал у меня парсинг города. Т.к. в основном запросе я получил лишь код города, то подумал - "Заюзаю-ка я ещё 1-у функцию, которая вернёт по коду города его название". Заюзал - функция getCities . Но и с ней не всё гладко - ей, в параметрах запроса нужно передать, цитирую:
sig
Подпись запроса по стандартной схеме.
Ну ладно, идём на эту самую "стандартную схему". Цитирую оттуда:
Как создавать подпись запроса?
Параметр sig равен md5 от контактенации следующих строк:
viewer_id – id текущего пользователя, переданный приложению посредством flashVars/GET запроса. (При запросе с сервера viewer_id указывать не нужно)
пар "parameter_name=parameter_value", расположенных в порядке возрастания имени параметра (по алфавиту).
секрета secret полученного через flashVars / GET запрос, или, если метод вызывается с сервера - секрета приложения api_secret (секрет Вы можете менять при редактировании страницы приложения).
Ну и всё это дело, конечно же, с MD5. Как же не зашифровать город алгоритмом, ведь город - это почти логин с паролем! MD5 модуль можно будет скачать где-то в конце статьи, либо прикреплённым, либо выложенным на файлообменник.
Вопрос - зачем так ебаться заморачиваться ИМЕННО с городом - остаётся открытым. Ну а запрос, по феншую, я составил - его можете наблюдать в коде выше.
В общем, данная процедурка выводит основные данные юзера в заданные на форме элементы. Так что с первой частью (а ей был парсинг) задания я справился. Осталось "всего ничего" - научиться воспроизводить звуковые файлы с vk.com.
Часть 2. BASS.DLL + vk.
Итак, как и было задумано - будем сейчас пилить что-то вроде плеера. Для воспроизведения звука я юзаю библиотеку bass.dll. Как её устанавливать - есть в сети, это я описывать не буду.
Кидаем в uses bass и инициаллизируем либу - в OnCreate формы пишем:
if (HIWORD(BASS_GetVersion) <> BASSVERSION) then //Проверка версии библиотеки
begin
MessageBox(0,'An incorrect version of BASS.DLL was loaded',nil,MB_IConerror);
Halt;
end;
if not BASS_Init(-1, 44100, 0, Handle, nil) then // Initialize audio - default device, 44100hz, stereo, 16 bits
ShowMessage('Error initializing audio!');
BASS_Init(-1, 44100, 0, Handle, nil) - собственно вот в этой строчке и есть основная настройка библиотеки. Функция инициализирует звуковой поток.
В первом параметре сидит девайс, значения могут быть 0 - первое устройство, -1 - по умолчанию, -2 - без звука. Далее частота - по-умолчанию лучше указывать 44100. А далее различные флаги.
Итак, проинициаллизировали, что дальше? А дальше немного ознакомимся с тем, как работает либа. Сначала можно провести небольшой эксперимент, и просто научиться воспроизводить звук с пк.
Напишем небольшую процедуру:
var Channel: HStream;
procedure BasicPlay(FileName: String);
var
FFileName: PChar;
begin
if not FileExists(FileName) then Exit;
FFileName:= PChar(FileName);
if Channel <> 0 then
begin
bass_ChannelStop(Channel);
bass_StreamFree(Channel);
Channel:= 0;
end;
Channel:= Bass_StreamCreateFile(False, FFileName, 0, 0, 0 {$IFDEF UNICODE} or BASS_UNICODE {$ENDIF});
if Channel =0 then begin
MessageBox(0,'Ошибка загрузки файла',0, Mb_ok);
end;
if not Bass_ChannelPlay(Channel, False) then
MessageBox(0, 'Ошибка воспроизведения файла',0, Mb_ok);
end;
Ну и вызываем её так:
if form1.sOpenDialog1.Execute then begin
BasicPlay(sopendialog1.FileName);
Собственно основная строчка - вот:
Channel:= Bass_StreamCreateFile(False, FFileName, 0, 0, 0 {$IFDEF UNICODE} or BASS_UNICODE {$ENDIF});
function BASS_StreamCreateFile(mem: BOOL; f: Pointer; offset, length, flags: DWORD): HSTREAM;
Параметры:
mem - если TRUE, то файл в оперативке. Если FALSE - то на диске. f - имя файла (если он на диске).
offset - смещение, с которого надо начинать. Обычно я начинаю с начала, но если у тебя другое мнение - сообщи его этому аргументу.
length - необходимое количество данных. Если ты хочешь использовать все до конца файла, то просто ставь 0.
flags - ставь 0.
Функция возвращает переменную типа HSTREAM, которая и есть хэндл новорожденного потока.
Потоку HStream передаём файл, после чего функция его создаёт, а затем с помощью процедуры Bass_ChannelPlay воспроизводим. Всё просто. Попробуй всё это проделать, и воспроизвести файл.
Теперь попробуем воспроизвести файл из сети, ссылкой. Собственно выражаю благодарность разработчикам bass.dll за то, что позаботились об такой фиче, как воспроизведение трека по ссылке.
Настройки оставляем те же (инициализация и прочее), но допишем лишь 1-у функцию :
var channel: HStream;
procedure BasicPlay(FileName: AnsiString);
begin
Channel:= BASS_StreamCreateURL(PAnsiChar(FileName),0,0,nil,0); //Создаём файл, функция аналогична предыдущей, так что без комментариев.
if not Bass_ChannelPlay(Channel, False) then //Воспроизводим
MessageBox(0, 'Ошибка воспроизведения файла',0, Mb_ok);
end;
Ну что? Вот и всё, в принципе, готово. Осталось самая малость - спарсить треки и ссылки к ним. Я этого не хотел, но пришлось подключить регулярки. Так же нужно создать 3 массива : в 1-ом у нас артист, во втором название трека, в третьем - линк на песню.
Наверное назрел вопрос, откуда парсить? Применим для этого api-функцию audio.get - ей в качестве параметров нужно передать:
owner_id - id юзера или группы (если группа, то перед id ставим минус (-) )
need_user - может быть 0 и 1. Если 1 - вернёт информацию о пользователе, загрузившим песню, если 1 - нет. Нам эта инфа нахер не сдалась, так что ставим 0.
v - версия протокола api-функций. Сейчас актуальная 5.5
И access_token - токен. Наконец-то он пригодился :)
Видим в каких тегах название, в каких название, а в каких линк. Всё, как я уже сказал, будем парсить в массивы. Ладно, хватит разглагольствовать, привожу код процедурки:
procedure TForm1.getaudio;
var
i,j: integer;
linktek: string;
begin
i:=0;
data.Text:= http.Get('https://api.vk.com/method/audio.get?owner_id='+tekinfo.id+'&need_user=0&v=5.5&access_token='+token); //Отправляем api-запрос
reg:= TRegExpr.Create; //Инициализируем переменную типа TregExpr (регулярные выражения. перед этим добавить нужно в uses модуль регулярок - RegExpr).
reg.InputString:= data.Text; //Загружаем текст для регулярки
reg.Expression:= '"artist":"(.*?)","title":"(.*?)","duration":(.*?),"url":"(.*?)"'; //Сама регулярка
if reg.Exec(data.Text) then //Если тебе не понятен алгоритм работы с регулярными - пиши в комментах или ищи в google.com
repeat
inc(i);
artist[i]:=reg.Match[1];
nazv[i]:= reg.Match[2];
linktek:= reg.Match[4];
for j := 1 to length(linktek) do //Ссылка все видели в каком формате. Там лишний символ - '\'. Удаляем его.
begin
if linktek[j] = '\' then
delete (linktek, pos(linktek[j],linktek), 1);
end;
link[i]:= linktek;
until not reg.ExecNext;
reg.Free;
end;
На этом, с гордостью заверяю, парсинг завершён. Осталось вывести ссылки в ListBox:
var i: integer;
begin
for i := 0 to High(artist)-1 do
form1.ListBox1.Items[i]:= artist[i]+' - '+nazv[i];
end;
И по нажатию на ссылку запускать песню:
В событии onmousedown ListBox'а:
procedure TForm1.ListBox1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var i: integer;
begin
if ListBox1.ItemIndex>=0 then
begin
i:= ListBox1.ItemIndex;
BasicPlay(link[i]);
end;
end;
Ну тут, думаю, всё понятно. Не комментирую. Итак, подведём итог 2-х статей.
Наша программа умеет:
1) Авторизироваться легальными методами, которые vk одобряет
2) Парсить профиль такими же методами
3) Выводить и запускать музыку юзера
На этом вторая статья окончена, всем спасибо, все свободны.
Мне осталось лишь допилить дизайн, обработать все ошибки в коде и оптимизировать сей код. Готовый исходник этой статьи я выложу уже в следующей :) По-поводу следующей, если есть идеи - пишите, буду рад)
Спасибо за внимание!