Пример интеграции Last.FM API и VK.COM API для обновления содержимого своего плеера

  • Tutorial
Всем привет!

Думаю, большинство из вас знакомы с социальными сетями "Вконтакте" и "Last.fm". В данной статье мы рассмотрим маленький пример интеграции между API обоих сервисов. Если быть точнее, будем получать список любимых треков с last.fm, искать бинарный контент в ВК и сохранять на HDD.
Статья будет скорее интересна новичкам, чем профессионалам, тем не менее, требует определенной технической подготовки. Тестовый проект реализован средствами Java.

Если читателю интересно, что из этого получилось, прошу пожаловать под кат.



Дисклаймер


В наше страшное время, когда корпорации пытаются отсудить друг у друга миллиард за несколько строк кода. Когда получить проблему можно на «раз-два», я просто вынужден сказать следующее.
Программная реализация в рамках данной статьи имеет сугубо ознакомительные побуждения и не преследует никаких коммерческих мотивов.

Intro


Не буду долго затягивать. Определимся с целями.
  1. Получить список любимых треков на last.fm
  2. Найти треки на серверах vk.com
  3. Скачать треки на плеер


Но прежде всего, необходимо получить доступ к API обоих сервисов.
На Хабре уже были статьи, например, эта. Не буду описывать все подробно, поэтому сразу ссылки:
API ключ last.fm
API ключ vk.com
Далее, внимательно изучаем спецификацию (last.fm, vk.com) и определяемся, чего же именно мы хотим.
Собственно, нам нужны методы audio.search и user.getLovedTracks.

Тестовые запросы


После того, как приложение уже создано, необходимо проверить с чем мы будем работать.
С last.fm все просто. Делаем обычный html запрос в строке браузера:
http://ws.audioscrobbler.com/2.0/?method=user.getlovedtracks&user=USER&api_key=API_KEY
Если все параметры были введены верно, то можно будет увидеть xml'ку, в которой помимо названия трека, а также его авторов, можно встретить всякую дополнительную информацию.
С vk.com все практически также, за тем лишь исключением, что без параметра access_token мы не сможем получить доступ к аудиозаписям.
Делаем запрос:
http://api.vk.com/oauth/authorize?client_id=ID&redirect_uri=http://api.vk.com/blank.html&scope=audio&display=page&response_type=token
В итоге, редирект отправит нас на ссылку вида:
http://api.vk.com/blank.html#access_token=XYZ&expires_in=86400&user_id=ID
XYZ — это, что нам нужно.
Теперь запрос вида:
https://api.vk.com/method/audio.search.xml?q=AC/DC%20-%20Highway%20to%20Hell&access_token=XYZ должен выдать xml'ку, где можно увидеть физический адрес бинарного музыкально файла.
Если все «взлетело», можем идти дальше.

Реализация ПО


Заведем себе файлик Configuration.properties, где будут храниться текущие настройки.
last_fm_api=
last_fm_user=
last_fm_ws_address=http://ws.audioscrobbler.com/2.0/
vk_com_access_token=
vk_com_ws_address=https://api.vk.com/method/

Для простоты, решил на GUI на заморачиваться. Данные удобно хранить в коллекции POJO бинов такого вида:
public class LovedTrackBean {
	
	public LovedTrackBean(String trackName, String trackArtist) {
		this.trackName = trackName;
		this.trackArtist = trackArtist;
	}
	
	//GETTERS & SETTERS

    public String toString() {
		return trackArtist + " - " + trackName;
	}
	
	private String trackName = null;
	private String trackArtist = null;
	private String trackURL = null;
}

Теперь нам нужно воспользоваться API last.fm и получить список любимых треков.
Примерно, вот так:
public class TrackLover {

	public TrackLover() {
		StringBuffer buff = new StringBuffer();
		buff.append(PropertyLoader.getProperties().get(PropertyLoader.LAST_FM_WS_ADDRESS));
		buff.append("?method=");
		buff.append(LAST_FM_API_METHOD);
		buff.append("&user=");
		buff.append(PropertyLoader.getProperties().get(PropertyLoader.LAST_FM_USER));
		buff.append("&api_key=");
		buff.append(PropertyLoader.getProperties().get(PropertyLoader.LAST_FM_API));
		connectToUrl = buff.toString();
	}
	
	public List<LovedTrackBean> getLovedTracks() throws IOException, ParserConfigurationException, SAXException {
		if(lovedTracks == null) {
				URL url = new URL(connectToUrl);
				URLConnection conn = url.openConnection();
				
				DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
				DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
				Document doc = docBuilder.parse(conn.getInputStream());
				doc.getDocumentElement().normalize();
				
				NodeList listOfLovedTracks = doc.getElementsByTagName("track");
				int tracksTotal = listOfLovedTracks.getLength();
				lovedTracks = new ArrayList<LovedTrackBean>(tracksTotal);
				
//...
				//Парсим содержимое
//...
				
				System.out.println("######### AFTER LAST.FM RESPONSE #########");
				for(LovedTrackBean track: lovedTracks) {
					System.out.println(track.toString());
				}
				
			return lovedTracks;
		} else {
			lovedTracks = new ArrayList<LovedTrackBean>(1);
			lovedTracks.add(new LovedTrackBean("Wish you were here", "Pink Floyd"));
			return lovedTracks;
		}
	}
	
	private String connectToUrl = null;
	private List<LovedTrackBean> lovedTracks = null;
	
	public static final String LAST_FM_API_METHOD = "user.getlovedtracks";
}

Данный код должен вернуть нам коллекцию с любимыми треками.
Следующая остановка — vk.com.
Попытаемся по парам «Автор — Композиция» получить аудиозапись. Для простоты, возьмем первую попавшуюся.
public class TrackURLFinder {
	public TrackURLFinder(LovedTrackBean lovedTrack) {
		this.lovedTrack = lovedTrack;
		StringBuffer buff = new StringBuffer();
		buff.append(PropertyLoader.getProperties().get(PropertyLoader.VK_COM_WS_ADDRESS));
		buff.append(VK_COM_API_METHOD);
		buff.append("?q=");
		buff.append(lovedTrack.toString());
		buff.append("&");
		buff.append("access_token=");
		buff.append(PropertyLoader.getProperties().get(PropertyLoader.VK_COM_ACCESS_TOKEN));
		connectToUrl = buff.toString();
		connectToUrl = connectToUrl.replaceAll(" ", "%20");
		System.out.println(connectToUrl);
	}
	
	public void addUrlToLovedTrack() throws SAXException, IOException, ParserConfigurationException {
		if(lovedTrack != null) {
				URL url = new URL(connectToUrl);
				URLConnection conn = url.openConnection();
				
				DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
				DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
				Document doc = docBuilder.parse(conn.getInputStream());
				doc.getDocumentElement().normalize();
				
				NodeList audioList = doc.getElementsByTagName("audio");
				Node currentNode = audioList.item(0);
				
				Element currentElement = (Element) currentNode;
				
				NodeList urlList = currentElement.getElementsByTagName("url");
				Element urlElement = (Element) urlList.item(0);
				NodeList urlFormattedList = urlElement.getChildNodes();
				
				String urlAddressOfTheMP3File = urlFormattedList.item(0).getNodeValue().trim();
				
				lovedTrack.setTrackURL(urlAddressOfTheMP3File);
				System.out.println(urlAddressOfTheMP3File);
		}
	}
	
	private String connectToUrl = null;
	private LovedTrackBean lovedTrack = null;
	
	public static final String VK_COM_API_METHOD = "audio.search.xml";
}

И, наконец, получаем контент:
public class MP3MusicContentDownloader {
	
	public MP3MusicContentDownloader(String urlAddress, String fileName) throws MalformedURLException {
		this.fileName = fileName;
		url = new URL(urlAddress);
	}

	public void download() throws IOException {
			conn = url.openConnection();
			
			System.out.println("Started downloading of " + fileName);
			
			String contentType = conn.getContentType();
			int contentLength = conn.getContentLength();
			if (contentType.startsWith("text/") || contentLength == -1) {
				throw new IOException("This is not a binary file.");
			}
			
			File folder = new File(DEFAULT_DIRECTORY_NAME);
			if (!folder.exists() || !folder.isDirectory()) {
				folder.mkdir();
			}
			
			File binaryFile = new File(DEFAULT_DIRECTORY_NAME + File.separator + fileName);
			if (!binaryFile.exists()) {

				BufferedInputStream in = new BufferedInputStream(conn.getInputStream());
				byte[] data = new byte[contentLength];
				int bytesRead = 0;
				int offset = 0;
				while (offset < contentLength) {
					bytesRead = in.read(data, offset, data.length - offset);
					if (bytesRead == -1)
						break;
					offset += bytesRead;
				}
				in.close();

				if (offset != contentLength) {
					throw new IOException("Only read " + offset + " bytes; Expected " + contentLength + " bytes");
				}
				FileOutputStream out = new FileOutputStream(binaryFile);
				out.write(data);
				out.flush();
				out.close();
			} else {
				return;
			}
	}

	private String fileName = null;
	private URL url = null;
	private URLConnection conn = null;

	private static final String DEFAULT_DIRECTORY_NAME = "music";
}

На этом все. Запустить, надеюсь, не составит труда.

TODOs


Так как это всего лишь пробный пример, который, кстати, был выполнен в целях самообучения, почти уверен, что содержит некоторое количество багов/недочетов. Но тем не менее, пример на моих треках отработал. Конкретно в данном случае, можно было бы реализовать многопоточность, или, например, полное сканирование всех композиций избранного (сейчас собираются первые 50), или, например, проверка на валидность access_token'ов (вдруг, истек срок действия). Да много чего. Но, если вы уже поняли, смысл статьи был не в этом :)

Всем спасибо, у кого хватило терпения дойти до этих строк!
  • +25
  • 19.6k
  • 5
Share post

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 5

    +1
    Как-то изучал данный вопрос для реализации сервис с которого можно скачивать песни с правильно прописанными id3 тегами (Альбом, артист, название трека итд). Но отказался от данной затеи из-за следующих аспектов: 1. Правовой, никто не даст просто так качать песни и не платить за это. 2. Так как не во всех песнях лежащих в контакте правильно прописаны id3 то нужно их менять на компьютере клиена (а браузеры не дадут совершить такое).
      +1
      Забыл добавить, идея изначально заключалась в проверке определенного плейлиста в контакте, и при нахождении там новых песен предлагать скачать их пользователю.
        0
        С 1 — абсолютно согласен. Второй немного не понял. Имеется ввиду, что может выпасть не оригинал песни, а ее какой-нибудь ремикс? Если да, то это тоже, действительно, проблема. Но вот один плагин для Google Chrome решил этот нюанс по-своему. Но опять же, он позволяет только прослушивать, но не скачивать.
        +1
        делал для себя скрипт прослойку для поиска музыки в vk, с возможностью отметки какого хочешь количества треков с разных результатов поиска, все это скачивал на сервер, прописывал имена mp3 файлам и архивом отдавал на клиента, т.е. себя
          0
          А на чем скрипт был? На php или на JS? Там ведь адрес файла зависит от ip и если получишь адрес файла в JS и передашь его серваку для скачивания, то у сервака уже будет другой IP и файл не скачается.

      Only users with full accounts can post comments. Log in, please.