⚡ WEBMASTER DOCS & API

Интеграция плеера FUNILM

Готовое руководство по встраиванию адаптивного мультиплеера на ваши сайты, киноблоги и в Telegram WebApp. 8 CDN-балансеров, мгновенный авто-фоллбек и защита от хотлинкинга.

01

Быстрый старт: Адаптивный плеер 16:9

Чтобы плеер автоматически подстраивался под ширину экрана смартфона или монитора без черных полос и деформаций, используйте адаптивный контейнер:

HTML5 Embed
<!-- Адаптивный плеер 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 блокируют нативное разворачивание на весь экран.
02

Подтверждение домена (3 способа на выбор)

Каждый токен закрепляется за вашим доменным именем. Это гарантирует, что чужие сайты не смогут украсть ваш плеер и транслировать его у себя. Выберите любой один способ из трёх:

🏷️ 1. Мета-тег в <head>

Самый быстрый способ. Вставьте мета-тег на главную страницу вашего сайта:

<meta name="funilm-token" content="ВАШ_ТОКЕН">
📄 2. Файл на сервере

Универсальный способ для любых хостингов и CMS. Создайте текстовый файл:

https://site.com/.well-known/funilm.txt
Содержимое: ВАШ_ТОКЕН
🌐 3. DNS TXT-запись

Для сайтов на Cloudflare, Reg.ru или когда сайт ещё в процессе разработки:

Тип: TXT  Хост: @
Значение: funilm-verification=ВАШ_ТОКЕН

После добавления тега, файла или DNS записи нажмите кнопку «Проверить установку» в кабинете вебмастера. Домен активируется автоматически.

03

Форматы ссылок (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}
04

Поддерживаемые 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
05

Client SDK & postMessage API (OOP / DRY)

Готовые клиентские и серверные SDK для интеграции с плеером по принципам OOP и DRY. Без кустарных switch-case и ручных строк: надёжная шина событий, fluent API, строгие контракты и чистый код.

FunilmPlayer.js — OOP Client SDK
/**
 * 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));
06

Защита от воровства и хотлинкинга

Плеер FUNILM защищён на уровне архитектуры:

✓ Скопировано в буфер!