Pull to refresh

Настройка Торрент ТВ на Android

Наверное, многие из вас слышали о торрентах и p2p-сетях, но мало кто знает, что с их помощью можно передавать и потоковое видео, в том числе и ТВ-сигнал. Для этого под ПК есть замечательная программа AceStream, и не менее замечательный сайт torrentstream.tv. На мобильных устройствах дела с этим обстоят куда печальнее. О том, как я пытался настроить торрент ТВ на Android и что из этого вышло пойдет речь в этой статье.

Устанавливаем AcePlayer


Как я уже говорил, дела с p2p-телевидением на Android обстоят довольно плохо: в Play Market я не смог найти ни одного вменяемого приложения, справляющегося с поставленной задачей. Пришлось гуглить в интернете. На официальном форуме AceStream нашел ссылку на бета-версию AcePlayer под Android (x86, ARMv7).

Установил, запустил:



О'кей, гугл, где мне найти «Ace Stream content id»? Недолго погуглив, нашел один не очень хороший способ:



Спасибо, как-то не очень хочется каждый раз при переключении канала лезть в компьютер за Content ID. Значит, решил я, нужно составить свой плей-лист.

Составляем плей-лист


Итак, цель — сделать программу-парсер, которая в автоматическом режиме обходит все страницы с каналами на torrentstream и вытягивает оттуда Content ID. Писать решил на java с помощью регулярных выражений, так как изучать что-либо более подходящее времени не было.

Для начала сделаем простой обходчик страниц с каналами:

public class ChannelParser {

	private static Pattern nextPagePattern = Pattern.compile("<a href=\"[^\"]+\">»</a>");
	private static Pattern chPattern = Pattern.compile("<a href=\"[^\"]+\" class=\"pm-title-link \" title=\"[^\"]+\">");

	public static void main(String[] args) throws IOException {
		List<Channel> channels = new ArrayList<>();
		URL cPage = new URL("http://torrentstream.tv/browse-vse-videos-1-date.html");
		while (cPage != null) {
			String cPageContent = urlContent(cPage);
			Matcher chMatcher = chPattern.matcher(cPageContent);
			while (chMatcher.find()) {
				channels.add(new Channel(chMatcher.group()));
			}
			cPage = nextPage(cPageContent);
		}
		//todo
	}

	private static String urlContent(URL page) throws IOException {
		BufferedReader reader = new BufferedReader(new InputStreamReader(page.openStream()));
		String inputLine;
		StringBuilder response = new StringBuilder();
		while ((inputLine = reader.readLine()) != null) {
			response.append(inputLine);
		}
		return new String(response.toString().getBytes("cp1251"), "utf8"); //Костыль, чтобы не было проблем с кодировкой
	}

	private static URL nextPage(String content) throws MalformedURLException {
		Matcher nextPageMatcher = nextPagePattern.matcher(content);
		if (nextPageMatcher.find()) {
			Matcher link = Pattern.compile("\"\\S+\"").matcher(nextPageMatcher.group());
			if (link.find()) {
				return new URL(nextPageMatcher.group().substring(link.start() + 1, link.end() - 1));
			}
		}
		return null;
	}

	private static class Channel {

		private static Pattern titlePattern = Pattern.compile("title=\"[^\"]+\"");
		private static Pattern linkPattern = Pattern.compile("href=\"[^\"]+\"");

		private String name;
		private String id;

		public Channel(String linkTag) throws IOException {
			Matcher titleMatcher = titlePattern.matcher(linkTag);
			if (titleMatcher.find()) {
				name = linkTag.substring(titleMatcher.start() + 7, titleMatcher.end() - 1);
			}
			Matcher chLinkMatcher = linkPattern.matcher(linkTag);
			if (chLinkMatcher.find()) {
				String content = urlContent(new URL(linkTag.substring(chLinkMatcher.start() + 6, chLinkMatcher.end() - 1)));
				//todo
			}
		}
	}
}

Итак, данные со страниц каналов мы получили, теперь надо найти в исходном коде искомый Content ID. Немного покопавшись в исходниках торрентстрима, я выяснил, что плеер встраивается на сайт с помощью iframe с сайта 1ttv.net:

<iframe src="http://1ttv.net/iframe.php?site=286&channel=388" style="width:650px; height: 586px; border: none; background-color: #000;" scrolling="no" frameborder="0"></iframe>

В коде самого iframe я нашел, в каком месте запускается плеер:

try {
	var p = this;
	player_context = this;
	this.loadPlayer("f01532846f38f27ae29b532bb599e9ddb3578950",{autoplay: true});
}

Видимо, f01532846f38f27ae29b532bb599e9ddb3578950 — и есть тот самый Content ID.

Ну что ж, достанем его:

//...
private static Pattern framePattern = Pattern.compile("http://1ttv\\.net/iframe\\.php[^\"]+");
private static Pattern idPattern = Pattern.compile("loadPlayer\\(\"[0-9a-f]+\"");
//...
public Channel(String linkTag) throws IOException {
	//...
	Matcher frameMatcher = framePattern.matcher(content);
	if (frameMatcher.find()) {
		String frameContent = urlContent(new URL(content.substring(frameMatcher.start(), frameMatcher.end())));
		Matcher idMatcher = idPattern.matcher(frameContent);
		if (idMatcher.find()) {
			id = frameContent.substring(idMatcher.start() + 12, idMatcher.end() - 1);
		}
	}
}

Осталось только прикрутить компаратор и отсортировать каналы по алфавиту:

public static void main(String[] args) throws IOException {
	//...
	Collections.sort(channels);
}
//...
private static class Channel implements Comparable<Channel> {
	//...
	@Override
	public int compareTo(Channel ch) {
		return name.compareToIgnoreCase(ch.name);
	}
}

И вывести это в html-файл:

public static void main(String[] args) throws IOException {
	//...
	File file = new File("list.html");
	if (!file.exists()) {
		file.createNewFile();
	}
	FileWriter writer = new FileWriter(file);
	writer.write("<!DOCTYPE html><html><head><title>Список каналов</title></head><body><table>");
	for (Channel channel : channels) {
		if (channel.id == null || channel.id.length() < 40) {
			continue;
		}
		writer.write("<tr><td>" + channel.name + "</td><td>" + channel.id + "</td></tr>");
	}
	writer.write("</table></body></html>");
	writer.flush();
	writer.close();
}

Теперь полученный файлик можно загрузить на android-устройство и наслаждаться списком из 260 каналов:

Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.