Как решить проблему с воспроизведением аудио в Поле HTML документа 1С после перехода на платформу 8.3.14 и выше?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При обновлении платформы 1С:Предприятие с более ранних версий (например, 8.3.12) на современные релизы (8.3.14, 8.3.16, 8.3.20 и выше) разработчики часто сталкиваются с тем, что привычный и ранее стабильно работавший функционал внутри элемента формы Поле HTML документа перестает функционировать. Одной из наиболее распространенных проблем является полная остановка воспроизведения аудиофайлов (таких как MP3). Вместо привычного плеера пользователь видит пустой экран, неактивные элементы управления или пресловутый «черный квадрат».

В рамках этой статьи мы подробно разберем, почему происходят такие изменения, проанализируем внутренние механизмы работы платформы 1С и предложим надежные, кроссплатформенные решения для восстановления воспроизведения звуков в управляемом интерфейсе 1С.

Выясняем причину: переход на движок WebKit и новые стандарты безопасности

Чтобы понять, почему старый код перестал работать, обратимся к архитектурным изменениям платформы 1С:Предприятие. До версии 8.3.14 элемент формы ПолеHTMLДокумента в операционной системе Windows базировался на движке Microsoft Internet Explorer (MSHTML), зачастую работая в режиме совместимости с устаревшими версиями IE7 или IE9. Этот движок обладал слабой поддержкой современных стандартов HTML5, но при этом легко интегрировался с локальной файловой системой Windows и не накладывал жестких ограничений на воспроизведение медиаконтента.

Начиная с версии 8.3.14, компания 1С перевела тонкий клиент на современный кроссплатформенный движок рендеринга WebKit (аналогичный тому, что используется в браузерах Safari и Chromium). Это позволило разработчикам использовать современные возможности верстки, CSS-анимации и новые стандарты JavaScript. Однако вместе с преимуществами пришли и жесткие требования безопасности, свойственные всем современным браузерам.

Выделим четыре основные причины, по которым аудиофайлы перестают воспроизводиться в новых версиях платформы 1С:

  1. Политика автовоспроизведения (Autoplay Policy): Современные браузерные движки блокируют воспроизведение звука без предварительного явного взаимодействия пользователя со страницей. Если ваш код пытается запустить аудиофайл сразу при открытии формы (например, в событии ПриОткрытии или при загрузке HTML-страницы через метод play() в JavaScript), WebKit заблокирует этот поток, оставив его в состоянии ожидания (suspended).
  2. Некорректный формат путей к файлам (URL Schemes): Старый движок Internet Explorer легко прощал использование прямых локальных путей Windows вида C:\Folder\sound.mp3 или сетевых UNC-путей вида \\server\volume\sound.mp3. Движок WebKit требует строгого соответствия стандартам RFC. Любые локальные файлы должны адресоваться через схему file:/// с заменой всех обратных слэшей на прямые.
  3. Графические баги отрисовки под RDP («Черный квадрат»): Стандартный тег HTML5 <audio controls> рендерится силами графической подсистемы WebKit. При работе пользователей через удаленный рабочий стол (RDP) или на компьютерах с определенными видеодрайверами аппаратное ускорение движка может некорректно отрисовывать элементы управления плеера. В результате кнопки воспроизведения становятся невидимыми или превращаются в черный прямоугольник, не реагирующий на клики мыши.
  4. Агрессивное кэширование WebKit: Браузерный движок внутри 1С оптимизирует сетевой трафик и повторные обращения к файлам. Если вы попытаетесь воспроизвести один и тот же файл несколько раз подряд, WebKit может вернуть его из кэша некорректно или посчитать запрос дублирующимся, заблокировав повторное воспроизведение.

Сравнительный анализ: MSHTML против WebKit в контексте мультимедиа

Рассмотрим подробнее ключевые различия между старым и новым движком, чтобы лучше понимать логику работы платформы:

Шаг 1. Переводим локальные пути в формат URI

Прежде всего, проанализируем, как правильно передавать пути к файлам в движок WebKit. Использование путей формата Windows напрямую в тегах HTML теперь недопустимо. Нам необходимо написать вспомогательную функцию, которая преобразует стандартный путь 1С в валидный URL-адрес схемы file.

Рассмотрим пример функции на встроенном языке 1С, которая выполняет такое преобразование:


&НаКлиенте
Функция ПреобразоватьПутьКФайлуДляWebKit(Знач ИсходныйПуть) Экспорт
    
    // Заменяем все обратные слэши на прямые
    ПрямыеСлэши = СтрЗаменить(ИсходныйПуть, "\", "/");
    
    // Если путь начинается с сетевого ресурса (UNC-путь)
    Если Лев(ПрямыеСлэши, 2) = "//" Тогда
        Возврат "file:" + ПрямыеСлэши;
    КонецЕсли;
    
    // Для локальных дисков (например, C:/...) добавляем три слэша
    Возврат "file:///" + ПрямыеСлэши;
    
КонецФункции

Используя эту функцию, мы гарантируем, что WebKit сможет правильно интерпретировать локальное или сетевое расположение аудиофайла и загрузить его в медиа-контекст.

Шаг 2. Отказываемся от тега controls в пользу JavaScript Audio API

Чтобы раз и навсегда решить проблему с графическим отображением встроенного плеера (проблема «черного квадрата»), мы полностью откажемся от визуального тега <audio controls>. Вместо этого мы будем управлять воспроизведением программно с помощью объекта JavaScript Audio(), который работает в фоновом режиме и не требует отрисовки графических элементов управления движком.

Для предоставления пользователю интерфейса управления (кнопок «Воспроизвести», «Пауза») мы разработаем собственные кнопки на чистом HTML и CSS, которые будут выглядеть одинаково хорошо на любой платформе и при любом типе подключения (включая RDP). Для отладки и визуального тестирования пользовательской разметки в форме можно использовать редактор HTML для управляемого интерфейса 1С.

Посмотрим на пример структуры HTML-документа, реализующего данный подход:


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 10px;
            background-color: #f0f0f0;
        }
        .btn {
            background-color: #4CAF50;
            border: none;
            color: white;
            padding: 10px 20px;
            text-align: center;
            text-decoration: none;
            display: inline-block;
            font-size: 14px;
            margin: 4px 2px;
            cursor: pointer;
            border-radius: 4px;
            transition: background-color 0.3s;
        }
        .btn:hover {
            background-color: #45a049;
        }
        .btn-stop {
            background-color: #f44336;
        }
        .btn-stop:hover {
            background-color: #da190b;
        }
    </style>
</head>
<body>
    <div>
        <button class="btn" onclick="playAudio()">Воспроизвести</button>
        <button class="btn btn-stop" onclick="pauseAudio()">Пауза</button>
    </div>

    <script>
        // Инициализируем переменную для хранения аудио-объекта
        var myAudio = null;

        function initAudio(filePath) {
            myAudio = new Audio(filePath);
        }

        function playAudio() {
            if (myAudio) {
                myAudio.play().catch(function(error) {
                    console.error("Ошибка воспроизведения: ", error);
                });
            }
        }

        function pauseAudio() {
            if (myAudio) {
                myAudio.pause();
            }
        }
    </script>
</body>
</html>

Шаг 3. Обходим политику автовоспроизведения (Autoplay Policy)

Как мы уже выяснили, WebKit жестко блокирует попытки вызвать метод play() программно сразу при загрузке документа. Для обхода этого ограничения необходимо соблюдать главное правило: первый запуск аудио должен быть инициирован непосредственным кликом пользователя по какому-либо интерактивному элементу внутри HTML-документа.

Если вам требуется запустить звук автоматически, как только открывается форма 1С, вам придется пересмотреть логику взаимодействия. Например, можно выводить для пользователя кнопку «Включить звук» или «Прослушать сообщение». Нажатие на эту кнопку внутри поля HTML-документа снимет блокировку безопасности WebKit, после чего последующие звуковые уведомления смогут проигрываться и программно.

Также важно обрабатывать возможные исключения в JavaScript с помощью конструкции catch при вызове метода play(). Это предотвратит «зависание» сценария в случае блокировки со стороны браузерного движка.

Шаг 4. Решаем проблему кэширования файлов

При частой смене аудиофайлов (например, когда пользователь прослушивает разные записи звонков по очереди в одной и той же форме) WebKit может продолжать проигрывать старый файл из своего кэша. Чтобы принудительно заставить движок загружать актуальную версию файла, мы применим классический веб-прием — добавление уникального GET-параметра к пути файла (так называемый cache-buster).

К пути файла в формате URI мы будем динамически добавлять параметр, содержащий текущее время в миллисекундах или случайное число. Например:

file:///C:/temp/sound.mp3?nocache=1602348912

Движок воспримет это как новый уникальный URL-адрес и выполнит повторное обращение к диску, игнорируя старый кэш.

Комплексный пример реализации на платформе 1С

Теперь объединим все наши шаги в единое комплексное решение. Создадим обработчик на клиенте, который будет динамически генерировать HTML-код, корректно преобразовывать пути к файлам, строить интерактивный плеер с индикацией прогресса и загружать результат в реквизит формы 1С, связанный с ПолемHTMLДокумента.

Для большей интерактивности мы добавим в наш кастомный плеер полосу прогресса (Progress Bar) и счетчик времени, которые будут управляться через события JavaScript timeupdate и durationchange. Это наглядно продемонстрирует пользователю, что воспроизведение идет успешно, даже если в системе временно отключены звуковые устройства.

Предположим, у нас на форме есть реквизит ТекстHTML с типом строки, который связан с элементом управления Поле HTML документа.


&НаКлиенте
Процедура ПриОткрытии(Отказ)
    
    // Исходный путь к файлу на сетевом диске
    ПутьКФайлу = "\\server\1C_Volume\YandexDisk\MobileData\message.mp3";
    
    // 1. Преобразуем путь в стандарт file:///
    ПодготовленныйПуть = ПреобразоватьПутьКФайлуДляWebKit(ПутьКФайлу);
    
    // 2. Добавляем генератор случайных чисел для обхода кэша
    ГСЧ = Новый ГенераторСлучайныхЧисел;
    СлучайноеЧисло = ГСЧ.СлучайноеЧисло(100000, 999999);
    ПутьСОбходомКэша = ПодготовленныйПуть + "?rnd=" + Формат(СлучайноеЧисло, "ЧГ=0");
    
    // 3. Формируем итоговый HTML-код с внедренным JavaScript и CSS-стилизацией
    ШаблонHTML = 
    "<!DOCTYPE html>
    |<html>
    |<head>
    |   <meta charset=""utf-8"">
    |   <style>
    |       body {
    |           font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    |           margin: 0;
    |           padding: 15px;
    |           background-color: #f3f3f3;
    |           display: flex;
    |           justify-content: center;
    |           align-items: center;
    |           height: 90vh;
    |       }
    |       .player-container {
    |           background-color: #ffffff;
    |           border: 1px solid #dcdcdc;
    |           border-radius: 6px;
    |           padding: 15px;
    |           width: 90%;
    |           max-width: 450px;
    |           box-shadow: 0 2px 8px rgba(0,0,0,0.08);
    |       }
    |       .title {
    |           font-size: 14px;
    |           font-weight: 600;
    |           color: #333333;
    |           margin-bottom: 12px;
    |           text-align: center;
    |       }
    |       .controls {
    |           display: flex;
    |           justify-content: space-between;
    |           align-items: center;
    |           margin-bottom: 15px;
    |       }
    |       .btn {
    |           background-color: #0078d4;
    |           border: none;
    |           color: white;
    |           padding: 8px 16px;
    |           font-size: 13px;
    |           font-weight: 500;
    |           cursor: pointer;
    |           border-radius: 3px;
    |           flex-grow: 1;
    |           margin-right: 8px;
    |       }
    |       .btn:hover {
    |           background-color: #106ebe;
    |       }
    |       .btn-stop {
    |           background-color: #a80000;
    |           margin-right: 0;
    |       }
    |       .btn-stop:hover {
    |           background-color: #8a0000;
    |       }
    |       .progress-container {
    |           background-color: #eaeaea;
    |           height: 6px;
    |           border-radius: 3px;
    |           cursor: pointer;
    |           position: relative;
    |           margin-bottom: 8px;
    |       }
    |       .progress-bar {
    |           background-color: #107c41;
    |           height: 100%;
    |           width: 0%;
    |           border-radius: 3px;
    |       }
    |       .time-info {
    |           display: flex;
    |           justify-content: space-between;
    |           font-size: 11px;
    |           color: #666666;
    |       }
    |       .status {
    |           font-size: 11px;
    |           color: #888888;
    |           text-align: center;
    |           margin-top: 10px;
    |           border-top: 1px solid #eeeeee;
    |           padding-top: 8px;
    |       }
    |   </style>
    |</head>
    |<body>
    |   <div class=""player-container"">
    |       <div class=""title"">Прослушивание записи</div>
    |       <div class=""controls"">
    |           <button class=""btn"" onclick=""playAudio()"">Воспроизвести</button>
    |           <button class=""btn btn-stop"" onclick=""pauseAudio()"">Пауза</button>
    |       </div>
    |       <div class=""progress-container"" onclick=""seek(event)"" id=""pContainer"">
    |           <div class=""progress-bar"" id=""pBar""></div>
    |       </div>
    |       <div class=""time-info"">
    |           <span id=""currentTime"">0:00</span>
    |           <span id=""durationTime"">0:00</span>
    |       </div>
    |       <div class=""status"" id=""statusText"">Ожидание запуска...</div>
    |   </div>
    |
    |   <script>
    |       var audioPath = """ + ПутьСОбходомКэша + """;
    |       var player = null;
    |
    |       function formatTime(seconds) {
    |           if (isNaN(seconds)) return '0:00';
    |           var mins = Math.floor(seconds / 60);
    |           var secs = Math.floor(seconds % 60);
    |           if (secs < 10) secs = '0' + secs;
    |           return mins + ':' + secs;
    |       }
    |
    |       function initPlayer() {
    |           try {
    |               player = new Audio(audioPath);
    |               
    |               player.addEventListener('timeupdate', function() {
    |                   if (player.duration) {
    |                       var pct = (player.currentTime / player.duration) * 100;
    |                       document.getElementById('pBar').style.width = pct + '%';
    |                       document.getElementById('currentTime').innerText = formatTime(player.currentTime);
    |                   }
    |               });
    |
    |               player.addEventListener('loadedmetadata', function() {
    |                   document.getElementById('durationTime').innerText = formatTime(player.duration);
    |               });
    |
    |               player.addEventListener('play', function() {
    |                   document.getElementById('statusText').innerText = 'Воспроизведение...';
    |               });
    |
    |               player.addEventListener('pause', function() {
    |                   document.getElementById('statusText').innerText = 'Приостановлено';
    |               });
    |
    |               player.addEventListener('ended', function() {
    |                   document.getElementById('statusText').innerText = 'Завершено';
    |                   document.getElementById('pBar').style.width = '0%';
    |                   document.getElementById('currentTime').innerText = '0:00';
    |               });
    |           } catch (e) {
    |               document.getElementById('statusText').innerText = 'Ошибка инициализации: ' + e.message;
    |           }
    |       }
    |
    |       function playAudio() {
    |           if (!player) {
    |               initPlayer();
    |           }
    |           if (player) {
    |               player.play().catch(function(error) {
    |                   document.getElementById('statusText').innerText = 'Ошибка запуска. Кликните еще раз.';
    |                   console.error(error);
    |               });
    |           }
    |       }
    |
    |       function pauseAudio() {
    |           if (player) {
    |               player.pause();
    |           }
    |       }
    |
    |       function seek(event) {
    |           if (player && player.duration) {
    |               var container = document.getElementById('pContainer');
    |               var rect = container.getBoundingClientRect();
    |               var clickX = event.clientX - rect.left;
    |               var width = rect.right - rect.left;
    |               var pct = clickX / width;
    |               player.currentTime = pct * player.duration;
    |           }
    |       }
    |   </script>
    |</body>
    |</html>";
    
    ТекстHTML = ШаблонHTML;
    
КонецПроцедуры

Дополнительные рекомендации при работе с сетевыми дисками и хранилищами

Иногда даже при правильном форматировании путей WebKit может блокировать доступ к сетевым шарам (UNC-путям вида \\server\...). Это связано со строгими политиками песочницы (sandboxing) и запретом смешанного содержимого (Mixed Content). Движок безопасности считает сетевой диск сторонним небезопасным доменом.

Для гарантированного воспроизведения в любых корпоративных инфраструктурах мы рекомендуем использовать следующую схему работы:

  1. Перед запуском воспроизведения скопируйте файл из сетевого каталога во временную локальную папку текущего пользователя Windows с помощью встроенного метода 1С КопироватьФайл().
  2. Передавайте в HTML-код путь к этой локальной временной копии.
  3. При закрытии формы или завершении сеанса удаляйте временный файл.

Проанализируем пример кода на стороне сервера и клиента 1С для реализации этого механизма:


&НаСервере
Функция ПолучитьЛокальнуюКопиюФайла(Знач СетевойПуть)
    
    // Получаем путь к уникальному временному файлу на локальном компьютере
    ИмяФайлаПриемника = ПолучитьИмяВременногоФайла("mp3");
    
    // Копируем файл с сетевого ресурса на локальный диск пользователя
    Попытка
        КопироватьФайл(СетевойПуть, ИмяФайлаПриемника);
        Возврат ИмяФайлаПриемника;
    Исключение
        ЗаписатьЛогОшибки("Не удалось скопировать аудиофайл во временную директорию: " + ОписаниеОшибки());
        Возврат "";
    КонецПопытки;
    
КонецФункции

&НаКлиенте
Процедура ПодготовитьИЗапуститьАудио(СетевойПуть)
    
    // Получаем локальную копию на сервере
    ЛокальныйПуть = ПолучитьЛокальнуюКопиюФайла(СетевойПуть);
    
    Если ЗначениеЗаполнено(ЛокальныйПуть) Тогда
        // Преобразуем локальный путь для WebKit и загружаем в HTML
        ПутьДляПлеера = ПреобразоватьПутьКФайлуДляWebKit(ЛокальныйПуть);
        ЗагрузитьПлеерВПолеHTML(ПутьДляПлеера);
    Иначе
        ПоказатьПредупреждение(, "Не удалось подготовить аудиофайл для воспроизведения.");
    EndIf;
    
КонецПроцедуры

Использование временной локальной папки пользователя решает 100% проблем, связанных с правами доступа WebKit к сетевым ресурсам, повышает скорость загрузки медиафайлов в плеер и избавляет от сетевых задержек во время проигрывания.

Применяя данные подходы, вы сможете легко адаптировать ваши конфигурации под требования современной платформы 1С:Предприятие 8.3, обеспечив стабильную и кроссплатформенную работу ваших мультимедийных решений.

← На главную