Интеграция плеера FUNILM
Готовое руководство по встраиванию адаптивного мультиплеера на ваши сайты, киноблоги и в Telegram WebApp. 8 CDN-балансеров, мгновенный авто-фоллбек и защита от хотлинкинга.
Быстрый старт: Адаптивный плеер 16:9
Чтобы плеер автоматически подстраивался под ширину экрана смартфона или монитора без черных полос и деформаций, используйте адаптивный контейнер:
<!-- Адаптивный плеер FUNILM 16:9 -->
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; border-radius: 8px; background: #000;">
<iframe
src="https://funilm.top/embed/kp/301?token=ВАШ_ТОКЕН"
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"
allow="autoplay; fullscreen; encrypted-media; picture-in-picture"
allowfullscreen>
</iframe>
</div>
allow="autoplay; fullscreen; encrypted-media; picture-in-picture" allowfullscreen обязательны. Без них Safari на iOS и Chrome на Android блокируют нативное разворачивание на весь экран.
Подтверждение домена (3 способа на выбор)
Каждый токен закрепляется за вашим доменным именем. Это гарантирует, что чужие сайты не смогут украсть ваш плеер и транслировать его у себя. Выберите любой один способ из трёх:
Самый быстрый способ. Вставьте мета-тег на главную страницу вашего сайта:
<meta name="funilm-token" content="ВАШ_ТОКЕН">
Универсальный способ для любых хостингов и CMS. Создайте текстовый файл:
https://site.com/.well-known/funilm.txt
Содержимое: ВАШ_ТОКЕН
Для сайтов на Cloudflare, Reg.ru или когда сайт ещё в процессе разработки:
Тип: TXT Хост: @
Значение: funilm-verification=ВАШ_ТОКЕН
После добавления тега, файла или DNS записи нажмите кнопку «Проверить установку» в кабинете вебмастера. Домен активируется автоматически.
Форматы ссылок (Pretty URLs)
Плеер FUNILM поддерживает привычные короткие адреса, полностью совместимые с популярными балансерами:
// 1. По Кинопоиск ID (автоматически определит название, год, озвучки и сезоны):
https://funilm.top/embed/kp/{kinopoisk_id}?token={token}
// 2. По IMDb ID:
https://funilm.top/embed/imdb/{imdb_id}?token={token}
// 3. По TMDb ID:
https://funilm.top/embed/tmdb/{tmdb_id}?token={token}
// 4. По названию и году:
https://funilm.top/embed?title=Жмурки&year=2005&token={token}
Поддерживаемые GET-параметры
Кастомизируйте плеер, стартовую серию, таймкод и цвет интерфейса через URL:
| Параметр | Алиас | Пример | Описание |
|---|---|---|---|
| token | — | fn_... |
Токен авторизации сайта из кабинета вебмастера |
| season | s |
2 |
Стартовый номер сезона для сериалов |
| episode | e |
5 |
Стартовый номер серии для сериалов |
| t | time |
120 / 01:30 |
Стартовый таймкод (секунда или минуты:секунды) |
| autoplay | — | 1 |
Автозапуск видео (с умным заглушением при запрете браузера) |
| color | — | %23e50914 |
HEX-цвет кнопок и ползунков плеера под ваш дизайн |
| kinopoisk_id | kp |
301 |
ID фильма/сериала на Кинопоиске |
| imdb_id | imdb |
tt0133093 |
Идентификатор IMDb |
| tmdb_id | tmdb |
603 |
Идентификатор TMDb |
Client SDK & postMessage API (OOP / DRY)
Готовые клиентские и серверные SDK для интеграции с плеером по принципам OOP и DRY. Без кустарных switch-case и ручных строк: надёжная шина событий, fluent API, строгие контракты и чистый код.
/**
* FUNILM Player Client SDK (ES6+)
* Паттерны: Observer / EventEmitter, Fluent Interface, DRY.
*/
class FunilmPlayer {
/**
* @param {string|HTMLIFrameElement} target Селектор или DOM-элемент iframe
* @param {Object} [options={}]
* @param {string} [options.origin='*'] Допустимый Origin сообщений
*/
constructor(target, options = {}) {
this.iframe = typeof target === 'string' ? document.querySelector(target) : target;
if (!this.iframe) {
throw new Error(`[FunilmPlayer] Iframe элемент '${target}' не найден.`);
}
this.options = { origin: '*', ...options };
this._listeners = new Map();
this._onMessage = this._handleMessage.bind(this);
window.addEventListener('message', this._onMessage);
}
/**
* Подписка на событие ('player:ready', 'player:play', 'player:timeupdate', etc.)
*/
on(event, handler) {
if (!this._listeners.has(event)) {
this._listeners.set(event, new Set());
}
this._listeners.get(event).add(handler);
return this; // Fluent chaining
}
/**
* Одноразовая подписка
*/
once(event, handler) {
const wrapper = (data) => {
this.off(event, wrapper);
handler(data);
};
return this.on(event, wrapper);
}
/**
* Отписка от события
*/
off(event, handler) {
if (this._listeners.has(event)) {
this._listeners.get(event).delete(handler);
}
return this;
}
/**
* Отправка команды в iframe плеера
*/
send(action, payload = {}) {
if (!this.iframe.contentWindow) {
console.warn('[FunilmPlayer] contentWindow недоступен.');
return this;
}
this.iframe.contentWindow.postMessage({ action, ...payload }, this.options.origin);
return this;
}
// === Fluent API управления плеером ===
play() { return this.send('play'); }
pause() { return this.send('pause'); }
togglePlay() { return this.send('togglePlay'); }
seek(seconds) { return this.send('seek', { time: Math.max(0, Number(seconds) || 0) }); }
setEpisode(season, episode) { return this.send('setEpisode', { season: Number(season), episode: Number(episode) }); }
setVolume(value) { return this.send('setVolume', { value: Math.max(0, Math.min(1, Number(value))) }); }
mute() { return this.send('mute'); }
unmute() { return this.send('unmute'); }
toggleFullscreen() { return this.send('toggleFullscreen'); }
/**
* Очистка памяти и удаление глобального слушателя событий
*/
destroy() {
window.removeEventListener('message', this._onMessage);
this._listeners.clear();
}
/** @private */
_handleMessage(e) {
if (this.options.origin !== '*' && e.origin !== this.options.origin) return;
if (e.data?.source !== 'funilm-player') return;
if (e.source !== this.iframe.contentWindow) return;
const { event, data } = e.data;
const handlers = this._listeners.get(event);
if (handlers) {
handlers.forEach(fn => {
try { fn(data); } catch (err) { console.error(`[FunilmPlayer] Error in '${event}':`, err); }
});
}
const wildcard = this._listeners.get('*');
if (wildcard) {
wildcard.forEach(fn => {
try { fn({ event, data }); } catch (err) { console.error('[FunilmPlayer] Wildcard error:', err); }
});
}
}
}
// --- Пример использования ---
const player = new FunilmPlayer('#my-player-frame');
// Подписка на аналитику
player
.on('player:ready', () => console.log('✓ Плеер готов к воспроизведению'))
.on('player:play', ({ currentTime }) => console.log('Старт воспроизведения на', currentTime))
.on('player:timeupdate', ({ currentTime, duration }) => {
// Трекинг прогресса просмотра (Яндекс.Метрика / GA4)
})
.on('player:ended', () => {
ym(XXXXXX, 'reachGoal', 'film_finished');
});
// Управление кнопками интерфейса
document.getElementById('play-btn')?.addEventListener('click', () => player.play());
document.getElementById('pause-btn')?.addEventListener('click', () => player.pause());
document.getElementById('forward-btn')?.addEventListener('click', () => player.seek(180));
Защита от воровства и хотлинкинга
Плеер FUNILM защищён на уровне архитектуры:
- Браузерная защита CSP: плеер отдаётся с заголовком
Content-Security-Policy: frame-ancestors 'self' https://ваш-сайт.com https://*.ваш-сайт.com;. Любой посторонний сайт, попытавшийся встроить ваш iframe, получит отказ прямо в браузере. - Проверка Referer / Origin: сервер сверяет адрес источника запроса с подтверждённым доменом.
- Защита от скачивания потока: потоки выдаются через защищённые сессионные токены с ограничением по времени жизни.