Создать APK бесплатно

JavaScript bridge API

Вибрация и звук

Тактильный отклик, собственный звук щелчка приложения по вашим смысловым событиям, а не по каждому случайному касанию, и фоновая музыка, выбранная создателем при сборке: getMusicVolume возвращает -1, если в приложении её нет, поэтому экран настроек может скрыть ползунок, а не показывать нерабочий.

navigator.vibrate веб-стандарт#

navigator.vibrate(pattern)

Пример

Вибрирует телефон через обычный веб-API Vibration. Собственный navigator.vibrate в Android WebView никогда не запускает мотор; приложение подменяет его при запуске документа, чтобы он действительно вибрировал.

Возвращает: true, когда запрос принят, false при неверных данных (отрицательная или нечисловая длительность). Требуется: включите Vibrate на шаге 4 (Доступ) при сборке. Для приложений, созданных ИИ, оно включается само, если код использует вибрацию.

function buzz(pattern) {
  if ('vibrate' in navigator) navigator.vibrate(pattern);
}

document.getElementById('save').addEventListener('click', function () {
  buzz(30);                    // one short buzz, 30 ms
});

document.getElementById('wrong').addEventListener('click', function () {
  buzz([80, 40, 80]);          // buzz 80, pause 40, buzz 80
});

document.getElementById('alarm-off').addEventListener('click', function () {
  buzz(0);                     // 0 or [] stops a running vibration
});

Используйте возвращаемое значение, чтобы поймать неверную длительность, например введённую пользователем:

document.getElementById('test-buzz').addEventListener('click', function () {
  var ms = parseInt(document.getElementById('buzz-ms').value, 10);   // '' gives NaN
  if (!('vibrate' in navigator)) return;
  if (!navigator.vibrate(ms)) {
    showError('Enter a duration of 0 ms or more.');
  }
});

Один короткий щелчок, когда при обновлении свайпом вниз достигнута точка, после которой отпускание запустит обновление:

var armed = false;
var list = document.getElementById('feed');

list.addEventListener('touchmove', function () {
  var ready = list.scrollTop <= 0 && pullDistance() > 80;
  if (ready && !armed && 'vibrate' in navigator) navigator.vibrate(15);
  armed = ready;
}, { passive: true });

list.addEventListener('touchend', function () {
  if (armed) refreshFeed();
  armed = false;
});

Примечания:

  • Шаблон такой же, как в вебе: первое число — это вибрация, затем пауза, вибрация, пауза ... (приложение преобразует его для Android).
  • Если при сборке Vibrate не был включён, вызов всё равно возвращает true, но телефон не вибрирует.
  • Десятичные числа округляются вниз. В массиве отрицательные числа считаются как 0.
  • В браузере на компьютере navigator.vibrate может отсутствовать, поэтому оставьте проверку 'vibrate' in navigator.

cancelVibrate мост#

window.WebToApk.cancelVibrate()

Простая тактильная отдача для страницы (navigator.vibrate и форма с шаблоном). Обе игнорируются, если в сборке не включено разрешение Vibrate.

Описывается в своей группе, Вибрация, а не отдельно.

Пример

Останавливает ещё идущую вибрацию. Большинство страниц используют стандартный navigator.vibrate(0), который вызывает это за вас.

Возвращает: ничего. Требуется: включите Вибрацию на шаге 4 (Доступ) при сборке.

Стандартный способ:

if ('vibrate' in navigator) navigator.vibrate(0);   // or navigator.vibrate([])

Прямой вызов, например чтобы остановить длинный шаблон сигнала, когда пользователь нажимает «Закрыть»:

if (window.WebToApk && window.WebToApk.vibratePattern) {
  window.WebToApk.vibratePattern(JSON.stringify([1000, 500, 1000, 500, 1000]));
}

document.getElementById('dismiss').addEventListener('click', function () {
  if (window.WebToApk && typeof window.WebToApk.cancelVibrate === 'function') {
    window.WebToApk.cancelVibrate();
  }
});

Остановите идущую вибрацию, когда пользователь покидает экран, чтобы телефон не продолжал жужжать у него в кармане:

document.addEventListener('visibilitychange', function () {
  if (!document.hidden) return;
  if (window.WebToApk && typeof window.WebToApk.cancelVibrate === 'function') {
    window.WebToApk.cancelVibrate();
  }
});

Примечания: Безопасно вызывать, когда ничего не вибрирует.

getMusicVolume мост#

window.WebToApk.getMusicVolume(): Int

Текущий уровень 0..100 или -1, если в этом приложении нет фоновой музыки.

Пример

Читает уровень зацикленной фоновой музыки приложения, от 0 до 100.

Возвращает: число, синхронно: 0–100 или -1, если приложение собрано без музыкального файла. Требуется: файл, выбранный в Background Music на шаге 3 (Интеграция), в разделе Display Options, при сборке.

Показывайте ползунок музыки только тогда, когда есть музыка:

var slider = document.getElementById('music-slider');
var level = -1;
if (window.WebToApk && typeof window.WebToApk.getMusicVolume === 'function') {
  level = window.WebToApk.getMusicVolume();
}

if (level < 0) {
  slider.parentNode.hidden = true;          // no music in this app: hide the setting
} else {
  slider.value = level;
  slider.addEventListener('input', function () {
    window.WebToApk.setMusicVolume(parseInt(slider.value, 10));
  });
}

Кнопка отключения звука, которая при включении возвращает собственный уровень пользователя (приложение тоже запоминает 0, поэтому храните прежний уровень сами):

function musicLevel() {
  return window.WebToApk && typeof window.WebToApk.getMusicVolume === 'function'
    ? window.WebToApk.getMusicVolume() : -1;
}
var muteBtn = document.getElementById('mute');

function paint() {
  var v = musicLevel();
  muteBtn.hidden = v < 0;
  muteBtn.textContent = v === 0 ? 'Unmute' : 'Mute';
}

muteBtn.addEventListener('click', function () {
  var v = musicLevel();
  if (v < 0) return;
  if (v > 0) {
    try { localStorage.setItem('musicBefore', String(v)); } catch (e) {}
    window.WebToApk.setMusicVolume(0);
  } else {
    var back = 40;
    try { back = parseInt(localStorage.getItem('musicBefore') || '40', 10); } catch (e) {}
    window.WebToApk.setMusicVolume(back);
  }
  paint();
});
paint();

Плавно измените громкость музыки с текущего уровня на новый, например во время диалоговой сцены:

function fadeMusicTo(target, ms) {
  if (!window.WebToApk || typeof window.WebToApk.getMusicVolume !== 'function') return;
  var start = window.WebToApk.getMusicVolume();
  if (start < 0 || start === target) return;
  var steps = 10, i = 0;
  var t = setInterval(function () {
    i++;
    window.WebToApk.setMusicVolume(Math.round(start + (target - start) * i / steps));
    if (i === steps) clearInterval(t);
  }, ms / steps);
}

fadeMusicTo(10, 800);     // dim under the dialogue
// later: fadeMusicTo(60, 800);  the last level set is the one kept for the next launch

Примечания: Возвращает уровень, заданный ползунком в сборке или вашим последним вызовом setMusicVolume. Когда звук занимает другое приложение или звонок, музыка сама становится тише или ставится на паузу; число, которое вы читаете, не меняется.

getVolume мост#

window.WebToApk.getVolume(stream: String): String

Уровень одного потока: `{stream, current, max, percent, muted}` в виде строки JSON. stream: 'media' | 'alarm' | 'ring' | 'notification' | 'call'.

Пример

Считывает один из собственных уровней громкости телефона.

Возвращает: строку JSON, синхронно: {"stream":"alarm","current":5,"max":7,"percent":71,"muted":false} — current/max это собственные шаги телефона (на разных телефонах они различаются), percent — от 0 до 100. stream — это 'media', 'alarm', 'ring', 'notification' или 'call'; любое другое имя даёт {"error":"unknown_stream"}. Требуется: ничего.

function warnIfAlarmSilent() {
  if (!(window.WebToApk && window.WebToApk.getVolume)) return;
  const v = JSON.parse(WebToApk.getVolume('alarm'));
  if (!v.error && v.percent === 0) {
    showMessage('Your alarm volume is off, so the alarm will not be heard.');
  }
}
warnIfAlarmSilent();

Показать все уровни громкости на экране настроек:

['media', 'alarm', 'ring', 'notification'].forEach(function (s) {
  const v = JSON.parse(WebToApk.getVolume(s));
  document.getElementById('vol-' + s).value = v.percent;
});

Задайте громкость и прочитайте результат. setVolume(stream, percent, showUi) отвечает строкой JSON.

function setAlarmVolume(percent) {
  if (!(window.WebToApk && WebToApk.setVolume)) return;
  const r = JSON.parse(WebToApk.setVolume('alarm', percent, true));
  if (!r.ok) showMessage('Could not change the volume: ' + r.error);   // fixed_volume, muted, ...
  else document.getElementById('vol-alarm').value = r.percent;
}

playClick мост#

window.WebToApk.playClick()

Воспроизводит звук нажатия приложения по запросу, чтобы страница могла запускать его на СВОИ смысловые события (нажатие кнопки), а не на каждое случайное касание — именно так делает нативное приложение, и поэтому clickSoundMode может быть «off», а это всё равно работает. Не делает ничего, только если приложение собрано БЕЗ звука нажатия (enableClickSound выключен). В режиме clickSoundMode «off» — без автоматического звука на каждое касание — явный вызов всё равно играет: этот режим нужен приложениям, которые озвучивают собственные события. В остальных случаях играет настроенную автором обратную связь.

Пример

Один раз воспроизводит звук нажатия приложения, чтобы ваши собственные кнопки щёлкали как в нативном приложении.

Возвращает: ничего. Требуется: переключатель Tap Sound на шаге 3 (Интеграция), в разделе Display Options, при сборке (включён по умолчанию). Если он выключен, вызов ничего не делает.

function clickSound() {
  if (window.WebToApk && typeof window.WebToApk.playClick === 'function') {
    window.WebToApk.playClick();
  }
}

document.querySelectorAll('button, .tab').forEach(function (el) {
  el.addEventListener('click', clickSound);
});

Один обработчик на всё игровое поле, щёлкающий только при нажатии, которое что-то делает:

document.getElementById('board').addEventListener('click', function (e) {
  var cell = e.target.closest('.cell');
  if (!cell || cell.classList.contains('taken')) return;   // a dead tap stays silent
  if (window.WebToApk && typeof window.WebToApk.playClick === 'function') {
    window.WebToApk.playClick();
  }
  placeMark(cell);
});

Дайте пользователям собственный переключатель «Звуки» поверх настройки сборки:

var toggle = document.getElementById('sounds-toggle');
var soundsOn = true;
try { soundsOn = localStorage.getItem('sounds') !== 'off'; } catch (e) {}
toggle.checked = soundsOn;

toggle.addEventListener('change', function () {
  soundsOn = toggle.checked;
  try { localStorage.setItem('sounds', soundsOn ? 'on' : 'off'); } catch (e) {}
});

function tapSound() {
  if (soundsOn && window.WebToApk && typeof window.WebToApk.playClick === 'function') {
    window.WebToApk.playClick();
  }
}

Примечания: То, что вы слышите, зависит от выбора в поле Звук нажатия:

  • Как в настройках телефона (рекомендуется): системный щелчок. Он беззвучен, если пользователь отключил «Звуки при касании» в настройках телефона. Так задумано.
  • Always play on every tap: собственный щелчок приложения, всегда слышимый. Каждое нажатие на страницу тоже щёлкает, поэтому в этом режиме playClick() обычно не нужен.

Вызывайте её по настоящему нажатию кнопки, а не по таймеру.

requestDndAccess мост#

window.WebToApk.requestDndAccess(): String

Доступ к режиму «Не беспокоить», нужный для изменения громкости звонка и уведомлений, пока он включён. «granted» | «opened» (открыт экран настроек) | «not_declared».

Пример

Запрашивает доступ к режиму «Не беспокоить», который Android требует, прежде чем приложение сможет менять громкость звонка или уведомлений при включённом режиме «Не беспокоить» (тогда setVolume отвечает dnd_access_required).

Возвращает: строку: 'granted' (уже разрешено), 'opened' (открыт системный экран, где пользователь это разрешает — проверьте снова, когда он вернётся) или 'not_declared' (в этой сборке нет разрешения, поэтому Android не покажет приложение в этом списке). Требуется: вызов requestDndAccess в коде вашего приложения: тогда сборка сама объявит ACCESS_NOTIFICATION_POLICY.

function allowRingVolume() {
  if (!(window.WebToApk && window.WebToApk.requestDndAccess)) return;
  const r = WebToApk.requestDndAccess();
  if (r === 'opened') showMessage('Allow Do Not Disturb access for this app, then come back.');
  if (r === 'not_declared') showMessage('Turn off Do Not Disturb to change the ring volume.');
}

Повторите изменение громкости один раз, когда пользователь разрешит доступ:

function setRing(percent) {
  const r = JSON.parse(WebToApk.setVolume('ring', percent, true));
  if (r.ok || r.error !== 'dnd_access_required') return r;
  const a = WebToApk.requestDndAccess();     // 'granted' | 'opened' | 'not_declared'
  if (a === 'granted') return JSON.parse(WebToApk.setVolume('ring', percent, true));
  showMessage(a === 'opened' ? 'Allow access, then press the button again.' : 'Turn off Do Not Disturb first.');
  return r;
}

Отключите звонок переключателем «режим встречи» и объясните причину, если это невозможно:

function meetingMode(on) {
  if (!(window.WebToApk && WebToApk.setVolume)) return;
  const r = JSON.parse(WebToApk.setVolume('ring', on ? 0 : 70, false));
  if (!r.ok && r.error === 'dnd_access_required') {
    const a = WebToApk.requestDndAccess();
    if (a === 'not_declared') showMessage('This build cannot change the ring volume while Do Not Disturb is on.');
  }
}

setMusicVolume мост#

window.WebToApk.setMusicVolume(percent: Int)

Задаёт громкость фоновой музыки со страницы, 0..100. Ползунок в мастере определяет, как приложение ЗАПУСКАЕТСЯ; а это способ дать собственным пользователям приложения управление музыкой, которое ожидается в любой игре с экраном настроек. Значение ограничивается в [BackgroundMusic.setVolume], так что страница, передающая 500 или -1, не сможет передать MediaPlayer значение вне допустимого диапазона. Уровень ЗАПОМИНАЕТСЯ: следующий запуск начнётся с него, а не с уровня из мастера. Ничего не делает, если приложение собрано без трека — задавать нечего, и честный ответ — [getMusicVolume], возвращающий -1, чтобы страница могла скрыть ползунок, а не показывать бесполезный.

Пример

Меняет громкость циклической фоновой музыки приложения от 0 до 100, например на экране настроек вашей игры.

Возвращает: ничего. Требуется: файл, выбранный в разделе Фоновая музыка на шаге 3 (Интеграция), в блоке Параметры отображения, при сборке. Без музыки ничего не происходит (проверьте через getMusicVolume() для -1).

Кнопка отключения звука — приложение запоминает уровень до следующего запуска:

function hasMusic() {
  return !!(window.WebToApk && window.WebToApk.getMusicVolume) && window.WebToApk.getMusicVolume() >= 0;
}

if (!hasMusic()) document.getElementById('mute').hidden = true;

document.getElementById('mute').addEventListener('click', function () {
  if (!hasMusic()) return;
  // clamped to 0..100 by the app, and kept for the next launch
  window.WebToApk.setMusicVolume(window.WebToApk.getMusicVolume() > 0 ? 0 : 40);
});

Кнопки «Выкл. / Слабо / Сильно» на экране настроек:

var LEVELS = { off: 0, low: 25, high: 70 };

document.querySelectorAll('[data-music]').forEach(function (btn) {
  btn.addEventListener('click', function () {
    if (!window.WebToApk || typeof window.WebToApk.setMusicVolume !== 'function') return;
    if (window.WebToApk.getMusicVolume() < 0) return;       // built without music
    window.WebToApk.setMusicVolume(LEVELS[btn.dataset.music]);
    markSelected(btn);
  });
});

Приглушите музыку, пока играет ваше собственное видео, а потом верните (уровень запоминается, поэтому всегда восстанавливайте его):

var video = document.getElementById('intro-video');
var before = -1;

video.addEventListener('play', function () {
  if (!window.WebToApk || typeof window.WebToApk.getMusicVolume !== 'function') return;
  before = window.WebToApk.getMusicVolume();
  if (before > 0) window.WebToApk.setMusicVolume(Math.round(before / 4));
});

function restoreMusic() {
  if (before > 0) window.WebToApk.setMusicVolume(before);
  before = -1;
}
video.addEventListener('pause', restoreMusic);
video.addEventListener('ended', restoreMusic);

Примечания: Заданный вами уровень сохраняется: при следующем запуске приложение начнёт с него, а не с уровня ползунка сборки (пока пользователь не очистит данные приложения). 0 делает музыку беззвучной, но она продолжает играть.

setVolume мост#

window.WebToApk.setVolume(stream: String, percent: Double, showUi: Boolean): String

Устанавливает поток на [percent] (0–100); [showUi] показывает ползунок громкости телефона. `{ok:true, stream, current, max, percent}` или `{ok:false, error}` — error: unknown_stream | bad_percent | fixed_volume | dnd_access_required | muted | not_applied | failed | foreign_origin (страница вне собственного сайта приложения).

Пример

Задаёт один из собственных уровней громкости телефона от 0 до 100 — например, делает громче поток будильника для будильника-побудки или ползунок медиа в музыкальном приложении.

Возвращает: строку JSON: {"ok":true,"stream":"alarm","current":6,"max":7,"percent":86} или {"ok":false,"error":"…"}, где error — это unknown_stream, bad_percent, fixed_volume (на этом устройстве нет регулируемой громкости), dnd_access_required (включён режим «Не беспокоить» и блокирует громкость звонка / уведомлений — см. requestDndAccess()), foreign_origin (страница вне сайта вашего приложения) или failed. showUi: true показывает собственный ползунок громкости телефона во время изменения. Требуется: ничего для медиа, будильника и звонка; для звонка и уведомлений может понадобиться доступ к режиму «Не беспокоить», пока он включён.

function setAlarmLoud() {
  if (!(window.WebToApk && window.WebToApk.setVolume)) return;
  const r = JSON.parse(WebToApk.setVolume('alarm', 80, true));
  if (!r.ok) showMessage('Could not change the alarm volume: ' + r.error);
}

document.getElementById('ring-slider').oninput = function (e) {
  const r = JSON.parse(WebToApk.setVolume('ring', Number(e.target.value), false));
  if (r.error === 'dnd_access_required' && WebToApk.requestDndAccess() === 'not_declared') {
    showMessage('Turn off Do Not Disturb to change the ring volume.');
  }
};

Прочитайте уровень, затем задайте ползунок медиа:

if (window.WebToApk && WebToApk.setVolume) {
  const now = JSON.parse(WebToApk.getVolume('media'));   // {stream, current, max, percent, muted}
  slider.value = now.percent;
  slider.oninput = () => {
    const r = JSON.parse(WebToApk.setVolume('media', Number(slider.value), false));
    if (r.ok) label.textContent = r.percent + '%';
    else if (r.error === 'fixed_volume') slider.disabled = true;   // this phone has no adjustable volume
  };
}

Показать текущую громкость звонка и реагировать на её изменение:

if (window.WebToApk && WebToApk.getVolume) {
  const ring = JSON.parse(WebToApk.getVolume('ring'));   // {stream, current, max, percent, muted}
  document.getElementById('ring-slider').value = ring.percent;
  WebToApk.watchVolume(true);
  window.addEventListener('appmint:volume', (e) => {
    if (e.detail.stream === 'ring') document.getElementById('ring-slider').value = e.detail.percent;
  });
}

Примечания: значение округляется до ближайшего шага телефона, а в ответе указан фактически установленный уровень. Поток вызовов никогда не опускается ниже минимума (обычно 1).

vibrate мост#

window.WebToApk.vibrate(durationMs: Long)

Простая тактильная отдача для страницы (navigator.vibrate и форма с шаблоном). Обе игнорируются, если в сборке не включено разрешение Vibrate.

Описывается в своей группе, Вибрация, а не отдельно.

Пример

Один раз включает вибрацию телефона на заданное число миллисекунд. Большинство страниц используют стандартный navigator.vibrate(ms), который вызывает её за вас.

Возвращает: ничего. Требуется: включите Вибрацию на шаге 4 (Доступ) при сборке. Без этого вызов ничего не делает.

Стандартный способ (работает так же в браузере):

if ('vibrate' in navigator) navigator.vibrate(50);

Прямой вызов:

document.getElementById('like').addEventListener('click', function () {
  if (window.WebToApk && typeof window.WebToApk.vibrate === 'function') {
    window.WebToApk.vibrate(40);      // 40 ms
  }
});

// 0 (or any value <= 0) stops a running vibration
if (window.WebToApk && window.WebToApk.vibrate) window.WebToApk.vibrate(0);

Лёгкий «тик» каждый раз, когда ползунок переходит на новый шаг, как в системном выборе:

var slider = document.getElementById('size');
var last = slider.value;
slider.addEventListener('input', function () {
  if (slider.value === last) return;          // same step, no tick
  last = slider.value;
  if (window.WebToApk && typeof window.WebToApk.vibrate === 'function') {
    window.WebToApk.vibrate(10);              // 10 ms: a light tick
  }
});

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

document.getElementById('signup').addEventListener('submit', function (e) {
  if (this.checkValidity()) return;
  e.preventDefault();
  if (window.WebToApk && typeof window.WebToApk.vibrate === 'function') {
    window.WebToApk.vibrate(250);
  }
  this.reportValidity();
});

Примечания: Передайте целое число. Сила — стандартная для телефона; для более сильных или слабых касаний используйте Capacitor.Plugins.Haptics.impact или Capacitor.Plugins.AppwrightHaptics.play. Для шаблона вибрация-пауза-вибрация используйте vibratePattern.

vibratePattern мост#

window.WebToApk.vibratePattern(patternJson: String)

Вибрирует по шаблону «вкл/выкл», например «[100,50,100]» = вибрация 100 мс, пауза 50, вибрация 100. Лежит в основе navigator.vibrate(array); предпочитайте стандартный API. Ничего не делает, если создатель не включил разрешение Vibrate.

Пример

Вибрирует по шаблону включения/выключения, заданному строкой JSON. Большинство страниц используют стандартный navigator.vibrate([...]), который вызывает её за вас.

Возвращает: ничего. Требуется: включите Вибрацию на шаге 4 (Доступ) при сборке. Без этого вызов ничего не делает.

Стандартный способ:

if ('vibrate' in navigator) navigator.vibrate([100, 50, 100]);   // buzz, pause, buzz

Прямой вызов принимает тот же шаблон в веб-стиле как строку JSON:

function pattern(list) {
  if (window.WebToApk && typeof window.WebToApk.vibratePattern === 'function') {
    window.WebToApk.vibratePattern(JSON.stringify(list));
  }
}

pattern([200]);                     // one 200 ms buzz
pattern([100, 50, 100, 50, 300]);   // short, short, long
pattern([]);                        // empty list stops a running vibration

Шаблон проигрывается один раз, поэтому будильник сам повторяет его, пока пользователь не остановит:

var alarm = null;
var ALARM = JSON.stringify([500, 300, 500, 300, 500]);   // about 2.1 s

function startAlarm() {
  if (!window.WebToApk || typeof window.WebToApk.vibratePattern !== 'function') return;
  window.WebToApk.vibratePattern(ALARM);
  alarm = setInterval(function () { window.WebToApk.vibratePattern(ALARM); }, 3000);
}

function stopAlarm() {
  if (alarm === null) return;
  clearInterval(alarm);
  alarm = null;
  window.WebToApk.vibratePattern('[]');                 // stop the buzz that is still running
}

Примечания: Первое число — это вибрация, затем пауза, вибрация, пауза ... (порядок как в вебе). Приложение само добавляет начальное «ожидание» Android, поэтому не ставьте 0 первым: [0, 100] означает «вибрация 0 мс, пауза 100 мс», и вы ничего не почувствуете. Отрицательные числа считаются как 0. Шаблон проигрывается один раз.

watchVolume мост#

window.WebToApk.watchVolume(on: Boolean): Boolean

Включает (true) или выключает (false) события `appmint:volume`.

Пример

Включает или выключает живые события изменения громкости: пока включено, каждое изменение уровня громкости (аппаратные кнопки, системный ползунок, другое приложение) доходит до страницы как appmint:volume.

Возвращает: true, когда изменение вступило в силу. Требуется: ничего.

if (window.WebToApk && window.WebToApk.watchVolume) {
  window.addEventListener('appmint:volume', function (e) {
    // e.detail = { stream: 'media', current: 9, max: 15, percent: 60, muted: false }
    document.getElementById('vol-' + e.detail.stream).value = e.detail.percent;
  });
  WebToApk.watchVolume(true);
  window.addEventListener('pagehide', function () { WebToApk.watchVolume(false); });
}

Тот же объект доступен и через хук страницы, если вам так удобнее:

window.onAppMintVolume = function (level) {
  if (level.stream === 'alarm' && level.percent === 0) showMessage('Alarm volume is now off.');
};
WebToApk.watchVolume(true);

Покажите уровень один раз через getVolume, затем обновляйте его вживую, только пока экран виден:

if (window.WebToApk && WebToApk.watchVolume && WebToApk.getVolume) {
  const meter = document.getElementById('media-meter');
  meter.value = JSON.parse(WebToApk.getVolume('media')).percent;
  const onVol = function (e) { if (e.detail.stream === 'media') meter.value = e.detail.percent; };
  document.addEventListener('visibilitychange', function () {
    if (document.hidden) { WebToApk.watchVolume(false); window.removeEventListener('appmint:volume', onVol); }
    else { window.addEventListener('appmint:volume', onVol); WebToApk.watchVolume(true); }
  });
}

Примечания: одно событие на каждый изменившийся поток (media, alarm, ring, notification, call). Отключите его, когда экран с уровнями громкости закрывается.