4PDA Radio v1.14

Радио на 4PDA с поиском через API и флагами стран

目前為 2025-05-22 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         4PDA Radio v1.14
// @author       brant34
// @namespace    http://tampermonkey.net/
// @version      1.14
// @description  Радио на 4PDA с поиском через API и флагами стран
// @match        https://4pda.to/forum/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// ==/UserScript==

(function () {
  'use strict';

  // Проверка поддержки localStorage
  if (!window.localStorage) {
    showNotification('localStorage недоступен', 'error');
    return;
  }

  // === [Стили] ===
  GM_addStyle(`
    .radio-toggle-button {
      position: fixed;
      top: 10px;
      right: 10px;
      background-color: #2e6d5e;
      color: #fff;
      border: none;
      border-radius: 50%;
      width: 32px;
      height: 32px;
      cursor: pointer;
      font-size: 16px;
      line-height: 32px;
      text-align: center;
      z-index: 99999;
    }
    .radio-toggle-button:hover {
      background-color: #3e8e77;
    }
    .radio-panel {
      display: none;
      background-color: #1a3c34;
      border-radius: 10px;
      padding: 10px;
      z-index: 99998;
      color: #fff;
      font-family: Arial, sans-serif;
      font-size: 14px;
      width: 320px;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
      position: fixed;
      max-width: 90vw;
    }
    .radio-panel-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 10px;
    }
    .radio-panel-header span {
      font-weight: bold;
    }
    .radio-panel-controls {
      display: flex;
      flex-wrap: wrap;
      gap: 5px;
    }
    .radio-panel-controls button {
      background-color: #17a2b8;
      color: #fff;
      border: none;
      border-radius: 5px;
      padding: 5px 10px;
      cursor: pointer;
      font-size: 12px;
    }
    .radio-panel-controls button:hover {
      background-color: #138496;
    }
    .radio-panel select {
      background-color: #fff;
      border: 1px solid #ccc;
      border-radius: 5px;
      padding: 5px;
      color: #333;
      font-size: 12px;
      flex-grow: 1;
    }
    .radio-player {
      background-color: transparent;
      border-radius: 5px;
      padding: 5px;
      margin: 10px 0;
      display: flex;
      align-items: center;
      gap: 5px;
    }
    .radio-player button {
      background: none;
      border: none;
      cursor: pointer;
      font-size: 16px;
      color: #fff;
    }
    .radio-player input[type="range"] {
      -webkit-appearance: none;
      appearance: none;
      background: transparent;
      cursor: pointer;
      width: 100%;
    }
    .radio-player input[type="range"]::-webkit-slider-runnable-track {
      background: #2e6d5e;
      height: 6px;
      border-radius: 3px;
    }
    .radio-player input[type="range"]::-webkit-slider-thumb {
      -webkit-appearance: none;
      appearance: none;
      background: #fff;
      height: 16px;
      width: 16px;
      border-radius: 50%;
      margin-top: -5px;
    }
    .radio-player input[type="range"]::-moz-range-track {
      background: #2e6d5e;
      height: 6px;
      border-radius: 3px;
    }
    .radio-player input[type="range"]::-moz-range-thumb {
      background: #fff;
      height: 16px;
      width: 16px;
      border-radius: 50%;
      border: none;
    }
    .radio-player input[type="range"]::-moz-range-progress {
      background: #fff;
      height: 6px;
      border-radius: 3px;
    }
    .radio-player input[type="range"]:disabled::-webkit-slider-runnable-track {
      background: #1a3c34;
      opacity: 0.7;
    }
    .radio-player input[type="range"]:disabled::-webkit-slider-thumb {
      background: #999;
    }
    .radio-player input[type="range"]:disabled::-moz-range-track {
      background: #1a3c34;
      opacity: 0.7;
    }
    .radio-player input[type="range"]:disabled::-moz-range-thumb {
      background: #999;
    }
    .radio-player input[type="range"]:disabled::-moz-range-progress {
      background: #999;
    }
    .radio-player .volume-icon {
      cursor: pointer;
    }
    .radio-player .volume-icon.muted::before {
      content: "🔇";
    }
    .radio-player .volume-icon:not(.muted)::before {
      content: "🔊";
    }
    .radio-panel input[type="checkbox"] {
      margin-right: 5px;
    }
    .radio-panel-settings {
      margin-top: 10px;
      display: flex;
      gap: 5px;
    }
    .radio-search {
      display: flex;
      gap: 5px;
      margin: 10px 0;
    }
    .radio-search input[type="text"] {
      flex-grow: 1;
      padding: 5px;
      border-radius: 5px;
      border: 1px solid #ccc;
      background-color: #fff;
      color: #333;
      font-size: 12px;
    }
    .radio-search button {
      background-color: #17a2b8;
      color: #fff;
      border: none;
      border-radius: 5px;
      padding: 5px 10px;
      cursor: pointer;
      font-size: 12px;
    }
    .radio-search button:hover {
      background-color: #138496;
    }
    .radio-search-results {
      max-height: 150px;
      overflow-y: auto;
      margin-top: 5px;
      padding: 5px;
      background-color: #2e6d5e;
      border-radius: 5px;
    }
    .radio-search-results div {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 5px;
      border-bottom: 1px solid #1a3c34;
    }
    .radio-search-results div:last-child {
      border-bottom: none;
    }
    .radio-search-results button {
      background-color: #17a2b8;
      color: #fff;
      border: none;
      border-radius: 5px;
      padding: 3px 8px;
      cursor: pointer;
      font-size: 10px;
    }
    .radio-search-results button:hover {
      background-color: #138496;
    }
    .notification {
      position: fixed;
      top: 50px;
      right: 10px;
      padding: 10px 20px;
      border-radius: 5px;
      color: white;
      z-index: 99999;
    }
    .notification.success { background-color: #28a745; }
    .notification.info { background-color: #17a2b8; }
    .notification.warning { background-color: #ffc107; }
    .notification.error { background-color: #dc3545; }
  `);

  // === [Эксклюзивное воспроизведение радио во вкладке] ===
  const tabId = Date.now().toString();
  const MASTER_KEY = '4pda-radio-master';

  function setAsMaster() {
    localStorage.setItem(MASTER_KEY, tabId);
    showNotification('Эта вкладка теперь воспроизводит радио', 'success');
  }

  function isMaster() {
    return localStorage.getItem(MASTER_KEY) === tabId;
  }

  // === [Инициализация аудиоплеера] ===
  let audio = document.getElementById('radioPlayer4PDA');
  if (!audio) {
    audio = document.createElement('audio');
    audio.id = 'radioPlayer4PDA';
    document.body.appendChild(audio);
  }

  // Проверка перед запуском радио
  const currentMaster = localStorage.getItem(MASTER_KEY);
  if (!currentMaster) {
    setAsMaster();
  } else if (!isMaster()) {
    audio.pause();
  }

  // Слушаем изменения мастера
  window.addEventListener('storage', (e) => {
    if (e.key === MASTER_KEY && e.newValue !== tabId) {
      audio.pause();
    }
  });

  // Убираем себя из мастеров при закрытии вкладки
  window.addEventListener('beforeunload', () => {
    if (isMaster()) {
      localStorage.removeItem(MASTER_KEY);
    }
  });

  // === [Сохраненные настройки] ===
  const savedAutoplay = GM_getValue('autoplay', false);
  const savedRadio = GM_getValue('radio', '');
  const savedVolume = GM_getValue('volume', 1);
  const savedTimer = GM_getValue('autotimer', 0);
  const savedPlaying = GM_getValue('isPlaying', false);
  const savedTime = GM_getValue('currentTime', 0);
  let panelPosition = GM_getValue('panelPos', 'top-right');
  const panelScale = GM_getValue('panelSize', '1');
  const savedCustomStations = GM_getValue('customStations', {});

  // === [Список радиостанций] ===
  let RADIO = {
    '🇷🇺 Европа Плюс': 'https://ep256.hostingradio.ru:8052/europaplus256.mp3',
    '🇷🇺 Русское Радио': 'https://rusradio.hostingradio.ru/rusradio128.mp3',
    '🇷🇺 Юмор FM': 'https://pub0301.101.ru:8443/stream/air/mp3/256/102',
    '🇷🇺 Радио Рекорд': 'https://radio-srv1.11one.ru/record192k.mp3',
    '🇷🇺 Ретро FM': 'https://retro.hostingradio.ru:8014/retro320.mp3',
    '🇷🇺 Радио Шансон': 'https://chanson.hostingradio.ru:8041/chanson256.mp3',
    '🇷🇺 DFM Russian Dance': 'https://stream03.pcradio.ru/dfm_russian_dance-hi',
    '🇷🇺 DFM': 'https://dfm.hostingradio.ru:80/dfm96.aacp',
    '🇷🇺 Дорожное Радио': 'https://dorognoe.hostingradio.ru:8000/dorognoe',
    '🇷🇺 Авторадио': 'https://srv01.gpmradio.ru/stream/air/aac/64/100?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJrZXkiOiIwZWM3MjU3YTFhNDM5MmMyNWUwZDZkZDQwYjdjNzQ5ZCIsIklQIjoiODEuMTczLjE2NS4yMjUiLCJVQSI6Ik1vemlsbGEvNS4wIChNYWNpbnRvc2g7IEludGVsIE1hYyBPUyBYIDEwXzE1XzcpIEFwcGxlV2ViS2l0LzUzNy4zNiAoS0hUTUwsIGxpa2UgR2Vja28pIENocm9tZS8xMzMuMC4wLjAgU2FmYXJpLzUzNy4zNiIsIlJlZiI6Imh0dHBzOi8vd3d3LmF2dG9yYWRpby5ydS8iLCJ1aWRfY2hhbm5lbCI6IjEwMCIsInR5cGVfY2hhbm5lbCI6ImNoYW5uZWwiLCJ0eXBlRGV2aWNlIjoiUEMiLCJCcm93c2VyIjoiQ2hyb21lIiwiQnJvd3NlclZlcnNpb24iOiIxMzMuMC4wLjAiLCJTeXN0ZW0iOiJNYWMgT1MgWCBQdW1hIiwiZXhwIjoxNzQyNjcxOTc1fQ.b1Hha0aGp4hWbgFELSzEapRcpOoejzs8tmdDARY0JyA',
    '🇩🇪 Радио Картина': 'https://rs.kartina.tv/kartina_320kb',
    '🇰🇿 LuxFM': 'https://icecast.luxfm.kz/luxfm',
    '🇰🇿 Radio NS': 'https://icecast.ns.kz/radions',
    '🇰🇿 NRJ Kazakhstan': 'https://stream03.pcradio.ru/energyfm_ru-med',
    '🇰🇿 Радио Жаңа FM': 'https://live.zhanafm.kz:8443/zhanafm_onair',
    '🇺🇦 Хіт FM': 'http://online.hitfm.ua/HitFM',
    '🇺🇦 Kiss FM UA': 'http://online.kissfm.ua/KissFM'
  };

  // Объединяем предопределенные станции с пользовательскими
  Object.assign(RADIO, savedCustomStations);

  // === [Проверка доступности радиопотоков] ===
  async function checkStream(url) {
    return true; // Заглушка, можно добавить реальную проверку
  }

  async function validateStations() {
    const validStations = {};
    for (const [name, url] of Object.entries(RADIO)) {
      if (await checkStream(url)) {
        validStations[name] = url;
      } else {
        showNotification(`Радиостанция ${name} недоступна`, 'warning');
      }
    }
    RADIO = validStations;
    updateStationList();
  }

  // === [Динамическое обновление списка радиостанций] ===
  async function loadStations() {
    try {
      const response = await new Promise((resolve) => {
        setTimeout(() => resolve({ ok: true, json: () => Promise.resolve(RADIO) }), 1000);
      });
      if (response.ok) {
        RADIO = await response.json();
        await validateStations();
        showNotification('Список радиостанций обновлен', 'success');
      } else {
        showNotification('Ошибка загрузки списка радиостанций', 'error');
      }
    } catch (error) {
      console.error('Ошибка обновления радиостанций:', error);
      showNotification('Ошибка обновления радиостанций', 'error');
    }
  }

  // === [Уведомления] ===
  function showNotification(message, type) {
    const notification = document.createElement('div');
    notification.className = `notification ${type}`;
    notification.textContent = message;
    document.body.appendChild(notification);
    setTimeout(() => notification.remove(), 3000);
  }

  // === [Преобразование кода страны в флаг] ===
  function countryCodeToFlag(countryCode) {
    if (!countryCode || countryCode.length !== 2) {
      return '🌐'; // Нейтральный флаг, если код страны отсутствует
    }

    const codePoints = countryCode
      .toUpperCase()
      .split('')
      .map(char => 0x1F1E6 + (char.charCodeAt(0) - 65)); // 'A' → 0x1F1E6, 'B' → 0x1F1E7, ..., 'U' → 0x1F1FA
    return String.fromCodePoint(...codePoints);
  }

  // === [Поиск через RadioBrowser API] ===
  function searchStations(query, callback) {
    showNotification('Поиск...', 'info');
    GM_xmlhttpRequest({
      method: 'GET',
      url: `https://de1.api.radio-browser.info/json/stations/search?name=${encodeURIComponent(query)}&limit=10`,
      onload: function(response) {
        try {
          const data = JSON.parse(response.responseText);
          const results = data.map(station => ({
            name: station.name,
            url: station.url_resolved,
            countryCode: station.countrycode || ''
          }));
          callback(results);
        } catch (error) {
          console.error('Ошибка парсинга ответа API:', error);
          showNotification('Ошибка поиска радиостанций', 'error');
          callback([]);
        }
      },
      onerror: function(error) {
        console.error('Ошибка запроса к API:', error);
        showNotification('Ошибка поиска радиостанций', 'error');
        callback([]);
      }
    });
  }

  function addStation(name, url, countryCode) {
    // Добавляем флаг к имени станции
    const flag = countryCodeToFlag(countryCode);
    const stationNameWithFlag = `${flag} ${name}`;

    if (RADIO[stationNameWithFlag]) {
      showNotification('Радиостанция уже добавлена', 'warning');
      return;
    }

    RADIO[stationNameWithFlag] = url;
    const customStations = GM_getValue('customStations', {});
    customStations[stationNameWithFlag] = url;
    GM_setValue('customStations', customStations);
    updateStationList();
    showNotification(`Радиостанция ${stationNameWithFlag} добавлена`, 'success');
  }

  // === [Интерфейс] ===
  function createInterface() {
    // Панель радио
    const panel = document.createElement('div');
    panel.className = 'radio-panel';
    panel.style.display = GM_getValue('panelVisible', false) ? 'block' : 'none';

    // Кнопка S
    const toggleButton = document.createElement('button');
    toggleButton.className = 'radio-toggle-button';
    toggleButton.textContent = '🎧';
    toggleButton.onclick = () => {
      if (!panel) {
        console.error('Панель не найдена');
        showNotification('Ошибка: панель не создана', 'error');
        return;
      }
      panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
      GM_setValue('panelVisible', panel.style.display === 'block');
      showNotification(`Панель ${panel.style.display === 'block' ? 'открыта' : 'закрыта'}`, 'info');
    };
    document.body.appendChild(toggleButton);

    // Применение позиции и масштаба
    updatePanelPosition(panel, panelPosition);
    updatePanelScale(panel, panelScale);

    // Заголовок
    const header = document.createElement('div');
    header.className = 'radio-panel-header';
    header.innerHTML = '<span>⚡ Громкость:</span>';
    panel.appendChild(header);

    // Кнопки пресетов громкости
    const controls = document.createElement('div');
    controls.className = 'radio-panel-controls';
    ['Тихо', 'Комфорт', 'Громко'].forEach((label, index) => {
      const button = document.createElement('button');
      button.textContent = label;
      button.onclick = () => {
        if (!audio) {
          console.error('Аудиоплеер не инициализирован');
          showNotification('Ошибка: аудиоплеер не доступен', 'error');
          return;
        }
        const volumes = [0.2, 0.5, 0.8];
        audio.volume = volumes[index];
        GM_setValue('volume', audio.volume);
        updateVolumeSlider(audio.volume);
        showNotification(`Громкость: ${label} (${volumes[index] * 100}%)`, 'info');
      };
      controls.appendChild(button);
    });
    panel.appendChild(controls);

    // Ползунок громкости
    const volumeSlider = document.createElement('input');
    volumeSlider.type = 'range';
    volumeSlider.min = '0';
    volumeSlider.max = '1';
    volumeSlider.step = '0.01';
    volumeSlider.value = savedVolume;
    volumeSlider.oninput = () => {
      if (!audio) {
        console.error('Аудиоплеер не инициализирован');
        showNotification('Ошибка: аудиоплеер не доступен', 'error');
        return;
      }
      audio.volume = volumeSlider.value;
      GM_setValue('volume', audio.volume);
    };
    controls.appendChild(volumeSlider);

    // Выбор радиостанции
    const stationSelect = document.createElement('select');
    stationSelect.id = 'radioStationSelect';
    updateStationList();
    controls.appendChild(stationSelect);
    panel.appendChild(controls);

    // Поиск радиостанций
    const searchSection = document.createElement('div');
    searchSection.className = 'radio-search';
    const searchInput = document.createElement('input');
    searchInput.type = 'text';
    searchInput.placeholder = 'Поиск радиостанций...';
    searchSection.appendChild(searchInput);
    const searchButton = document.createElement('button');
    searchButton.textContent = 'Поиск';
    searchSection.appendChild(searchButton);
    panel.appendChild(searchSection);

    // Результаты поиска
    const searchResults = document.createElement('div');
    searchResults.className = 'radio-search-results';
    searchResults.style.display = 'none';
    panel.appendChild(searchResults);

    searchButton.onclick = () => {
      const query = searchInput.value.trim();
      if (!query) {
        showNotification('Введите запрос для поиска', 'warning');
        return;
      }

      searchStations(query, (results) => {
        searchResults.innerHTML = '';
        searchResults.style.display = results.length ? 'block' : 'none';

        if (!results.length) {
          showNotification('Радиостанции не найдены', 'info');
          return;
        }

        results.forEach(station => {
          const resultItem = document.createElement('div');
          const flag = countryCodeToFlag(station.countryCode);
          resultItem.textContent = `${flag} ${station.name}`;
          const addButton = document.createElement('button');
          addButton.textContent = 'Добавить';
          addButton.onclick = () => {
            addStation(station.name, station.url, station.countryCode);
            searchResults.style.display = 'none';
            searchInput.value = '';
          };
          resultItem.appendChild(addButton);
          searchResults.appendChild(resultItem);
        });
      });
    };

    // Плеер
    const player = document.createElement('div');
    player.className = 'radio-player';
    const playButton = document.createElement('button');
    playButton.textContent = savedPlaying ? '⏸' : '▶';
    playButton.onclick = togglePlay;
    player.appendChild(playButton);
    const timeSlider = document.createElement('input');
    timeSlider.type = 'range';
    timeSlider.min = '0';
    timeSlider.max = '100';
    timeSlider.value = '0';
    timeSlider.disabled = true;
    player.appendChild(timeSlider);
    const timeDisplay = document.createElement('span');
    timeDisplay.textContent = '0:00';
    player.appendChild(timeDisplay);
    const volumeIcon = document.createElement('span');
    volumeIcon.className = 'volume-icon';
    volumeIcon.onclick = () => {
      if (!audio) {
        console.error('Аудиоплеер не инициализирован');
        showNotification('Ошибка: аудиоплеер не доступен', 'error');
        return;
      }
      audio.muted = !audio.muted;
      volumeIcon.classList.toggle('muted', audio.muted);
      showNotification(audio.muted ? 'Звук выключен' : 'Звук включен', 'info');
    };
    player.appendChild(volumeIcon);
    panel.appendChild(player);

    // Таймер, автостарт и обновление
    const footer = document.createElement('div');
    footer.className = 'radio-panel-controls';
    const timerSelect = document.createElement('select');
    timerSelect.innerHTML = `
      <option value="0">Без таймера</option>
      <option value="10">10 мин</option>
      <option value="30">30 мин</option>
      <option value="60">60 мин</option>
    `;
    timerSelect.value = savedTimer;
    timerSelect.onchange = () => {
      GM_setValue('autotimer', parseInt(timerSelect.value) || 0);
      setAutoTimer(parseInt(timerSelect.value) || 0);
    };
    footer.appendChild(timerSelect);
    const autostartLabel = document.createElement('label');
    const autostartCheckbox = document.createElement('input');
    autostartCheckbox.type = 'checkbox';
    autostartCheckbox.checked = savedAutoplay;
    autostartCheckbox.onchange = () => {
      GM_setValue('autoplay', autostartCheckbox.checked);
    };
    autostartLabel.appendChild(autostartCheckbox);
    autostartLabel.appendChild(document.createTextNode('Автостарт'));
    footer.appendChild(autostartLabel);
    const refreshButton = document.createElement('button');
    refreshButton.textContent = '↻';
    refreshButton.title = 'Обновить станции';
    refreshButton.onclick = loadStations;
    footer.appendChild(refreshButton);
    panel.appendChild(footer);

    // Настройки панели
    const settings = document.createElement('div');
    settings.className = 'radio-panel-settings';
    const positionSelect = document.createElement('select');
    positionSelect.innerHTML = `
      <option value="top-left">Вверху слева</option>
      <option value="top-center">Вверху посередине</option>
      <option value="top-right">Вверху справа</option>
    `;
    positionSelect.value = panelPosition;
    positionSelect.onchange = () => {
      GM_setValue('panelPos', positionSelect.value);
      panelPosition = positionSelect.value;
      updatePanelPosition(panel, positionSelect.value);
      showNotification(`Панель перемещена: ${positionSelect.options[positionSelect.selectedIndex].text}`, 'info');
    };
    settings.appendChild(positionSelect);
    const scaleSelect = document.createElement('select');
    scaleSelect.innerHTML = `
      <option value="0.8">Маленький</option>
      <option value="1">Средний</option>
      <option value="1.1">Большой</option>
    `;
    scaleSelect.value = panelScale;
    scaleSelect.onchange = () => {
      GM_setValue('panelSize', scaleSelect.value);
      updatePanelScale(panel, scaleSelect.value);
      showNotification(`Масштаб панели: ${scaleSelect.options[scaleSelect.selectedIndex].text}`, 'info');
    };
    settings.appendChild(scaleSelect);
    panel.appendChild(settings);

    document.body.appendChild(panel);

    // Функция для обновления ползунка громкости
    function updateVolumeSlider(value) {
      volumeSlider.value = value;
    }
  }

  function updatePanelPosition(panel, position) {
    if (!panel) {
      console.error('Панель не найдена для обновления позиции');
      return;
    }
    panel.style.top = '10px';
    panel.style.bottom = '';
    panel.style.left = '';
    panel.style.right = '';
    panel.style.transform = '';
    switch (position) {
      case 'top-left':
        panel.style.left = '10px';
        break;
      case 'top-center':
        panel.style.left = '50%';
        panel.style.transform = 'translateX(-50%)';
        break;
      case 'top-right':
        panel.style.right = '10px';
        break;
    }
  }

  function updatePanelScale(panel, scale) {
    if (!panel) {
      console.error('Панель не найдена для обновления масштаба');
      return;
    }
    panel.style.transform = `scale(${scale})`;
    panel.style.transformOrigin = panelPosition.includes('left') ? 'top left' : panelPosition.includes('right') ? 'top right' : 'top center';
    if (parseFloat(scale) > 1) {
      panel.style.maxWidth = '80vw';
      if (panelPosition === 'top-center') {
        panel.style.left = '50%';
        panel.style.transform = `translateX(-50%) scale(${scale})`;
      }
    } else {
      panel.style.maxWidth = '90vw';
    }
  }

  function updateStationList() {
    const stationSelect = document.getElementById('radioStationSelect');
    if (!stationSelect) {
      return;
    }
    stationSelect.innerHTML = '<option value="">Выберите радиостанцию</option>';
    Object.keys(RADIO).forEach(name => {
      const option = document.createElement('option');
      option.value = RADIO[name];
      option.textContent = name;
      if (RADIO[name] === savedRadio) option.selected = true;
      stationSelect.appendChild(option);
    });
    stationSelect.onchange = () => {
      if (stationSelect.value) {
        audio.src = stationSelect.value;
        GM_setValue('radio', stationSelect.value);
        if (savedAutoplay || savedPlaying) {
          audio.play().catch(e => {
            console.error('Ошибка воспроизведения:', e);
            showNotification('Ошибка воспроизведения радиостанции', 'error');
          });
        }
      }
    };
  }

  // === [Управление воспроизведением] ===
  function togglePlay() {
    const playButton = document.querySelector('.radio-player button');
    if (!playButton) {
      return;
    }
    if (audio.paused) {
      if (isMaster()) {
        audio.play().catch(e => {
          console.error('Ошибка воспроизведения:', e);
          showNotification('Ошибка воспроизведения радиостанции', 'error');
        });
        GM_setValue('isPlaying', true);
        playButton.textContent = '⏸';
      }
    } else {
      audio.pause();
      GM_setValue('isPlaying', false);
      playButton.textContent = '▶';
    }
  }

  // === [Таймер автовыключения] ===
  let timerId;
  function setAutoTimer(minutes) {
    clearTimeout(timerId);
    if (minutes > 0) {
      timerId = setTimeout(() => {
        audio.pause();
        GM_setValue('isPlaying', false);
        const playButton = document.querySelector('.radio-player button');
        if (playButton) {
          playButton.textContent = '▶';
        }
        showNotification('Радио остановлено по таймеру', 'info');
      }, minutes * 60 * 1000);
    }
  }

  // === [Инициализация] ===
  try {
    createInterface();

    // Инициализация аудио
    audio.volume = savedVolume;
    if (savedRadio) {
      audio.src = savedRadio;
      if (savedAutoplay && isMaster()) {
        audio.play().catch(e => {
          console.error('Ошибка воспроизведения:', e);
          showNotification('Ошибка воспроизведения радиостанции', 'error');
        });
      }
    }
    audio.ontimeupdate = () => GM_setValue('currentTime', audio.currentTime);
    audio.onerror = () => {
      console.error('Ошибка загрузки радиопотока');
      showNotification('Ошибка загрузки радиопотока', 'error');
    };

    // Обновление списка радиостанций без проверки
    updateStationList();
    setAutoTimer(savedTimer);
  } catch (error) {
    console.error('Критическая ошибка инициализации:', error);
    showNotification('Ошибка запуска скрипта', 'error');
  }
})();