FXML: различия между версиями
Mentos (обсуждение | вклад) |
Mentos (обсуждение | вклад) Реструктуризация: спецификация FXML для новичков (разделы 1-8) |
||
| (не показано 5 промежуточных версий этого же участника) | |||
| Строка 1: | Строка 1: | ||
=='''FXML''' | <div class="toccolours" style="width:100%; max-width:900px; padding:12px 16px; line-height:1.5;"> | ||
'''FXML''' — официальная спецификация языка разметки порталов для Fork browser и совместимых приложений.<br /> | |||
Формат ответа: '''валидный JSON'''. Навигация — с пульта, без мыши и тач-экрана. | |||
</div> | |||
==== | == 1. Область применения == | ||
FXML (Fork eXtensible Markup Language) описывает структуру страницы, которую сервер отдаёт клиенту. Клиент интерпретирует JSON и показывает список элементов, видео или произвольную вёрстку на экране телевизора. | |||
'''Совместимые приложения:''' ForkPlayer, OTT Player, OTT-play. | |||
'''Связанные документы:''' | |||
* [https://wiki.forkbrowser.top/wiki/CHANGELOG CHANGELOG] — история изменений протокола | |||
* [https://wiki.forkbrowser.top/wiki/FXMLbaseparser FXMLbaseparser] — PHP-класс для генерации FXML | |||
* [https://github.com/vengo634/kino.pub_forkplayerPHP/blob/master/index.php Пример портала (PHP)] | |||
== 2. Быстрый старт == | |||
Минимальная страница состоит из заголовка и массива элементов <code>channels</code>. У каждого элемента — как минимум <code>title</code> и ссылка (<code>playlist_url</code> или <code>stream_url</code>). | |||
'''Минимальный пример (JSON):''' | |||
[ | <pre>{ | ||
"title": "Мой портал", | |||
"channels": [ | |||
{"title": "Главная", "logo_30x30": "http://example.com/icon.png", "playlist_url": "http://example.com/"}, | |||
{"title": "Фильм", "stream_url": "http://example.com/video.m3u8"} | |||
] | |||
}</pre> | |||
'''Вывод на PHP:''' | |||
<pre><?php | |||
$_PL = ["title" => "Мой портал"]; | |||
$_CH = [ | |||
["title" => "Главная", "logo_30x30" => "http://example.com/icon.png", "playlist_url" => "http://example.com/"], | |||
["title" => "Фильм", "stream_url" => "http://example.com/video.m3u8"] | |||
]; | |||
$_PL["channels"] = $_CH; | |||
header("Content-Type: application/json; charset=utf-8"); | |||
print json_encode($_PL, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); | |||
?></pre> | |||
* <code>playlist_url</code> — открыть другую FXML/XML/M3U страницу | |||
* <code>stream_url</code> — запустить видеоплеер | |||
'''Полный пример портала (COOLTV):''' | |||
====Следующий код (валидный JSON):==== | |||
{"title":"COOLTV - портал нового поколения","background-image":"<nowiki>http://cooltv.info/img/tvcool.jpg</nowiki>","typeList":"start","icon":"<nowiki>http://cooltv.info/img/tvcool23.jpg</nowiki>","channels":[{"title":"Вход","logo_30x30":"<nowiki>http://cooltv.info/img/profle22.jpg</nowiki>","playlist_url":"<nowiki>http://cooltv.info/auth</nowiki>"},{"title":"Новости ","logo_30x30":"<nowiki>http://cooltv.info/img/rss-96.png</nowiki>","playlist_url":"<nowiki>http://cooltv.info/news</nowiki>"},{"title":"Поиск ","logo_30x30":"<nowiki>http://cooltv.info/img/icons8-search-folder-96.png</nowiki>","playlist_url":"<nowiki>http://cooltv.info/media/search.php</nowiki>"},{"title":"Кинозал ","logo_30x30":"<nowiki>http://cooltv.info/img/1icons8-film-reel-96.png</nowiki>","playlist_url":"<nowiki>http://cooltv.info/media</nowiki>"},{"title":"AceStream ","logo_30x30":"<nowiki>http://cooltv.info/img/icons8-wave-arrows-96.png</nowiki>","playlist_url":"<nowiki>http://cooltv.info/vdt/12</nowiki>"},{"title":"Обменник ","logo_30x30":"<nowiki>http://cooltv.info/img/icons8-next-100.png</nowiki>","playlist_url":"<nowiki>http://cooltv.info/media/obmen/</nowiki>"},{"title":"Сервисы ","logo_30x30":"<nowiki>http://cooltv.info/img/icons8s3-96.png</nowiki>","playlist_url":"<nowiki>http://cooltv.info/vdt/9</nowiki>"},{"title":"IPTV ","logo_30x30":"<nowiki>http://cooltv.info/img/icons8-hdtv-96.png</nowiki>","playlist_url":"<nowiki>http://cooltv.info/vdt/8</nowiki>"},{"title":"Мультимедиа ","logo_30x30":"<nowiki>http://cooltv.info/img/icons8-documentary-96.png</nowiki>","playlist_url":"<nowiki>http://cooltv.info/vdt/7</nowiki>"},{"title":"Чат ","logo_30x30":"<nowiki>http://cooltv.info/img/icons8-chat-96.png</nowiki>","playlist_url":"<nowiki>http://cooltv.info/guest</nowiki>"},{"title":"FAQ ","logo_30x30":"<nowiki>http://cooltv.info/img/icons8-info-popup-96.png</nowiki>","playlist_url":"<nowiki>http://cooltv.info/faq/</nowiki>"},{"title":"Копилка","logo_30x30":"<nowiki>http://cooltv.info/img/icons8-bad-piggies-96.png</nowiki>","playlist_url":"<nowiki>http://cooltv.info/copilka</nowiki>"}]} | |||
даст такой результат (в ForkPlayer): | |||
[[Файл:Cooltv.png|мини|600x600пкс|без]] | |||
== | ====Просмотр исходного кода страницы в ForkPlayer==== | ||
[ | [[Файл:VmGe53a9.png|мини|без]] | ||
== | == 3. Термины и модель данных == | ||
{| class="wikitable" | |||
! Термин !! Описание | |||
|- | |||
| '''Документ FXML''' || JSON-объект верхнего уровня, который сервер отдаёт клиенту | |||
|- | |||
| '''$_PL''' / '''Playlist''' || Глобальные поля документа (заголовок страницы, стили, команды) | |||
|- | |||
| '''$_CH''' / '''Channel''' || Один элемент списка в массиве <code>channels[]</code> | |||
|- | |||
| <code>playlist_url</code> || Ссылка на следующую страницу (FXML, XML, M3U, команда) | |||
|- | |||
| <code>stream_url</code> || Ссылка на видеопоток; при наличии у channel не используется <code>playlist_url</code> | |||
|- | |||
| <code>typeList</code> || Режим отображения страницы: список (по умолчанию) или плитка (<code>start</code>) | |||
|- | |||
| <code>position</code> || Вид отдельного элемента при <code>typeList="start"</code> (плитка, список, html и др.) | |||
|} | |||
'''Правила:''' | |||
# Ответ сервера — один JSON-объект с ключом <code>channels</code> (массив). | |||
# У channel должен быть непустой <code>title</code>. | |||
# У channel заполняется '''либо''' <code>playlist_url</code>, '''либо''' <code>stream_url</code> (если элемент не служебный). | |||
# Новые теги добавляются по мере развития протокола — см. [https://wiki.forkbrowser.top/wiki/CHANGELOG CHANGELOG]. | |||
<div class="toccolours mw-collapsible" style="width:100%; max-width:900px;"> | |||
<div style="font-weight:bold;">Содержание спецификации</div> | |||
<div class="mw-collapsible-content"> | |||
# [[#1. Область применения|1. Область применения]] | |||
# [[#2. Быстрый старт|2. Быстрый старт]] — минимальный пример | |||
# [[#3. Термины и модель данных|3. Термины и модель данных]] | |||
# [[#4. Справочник глобальных тегов ($\_PL)|4. Глобальные теги $_PL]] | |||
# [[#5. Справочник тегов channel ($\_CH)|5. Теги channel $_CH]] | |||
# [[#6. Оформление и вёрстка элементов|6. Оформление и вёрстка]] | |||
# [[#7. Формирование ответа на сервере|7. Формирование ответа на сервере]] | |||
# [[#8. Связанные материалы|8. Связанные материалы]] | |||
</div></div> | |||
$_PL[" | == 4. Справочник глобальных тегов ($_PL) == | ||
Теги задают свойства '''всей страницы'''. В PHP соответствуют элементам массива <code>$_PL</code>. | |||
{| class="wikitable" | |||
! Тег !! Назначение !! Раздел | |||
|- | |||
| <code>title</code> || Заголовок страницы || — | |||
|- | |||
| <code>typeList</code> || <code>start</code> — плитка; иначе — список || [[#position при заданном $_PL["typeList"]="start";|position]] | |||
|- | |||
| <code>channels</code> || Массив элементов списка || [[#5. Справочник тегов channel ($_CH)|§5]] | |||
|- | |||
| <code>css</code> || CSS-стили страницы (строка) || [[#4.1. Оформление страницы (css, link, align)|§4.1]] | |||
|- | |||
| <code>link[]</code> || Подключение внешних CSS || [[#Подключения css файлов стилей|link]] | |||
|- | |||
| <code>align</code> || Выравнивание плитки (<code>left</code>, по умолчанию — center) || [[#Глобальный тег align|align]] | |||
|- | |||
| <code>cmd</code> || Команда при загрузке страницы || [[#cmd тег (как глобальный так и в playlist_url) - допустимые команды|cmd]] | |||
|- | |||
| <code>ping</code> || Опрос URL до появления channels || [[#Глобальный тег ping|ping]] | |||
|- | |||
| <code>aside</code> || Боковое меню || [[#Глобальный тег aside для вывода бокового меню|aside]] | |||
|- | |||
| <code>advertising</code> || Реклама перед видео || [[#advertising для показа рекламы перед запуском видео|advertising]] | |||
|- | |||
| <code>url_tvg</code> || URL телепрограммы (xmltv) || [[#Глобальный тег url_tvg — своя телепрограмма|url_tvg]] | |||
|- | |||
| <code>menu</code> || Глобальная полоса меню вверху || [[#menu — контекстное меню элемента channel|menu]] | |||
|- | |||
| <code>cacheinfo</code> || <code>nocache</code> — не кешировать страницу || ниже | |||
|- | |||
| <code>setcookie</code> || Cookie для последующих запросов к домену || ниже | |||
|- | |||
| <code>info</code>, <code>confirm</code> || Уведомление или диалог при загрузке || ниже | |||
|} | |||
[ | === 4.1. Оформление страницы (css, link, align) === | ||
<code>$_PL["css"]</code> — CSS одной строкой. При <code>typeList="start"</code> доступны классы <code>.start</code>, <code>.list</code>, <code>.html</code> и др. (см. [[#position при заданном $_PL["typeList"]="start";|position]]). | |||
<code>$_PL["css"]="start";</code> — плиточный вид + поддержка <code>$_CH["position"]</code>. | |||
$_PL["css"]="start"; / | |||
====Стили сайта по умолчанию==== | ====Стили сайта по умолчанию==== | ||
| Строка 388: | Строка 481: | ||
$_PL["confirm"]=["title"=>"Открыть вложенный CHannel?","channel"=>["playlist_url"=>"http://.."]]; | $_PL["confirm"]=["title"=>"Открыть вложенный CHannel?","channel"=>["playlist_url"=>"http://.."]]; | ||
=== 4.2. Поведение страницы (cmd, ping, служебные теги) === | |||
===cmd тег (как глобальный так и в playlist_url) - допустимые команды=== | ===cmd тег (как глобальный так и в playlist_url) - допустимые команды=== | ||
| Строка 410: | Строка 505: | ||
Например $_CH["playlist_url"]="reload();"; перезагрузит страницу при нажатии | Например $_CH["playlist_url"]="reload();"; перезагрузит страницу при нажатии | ||
===Глобальный тег ping=== | |||
Для выполнения действий когда они станут доступны. Например в Fork browser можно отслеживать введение кода с телефона и автоматически загружать страницу. | |||
$_PL["ping"]["link"]="<nowiki>http://example.com/check.php?hash</nowiki>"; | |||
Если check.php отдаёт пустой ответ, Fork продолжает проверять страницу каждые 6 сек. | |||
Если check.php отдаёт страницу с <code>channels</code>, загружается она, например: | |||
$_PL["channels"]=[["location"=>1,"title"=>"Успешно вошли! Нажмите для переадресации...","playlist_url"=>"<nowiki>http://example.com/</nowiki>"]]; | |||
===Глобальный тег align=== | |||
Выравнивание элементов при виде плиткой (<code>$_PL["typeList"]="start"</code>). По умолчанию — по центру. | |||
$_PL["align"]="left"; | |||
===Глобальный тег url_tvg — своя телепрограмма=== | |||
Поддерживается только формат xmltv (сжатый и нет). | |||
$_PL["url_tvg"]="<nowiki>http://epg.it999.ru/edem.xml.gz</nowiki>"; | |||
В M3U плейлисте аналогичный параметр задаётся в начале: | |||
<nowiki>#</nowiki>EXTM3U url-tvg="<nowiki>http://epg.it999.ru/edem.xml.gz</nowiki>" | |||
===Глобальный тег aside для вывода бокового меню=== | ===Глобальный тег aside для вывода бокового меню=== | ||
| Строка 453: | Строка 573: | ||
В настройках форка для тестирования включите Ads: always | В настройках форка для тестирования включите Ads: always | ||
== | == 5. Справочник тегов channel ($_CH) == | ||
Каждый объект в <code>channels[]</code> — элемент списка. Ниже — теги в порядке от базовых к расширенным. | |||
{| class="wikitable" | |||
! Уровень !! Тег !! Назначение | |||
|- | |||
| '''Базовый''' || <code>title</code> || Заголовок элемента (обязательно) | |||
|- | |||
| '''Базовый''' || <code>logo_30x30</code> || URL иконки 30×30 | |||
|- | |||
| '''Базовый''' || <code>playlist_url</code> || Переход на другую страницу | |||
|- | |||
| '''Базовый''' || <code>stream_url</code> || Запуск видеоплеера | |||
|- | |||
| '''Базовый''' || <code>description</code> || Текст справа (режим списка) или в шаблоне | |||
|- | |||
| Средний || <code>position</code>, <code>br</code>, <code>template</code> || Вид и вёрстка элемента | |||
|- | |||
| Средний || <code>menu</code>, <code>confirm</code> || Меню и диалоги | |||
|- | |||
| Средний || <code>location</code> || Переадресация (1 — заменить URL, 3 — временная) | |||
|- | |||
| Средний || <code>before</code>, <code>after</code> || HTML до/после элемента | |||
|- | |||
| Плеер || <code>poster</code>, <code>label</code>, <code>information</code> || Подписи в видеоплеере | |||
|- | |||
| Плеер || <code>subtitles</code>, <code>event</code>, <code>start_time</code> || Субтитры, события, старт с позиции | |||
|- | |||
| Расширенный || <code>parser</code>, <code>cors</code>, <code>iframe</code> || Загрузка и встраивание данных | |||
|- | |||
| Расширенный || <code>SetTimeInterval</code> || Периодические запросы к серверу | |||
|} | |||
=== 5.1. Основные теги === | |||
===title=== | ===title=== | ||
Заголовок | Заголовок элемента. Отображается в списке и используется как fallback для <code>label</code> в плеере. | ||
===playlist_url или stream_url=== | ===playlist_url или stream_url=== | ||
Адрес страницы или адрес видеопотока | Адрес страницы или адрес видеопотока. Непустым может быть только один из этих тегов. | ||
====Параметры ссылок #direct и #stream_url==== | |||
Добавляются в конец ссылки: | |||
'''#direct''' — если сайт требует чистой ссылки без добавлений Fork (параметры, cookie, идентификатор): | |||
<nowiki>http://pastebin.com/yuUtYu56g#direct</nowiki> → откроется как <nowiki>http://pastebin.com/yuUtYu56g</nowiki> | |||
'''#stream_url''' — ссылка откроется сразу в видеоплеере. | |||
====AddFavorite и AddSearch==== | |||
Команды в <code>playlist_url</code> для добавления портала в закладки или глобальный поиск: | |||
$_CH[]=["logo_30x30"=>"none","title"=>"Добавить этот портал в закладки / стартовое меню","playlist_url"=>"AddFavorite(Кинопаб,<nowiki>https://kino.pub/images/logo.png,http://195.88.208.101/kinopub/</nowiki>);"]; | |||
$_CH[]=["logo_30x30"=>"none","title"=>"Добавить этот портал в Глобальный поиск","playlist_url"=>"AddSearch(Кинопаб,<nowiki>https://kino.pub/images/logo.png,http://195.88.208.101/kinopub/?cat=search</nowiki>);"]; | |||
(пример всей страницы на PHP: [https://github.com/alexkdpu/kino.pub_forkplayerPHP/blob/master/index.php Кинопаб]) | |||
====iframe — вставка содержимого другой страницы==== | |||
На место элемента списка вставляется содержимое из ссылки. Можно использовать для поиска по нескольким плейлистам (глобальный поиск). | |||
$_CH[]=["title"=>"Поиск Terminator в filmix.red","playlist_url"=>"<nowiki>https://filmix.red/fork/search?search=Terminator</nowiki>","iframe"=>"4","timeout"=>8]; | |||
<code>iframe</code> — обязательный параметр. Число — сколько ссылок из дочерней страницы показать сразу; остальное будет внизу списка. <code>"iframe"=>"0"</code> — показать все. | |||
<code>timeout</code> — таймаут ожидания содержимого из дочерней ссылки (секунды). | |||
====Альтернативный playlist_url2, время ожидания таймаута, способы получения==== | ====Альтернативный playlist_url2, время ожидания таймаута, способы получения==== | ||
Можно указать запасной источник страницы, время ожидания таймаута, способы получения | Можно указать запасной источник страницы, время ожидания таймаута, способы получения | ||
| Строка 522: | Строка 703: | ||
echo $_CH["stream_url"]; | echo $_CH["stream_url"]; | ||
?> | ?> | ||
====Поддержка ссылок Яндекс.Диска==== | |||
Ссылка должна быть в поле <code>stream_url</code> или в m3u плейлисте в формате <nowiki>https://yadi.sk/i/idfile</nowiki> | |||
====playlist — похожие видео после окончания (YouTube)==== | |||
<?php | |||
$PLAYLIST=[]; | |||
$PLAYLIST[]=["title"=>"Похожее видео 1","stream_url"=>"<nowiki>http://www.youtube.com/watch?v=ZNLZla2xHUQ</nowiki>"]; | |||
$PLAYLIST[]=["title"=>"Похожее видео 2","stream_url"=>"<nowiki>http://www.youtube.com/watch?v=ZNLZla2xHUQ</nowiki>"]; | |||
$_CH[]=["title"=>"Основное видео","stream_url"=>"<nowiki>http://www.youtube.com/watch?v=xhFCmwrSxCU</nowiki>","playlist"=>$PLAYLIST]; | |||
$_PL["channels"]=$_CH; | |||
print json_encode($_PL); | |||
?> | |||
===logo_30x30=== | ===logo_30x30=== | ||
URL иконки элемента (рекомендуемый размер 30×30 px). Используется также как fallback для <code>poster</code> в плеере. | |||
=== 5.2. Видеоплеер === | |||
===Теги видеоплеера poster, label, information=== | |||
Нужны чтобы по-разному выдавать информацию на сайте и в видеоплеере. | |||
$_CH["title"]="Хало 1 сезон 1 серия"; | |||
$_CH["label"]="Хало"; | |||
$_CH["information"]="1 сезон 1 серия"; | |||
[[Файл:Halo.png|мини|Вид экрана плеера]] | |||
Плеер сначала ищет значение в специализированном теге; если он пустой — берёт из общего тега. | |||
====Картинка в видеоплеере слева внизу==== | |||
$_CH["poster"]="<nowiki>http://example.com/videoposter.png</nowiki>"; | |||
если пустой — используется <code>logo_30x30</code>. | |||
====Название над полосой прогресса==== | |||
$_CH["label"]="Хало"; | |||
если пустой — используется <code>title</code>. | |||
====Информация под прогрессом==== | |||
$_CH["information"]="1 сезон 1 серия"; | |||
если пустой — используется <code>description</code>. | |||
===Тег subtitles — субтитры=== | |||
$_CH["subtitles"][0]=["UA","<nowiki>https://tortuga.wtf/player/subtitle/18625_ua.vtt</nowiki>"]; | |||
$_CH["subtitles"][1]=["EN","<nowiki>https://tortuga.wtf/player/subtitle/18625_en.vtt</nowiki>"]; | |||
$_CH["subtitles"][2]=["RU","<nowiki>https://tortuga.wtf/player/subtitle/18625_ru.vtt</nowiki>"]; | |||
===event — события видеоплеера=== | |||
Отправка на сервер событий при старте и остановке видео: | |||
$_CH[]=["logo_30x30"=>"hidden","title"=>"Video","stream_url"=>"<nowiki>http://...</nowiki>","event"=>["onstartvideo"=>"$siteurl/?event=onstartvideo&videoid=1","onstopvideo"=>"$siteurl/?event=onstopvideo&curTime=[curTime]&totalTime=[totalTime]&videoid=1"]]; | |||
<code>[curTime]</code> и <code>[totalTime]</code> заменяются на время остановки и общую длительность видео в секундах. | |||
===start_time — начало воспроизведения=== | |||
$_CH[]=["logo_30x30"=>"hidden","title"=>"Video","stream_url"=>"<nowiki>http://...</nowiki>","start_time"=>340]; | |||
<code>start_time</code> — время в секундах. Предлагается на кнопке Play только если пользователь ранее не смотрел это видео; иначе нужно отметить видео непросмотренным в Меню / Отметить непросмотренным. | |||
=== 5.3. Интерактивные элементы === | |||
===confirm - диалоговое окно с действиями при нажатии на элемент=== | ===confirm - диалоговое окно с действиями при нажатии на элемент=== | ||
| Строка 531: | Строка 772: | ||
$_CH[]=["title"=>"Выйти","playlist_url"=>"confirm","confirm"=>["http://host/?do<nowiki>=exit"],"description"=>"Выйти с аккаунта?"];</nowiki> | $_CH[]=["title"=>"Выйти","playlist_url"=>"confirm","confirm"=>["http://host/?do<nowiki>=exit"],"description"=>"Выйти с аккаунта?"];</nowiki> | ||
===menu - | ===menu — контекстное меню элемента channel=== | ||
$menu=[]; | |||
Массив <code>$_CH["menu"]</code> задаёт '''контекстное меню одного элемента''' списка (<code>channels[]</code>). Каждый пункт меню — мини-channel: <code>title</code>, <code>playlist_url</code> (или <code>stream_url</code>) и опциональные поля оформления. | |||
{| class="wikitable" | |||
! Тег !! Область !! Назначение | |||
|- | |||
| <code>$_CH["menu"]</code> || Элемент списка || Меню '''конкретного''' channel (качество, реакция, настройка строки) | |||
|- | |||
| <code>$_PL["menu"]</code> || Страница целиком || Глобальная полоса меню '''вверху''' страницы (зеркала, навигация портала) | |||
|} | |||
<div class="toccolours mw-collapsible" style="width:720px;"> | |||
'''Содержание раздела''' | |||
* [[#menu — быстрый старт|Быстрый старт]] | |||
* [[#menu — режимы отображения|Режимы отображения]] | |||
* [[#menu — опции type|Опции type (menu[0])]] | |||
* [[#menu — навигация|Навигация и управление]] | |||
* [[#menu — встраивание $menu|Встраивание в template ($menu)]] | |||
* [[#menu — справочник полей|Справочник полей menu[]]] | |||
* [[#menu — плейсхолдеры template|Плейсхолдеры template пункта]] | |||
* [[#menu — подменю|Подменю]] | |||
* [[#menu — ответ сервера cmd|Ответ сервера cmd без перехода]] | |||
* [[#menu — стилизация|Стилизация]] | |||
* [[#menu — совместимость|Совместимость]] | |||
</div> | |||
====menu — быстрый старт==== | |||
Минимальное '''вертикальное popup-меню''' по OK на channel: | |||
<pre>$menu = []; | |||
$menu[] = ["title" => "Пункт 1", "playlist_url" => "http://example.com/1"]; | |||
$menu[] = ["title" => "Пункт 2", "playlist_url" => "http://example.com/2"]; | |||
$_CH["title"] = "Настройки"; | |||
$_CH["playlist_url"] = "menu"; // обязательно для открытия меню | |||
$_CH["menu"] = $menu;</pre> | |||
'''Обязательные условия:''' | |||
* у channel: <code>"playlist_url": "menu"</code>; | |||
* массив <code>menu</code> с одним и более пунктов; | |||
* каждый пункт — обычный channel (URL, <code>cmd:...</code>, <code>submenu</code> и т.д.). | |||
====menu — режимы отображения==== | |||
Поведение определяется комбинацией '''<code>menu[0].type</code>''' и наличия '''<code>$menu</code>''' в <code>$_CH["template"]</code>. | |||
{| class="wikitable" | |||
! Режим !! Условие !! Вид !! Как открыть | |||
|- | |||
| '''Popup (вертикальный)''' || нет <code>inline</code> в <code>type</code> || Список поверх страницы || OK на channel с <code>playlist_url":"menu"</code> | |||
|- | |||
| '''Inline в строке''' || <code>type</code> содержит <code>inline</code> + в template есть <code>$menu</code> || Горизонтальный ряд кнопок внутри элемента || Кнопки видны сразу; popup не открывается | |||
|- | |||
| '''Inline popup''' || <code>inline</code> + в template '''нет''' <code>$menu</code> || Горизонтальный popup || OK на channel; навигация ←/→ | |||
|} | |||
'''Схема подключения inline в template:''' | |||
# <code>$_CH["position"] = "html";</code> | |||
# В <code>template</code> — плейсхолдер <code>$menu</code> | |||
# Заполнить <code>menu[]</code> | |||
# У первого пункта: <code>"type": "inline"</code> (и при необходимости <code>|nocancel</code>) | |||
====menu — опции type==== | |||
Поле <code>type</code> задаётся '''только у первого пункта''' <code>menu[0]</code>. Несколько опций комбинируются через <code>|</code> (порядок не важен): | |||
<pre>"type": "inline" | |||
"type": "inline|nocancel" | |||
"type": "nocancel"</pre> | |||
{| class="wikitable" | |||
! Значение !! Описание | |||
|- | |||
| <code>inline</code> || Горизонтальное меню: в строке (<code>$menu</code> в template) или горизонтальный popup | |||
|- | |||
| <code>nocancel</code> || Не добавлять пункт «Отмена» в popup; закрытие — кнопкой '''Назад''' | |||
|} | |||
'''Примечание:''' при <code>inline</code> первый пункт меню — обычная кнопка (например «1080p»), а не пустой служебный маркер. | |||
====menu — навигация==== | |||
{| class="wikitable" | |||
! Контекст !! Клавиши !! Действие | |||
|- | |||
| Popup (вертикальный) || ↑ / ↓ || Перемещение между пунктами | |||
|- | |||
| Popup || OK || Выбор пункта | |||
|- | |||
| Popup || Назад || Закрыть меню (без перехода по истории) | |||
|- | |||
| Inline popup || ← / → || Перемещение между пунктами | |||
|- | |||
| Inline в строке (<code>$menu</code>) || OK / клик || Выбор пункта напрямую | |||
|- | |||
| Inline в строке || ← / → || Фокус по горизонтальному ряду (если меню в popup после OK) | |||
|} | |||
'''Поле <code>default</code>''' — начальный фокус при первом открытии меню: | |||
<pre>"menu": [ | |||
{"title": "Отлично", "playlist_url": "..."}, | |||
{"title": "Нормально", "default": 1, "playlist_url": "..."}, | |||
{"title": "Плохо", "playlist_url": "..."} | |||
]</pre> | |||
* у пункта с <code>"default": 1</code> (или любое истинное значение) курсор ставится при открытии; | |||
* если ни у одного пункта нет <code>default</code> — выделяется первый (индекс 0); | |||
* работает в popup-меню и во '''вложенном submenu'''; | |||
* пункт «Отмена» в фокус по <code>default</code> не попадает. | |||
'''Поле <code>hint</code>''' — подсказка у пункта в popup: появляется через 1 с после фокуса, скрывается через 5 с (жёлтый tooltip у элемента). | |||
====menu — встраивание $menu==== | |||
'''Пример JSON''' (переключатель качества): | |||
<pre>{ | |||
"title": "Качество", | |||
"position": "html", | |||
"playlist_url": "menu", | |||
"template": "<div class='setting-item'><div class='setting-title'>$title</div><div class='chmenu'>$menu</div></div>", | |||
"menu": [ | |||
{ | |||
"type": "inline|nocancel", | |||
"title": "1080p", | |||
"logo_30x30": "http://example.com/icons/hd.png", | |||
"playlist_url": "http://example.com/?q=1080", | |||
"default": 1, | |||
"class": "pill active", | |||
"hint": "Full HD", | |||
"template": "<div><img src=$logo_30x30> $title</div>" | |||
}, | |||
{"title": "720p", "playlist_url": "http://example.com/?q=720", "class": "pill"}, | |||
{"title": "480p", "playlist_url": "http://example.com/?q=480", "class": "pill"} | |||
] | |||
}</pre> | |||
'''Пример PHP:''' | |||
<pre><?php | |||
$_CH["title"] = "Качество"; | |||
$_CH["position"] = "html"; | |||
$_CH["playlist_url"] = "menu"; | |||
$_CH["template"] = '<div class="setting-item">' | |||
. '<div class="setting-title">$title</div>' | |||
. '<div class="chmenu">$menu</div></div>'; | |||
$_CH["menu"] = [ | |||
[ | |||
"type" => "inline|nocancel", | |||
"title" => "1080p", | |||
"default" => 1, | |||
"logo_30x30" => "http://example.com/icons/hd.png", | |||
"playlist_url" => "http://example.com/?q=1080", | |||
"class" => "pill active", | |||
"template" => '<div><img src=$logo_30x30> $title</div>' | |||
], | |||
["title" => "720p", "playlist_url" => "http://example.com/?q=720", "class" => "pill"], | |||
["title" => "480p", "playlist_url" => "http://example.com/?q=480", "class" => "pill"] | |||
]; | |||
$_PL["css"] = ".pill{border-radius:999px;padding:4px 14px;margin:0 4px;display:inline-block;}" | |||
. ".pill.active{background:#3366cc;color:#fff;}"; | |||
$_PL["channels"][] = $_CH; | |||
print json_encode($_PL); | |||
?></pre> | |||
При <code>$menu</code> в template + <code>type:inline</code> повторный popup по OK '''не''' открывается. | |||
====menu — справочник полей==== | |||
{| class="wikitable" | |||
! Поле !! Тип !! Описание | |||
|- | |||
| <code>title</code> || string || Текст пункта (обязательно для отображения) | |||
|- | |||
| <code>playlist_url</code> || string || URL или команда при выборе (<code>cmd:...</code>, <code>submenu</code> и др.) | |||
|- | |||
| <code>stream_url</code> || string || Альтернатива <code>playlist_url</code> для видеопотока | |||
|- | |||
| <code>type</code> || string || '''Только menu[0]:''' опции через <code>|</code> — <code>inline</code>, <code>nocancel</code> | |||
|- | |||
| <code>default</code> || int/bool || <code>1</code> — пункт выделен при первом открытии меню | |||
|- | |||
| <code>logo_30x30</code> || string || URL иконки пункта | |||
|- | |||
| <code>class</code> || string || CSS-класс обёртки (по умолчанию <code>menucontext</code>) | |||
|- | |||
| <code>style</code> || string || Inline-стили обёртки пункта | |||
|- | |||
| <code>img_style</code> || string || Inline-стили иконки (если нет своего <code>template</code>) | |||
|- | |||
| <code>template</code> || string || Свой HTML тела пункта (как у channel) | |||
|- | |||
| <code>before</code> / <code>after</code> || string || HTML до / после пункта | |||
|- | |||
| <code>hint</code> || string || Подсказка при фокусе в popup (1 с задержка, 5 с показ) | |||
|- | |||
| <code>menu</code> || array || Вложенное подменю (см. [[#menu — подменю|Подменю]]) | |||
|} | |||
====menu — плейсхолдеры template==== | |||
$menu | Используются в <code>template</code> '''пункта''' menu (не путать с <code>$menu</code> в template channel): | ||
{| class="wikitable" | |||
! Плейсхолдер !! Подставляется | |||
|- | |||
| <code>$title</code> || <code>title</code> пункта | |||
|- | |||
| <code>$description</code> || <code>description</code> пункта | |||
|- | |||
| <code>$logo_30x30</code> || URL иконки (для атрибута <code>src</code>) | |||
|- | |||
| <code>$logo</code> || Готовый тег <code><img></code> | |||
|- | |||
| <code>$class</code> || Значение <code>class</code> пункта | |||
|- | |||
| <code>$index</code> || Индекс пункта в menu (с 0) | |||
|} | |||
=== | Плейсхолдер '''channel''': <code>$menu</code> — в <code>$_CH["template"]</code>; подставляется HTML всех пунктов inline-меню. | ||
====menu — подменю==== | |||
Пункт с вложенным списком через <code>playlist_url":"submenu"</code> и массив <code>submenu</code>: | |||
<pre>{ | |||
"menu": [ | |||
{ | |||
"type": "inline", | |||
"title": "Ещё ▾", | |||
"playlist_url": "submenu", | |||
"submenu": [ | |||
{"title": "Обновить", "playlist_url": "cmd:reload();"}, | |||
{"title": "Настройки", "default": 1, "playlist_url": "http://example.com/settings"} | |||
] | |||
} | |||
] | |||
}</pre> | |||
Во встроенном <code>$menu</code> клик по такому пункту открывает подменю; для пунктов <code>submenu</code> также поддерживается <code>default</code>. | |||
==== | ====menu — ответ сервера cmd без перехода==== | ||
Если пункт menu ведёт на URL, сервер может вернуть JSON с командой вместо новой страницы — пользователь остаётся на текущем списке, история не растёт. | |||
<pre>{"cmd": "seticon([index],http://example.com/icon.svg);stop();"}</pre> | |||
{| class="wikitable" | |||
! Команда !! Назначение | |||
|- | |||
| <code>seticon(индекс, url)</code> || Обновить <code>logo_30x30</code> / <code>icon</code> channel; <code>[index]</code> — текущий выделенный элемент | |||
|- | |||
| <code>settitle(индекс, текст)</code> || Изменить заголовок channel по индексу | |||
|- | |||
| <code>stop()</code> || Прервать загрузку «новой страницы»; остаться на текущей (обязательно в конце цепочки cmd) | |||
|} | |||
'''Типичный сценарий''' (реакция, переключатель без перезагрузки): | |||
# Пункт menu → <code>playlist_url</code> с параметром действия (например <code>&react=1</code>) | |||
# Сервер сохраняет данные и отвечает: <code>{"cmd":"seticon([index],URL_иконки);stop();"}</code> | |||
# Fork обновляет иконку строки; кнопка '''Назад''' сразу уходит на предыдущую страницу (без «лишнего» шага) | |||
См. также глобальный тег [[#cmd_тег_.28как_глобальный_так_и_в_playlist_url.29_.2D_допустимые_команды.29|cmd]] и <code>stop();</code> в <code>$_PL["cmd"]</code>. | |||
===== | ====menu — стилизация==== | ||
* Оформление пунктов — через <code>class</code>, <code>style</code>, <code>template</code> каждого пункта. | |||
* Общие стили (pill, tab, badge) — <code>$_PL["css"]</code> или внешний файл <code>$_PL["link"][]</code>. | |||
* Длинное inline-меню прокручивается по горизонтали (<code>overflow-x</code>). | |||
==== | ====menu — совместимость==== | ||
Channel с <code>menu</code> без <code>type:inline</code> и без <code>$menu</code> в template работает как раньше: вертикальный popup по <code>playlist_url":"menu"</code>, пункт «Отмена» внизу (если нет <code>nocancel</code>). | |||
=== | === 5.3. Навигация и оформление channel === | ||
===location=== | ===location=== | ||
| Строка 580: | Строка 1065: | ||
$_CH["after"]="<nowiki><div style='color:red;'>Текст после элемента</div></nowiki>"; | $_CH["after"]="<nowiki><div style='color:red;'>Текст после элемента</div></nowiki>"; | ||
=== | === 5.4. Загрузка данных и расширенные ссылки === | ||
===Локальные переменные LOCAL_IP, TORRSERVE_IP, ACE_IP=== | |||
[[Файл:SetIP.png|мини]] | |||
В URL портала подставляются значения из настроек ForkPlayer на устройстве пользователя. | |||
'''Если не указан TORRSERVE_IP или ACE_IP''' — вместо них подставляется LOCAL_IP (устройство с RemoteFork). | |||
Если не указан также LOCAL_IP — подставляется <code>127.0.0.1</code>. | |||
====Примеры в magnet-ссылках==== | |||
$_CH[]=["title"=>"(magnet) '''acestream'''","playlist_url"=>"<nowiki>http://ACE_IP:6878/server/api?method=get_media_files&magnet=</nowiki>".urlencode("<nowiki>magnet:?xt=urn:btih:642a36ec9dcb2c5ba7b08835bd04ae8738281bb1</nowiki>")]; | |||
$_CH[]=["title"=>"(magnet) '''torrserve'''","playlist_url"=>"<nowiki>http://TORRSERVE_IP:8090/torrent/play?m3u=true&link=</nowiki>".urlencode("<nowiki>magnet:?xt=urn:btih:642a36ec9dcb2c5ba7b08835bd04ae8738281bb1</nowiki>")]; | |||
====Автовыбор torrserve или ace stream==== | |||
$_CH[]=["title"=>"(magnet) через ace stream или torrserve","playlist_url"=>"<nowiki>magnet:?xt=urn:btih:642a36ec9dcb2c5ba7b08835bd04ae8738281bb1</nowiki>"]; | |||
===SetTimeInterval — периодические запросы к серверу=== | |||
$_CH["SetTimeInterval"]=["time"=>1,"onmenu"=>"<nowiki>http://19onmenu</nowiki>","onplay"=>"<nowiki>http://19onplay</nowiki>"]; | $_CH["SetTimeInterval"]=["time"=>1,"onmenu"=>"<nowiki>http://19onmenu</nowiki>","onplay"=>"<nowiki>http://19onplay</nowiki>"]; | ||
| Строка 617: | Строка 1121: | ||
В примере выше на адрес http://parser.co/ отправятся POST данные html=md5hash, где md5hash заменятся на полученное от parser | В примере выше на адрес http://parser.co/ отправятся POST данные html=md5hash, где md5hash заменятся на полученное от parser | ||
====Допустимо в parser использовать curl запросы (доступны на андроид, частично если форк установлен в память ТВ, или через | ====Допустимо в parser использовать curl запросы (доступны на андроид, частично если форк установлен в память ТВ, или через ремотeфорк)==== | ||
====Можно перечислить порядок методов запроса парсер, например <cors>rf|android|direct|directnoheader</cors>==== | ====Можно перечислить порядок методов запроса парсер, например <cors>rf|android|direct|directnoheader</cors>==== | ||
| Строка 663: | Строка 1167: | ||
====Скачивание сторонней страницы по необходимости, страница <nowiki>http://parser.co/</nowiki> может дать json ответ==== | ====Скачивание сторонней страницы по необходимости, страница <nowiki>http://parser.co/</nowiki> может дать json ответ==== | ||
{"parse":"<nowiki>http://www.youtube.com/watch?v=qZ3xj_UF4I8&gl=US&hl=en&has_verified=1&bpctr=9999999999</nowiki>"} | {"parse":"<nowiki>http://www.youtube.com/watch?v=qZ3xj_UF4I8&gl=US&hl=en&has_verified=1&bpctr=9999999999</nowiki>"} | ||
Скачанная страница шлется в параметре | Скачанная страница шлется в параметре $_POST['remoteparse'] | ||
== 6. Оформление и вёрстка элементов == | |||
===position при заданном $_PL["typeList"]="start";=== | |||
При <code>$_PL["typeList"]="start"</code> каждый channel может задать свой вид через <code>position</code>: | |||
====$_CH["position"]=""; // - Плитка как в Стартовом меню размером 128х101px)==== | |||
====$_CH["position"]="html"; // - Пример элемента со своим дизайном в $_CH["template"]==== | |||
$_CH["position"]="html"; | |||
$_CH["br"]=0; // Не переносить навигацию на новую строку | |||
$_CH["title"]="Новый вид"; | |||
$_CH["description"]="Описание вида"; | |||
$_CH["logo_30x30"]="<nowiki>http://p.lnka.ru/icons/yapfiles.png</nowiki>"; | |||
$_CH["template"]='<nowiki><div style="width:242px;overflow:hidden;margin:4px;text-align:center;"><img src="$logo_30x30" style="width:242px;height:171px;padding:2px 2px 0px 2px;"></nowiki><nowiki><br></nowiki>$title<nowiki><br></nowiki><nowiki><small>$description</small></nowiki><nowiki></div></nowiki>'; | |||
Элементы будут выстраиваться в ряд и навигация по ним будет предполагать что они в одном ряду | |||
=====$_CH["br"]===== | |||
В месте где навигация должна перейти на новую строку задайте $_CH["br"]=1; При этом в before будет добавлено значение <nowiki><br clear=both></nowiki> | |||
====$_CH["position"]="fulleditline"; //- Поле ввода текста шириной почти на всю страницу==== | |||
====$_CH["position"]="hlist"; // - горизонтальная ссылка (может быть несколько на одном горизонтальном уровне)==== | |||
====$_CH["position"]="list"; // -обычный вид списка шириной в половину экрана и с description справа==== | |||
====$_CH["position"]="label"; // -невысокая строка на всю ширину экрана==== | |||
====$_CH["position"]="bigtile"; // - Плитка увеличенной в 2 раза высоты (128х215px)==== | |||
===description — прокрутка части большого текста=== | |||
Добавьте в описание div с id <code>scrolled</code> — прокрутка кнопками PG_UP / PG_DOWN: | |||
$_CH["description"]='MY TITLE STATIC<nowiki><div id="scrolled"></nowiki> | |||
PG_UP PG_DOWN | |||
large scrolled content | |||
. | |||
. | |||
. | |||
<nowiki></div></nowiki> | |||
=== | MY FOOTER STATIC'; | ||
=== 6.1. Произвольная вёрстка (position=html, template, br) === | |||
Пошаговая схема для своего дизайна плитки: | |||
# <code>$_PL["typeList"]="start";</code> | |||
# Задать CSS в <code>$_PL["css"]</code> | |||
# Для каждого channel: <code>position="html"</code>, <code>template</code>, при необходимости <code>br</code> | |||
===Прописываем свои стили=== | ===Прописываем свои стили=== | ||
| Строка 710: | Строка 1270: | ||
$_CH["br"]=1;// Этот элемент разместится уже на новой строке | $_CH["br"]=1;// Этот элемент разместится уже на новой строке | ||
==Горизонтальная прокрутка== | ===coordination (устаревший способ навигации)=== | ||
[[Файл:Координаты.png|мини]] | |||
До появления тега <code>br</code> навигацию по произвольному шаблону задавали координатами. Обязательно при <code>position=html</code> и своём <code>template</code>. | |||
$_CH["coordination"]=[x,y]; | |||
Где <code>x</code> — позиция по горизонтали, <code>y</code> — по вертикали. Первый элемент: <code>[0,0]</code>. Элемент на новой строке: <code>[1,0]</code>. | |||
=== 6.2. Горизонтальная прокрутка (nowrap) === | |||
Для страниц с <code>typeList=html</code> или <code>position=html</code>. Задайте <code>nowrap=1</code> только первому элементу блока (у него же <code>br=1</code>): | |||
$_CH=["br"=>1,"nowrap"=1,"title"=>"Первый элемент горизонтального блока прокрутки"]; | $_CH=["br"=>1,"nowrap"=1,"title"=>"Первый элемент горизонтального блока прокрутки"]; | ||
| Строка 725: | Строка 1294: | ||
$_CH=["br"=>1,"title"=>"Новый элемент с новой строки"]; | $_CH=["br"=>1,"title"=>"Новый элемент с новой строки"]; | ||
== | == 7. Формирование ответа на сервере == | ||
<? | |||
=== 7.1. Параметры GET от клиента === | |||
При открытии портала ForkPlayer добавляет к запросу идентификаторы устройства. | |||
{| class="wikitable" | |||
! Параметр !! Описание | |||
|- | |||
| <code>box_mac</code> || Виртуальный MAC-адрес устройства | |||
|- | |||
| <code>box_user</code> || Email пользователя (если выполнен вход в аккаунт Fork) | |||
|} | |||
Пример: <nowiki>http://nserv.host/?box_mac=</nowiki>'''your_mac_address'''&box_user='''your_forkplayer_tv@email''' | |||
// | === 7.2. Шаблон вывода JSON (PHP) === | ||
<pre><?php | |||
// $_PL — глобальные теги страницы | |||
// $_CH — массив channel; каждый элемент — ассоциативный массив тегов | |||
$_PL["channels"]=$_CH; | $_PL["channels"] = $_CH; | ||
print json_encode($_PL); | header("Content-Type: application/json; charset=utf-8"); | ||
print json_encode($_PL, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); | |||
?></pre> | |||
== 8. Связанные материалы == | |||
* [https://wiki.forkbrowser.top/wiki/FXMLbaseparser FXMLbaseparser] — PHP-класс, упрощающий генерацию FXML | |||
* [https://wiki.forkbrowser.top/wiki/CHANGELOG CHANGELOG] — история изменений протокола | |||
* [https://github.com/vengo634/kino.pub_forkplayerPHP/blob/master/index.php Пример портала на PHP] | |||
* FXML CMS — визуальный редактор порталов | |||
* Исходный код любой страницы можно просмотреть в ForkPlayer (см. скриншот выше) | |||
<br /> | <br /> | ||
Текущая версия от 12:29, 20 июня 2026
FXML — официальная спецификация языка разметки порталов для Fork browser и совместимых приложений.
Формат ответа: валидный JSON. Навигация — с пульта, без мыши и тач-экрана.
1. Область применения
FXML (Fork eXtensible Markup Language) описывает структуру страницы, которую сервер отдаёт клиенту. Клиент интерпретирует JSON и показывает список элементов, видео или произвольную вёрстку на экране телевизора.
Совместимые приложения: ForkPlayer, OTT Player, OTT-play.
Связанные документы:
- CHANGELOG — история изменений протокола
- FXMLbaseparser — PHP-класс для генерации FXML
- Пример портала (PHP)
2. Быстрый старт
Минимальная страница состоит из заголовка и массива элементов channels. У каждого элемента — как минимум title и ссылка (playlist_url или stream_url).
Минимальный пример (JSON):
{
"title": "Мой портал",
"channels": [
{"title": "Главная", "logo_30x30": "http://example.com/icon.png", "playlist_url": "http://example.com/"},
{"title": "Фильм", "stream_url": "http://example.com/video.m3u8"}
]
}
Вывод на PHP:
<?php
$_PL = ["title" => "Мой портал"];
$_CH = [
["title" => "Главная", "logo_30x30" => "http://example.com/icon.png", "playlist_url" => "http://example.com/"],
["title" => "Фильм", "stream_url" => "http://example.com/video.m3u8"]
];
$_PL["channels"] = $_CH;
header("Content-Type: application/json; charset=utf-8");
print json_encode($_PL, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
?>
playlist_url— открыть другую FXML/XML/M3U страницуstream_url— запустить видеоплеер
Полный пример портала (COOLTV):
Следующий код (валидный JSON):
{"title":"COOLTV - портал нового поколения","background-image":"http://cooltv.info/img/tvcool.jpg","typeList":"start","icon":"http://cooltv.info/img/tvcool23.jpg","channels":[{"title":"Вход","logo_30x30":"http://cooltv.info/img/profle22.jpg","playlist_url":"http://cooltv.info/auth"},{"title":"Новости ","logo_30x30":"http://cooltv.info/img/rss-96.png","playlist_url":"http://cooltv.info/news"},{"title":"Поиск ","logo_30x30":"http://cooltv.info/img/icons8-search-folder-96.png","playlist_url":"http://cooltv.info/media/search.php"},{"title":"Кинозал ","logo_30x30":"http://cooltv.info/img/1icons8-film-reel-96.png","playlist_url":"http://cooltv.info/media"},{"title":"AceStream ","logo_30x30":"http://cooltv.info/img/icons8-wave-arrows-96.png","playlist_url":"http://cooltv.info/vdt/12"},{"title":"Обменник ","logo_30x30":"http://cooltv.info/img/icons8-next-100.png","playlist_url":"http://cooltv.info/media/obmen/"},{"title":"Сервисы ","logo_30x30":"http://cooltv.info/img/icons8s3-96.png","playlist_url":"http://cooltv.info/vdt/9"},{"title":"IPTV ","logo_30x30":"http://cooltv.info/img/icons8-hdtv-96.png","playlist_url":"http://cooltv.info/vdt/8"},{"title":"Мультимедиа ","logo_30x30":"http://cooltv.info/img/icons8-documentary-96.png","playlist_url":"http://cooltv.info/vdt/7"},{"title":"Чат ","logo_30x30":"http://cooltv.info/img/icons8-chat-96.png","playlist_url":"http://cooltv.info/guest"},{"title":"FAQ ","logo_30x30":"http://cooltv.info/img/icons8-info-popup-96.png","playlist_url":"http://cooltv.info/faq/"},{"title":"Копилка","logo_30x30":"http://cooltv.info/img/icons8-bad-piggies-96.png","playlist_url":"http://cooltv.info/copilka"}]}
даст такой результат (в ForkPlayer):

Просмотр исходного кода страницы в ForkPlayer

3. Термины и модель данных
| Термин | Описание |
|---|---|
| Документ FXML | JSON-объект верхнего уровня, который сервер отдаёт клиенту |
| $_PL / Playlist | Глобальные поля документа (заголовок страницы, стили, команды) |
| $_CH / Channel | Один элемент списка в массиве channels[]
|
playlist_url |
Ссылка на следующую страницу (FXML, XML, M3U, команда) |
stream_url |
Ссылка на видеопоток; при наличии у channel не используется playlist_url
|
typeList |
Режим отображения страницы: список (по умолчанию) или плитка (start)
|
position |
Вид отдельного элемента при typeList="start" (плитка, список, html и др.)
|
Правила:
- Ответ сервера — один JSON-объект с ключом
channels(массив). - У channel должен быть непустой
title. - У channel заполняется либо
playlist_url, либоstream_url(если элемент не служебный). - Новые теги добавляются по мере развития протокола — см. CHANGELOG.
4. Справочник глобальных тегов ($_PL)
Теги задают свойства всей страницы. В PHP соответствуют элементам массива $_PL.
| Тег | Назначение | Раздел |
|---|---|---|
title |
Заголовок страницы | — |
typeList |
start — плитка; иначе — список |
[[#position при заданном $_PL["typeList"]="start";|position]] |
channels |
Массив элементов списка | §5 |
css |
CSS-стили страницы (строка) | §4.1 |
link[] |
Подключение внешних CSS | link |
align |
Выравнивание плитки (left, по умолчанию — center) |
align |
cmd |
Команда при загрузке страницы | cmd |
ping |
Опрос URL до появления channels | ping |
aside |
Боковое меню | aside |
advertising |
Реклама перед видео | advertising |
url_tvg |
URL телепрограммы (xmltv) | url_tvg |
menu |
Глобальная полоса меню вверху | menu |
cacheinfo |
nocache — не кешировать страницу |
ниже |
setcookie |
Cookie для последующих запросов к домену | ниже |
info, confirm |
Уведомление или диалог при загрузке | ниже |
4.1. Оформление страницы (css, link, align)
$_PL["css"] — CSS одной строкой. При typeList="start" доступны классы .start, .list, .html и др. (см. [[#position при заданном $_PL["typeList"]="start";|position]]).
$_PL["css"]="start"; — плиточный вид + поддержка $_CH["position"].
Стили сайта по умолчанию
По умолчанию для вашей страницы задаются такие стили (вы их можете изменить задав нужные в $_PL["css"]
- -webkit-scrollbar-button:single-button {
background-color: #bbbbbb; display: block; border-style: solid; height: 10px; width: 10px;
}
- -webkit-scrollbar-button:single-button:vertical:decrement {
border-width: 5px; border-color: transparent transparent #555555;
}
- -webkit-scrollbar-button:single-button:vertical:increment {
border-width: 5px; border-color: #555555 transparent transparent transparent;
}
- -webkit-scrollbar {
width: 10px;
}
- -webkit-scrollbar-track {
background: #ddd;
}
- -webkit-scrollbar-thumb {
background: #888;
}
- -webkit-scrollbar-thumb:hover {
background: #777;
}
.start {
position: relative; border: 0px; text-align: center; display: inline-block; ; overflow: hidden; text-align: center; margin-left: 4px; margin-top: 1px; height: 10px; width: 10%;
}
.start .rating {
position: absolute; display: none; text-align: left; height: 23px; width: 28px; padding-top: 5px; padding-left: 5px; font-size: 11px;
}
.start .icon {
padding: 2px 2px 0px 2px; height: 76%;
}
.start .name {
font-size: 17px; line-height: 0.9; position: absolute; bottom: 0px; width: 100%; max-height: 50%;
}
.start .title {
background: rgba(0, 0, 0, 0.65); border-radius: 3px; width: auto; display: display: inline-block; margin: 2px; padding: 1px 4px 4px 4px;
}
.start .side_icon {
position: relative; padding: 2px;
}
.list {
text-align: left; border-radius: 4px 0px 0px 4px; cursor: default; margin: 2px 0px; width: 48.87708333333334%; height: 35px;
}
.list .contmenu {
position: relative; float: right; display: none;
}
.list .numb {
float: left; padding-top: 2px; font-size: 60%; min-width: 38px; text-align: center;
}
.list .icon {
margin: 0px 2px; padding-right: 2px; float: left;
}
.list .title {
display: inline-block; float: left; position: relative; white-space: nowrap;
}
.listsearch {
background-color: #ddd; color: #333; overflow: hidden; border-radius: 4px; padding: 3px; margin-top: 2px; font-size: 85%; width: 90%; height: 68%;
}
.infolinktitle {
height: 63%; font-size: 80%; overflow: hidden;
}
.infolink {
padding-left: 3px; font-size: 11px;
}
.default .contmenu {
display: none;
}
.selected .contmenu {
display: block;
}
.html {
position: relative; display: inline-block; vertical-align: top;
}
.hlink {
position: relative; border: 0px solid transparent; margin: 0px 4px; border-radius: 4px; display: inline-block; overflow: hidden;
}
.hlink .title {
overflow: hidden; max-width: 310px; font-size: 90%; padding: 0px 4px; float: left; height: 28px; text-decoration: underline;
}
.hlink .icon {
float: left; height: 22px; padding-top: 5px;
}
.fulleditline {
border: 1px solid transparent; width: 80%; margin: 1px 9%; border-radius: 4px; display: inline-block; overflow: hidden;
}
.fulleditline .title {
overflow: hidden; font-size: 24px; height: 24px; margin: 3px; color: #2b2525; background-color: #eee; padding: 4px;
}
.fulleditline .icon {
float: right; height: 22px; padding-top: 2px;
}
.label {
position: relative; text-align: left; border-radius: 4px; margin: -2px 4%; font-size: 80%; width: 92%; display: inline-block; overflow: hidden; height: 26px;
}
.label .icon {
float: left; height: 20px;
}
.default {
background: none;
}
.selected {
background: rgba(180, 180, 180, 0.7);
}
.listselected {
color: black;
}
.site {
height: 100%; width: 100%; overflow-y: auto; overflow-x: hidden; font-size: 27px;
}
body {
color: rgb(238, 238, 238); margin: 0px; padding: 0px; height: 100%; width: 100%; overflow: hidden; background-color: transparent;
}
small {
font-size: 70%; color: gray;
}
.buttons {
position: fixed; top: 80%; left: 50%; width: 47%; margin: 2px; display: none; padding: 0px 3px 0px 3px; color: #cccccc; font-size: 80%;
} \#description {
top: 0px; position: fixed; margin: 2px; overflow-x: hidden; overflow-y: auto; display: none; padding: 0px 3px 0px 3px; font-size: 28px; left: 51%; width: 47.2%; word-break: break-word;
}
Пример своего стиля (пишите чистый css, он будет преобразован в одну строку)
Если строка стилей очень большая, их лучше вынести в отдельный css файл
$_PL["link"][]=["type"=>"text/css","href"=>"http://example.com/main.css"];
#content - стиль страницы (без description)
$_PL["css"]
#rightHalf- стиль description
$_PL["css"]="#content {font-size:25px;} #rightHalf{font-size:10 px;} "; // Установим размер шрифта сайта и описания
.selected - стиль при выделении элемента
$_PL["css"]=" .selected { color: black; } "; // Делаем смену цвета выделенного элемента
.default - стиль невыделенного элемента
$_PL["css"]=" .default{ color: gray; } ";
$_PL["css"]=" .contmenu{ display:none; } "; // Скрываем кнопку контекстного меню
.label, .start, .list, .fulleditline, .hlink, .html - стили(верхний уровень) для разных типов элементов $_CH["position"] [1]
Если не задан $_CH["position"] элемента то по умолчанию элементы отображаются на странице списком и классом .list
Если при этом задано отображение плиткой ($_PL["typeList"]="start" ) то по умолчанию элементы отображаются с классом .start
.labelselected, .startselected, .listselected, .fulleditlineselected, .hlinkselected, .htmlselected - стили(верхний уровень) для выделенных элементов
Установим цвет для выделенных элементов только .list
$_PL["css"]=" .listselected{color: red; }";
.labeldefault, .startdefault, .listdefault, .fulleditlinedefault, .hlinkdefault, .htmldefault - стили(верхний уровень) для невыделенных элементов
.list - зададим свой вид списка
$_PL["css"]=" .list{color: red; }"; // Меняем цвет
.title .icon .rating .numb вложенные в верхний уровень стили заголовка, иконки, рейтинга(для .start), порядкового номера(для .list)
$_PL["css"]=" .title{color: red; }"; // Меняем цвет заголовков
$_PL["css"]=" .numb { display:none; } "; // Скрываем отображение номера
Комбинация классов, зададим стиль иконок только для списка (.list)
$_PL["css"]=" .list .icon{background-color:white; margin:2px;}"; // Зададим белую рамку фона для иконок
$_PL["cacheinfo"]="nocache"; // Не кешировать страницу (при возврате назад страница будет грузиться заново по адресу)
$_PL["setcookie"]["name1"]="value1"; // Аналог cookie вебсайтов. Запомнит для последующих запросов страниц с этого же домена в переменной $_GET["cookie"]["name1"] значение value1
$_PL["info"]="Вам уведомление"; // Показывает alert "Вам уведомление" с кнопкой ОК (закрыть)
$_PL["confirm"]=["title"=>"Открыть вложенный CHannel?","channel"=>["playlist_url"=>"http://.."]];
4.2. Поведение страницы (cmd, ping, служебные теги)
cmd тег (как глобальный так и в playlist_url) - допустимые команды
Примеры использования cmd в глобальном теге
$_PL["cmd"]="historyback(1);" // Вернуться назад на 1 страницу
$_PL["cmd"]="info(Вам уведомление);" // Показывает alert "Вам уведомление" с кнопкой ОК (закрыть)
$_PL["cmd"]="settitle(2, Новый заголовок);" // элементу 2 дать новый title
$_PL["cmd"]="setdescription(2, Новый заголовок);" // элементу 2 дать новый description
$_PL["cmd"]="reload(2);" // Перезагрузить страницу через 2секунды
В конец cmd к командам выше можно добавить stop(); чтобы выполнить команду выше и остановить загрузку новой страницы
например $_PL["cmd"]="info(Доступ к этой странице Вам запрещен);stop();" выведет уведомление но оставить пользователя на предыдущей странице.
Примеры использования cmd в $_CH["playlist_url"]
$_CH["playlist_url"]="cmd:<Команда из списка допустимых>";
Например $_CH["playlist_url"]="reload();"; перезагрузит страницу при нажатии
Глобальный тег ping
Для выполнения действий когда они станут доступны. Например в Fork browser можно отслеживать введение кода с телефона и автоматически загружать страницу.
$_PL["ping"]["link"]="http://example.com/check.php?hash";
Если check.php отдаёт пустой ответ, Fork продолжает проверять страницу каждые 6 сек.
Если check.php отдаёт страницу с channels, загружается она, например:
$_PL["channels"]=[["location"=>1,"title"=>"Успешно вошли! Нажмите для переадресации...","playlist_url"=>"http://example.com/"]];
Глобальный тег align
Выравнивание элементов при виде плиткой ($_PL["typeList"]="start"). По умолчанию — по центру.
$_PL["align"]="left";
Глобальный тег url_tvg — своя телепрограмма
Поддерживается только формат xmltv (сжатый и нет).
$_PL["url_tvg"]="http://epg.it999.ru/edem.xml.gz";
В M3U плейлисте аналогичный параметр задаётся в начале:
#EXTM3U url-tvg="http://epg.it999.ru/edem.xml.gz"
Глобальный тег aside для вывода бокового меню

$_PL["aside"]["channels"][]=["title"=>"Home","playlist_url"=>"http://site.com/","style"=>"color:blue;","logo_30x30"=>"https://static.vecteezy.com/system/resources/thumbnails/022/013/913/small/home-icon-illustration-image-vector.jpg"];
$_PL["aside"]["channels"][]=["title"=>"Category 1","playlist_url"=>"http://site.com/cat1","logo_30x30"=>"https://static.vecteezy.com/system/resources/thumbnails/022/013/913/small/home-icon-illustration-image-vector.jpg"];
Изменить стили бокового меню по умолчанию:
$_PL["css"].=""#aside{position:absolute;z-index:1;top:0px;left:0px;height:inherit;border-radius: 0px 10px 10px 0px;padding:0px 10px 0px 2px;font-size: 27px;line-height: 27px;white-space: nowrap;overflow-x: hidden;}
#aside img{width:27px;height:27px;margin-right:8px;}
.aside_default{margin:5px 0px;padding: 4px;border-radius: 8px;border: 2px solid transparent;}
.aside_selected{margin:5px 0px;padding: 4px;border-radius: 8px;border: 2px solid #5590e7;}
.aside_hide{width:30px;background:none;overflow-y: hidden;}
.aside_show{width:auto;overflow-y: auto;background-color:rgba(0, 0, 0, 0.8);}";
Если стили свои, то лучше вынести их в отдельный css файл
Подключения css файлов стилей
$_PL["link"][]=["type"=>"text/css","href"=>"http://example.com/main.css"];
advertising для показа рекламы перед запуском видео
Видеоролик
$_PL["advertising"]=["client"=>"video", "title"=>"Реклама",// Надпись внизу с названием или описанием обьявления "skip"=>3,//Добавьте skip в секундах когда можно будет нажать кнопку пропуск "tag"=>"https://mlb2.adriver.ru/mf/0008908/0008908282/0/720_480.mp4" ];
VAST
XML формат рекламы. Поддерживаются перенаправления, пропуск, отправка событий
$_PL["advertising"]=["client"=>"vast", "title"=>"Реклама", "tag"=>"https://raw.githubusercontent.com/InteractiveAdvertisingBureau/VAST_Samples/master/VAST%203.0%20Samples/Inline_Companion_Tag-test.xml" ];
В настройках форка для тестирования включите Ads: always
5. Справочник тегов channel ($_CH)
Каждый объект в channels[] — элемент списка. Ниже — теги в порядке от базовых к расширенным.
| Уровень | Тег | Назначение |
|---|---|---|
| Базовый | title |
Заголовок элемента (обязательно) |
| Базовый | logo_30x30 |
URL иконки 30×30 |
| Базовый | playlist_url |
Переход на другую страницу |
| Базовый | stream_url |
Запуск видеоплеера |
| Базовый | description |
Текст справа (режим списка) или в шаблоне |
| Средний | position, br, template |
Вид и вёрстка элемента |
| Средний | menu, confirm |
Меню и диалоги |
| Средний | location |
Переадресация (1 — заменить URL, 3 — временная) |
| Средний | before, after |
HTML до/после элемента |
| Плеер | poster, label, information |
Подписи в видеоплеере |
| Плеер | subtitles, event, start_time |
Субтитры, события, старт с позиции |
| Расширенный | parser, cors, iframe |
Загрузка и встраивание данных |
| Расширенный | SetTimeInterval |
Периодические запросы к серверу |
5.1. Основные теги
title
Заголовок элемента. Отображается в списке и используется как fallback для label в плеере.
playlist_url или stream_url
Адрес страницы или адрес видеопотока. Непустым может быть только один из этих тегов.
Параметры ссылок #direct и #stream_url
Добавляются в конец ссылки:
#direct — если сайт требует чистой ссылки без добавлений Fork (параметры, cookie, идентификатор):
http://pastebin.com/yuUtYu56g#direct → откроется как http://pastebin.com/yuUtYu56g
#stream_url — ссылка откроется сразу в видеоплеере.
AddFavorite и AddSearch
Команды в playlist_url для добавления портала в закладки или глобальный поиск:
$_CH[]=["logo_30x30"=>"none","title"=>"Добавить этот портал в закладки / стартовое меню","playlist_url"=>"AddFavorite(Кинопаб,https://kino.pub/images/logo.png,http://195.88.208.101/kinopub/);"];
$_CH[]=["logo_30x30"=>"none","title"=>"Добавить этот портал в Глобальный поиск","playlist_url"=>"AddSearch(Кинопаб,https://kino.pub/images/logo.png,http://195.88.208.101/kinopub/?cat=search);"];
(пример всей страницы на PHP: Кинопаб)
iframe — вставка содержимого другой страницы
На место элемента списка вставляется содержимое из ссылки. Можно использовать для поиска по нескольким плейлистам (глобальный поиск).
$_CH[]=["title"=>"Поиск Terminator в filmix.red","playlist_url"=>"https://filmix.red/fork/search?search=Terminator","iframe"=>"4","timeout"=>8];
iframe — обязательный параметр. Число — сколько ссылок из дочерней страницы показать сразу; остальное будет внизу списка. "iframe"=>"0" — показать все.
timeout — таймаут ожидания содержимого из дочерней ссылки (секунды).
Альтернативный playlist_url2, время ожидания таймаута, способы получения
Можно указать запасной источник страницы, время ожидания таймаута, способы получения
<?php $_CH[]= [ "title" => "Каталог", "playlist_url" => "http://domain1.in.net/", "playlist_url2" => "http://domain2.in.net/", "playlist_url3" => "http://domain3.in.net/", "cors" => "directnoheader", // Тип получения страницы(напрямую без заголовков). Если не указать то будет перебор direct, directnoheader, remotefork и потом альтернативный источник "timeout" => 4, // В секундах "error" => "http://domainstat.in.net//err.php?info=%INFO" // Адрес куда отправлять ошибку если все страницы не откроются ];
stream_url мультиссылки с разными качествами и (или) несколькими источниками, Err format json link
Для видеопотока можно задавать мультиссылки с разным качеством (можно и разные озвучки, но это будет не так явно для пользователя), выбор качества будет браться автоматически с настроек Fork(1080 или 720 и т.д.) и (или) несколькими источниками
Пример использования в PHP с json_encode В данном примере создается массив с различными ссылками на видеопотоки, и с помощью функции json_encode он преобразуется в JSON-формат для последующего использования:
<?php
// Массив с данными для разных качеств видео и двумя источниками
$stream_links = [
"360" => [
"title" => "360p",
"url" => [
"http://server.in/240.mp4",
"http://server2.in/hls/240.mp4"
]
],
"720" => [
"title" => "720p",
"url" => [
"http://server.in/720.mp4",
"http://server2.in/hls/720.mp4"
]
]
];
// Преобразование массива в JSON
$json_stream_url = json_encode($stream_links);
// Использование JSON в $_CH["stream_url"]
$_CH["stream_url"] = $json_stream_url;
// Вывод JSON для проверки
echo $_CH["stream_url"];
?>
Пример с несколькими источниками:
<?php
$stream_links = [
"http://server.in/240.mp4",
"http://server2.in/hls/240.mp4"
];
// Преобразование массива в JSON
$json_stream_url = json_encode($stream_links);
// Использование JSON в $_CH["stream_url"]
$_CH["stream_url"] = $json_stream_url;
// Вывод JSON для проверки
echo $_CH["stream_url"];
?>
Поддержка ссылок Яндекс.Диска
Ссылка должна быть в поле stream_url или в m3u плейлисте в формате https://yadi.sk/i/idfile
playlist — похожие видео после окончания (YouTube)
<?php $PLAYLIST=[]; $PLAYLIST[]=["title"=>"Похожее видео 1","stream_url"=>"http://www.youtube.com/watch?v=ZNLZla2xHUQ"]; $PLAYLIST[]=["title"=>"Похожее видео 2","stream_url"=>"http://www.youtube.com/watch?v=ZNLZla2xHUQ"]; $_CH[]=["title"=>"Основное видео","stream_url"=>"http://www.youtube.com/watch?v=xhFCmwrSxCU","playlist"=>$PLAYLIST]; $_PL["channels"]=$_CH; print json_encode($_PL); ?>
logo_30x30
URL иконки элемента (рекомендуемый размер 30×30 px). Используется также как fallback для poster в плеере.
5.2. Видеоплеер
Теги видеоплеера poster, label, information
Нужны чтобы по-разному выдавать информацию на сайте и в видеоплеере.
$_CH["title"]="Хало 1 сезон 1 серия"; $_CH["label"]="Хало"; $_CH["information"]="1 сезон 1 серия";

Плеер сначала ищет значение в специализированном теге; если он пустой — берёт из общего тега.
Картинка в видеоплеере слева внизу
$_CH["poster"]="http://example.com/videoposter.png";
если пустой — используется logo_30x30.
Название над полосой прогресса
$_CH["label"]="Хало";
если пустой — используется title.
Информация под прогрессом
$_CH["information"]="1 сезон 1 серия";
если пустой — используется description.
Тег subtitles — субтитры
$_CH["subtitles"][0]=["UA","https://tortuga.wtf/player/subtitle/18625_ua.vtt"]; $_CH["subtitles"][1]=["EN","https://tortuga.wtf/player/subtitle/18625_en.vtt"]; $_CH["subtitles"][2]=["RU","https://tortuga.wtf/player/subtitle/18625_ru.vtt"];
event — события видеоплеера
Отправка на сервер событий при старте и остановке видео:
$_CH[]=["logo_30x30"=>"hidden","title"=>"Video","stream_url"=>"http://...","event"=>["onstartvideo"=>"$siteurl/?event=onstartvideo&videoid=1","onstopvideo"=>"$siteurl/?event=onstopvideo&curTime=[curTime]&totalTime=[totalTime]&videoid=1"]];
[curTime] и [totalTime] заменяются на время остановки и общую длительность видео в секундах.
start_time — начало воспроизведения
$_CH[]=["logo_30x30"=>"hidden","title"=>"Video","stream_url"=>"http://...","start_time"=>340];
start_time — время в секундах. Предлагается на кнопке Play только если пользователь ранее не смотрел это видео; иначе нужно отметить видео непросмотренным в Меню / Отметить непросмотренным.
5.3. Интерактивные элементы
confirm - диалоговое окно с действиями при нажатии на элемент
Пример подтверждения выхода с аккаунта
$_CH[]=["title"=>"Выйти","playlist_url"=>"confirm","confirm"=>["http://host/?do=exit"],"description"=>"Выйти с аккаунта?"];
Массив $_CH["menu"] задаёт контекстное меню одного элемента списка (channels[]). Каждый пункт меню — мини-channel: title, playlist_url (или stream_url) и опциональные поля оформления.
| Тег | Область | Назначение |
|---|---|---|
$_CH["menu"] |
Элемент списка | Меню конкретного channel (качество, реакция, настройка строки) |
$_PL["menu"] |
Страница целиком | Глобальная полоса меню вверху страницы (зеркала, навигация портала) |
Содержание раздела
Минимальное вертикальное popup-меню по OK на channel:
$menu = []; $menu[] = ["title" => "Пункт 1", "playlist_url" => "http://example.com/1"]; $menu[] = ["title" => "Пункт 2", "playlist_url" => "http://example.com/2"]; $_CH["title"] = "Настройки"; $_CH["playlist_url"] = "menu"; // обязательно для открытия меню $_CH["menu"] = $menu;
Обязательные условия:
- у channel:
"playlist_url": "menu"; - массив
menuс одним и более пунктов; - каждый пункт — обычный channel (URL,
cmd:...,submenuи т.д.).
Поведение определяется комбинацией menu[0].type и наличия $menu в $_CH["template"].
| Режим | Условие | Вид | Как открыть |
|---|---|---|---|
| Popup (вертикальный) | нет inline в type |
Список поверх страницы | OK на channel с playlist_url":"menu"
|
| Inline в строке | type содержит inline + в template есть $menu |
Горизонтальный ряд кнопок внутри элемента | Кнопки видны сразу; popup не открывается |
| Inline popup | inline + в template нет $menu |
Горизонтальный popup | OK на channel; навигация ←/→ |
Схема подключения inline в template:
$_CH["position"] = "html";- В
template— плейсхолдер$menu - Заполнить
menu[] - У первого пункта:
"type": "inline"(и при необходимости|nocancel)
Поле type задаётся только у первого пункта menu[0]. Несколько опций комбинируются через | (порядок не важен):
"type": "inline" "type": "inline|nocancel" "type": "nocancel"
| Значение | Описание |
|---|---|
inline |
Горизонтальное меню: в строке ($menu в template) или горизонтальный popup
|
nocancel |
Не добавлять пункт «Отмена» в popup; закрытие — кнопкой Назад |
Примечание: при inline первый пункт меню — обычная кнопка (например «1080p»), а не пустой служебный маркер.
| Контекст | Клавиши | Действие |
|---|---|---|
| Popup (вертикальный) | ↑ / ↓ | Перемещение между пунктами |
| Popup | OK | Выбор пункта |
| Popup | Назад | Закрыть меню (без перехода по истории) |
| Inline popup | ← / → | Перемещение между пунктами |
Inline в строке ($menu) |
OK / клик | Выбор пункта напрямую |
| Inline в строке | ← / → | Фокус по горизонтальному ряду (если меню в popup после OK) |
Поле default — начальный фокус при первом открытии меню:
"menu": [
{"title": "Отлично", "playlist_url": "..."},
{"title": "Нормально", "default": 1, "playlist_url": "..."},
{"title": "Плохо", "playlist_url": "..."}
]
- у пункта с
"default": 1(или любое истинное значение) курсор ставится при открытии; - если ни у одного пункта нет
default— выделяется первый (индекс 0); - работает в popup-меню и во вложенном submenu;
- пункт «Отмена» в фокус по
defaultне попадает.
Поле hint — подсказка у пункта в popup: появляется через 1 с после фокуса, скрывается через 5 с (жёлтый tooltip у элемента).
Пример JSON (переключатель качества):
{
"title": "Качество",
"position": "html",
"playlist_url": "menu",
"template": "<div class='setting-item'><div class='setting-title'>$title</div><div class='chmenu'>$menu</div></div>",
"menu": [
{
"type": "inline|nocancel",
"title": "1080p",
"logo_30x30": "http://example.com/icons/hd.png",
"playlist_url": "http://example.com/?q=1080",
"default": 1,
"class": "pill active",
"hint": "Full HD",
"template": "<div><img src=$logo_30x30> $title</div>"
},
{"title": "720p", "playlist_url": "http://example.com/?q=720", "class": "pill"},
{"title": "480p", "playlist_url": "http://example.com/?q=480", "class": "pill"}
]
}
Пример PHP:
<?php
$_CH["title"] = "Качество";
$_CH["position"] = "html";
$_CH["playlist_url"] = "menu";
$_CH["template"] = '<div class="setting-item">'
. '<div class="setting-title">$title</div>'
. '<div class="chmenu">$menu</div></div>';
$_CH["menu"] = [
[
"type" => "inline|nocancel",
"title" => "1080p",
"default" => 1,
"logo_30x30" => "http://example.com/icons/hd.png",
"playlist_url" => "http://example.com/?q=1080",
"class" => "pill active",
"template" => '<div><img src=$logo_30x30> $title</div>'
],
["title" => "720p", "playlist_url" => "http://example.com/?q=720", "class" => "pill"],
["title" => "480p", "playlist_url" => "http://example.com/?q=480", "class" => "pill"]
];
$_PL["css"] = ".pill{border-radius:999px;padding:4px 14px;margin:0 4px;display:inline-block;}"
. ".pill.active{background:#3366cc;color:#fff;}";
$_PL["channels"][] = $_CH;
print json_encode($_PL);
?>
При $menu в template + type:inline повторный popup по OK не открывается.
| Поле | Тип | Описание |
|---|---|---|
title |
string | Текст пункта (обязательно для отображения) |
playlist_url |
string | URL или команда при выборе (cmd:..., submenu и др.)
|
stream_url |
string | Альтернатива playlist_url для видеопотока
|
type |
string | — inline, nocancel
|
default |
int/bool | 1 — пункт выделен при первом открытии меню
|
logo_30x30 |
string | URL иконки пункта |
class |
string | CSS-класс обёртки (по умолчанию menucontext)
|
style |
string | Inline-стили обёртки пункта |
img_style |
string | Inline-стили иконки (если нет своего template)
|
template |
string | Свой HTML тела пункта (как у channel) |
before / after |
string | HTML до / после пункта |
hint |
string | Подсказка при фокусе в popup (1 с задержка, 5 с показ) |
menu |
array | Вложенное подменю (см. Подменю) |
Используются в template пункта menu (не путать с $menu в template channel):
| Плейсхолдер | Подставляется |
|---|---|
$title |
title пункта
|
$description |
description пункта
|
$logo_30x30 |
URL иконки (для атрибута src)
|
$logo |
Готовый тег <img>
|
$class |
Значение class пункта
|
$index |
Индекс пункта в menu (с 0) |
Плейсхолдер channel: $menu — в $_CH["template"]; подставляется HTML всех пунктов inline-меню.
Пункт с вложенным списком через playlist_url":"submenu" и массив submenu:
{
"menu": [
{
"type": "inline",
"title": "Ещё ▾",
"playlist_url": "submenu",
"submenu": [
{"title": "Обновить", "playlist_url": "cmd:reload();"},
{"title": "Настройки", "default": 1, "playlist_url": "http://example.com/settings"}
]
}
]
}
Во встроенном $menu клик по такому пункту открывает подменю; для пунктов submenu также поддерживается default.
Если пункт menu ведёт на URL, сервер может вернуть JSON с командой вместо новой страницы — пользователь остаётся на текущем списке, история не растёт.
{"cmd": "seticon([index],http://example.com/icon.svg);stop();"}
| Команда | Назначение |
|---|---|
seticon(индекс, url) |
Обновить logo_30x30 / icon channel; [index] — текущий выделенный элемент
|
settitle(индекс, текст) |
Изменить заголовок channel по индексу |
stop() |
Прервать загрузку «новой страницы»; остаться на текущей (обязательно в конце цепочки cmd) |
Типичный сценарий (реакция, переключатель без перезагрузки):
- Пункт menu →
playlist_urlс параметром действия (например&react=1) - Сервер сохраняет данные и отвечает:
{"cmd":"seticon([index],URL_иконки);stop();"} - Fork обновляет иконку строки; кнопка Назад сразу уходит на предыдущую страницу (без «лишнего» шага)
См. также глобальный тег cmd и stop(); в $_PL["cmd"].
- Оформление пунктов — через
class,style,templateкаждого пункта. - Общие стили (pill, tab, badge) —
$_PL["css"]или внешний файл$_PL["link"][]. - Длинное inline-меню прокручивается по горизонтали (
overflow-x).
Channel с menu без type:inline и без $menu в template работает как раньше: вертикальный popup по playlist_url":"menu", пункт «Отмена» внизу (если нет nocancel).
5.3. Навигация и оформление channel
location
$_CH["location"]=1; // Переход c заменой текущей ссылки окна на новую (при перезапуске форкплеера будет открываться уже с этой новой ссылки)
$_CH["location"]=3; // Переход с отображением в текущем окне (при перезапуске форкплеера будет открываться старая ссылка и опять переадресовываться, необходимо если например используются устаревающие сессии)
after и before - свой html код после и перед элементом списка
$_CH["before"]="<div style='color:red;'>Текст перед элементом</div>";
$_CH["after"]="<div style='color:red;'>Текст после элемента</div>";
5.4. Загрузка данных и расширенные ссылки
Локальные переменные LOCAL_IP, TORRSERVE_IP, ACE_IP

В URL портала подставляются значения из настроек ForkPlayer на устройстве пользователя.
Если не указан TORRSERVE_IP или ACE_IP — вместо них подставляется LOCAL_IP (устройство с RemoteFork).
Если не указан также LOCAL_IP — подставляется 127.0.0.1.
Примеры в magnet-ссылках
$_CH[]=["title"=>"(magnet) acestream","playlist_url"=>"http://ACE_IP:6878/server/api?method=get_media_files&magnet=".urlencode("magnet:?xt=urn:btih:642a36ec9dcb2c5ba7b08835bd04ae8738281bb1")];
$_CH[]=["title"=>"(magnet) torrserve","playlist_url"=>"http://TORRSERVE_IP:8090/torrent/play?m3u=true&link=".urlencode("magnet:?xt=urn:btih:642a36ec9dcb2c5ba7b08835bd04ae8738281bb1")];
Автовыбор torrserve или ace stream
$_CH[]=["title"=>"(magnet) через ace stream или torrserve","playlist_url"=>"magnet:?xt=urn:btih:642a36ec9dcb2c5ba7b08835bd04ae8738281bb1"];
SetTimeInterval — периодические запросы к серверу
$_CH["SetTimeInterval"]=["time"=>1,"onmenu"=>"http://19onmenu","onplay"=>"http://19onplay"];
time - интервал между отправкой событий в минутах
onmenu - ссылка для отправки событий если пользователь находится в списке
в ответ по ссылке onmenu http://193.30.240.2/try/ajax/send_status_log.php?action=onmenu можно давать json
{"message":"Hello user"}
Это уведомление будет выводится вверху форкплеера
onplay- ссылка для отправки событий если пользователь смотрит видео
Можно использовать один или два сразу тега onmenu и onplay
parser - тег загрузки другой страницы перед переходом по playlist_url или stream_url
Пример в XML
<parser><![CDATA[http://www.youtube.com/watch?v=qZ3xj_UF4I8|js=|;]]></parser>
Пример в JSON FXML
$_CH["parser"]="http://www.youtube.com/watch?v=qZ3xj_UF4I8|js=|;";
результат отдастся то что между js= и ;
Регулярное выражение (по маркеру .*? )
<parser><![CDATA[http://www.youtube.com/watch?v=qZ3xj_UF4I8|<script>.*?js=|;]]></parser>
или
<parser><![CDATA[http://www.youtube.com/watch?v=qZ3xj_UF4I8|js=|.*?js]]></parser>
преобразуется в RegExp("<script>.*?js=(.*?);","i") - результат отдастся тот что в ()
<channel><title>Test </title> <playlist_url><![CDATA[http://parser.co/#POSThtml=md5hash]]></playlist_url> <parser><![CDATA[http://www.youtube.com/watch?v=qZ3xj_UF4I8]]></parser> </channel>
#POST в playlist_url означает что все что после него будет передавать методом POST
В примере выше на адрес http://parser.co/ отправятся POST данные html=md5hash, где md5hash заменятся на полученное от parser
Допустимо в parser использовать curl запросы (доступны на андроид, частично если форк установлен в память ТВ, или через ремотeфорк)
Можно перечислить порядок методов запроса парсер, например <cors>rf|android|direct|directnoheader</cors>
Сначала будет попытка запроса через remotefork (если включен), потом через андроид класс если Fork запущен на андроиде, потом прямой XHR запрос с попыткой установить заголовки, потом прямой XHR запрос без заголовков
<channel> <title>Test curl and method</title> <cors>rf|android|direct|directnoheader</cors> <playlist_url><![CDATA[http://parser.co/#POSThtml=md5hash]]></playlist_url> <parser><![CDATA[curl "https://mysite.com/" -H "Accept-Encoding: deflate" -H "Connection: keep-alive" -H "DNT: 1" -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"]]> </parser> </channel>
Или в JSON
{
"channels": [
{
"title": "Test curl and method",
"cors": "rf|android|direct|directnoheader",
"playlist_url": "http://parser.co/#POSThtml=md5hash",
"parser": 'curl "https://mysite.com/" -H "Accept-Encoding: deflate" -H "Connection: keep-alive" -H "DNT: 1" -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"'
}
]
}
Результат будет в $_POST['html']
Результат будет аналогично md5hash в $_GET['page']
Методом POST для видеоссылок ( stream_url )
<channel> <title>stream post</title> <stream_url><![CDATA[http://test.ru/index.php#POSTs=md5hash]]></stream_url> <parser><![CDATA[https://yadi.sk/d/0BpgziSQ0VWiFg]]></parser> </channel>
Будет идти отправка POST на http://test.ru/index.php#POSTs=md5hash в $_POST["s"]
Ответ страницы попадет в проигрыватель в виде ссылки.
Тоесть http://test.ru/index.php должен дать текст ссылки на медиафайл вида http://.../myvideo.mp4
Скачивание сторонней страницы по необходимости, страница http://parser.co/ может дать json ответ
{"parse":"http://www.youtube.com/watch?v=qZ3xj_UF4I8&gl=US&hl=en&has_verified=1&bpctr=9999999999"}
Скачанная страница шлется в параметре $_POST['remoteparse']
6. Оформление и вёрстка элементов
position при заданном $_PL["typeList"]="start";
При $_PL["typeList"]="start" каждый channel может задать свой вид через position:
$_CH["position"]=""; // - Плитка как в Стартовом меню размером 128х101px)
$_CH["position"]="html"; // - Пример элемента со своим дизайном в $_CH["template"]
$_CH["position"]="html";
$_CH["br"]=0; // Не переносить навигацию на новую строку
$_CH["title"]="Новый вид";
$_CH["description"]="Описание вида";
$_CH["logo_30x30"]="http://p.lnka.ru/icons/yapfiles.png";
$_CH["template"]='<div style="width:242px;overflow:hidden;margin:4px;text-align:center;"><img src="$logo_30x30" style="width:242px;height:171px;padding:2px 2px 0px 2px;"><br>$title<br><small>$description</small></div>';
Элементы будут выстраиваться в ряд и навигация по ним будет предполагать что они в одном ряду
$_CH["br"]
В месте где навигация должна перейти на новую строку задайте $_CH["br"]=1; При этом в before будет добавлено значение <br clear=both>
$_CH["position"]="fulleditline"; //- Поле ввода текста шириной почти на всю страницу
$_CH["position"]="hlist"; // - горизонтальная ссылка (может быть несколько на одном горизонтальном уровне)
$_CH["position"]="list"; // -обычный вид списка шириной в половину экрана и с description справа
$_CH["position"]="label"; // -невысокая строка на всю ширину экрана
$_CH["position"]="bigtile"; // - Плитка увеличенной в 2 раза высоты (128х215px)
description — прокрутка части большого текста
Добавьте в описание div с id scrolled — прокрутка кнопками PG_UP / PG_DOWN:
$_CH["description"]='MY TITLE STATIC<div id="scrolled">
PG_UP PG_DOWN
large scrolled content
.
.
.
</div>
MY FOOTER STATIC';
6.1. Произвольная вёрстка (position=html, template, br)
Пошаговая схема для своего дизайна плитки:
$_PL["typeList"]="start";- Задать CSS в
$_PL["css"] - Для каждого channel:
position="html",template, при необходимостиbr
Прописываем свои стили
$_PL["css"]="
.myStyle1{
width:242px;
overflow:hidden;
margin:4px;
text-align:center;
}
.myStyle2{ width:242px; height:171px; padding:2px 2px 0px 2px; }
";
Задаем шаблон (template) каждому элементу списка
$_CH["position"]="html"; // Задает возможность использовать template
$_CH["title"]="Новый вид"; // Заголовок - подставляет в шаблоне вместо $title
$_CH["logo_30x30"]="http://p.lnka.ru/icons/yapfiles.png"; // Иконка $logo_30x30
$_CH["description"]="Описание, если нужно"; // Описание- подставляет в шаблоне вместо $description
$_CH["template"]='<div class="myStyle1"><img src="$logo_30x30" style="myStyle2"><br>$title</div>';
Перенос на новую строке при html дизайне $_CH["br"]=1;
Для правильной навигации по странице нужно знать когда у вас там новая строка. Для этого в элементе с которого нужно чтоб начиналась новая строка присваиваем
$_CH["br"]=1;// Этот элемент разместится уже на новой строке
coordination (устаревший способ навигации)

До появления тега br навигацию по произвольному шаблону задавали координатами. Обязательно при position=html и своём template.
$_CH["coordination"]=[x,y];
Где x — позиция по горизонтали, y — по вертикали. Первый элемент: [0,0]. Элемент на новой строке: [1,0].
6.2. Горизонтальная прокрутка (nowrap)
Для страниц с typeList=html или position=html. Задайте nowrap=1 только первому элементу блока (у него же br=1):
$_CH=["br"=>1,"nowrap"=1,"title"=>"Первый элемент горизонтального блока прокрутки"];
$_CH=["title"=>"Второй элемент горизонтального блока прокрутки"];
$_CH=["title"=>"Третий элемент горизонтального блока прокрутки"];
...
$_CH=["title"=>"Последний элемент горизонтального блока прокрутки"];
$_CH=["br"=>1,"title"=>"Новый элемент с новой строки"];
7. Формирование ответа на сервере
7.1. Параметры GET от клиента
При открытии портала ForkPlayer добавляет к запросу идентификаторы устройства.
| Параметр | Описание |
|---|---|
box_mac |
Виртуальный MAC-адрес устройства |
box_user |
Email пользователя (если выполнен вход в аккаунт Fork) |
Пример: http://nserv.host/?box_mac=your_mac_address&box_user=your_forkplayer_tv@email
7.2. Шаблон вывода JSON (PHP)
<?php
// $_PL — глобальные теги страницы
// $_CH — массив channel; каждый элемент — ассоциативный массив тегов
$_PL["channels"] = $_CH;
header("Content-Type: application/json; charset=utf-8");
print json_encode($_PL, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
?>
8. Связанные материалы
- FXMLbaseparser — PHP-класс, упрощающий генерацию FXML
- CHANGELOG — история изменений протокола
- Пример портала на PHP
- FXML CMS — визуальный редактор порталов
- Исходный код любой страницы можно просмотреть в ForkPlayer (см. скриншот выше)