mirror of
https://github.com/anthonyaxenov/iptv.git
synced 2024-11-22 13:34:45 +00:00
v2
This commit is contained in:
parent
da54db9c50
commit
fb69c92ead
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/.idea
|
7
css/bootstrap.min.css
vendored
Normal file
7
css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
289
index.php
289
index.php
@ -1,114 +1,233 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
ini_set('display_errors', 1);
|
//ini_set('display_errors', 1);
|
||||||
ini_set('display_startup_errors', 1);
|
//ini_set('display_startup_errors', 1);
|
||||||
error_reporting(E_ALL);
|
//error_reporting(E_ALL);
|
||||||
|
|
||||||
/**
|
$updated_at = date('d.m.Y h:i', filemtime('playlists.ini'));
|
||||||
* Возвращает количество каналов в плейлисте
|
$my_url = $_SERVER['SERVER_NAME'] . '?';
|
||||||
*
|
$ini = parse_ini_file('playlists.ini', true);
|
||||||
* @param string $pls_url URL плейлиста
|
|
||||||
* @return int
|
// получение инфы о плейлисте
|
||||||
*/
|
if (!empty($_GET['getinfo'])) {
|
||||||
function getChannelCount($pls_url) {
|
$ch = curl_init();
|
||||||
$content = file_get_contents($pls_url);
|
curl_setopt($ch, CURLOPT_URL, $ini[$_GET['getinfo']]['pls']);
|
||||||
|
unset($ini);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||||
|
curl_setopt($ch, CURLOPT_HEADER, 1);
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||||||
|
$headers = explode("\r\n", substr($response, 0, $header_size));
|
||||||
|
$content = substr($response, $header_size);
|
||||||
|
unset($response);
|
||||||
|
unset($header_size);
|
||||||
|
curl_close($ch);
|
||||||
|
unset($ch);
|
||||||
$matches = [];
|
$matches = [];
|
||||||
preg_match_all('[EXTINF]', $content, $matches);
|
preg_match_all("/^#EXTINF:-?\d[\s]?,[\s]?(.*$)/m", $content, $matches);
|
||||||
return count($matches[0]);
|
unset($content);
|
||||||
|
$channels = $matches[1];
|
||||||
|
unset($matches);
|
||||||
|
$is_online = is_array($headers) && !empty($headers) && strpos($headers[0], ' 200') !== false;
|
||||||
|
unset($headers);
|
||||||
|
array_walk($channels, function (&$str) { $str = trim($str); });
|
||||||
|
die(json_encode([
|
||||||
|
'is_online' => $is_online,
|
||||||
|
'count' => $is_online ? count($channels) : '-',
|
||||||
|
'channels' => $channels,
|
||||||
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Шаблон короткой ссылки на плейлист
|
if (array_intersect(array_keys($_GET), array_keys($ini))) {
|
||||||
// $my_url = $_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI'].'?s=';
|
$id = array_keys($_GET)[0];
|
||||||
$my_url = $_SERVER['SERVER_NAME'].'/iptv?s=';
|
if (!empty($ini[$id]['redirect'])) {
|
||||||
|
header('Location: ' . $ini[$ini[$id]['redirect']]['pls']);
|
||||||
// Чтение списка плейлистов из ini-файла
|
die;
|
||||||
$data = parse_ini_file('playlists.ini', true);
|
} elseif (!empty($ini[$id]['pls'])) {
|
||||||
|
header('Location: ' . $ini[$id]['pls']);
|
||||||
if (!empty($_GET['s'])) {
|
die;
|
||||||
if (!empty($data[$_GET['s']]['redirect'])) {
|
|
||||||
header('Location: '.$data[$data[$_GET['s']]['redirect']]['pls']);
|
|
||||||
} elseif (!empty($data[$_GET['s']]['pls'])) {
|
|
||||||
header('Location: '.$data[$_GET['s']]['pls']);
|
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
?>
|
?>
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="ru">
|
<html lang="ru">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>IPTV Playlists</title>
|
<title>IPTV Playlists</title>
|
||||||
<style>
|
<link href="css/bootstrap.min.css" rel="stylesheet">
|
||||||
.myurl {
|
<script src="js/bootstrap.bundle.min.js"></script>
|
||||||
font-weight: bold;
|
</head>
|
||||||
line-height: 2em;
|
<body class="bg-dark text-light">
|
||||||
display: block;
|
<div class="col-lg-8 mx-auto p-3 py-md-5">
|
||||||
}
|
<header class="pb-3 mb-5 border-bottom">
|
||||||
.center {
|
<a href="/" class="text-light text-decoration-none">
|
||||||
text-align: center;
|
<h1>Самообновляемые плейлисты IPTV</h1>
|
||||||
}
|
</a>
|
||||||
.pointer {
|
<p class="small text-muted">
|
||||||
cursor:pointer;
|
Обновлено: <?=$updated_at?> МСК<br/>
|
||||||
}
|
Плейлистов в списке: <strong><?=count($ini)?></strong>
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Список самообновляемых плейлистов для IPTV</h1>
|
|
||||||
<p>
|
|
||||||
Дата обновления списка: <strong>
|
|
||||||
<?=date('d-m-Y h:i:s', filemtime('playlists.ini'))?> МСК<br>
|
|
||||||
<a href="https://github.com/anthonyaxenov/iptv">github.com/anthonyaxenov/iptv</a>
|
|
||||||
</strong>
|
|
||||||
<p>
|
|
||||||
Поддержкой этих плейлистов занимаются сервисы и ресурсы, указанные как источник (если таковые имеются).<br>
|
|
||||||
Эти плейлисты собраны здесь вручную и бесплатны.
|
|
||||||
</p>
|
</p>
|
||||||
<p>Чтобы подключить плейлист, нужно в настройках IPTV-плеера добавить ссылку в следующем формате:</p>
|
</header>
|
||||||
<pre><?=$my_url?><strong>ID</strong></pre>
|
|
||||||
<p>где <strong>ID</strong> - один из указанных ниже идентификаторов.</p>
|
<main>
|
||||||
<table width="100%" border="1" cellpadding="1">
|
<div class="container">
|
||||||
|
<p>
|
||||||
|
На этой странице собраны ссылки на IPTV-плейлисты, которые находятся в открытом доступе.<br/>
|
||||||
|
Они бесплатны для использования. Список проверяется и обновляется мной вручную.<br/>
|
||||||
|
Поддержкой этих плейлистов занимаются сервисы и ресурсы, указанные как источник.<br/>
|
||||||
|
Вопросы работоспособности плейлистов адресуйте тем, кто несёт за них ответственность.
|
||||||
|
</p>
|
||||||
|
<p>Чтобы подключить плейлист, нужно в настройках IPTV-плеера указать ссылку из последней колонки.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container py-5">
|
||||||
|
<h2>Пояснение статусов проверки плейлистов</h2>
|
||||||
|
<ui>
|
||||||
|
<li>
|
||||||
|
<span class="badge small bg-warning text-dark">?</span> Загрузка данных.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="badge small text-dark bg-success">online</span> Плейлист активен. В этом случае
|
||||||
|
могут даже подгрузиться список и количество каналов. А может и нет.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="badge small text-dark bg-secondary">unknown</span> Состояние неизвестно.
|
||||||
|
Скорее всего, плейлист активен, но получить данные о нём не удалось. Следует проверить вручную.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="badge small text-dark bg-secondary">timeout</span> Не удалось вовремя проверить плейлист.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="badge small text-dark bg-danger">offline</span> Плейлист неактивен.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="badge small text-dark bg-danger">error</span> Ошибка при проверке плейлиста.
|
||||||
|
</li>
|
||||||
|
</ui>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container py-5">
|
||||||
|
<h2>Список плейлистов</h2>
|
||||||
|
<table class="table table-dark table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="center">ID</td>
|
<th>ID</th>
|
||||||
<td>Информация о плейлисте</td>
|
<th>Информация о плейлисте</th>
|
||||||
<td class="center">Каналов</td>
|
<th>Каналов</th>
|
||||||
<td title="Нажмите на ссылку, чтобы скопировать адрес">Ссылка на плейлист</td>
|
<th title="Нажмите на ссылку, чтобы скопировать её в буфер обмена">Ссылка</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php foreach ($data as $id => $element) {
|
<?php
|
||||||
|
foreach ($ini as $id => $element) {
|
||||||
if (empty($element['pls'])) {
|
if (empty($element['pls'])) {
|
||||||
continue;
|
continue;
|
||||||
} ?>
|
}
|
||||||
<tr>
|
?>
|
||||||
<td class="center">
|
<tr class="pls" data-playlist-id="<?=$id?>">
|
||||||
|
<td class="text-center id">
|
||||||
<strong><?=$id?></strong>
|
<strong><?=$id?></strong>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="info">
|
||||||
<strong><?=$element['name'] ?: "Плейлист #".$id?></strong>
|
<strong><?=$element['name'] ?: "Плейлист #" . $id?></strong>
|
||||||
<?php if (!empty($element['src'])) { ?>
|
<span class="badge small bg-warning text-dark status">?</span>
|
||||||
<br><a href="<?=$element['src']?>" target="_blank" rel="noopener nofollow">Источник</a>
|
<div class="small">
|
||||||
<?php } ?>
|
<a href="<?=$element['pls']?>"
|
||||||
<?php if (!empty($element['desc'])) { ?>
|
target="_blank"
|
||||||
<br><?=$element['desc']?>
|
rel="noopener nofollow">M3U</a>
|
||||||
<?php } ?>
|
<?php
|
||||||
|
if (!empty($element['src'])) { ?>
|
||||||
|
| <a href="<?=$element['src']?>"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener nofollow">Источник</a>
|
||||||
|
<?php
|
||||||
|
} ?>
|
||||||
|
<?php
|
||||||
|
if (!empty($element['desc'])) { ?>
|
||||||
|
<br/><p class="my-1"><?=$element['desc']?></p>
|
||||||
|
<?php
|
||||||
|
} ?>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="center"><?=getChannelCount($element['pls'])?></td>
|
<td class="text-center count">
|
||||||
<td width="250">
|
<div class="spinner-border text-success" role="status">
|
||||||
|
<span class="visually-hidden">загрузка...</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="col-3">
|
||||||
<span onclick="prompt('Скопируйте адрес плейлиста', '<?=$my_url?><?=$id?>')"
|
<span onclick="prompt('Скопируйте адрес плейлиста', '<?=$my_url?><?=$id?>')"
|
||||||
title="Нажмите, чтобы скопировать адрес"
|
data-bs-toggle="tooltip"
|
||||||
class="pointer myurl">
|
data-bs-placement="top"
|
||||||
|
title="Нажмите на ссылку, чтобы скопировать её в буфер обмена"
|
||||||
|
class="font-monospace">
|
||||||
<?=$my_url?><?=$id?>
|
<?=$my_url?><?=$id?>
|
||||||
</span>
|
</span>
|
||||||
<span>
|
|
||||||
Прямая ссылка: <a href="<?=$element['pls']?>">M3U</a>
|
|
||||||
</span>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php } ?>
|
<?php
|
||||||
|
} ?>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</body>
|
</div>
|
||||||
</html>
|
</main>
|
||||||
<?php
|
|
||||||
}
|
<footer class="py-4 text-center">
|
||||||
|
<a href="https://github.com/anthonyaxenov/iptv">GitHub</a> | <a href="https://axenov.dev">axenov.dev</a>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.querySelectorAll('tr.pls').forEach((tr) => {
|
||||||
|
const id = tr.attributes['data-playlist-id'].value
|
||||||
|
const xhr = new XMLHttpRequest()
|
||||||
|
xhr.responseType = 'json'
|
||||||
|
xhr.timeout = 10000 // ms
|
||||||
|
let st_el = tr.querySelector('span.status')
|
||||||
|
xhr.onreadystatechange = () => {
|
||||||
|
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||||
|
console.log('[' + id + '] DONE', xhr.response)
|
||||||
|
st_el.classList.remove('bg-warning')
|
||||||
|
if (xhr.response) {
|
||||||
|
tr.querySelector('td.count').innerHTML = xhr.response.count
|
||||||
|
if (xhr.response.is_online === true) {
|
||||||
|
st_el.innerHTML = 'online'
|
||||||
|
st_el.classList.add('bg-success')
|
||||||
|
tr.querySelector('td.info').innerHTML += '<a class="small" ' +
|
||||||
|
'data-bs-toggle="collapse" data-bs-target="#channels-' + id + '" aria-expanded="false" ' +
|
||||||
|
'aria-controls="channels-' + id + '">Список каналов</a><div class="collapse" id="channels-' + id +
|
||||||
|
'"><p class="card card-body bg-dark small" style="max-height:250px;overflow-y:auto;">' +
|
||||||
|
xhr.response.channels.join('<br />') + '</p></div>'
|
||||||
|
} else {
|
||||||
|
st_el.innerHTML = 'offline'
|
||||||
|
st_el.classList.add('bg-danger')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tr.querySelector('td.count').innerHTML = '-'
|
||||||
|
st_el.classList.add('bg-secondary')
|
||||||
|
st_el.innerHTML = 'unknown'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xhr.onerror = () => {
|
||||||
|
console.log('[' + id + '] ERROR', xhr.response)
|
||||||
|
st_el.classList.add('bg-danger')
|
||||||
|
st_el.innerHTML = 'error'
|
||||||
|
tr.querySelector('td.count').innerHTML = '-'
|
||||||
|
}
|
||||||
|
xhr.onabort = () => {
|
||||||
|
console.log('[' + id + '] ABORTED', xhr.response)
|
||||||
|
st_el.classList.add('bg-secondary')
|
||||||
|
tr.querySelector('td.count').innerHTML = '-'
|
||||||
|
}
|
||||||
|
xhr.ontimeout = () => {
|
||||||
|
console.log('[' + id + '] TIMEOUT', xhr.response)
|
||||||
|
st_el.classList.add('bg-secondary')
|
||||||
|
st_el.innerHTML = 'timeout'
|
||||||
|
tr.querySelector('td.count').innerHTML = '-'
|
||||||
|
}
|
||||||
|
xhr.open('GET', '/?getinfo=' + id)
|
||||||
|
xhr.send()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
7
js/bootstrap.bundle.min.js
vendored
Normal file
7
js/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user