☰

Sleazy Fork is available in English.

Cams Unchained

BongaCams, Chaturbate, Stripchat: Interface épurée, PiP, plein écran, enregistrement, captures d'écran, favoris et bien plus.

Avant de procéder à l'installation, Sleazy Forkattention ce script contient des contre-fonctionnalités, qui sont là pour le bénéfice de l'auteur du script, plutôt que pour le vôtre.

Ce script comporte une ou plusieurs commissions pour l'auteur, par exemple en réécrivant des liens ou en fournissant des codes promo pour inclure un code de parrainage ou d'affiliation.

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name         Cams Unchained
// @name:ru       Cams Unchained
// @name:uk       Cams Unchained
// @name:es       Cams Unchained
// @name:de       Cams Unchained
// @name:fr       Cams Unchained
// @name:pt       Cams Unchained
// @name:it       Cams Unchained
// @name:pl       Cams Unchained
// @name:tr       Cams Unchained
// @name:nl       Cams Unchained
// @name:cs       Cams Unchained
// @name:ro       Cams Unchained
// @name:hu       Cams Unchained
// @name:ja       Cams Unchained
// @name:zh-CN    Cams Unchained
// @name:ko       Cams Unchained
// @name:he       Cams Unchained
// @name:ar       Cams Unchained
// @name:hi       Cams Unchained
// @name:id       Cams Unchained
// @name:vi       Cams Unchained
// @name:th       Cams Unchained
// @namespace    DiadiaBonga
// @version      4.9.0
// @description:ru BongaCams, Chaturbate, Stripchat: Чистый интерфейс, PiP, полный экран, запись, скриншоты, закладки и многое другое.
// @description BongaCams, Chaturbate, Stripchat: Clean interface, PiP, fullscreen, recording, screenshots, bookmarks and more.
// @description:uk BongaCams, Chaturbate, Stripchat: Чистий інтерфейс, PiP, повний екран, запис, скріншоти, закладки та багато іншого.
// @description:es BongaCams, Chaturbate, Stripchat: Interfaz limpia, PiP, pantalla completa, grabación, capturas, favoritos y mucho más.
// @description:de BongaCams, Chaturbate, Stripchat: Saubere Oberfläche, PiP, Vollbild, Aufnahme, Screenshots, Favoriten und vieles mehr.
// @description:fr BongaCams, Chaturbate, Stripchat: Interface épurée, PiP, plein écran, enregistrement, captures d'écran, favoris et bien plus.
// @description:pt BongaCams, Chaturbate, Stripchat: Interface limpa, PiP, tela cheia, gravação, capturas de tela, favoritos e muito mais.
// @description:it BongaCams, Chaturbate, Stripchat: Interfaccia pulita, PiP, schermo intero, registrazione, screenshot, preferiti e molto altro.
// @description:pl BongaCams, Chaturbate, Stripchat: Czysty interfejs, PiP, pełny ekran, nagrywanie, zrzuty ekranu, zakładki i wiele więcej.
// @description:tr BongaCams, Chaturbate, Stripchat: Temiz arayüz, PiP, tam ekran, kayıt, ekran görüntüsü, yer imleri ve daha fazlası.
// @description:nl BongaCams, Chaturbate, Stripchat: Schone interface, PiP, volledig scherm, opnemen, screenshots, favorieten en meer.
// @description:cs BongaCams, Chaturbate, Stripchat: Čisté rozhraní, PiP, celá obrazovka, nahrávání, snímky obrazovky, záložky a mnoho dalšího.
// @description:ro BongaCams, Chaturbate, Stripchat: Interfață curată, PiP, ecran complet, înregistrare, capturi de ecran, favorite și multe altele.
// @description:hu BongaCams, Chaturbate, Stripchat: Tiszta felület, PiP, teljes képernyő, felvétel, képernyőképek, kedvencek és még sok más.
// @description:ja BongaCams, Chaturbate, Stripchat: クリーンなインターフェース、PiP、全画面、録画、スクリーンショット、ブックマークなど。
// @description:zh-CN BongaCams, Chaturbate, Stripchat: 简洁界面、PiP、全屏、录制、截图、收藏等多种功能。
// @description:ko BongaCams, Chaturbate, Stripchat: 깔끔한 인터페이스, PiP, 전체 화면, 녹화, 스크린샷, 즐겨찾기 등.
// @description:he BongaCams, Chaturbate, Stripchat: ממשק נקי, PiP, מסך מלא, הקלטה, צילומי מסך, מועדפים ועוד.
// @description:ar BongaCams, Chaturbate, Stripchat: واجهة نظيفة، PiP، ملء الشاشة، تسجيل، لقطات شاشة، مفضلة والمزيد.
// @description:hi BongaCams, Chaturbate, Stripchat: साफ़ इंटरफ़ेस, PiP, फुलस्क्रीन, रिकॉर्डिंग, स्क्रीनशॉट, पसंदीदा और बहुत कुछ।
// @description:id BongaCams, Chaturbate, Stripchat: Antarmuka bersih, PiP, layar penuh, perekaman, tangkapan layar, favorit, dan lainnya.
// @description:vi BongaCams, Chaturbate, Stripchat: Giao diện gọn gàng, PiP, toàn màn hình, ghi hình, chụp màn hình, yêu thích và nhiều hơn nữa.
// @description:th BongaCams, Chaturbate, Stripchat: อินเทอร์เฟซสะอาด, PiP, เต็มจอ, อัดวิดีโอ, แคปหน้าจอ, รายการโปรด และอื่นๆ
// @author       DiaDiaBogDan
// @license      MIT
// @antifeature   referral-link
// @match        *://*.bongacams.com/*
// @match        *://*.bongacams2.com/*
// @match        *://*.bongocams.com/*
// @match        *://*.webcamsluts.ru/*
// @match        *://*.stripchat.com/*
// @match        *://stripchat.com/*
// @match        *://*.chaturbate.com/*
// @match        *://chaturbate.com/*
// @icon         data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJiZyIgeDE9IjAiIHkxPSIwIiB4Mj0iMCIgeTI9IjEiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iIzJiMmIzMCIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzEyMTIxNSIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJoYWxvIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAiIHkxPSItNCIgeDI9IjAiIHkyPSI1MDgiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iIzE4MTgxYiIvPjxzdG9wIG9mZnNldD0iLjQyIiBzdG9wLWNvbG9yPSIjMTgxODFiIi8+PHN0b3Agb2Zmc2V0PSIuNTgiIHN0b3AtY29sb3I9InJnYigyOC41LDI4LjUsMzIuMzQpIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSJyZ2IoMTgsMTgsMjEpIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9ImhlYXJ0IiB4MT0iMC4xIiB5MT0iMCIgeDI9IjAuOSIgeTI9IjEiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iI2ZmOWRiNSIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2Q0MmQ2OCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5rIiB4MT0iMCIgeTE9IjAiIHgyPSIxIiB5Mj0iMSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjZmZlM2VhIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjZmY5ZmI4Ii8+PC9saW5lYXJHcmFkaWVudD48Y2xpcFBhdGggaWQ9Im92ZXIiPjxyZWN0IHg9Ii0xMzAiIHk9Ii02NCIgd2lkdGg9IjI2MCIgaGVpZ2h0PSI2NCIvPjwvY2xpcFBhdGg+PGNsaXBQYXRoIGlkPSJjcm9zcyI+PHJlY3QgeD0iLTUyIiB5PSItNjQiIHdpZHRoPSIxMDQiIGhlaWdodD0iNjQiLz48L2NsaXBQYXRoPjxwYXRoIGlkPSJoIiBkPSJNMjU2IDM5OCBDMTUyIDMyMiA5MCAyNjIgOTAgMTk4IEM5MCAxNTAgMTI2IDExNiAxNzAgMTE2IEMyMTAgMTE2IDI0MiAxMzggMjU2IDE3MiBDMjcwIDEzOCAzMDIgMTE2IDM0MiAxMTYgQzM4NiAxMTYgNDIyIDE1MCA0MjIgMTk4IEM0MjIgMjYyIDM2MCAzMjIgMjU2IDM5OCBaIi8+PHJlY3QgaWQ9ImwiIHg9Ii04OCIgeT0iLTM4IiB3aWR0aD0iMTc2IiBoZWlnaHQ9Ijc2IiByeD0iMzgiLz48bWFzayBpZD0iY3V0IiBtYXNrVW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4PSIwIiB5PSItMTAiIHdpZHRoPSI1MTIiIGhlaWdodD0iNTIwIj48cmVjdCB4PSIwIiB5PSItMTAiIHdpZHRoPSI1MTIiIGhlaWdodD0iNTIwIiBmaWxsPSIjZmZmIi8+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMzY4IDMyMikgcm90YXRlKDQ1KSI+PGcgY2xpcC1wYXRoPSJ1cmwoI2Nyb3NzKSI+PHVzZSB4bGluazpocmVmPSIjbCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utd2lkdGg9IjUwIi8+PC9nPjwvZz48L21hc2s+PC9kZWZzPjxyZWN0IHdpZHRoPSI1MTIiIGhlaWdodD0iNTEyIiByeD0iMTEyIiBmaWxsPSJ1cmwoI2JnKSIvPjxyZWN0IHg9IjMiIHk9IjMiIHdpZHRoPSI1MDYiIGhlaWdodD0iNTA2IiByeD0iMTA5IiBmaWxsPSJub25lIiBzdHJva2U9IiNmZmYiIHN0cm9rZS1vcGFjaXR5PSIuMDciIHN0cm9rZS13aWR0aD0iMiIvPjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAgNCkiPjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDM2OCAzMjIpIHJvdGF0ZSg0NSkiPjx1c2UgeGxpbms6aHJlZj0iI2wiIGZpbGw9Im5vbmUiIHN0cm9rZT0idXJsKCNsaW5rKSIgc3Ryb2tlLXdpZHRoPSIzMCIvPjwvZz48ZyBtYXNrPSJ1cmwoI2N1dCkiPjx1c2UgeGxpbms6aHJlZj0iI2giIGZpbGw9Im5vbmUiIHN0cm9rZT0idXJsKCNoYWxvKSIgc3Ryb2tlLXdpZHRoPSI2NCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjx1c2UgeGxpbms6aHJlZj0iI2giIGZpbGw9Im5vbmUiIHN0cm9rZT0idXJsKCNoZWFydCkiIHN0cm9rZS13aWR0aD0iNDIiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48L2c+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMzY4IDMyMikgcm90YXRlKDQ1KSI+PGcgY2xpcC1wYXRoPSJ1cmwoI292ZXIpIj48dXNlIHhsaW5rOmhyZWY9IiNsIiBmaWxsPSJub25lIiBzdHJva2U9InVybCgjbGluaykiIHN0cm9rZS13aWR0aD0iMzAiLz48L2c+PC9nPjwvZz48L3N2Zz4=
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addValueChangeListener
// @grant        unsafeWindow
// @grant        GM_registerMenuCommand
// @connect      bongacams.com
// @connect      bongacams2.com
// @connect      bongocams.com
// @connect      webcamsluts.ru
// @connect      stripchat.com
// @connect      chaturbate.com
// @connect      mmcdn.com
// @connect      doppiocdn.com
// @connect      doppiocdn.media
// @connect      doppiocdn.net
// @connect      doppiocdn.org
// @connect      bgicdn.com
// @connect      bcvcdn.com
// @connect      bestcam.tv
// @connect      camshowrecordings.com
// @connect      camshowrecord.net
// @connect      livecamsrip.com
// @connect      camdownload.net
// @connect      camshow.cc
// @connect      camtapes.com
// @connect      cdncontent.top
// @connect      pornotorrent.top
// @connect      pornolab.net
// @connect      simpcity.cr
// @connect      camsclips.com
// @noframes
// @run-at       document-start
// ==/UserScript==

(function () {
'use strict';

const DEBUG = false;
const DBG_LOG = [];
const dbgClean = v => String(v).replace(/(https?:\/\/[^\s?#|)"']+)[^\s|)"']*/g, '$1');
function dbg(err, where) {
    try {
        const m = err && err.stack ? String(err.stack).split('\n').slice(0, 3).join(' | ') : String(err);
        DBG_LOG.push({ t: new Date().toISOString(), w: String(where || ''), m: dbgClean(m).slice(0, 400) });
        if (DBG_LOG.length > 50) DBG_LOG.shift();
    } catch (_) { }
    if (!DEBUG) return;
    try { console.debug('[Cams Unchained]', where || '', err); } catch (_) { }
}

const parseUrl = (u, base) => { try { return new URL(u, base); } catch { return null; } };
const originOf = u => parseUrl(u)?.origin || '';
const ICON_CLOSE = '<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><path d="M6 6l12 12M18 6L6 18"/></svg>';

const realHidden = () => {
    try { return Object.getOwnPropertyDescriptor(Document.prototype, 'hidden').get.call(document); } catch { return document.hidden; }
};
const BG_SUBS = { visibilitychange: [], pagehide: [] };
const onPageEvent = (type, fn, target = document) => {
    target.addEventListener(type, fn, true);
    BG_SUBS[type].push(fn);
};

let streamUrl = null, streamPath = '';
const previewDirs = new Set();
const hlsDir = u => { const x = parseUrl(u); return x ? x.origin + x.pathname.slice(0, x.pathname.lastIndexOf('/') + 1) : ''; };
const hlsIsVariantOf = (u, master) => { const d = hlsDir(u), m = hlsDir(master); return !!d && !!m && d.length > m.length && d.startsWith(m); };
window.addEventListener('bc_hls', e => {
    const u = e.detail;
    for (const d of previewDirs) if (String(u).startsWith(d)) return;
    if (streamUrl && streamPath === location.pathname && hlsIsVariantOf(u, streamUrl)) return;
    streamUrl = u; streamPath = location.pathname;
});
const getStreamUrl = () => (streamUrl && streamPath === location.pathname) ? streamUrl : null;

const injectPage = code => {
    const s = document.createElement('script');
    s.textContent = code;
    (document.head || document.documentElement).appendChild(s);
    s.remove();
};

const installNetHooks = () => {
    injectPage(`try {
    const bcHost = /bongacams|bongocams|webcamsluts/.test(location.hostname);
    const emit = u => { try {
        u = String(u);
        if (u.includes('.m3u8') && !u.includes('chunk'))
            window.dispatchEvent(new CustomEvent('bc_hls', { detail: new URL(u, location.href).href }));
    }
    catch { } };
    const o = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function (m, u) { emit(u); return o.apply(this, arguments); };
    const f = window.fetch;
    if (f)
        window.fetch = function (i) { try {
            emit(i && i.url ? i.url : i);
        }
        catch { } return f.apply(this, arguments); };
    if (bcHost && window.WebSocket && !window.__bcWS) {
        window.__bcWS = 1;
        var pend = {}, myId = 2e9, live = [], last = 0, chatWs = null, os = WebSocket.prototype.send;
        var fire = function (r) { try {
            var u = +r.totalUsers || 0, g = +r.totalGuests || 0;
            last = Date.now();
            window.dispatchEvent(new CustomEvent('bc_viewers', { detail: JSON.stringify({ n: u + g, u: u, g: g, t: last }) }));
        }
        catch (x) { } };
        WebSocket.prototype.send = function (d) { try {
            if (typeof d === 'string' && d.indexOf('ChatModule.') > 0) {
                var j = JSON.parse(d);
                if (j && typeof j.name === 'string' && j.name.indexOf('ChatModule.') === 0) {
                    chatWs = this;
                    if (j.name === 'ChatModule.syncUserList')
                        pend[j.id] = 1;
                }
            }
        }
        catch (x) { } return os.apply(this, arguments); };
        var hook = function (ws) { live.push(new WeakRef(ws)); ws.addEventListener('message', function (e) { try {
            var d = e.data;
            if (typeof d !== 'string')
                return;
            if (d.indexOf('TOPIC_CHANGE') > 0) {
                var q = JSON.parse(d);
                if (q && q.type === 'ServerMessageEvent:TOPIC_CHANGE' && q.body && typeof q.body.topic === 'string')
                    window.dispatchEvent(new CustomEvent('bc_topic', { detail: JSON.stringify({ t: q.body.topic }) }));
                return;
            }
            if (d.indexOf('totalUsers') < 0)
                return;
            var j = JSON.parse(d);
            if (!j || j.id == null || !j.result || j.result.totalUsers == null)
                return;
            if (pend[j.id]) {
                delete pend[j.id];
                fire(j.result);
            }
            else if (j.id >= 2e9) {
                e.stopImmediatePropagation();
                fire(j.result);
            }
        }
        catch (x) { } }); };
        window.WebSocket = new Proxy(window.WebSocket, { construct: function (t, a, nt) { var ws = Reflect.construct(t, a, nt); hook(ws); return ws; } });
        setInterval(function () { try {
            if (Date.now() - last < 45000)
                return;
            var w = chatWs;
            if (!w || w.readyState !== 1)
                return;
            os.call(w, JSON.stringify({ id: ++myId, name: 'ChatModule.syncUserList', args: ['public-chat'] }));
        }
        catch (x) { } }, 15000);
    }
}
catch { }`);
};
installNetHooks();

const installMediaHook = () => {
    injectPage(`(() => {
    try {
        if (window.__bcMH)
            return;
        window.__bcMH = 1;
        var refs = [], ctxs = [], dead = false;
        var reg = function (e) { if (!e)
            return e; if (refs.length >= 300)
            refs = refs.filter(function (r) { return r.deref(); }); if (refs.length < 300 && refs.every(function (r) { return r.deref() !== e; }))
            refs.push(new WeakRef(e)); return e; };
        var P = HTMLMediaElement.prototype, op = P.play;
        P.play = function () { reg(this); if (dead) {
            try {
                this.pause();
            }
            catch (x) { }
            return Promise.resolve();
        } return op.apply(this, arguments); };
        var kk = false;
        ['src', 'srcObject'].forEach(function (n) { var d = Object.getOwnPropertyDescriptor(P, n); if (d && d.set) {
            var st = d.set;
            Object.defineProperty(P, n, { configurable: true, enumerable: d.enumerable, get: d.get, set: function (v) { if (dead && !kk)
                    return; return st.call(this, v); } });
        } });
        var sa = Element.prototype.setAttribute;
        Element.prototype.setAttribute = function (n) { if (dead && this instanceof HTMLMediaElement && String(n).toLowerCase() === 'src')
            return; return sa.apply(this, arguments); };
        var ce = Document.prototype.createElement;
        Document.prototype.createElement = function (t) { var e = ce.apply(this, arguments); if (/^(video|audio)$/i.test(String(t)))
            reg(e); return e; };
        var OA = window.Audio;
        if (OA) {
            var NA = function () { var e = arguments.length ? new OA(arguments[0]) : new OA(); reg(e); return e; };
            NA.prototype = OA.prototype;
            window.Audio = NA;
        }
        var A = window.AudioContext || window.webkitAudioContext;
        if (A) {
            var R = A.prototype.resume;
            A.prototype.resume = function () { if (ctxs.indexOf(this) < 0)
                ctxs.push(this); if (dead)
                return Promise.resolve(); return R.apply(this, arguments); };
            ['createMediaElementSource', 'createMediaStreamSource', 'createBufferSource'].forEach(function (n) { var f = A.prototype[n]; if (f)
                A.prototype[n] = function () { if (ctxs.indexOf(this) < 0)
                    ctxs.push(this); return f.apply(this, arguments); }; });
        }
        var kill = function () { dead = true; kk = true; refs.forEach(function (r) { var e = r.deref(); if (!e)
            return; try {
            e.pause();
            var m = e.srcObject;
            if (m && m.getTracks)
                m.getTracks().forEach(function (t) { t.stop(); });
            e.srcObject = null;
            e.removeAttribute('src');
            e.load();
            e.remove();
        }
        catch (x) { } }); ctxs.forEach(function (c) { try {
            c.suspend();
        }
        catch (x) { } }); kk = false; };
        window.addEventListener('bc_sleep_kill', kill);
    }
    catch (e) { }
})();`);
};
installMediaHook();

const COUNT_DIGIT_BASES = [0x660, 0x6F0, 0x966, 0xE50, 0xFF10];
const COUNT_SUF = {
    k: 1e3, 'к': 1e3, 'тыс': 1e3, 'тис': 1e3, 'tys': 1e3, 'tis': 1e3, 'mil': 1e3, '千': 1e3, '천': 1e3,
    m: 1e6, 'м': 1e6, 'млн': 1e6, 'mln': 1e6, 'mio': 1e6,
    '万': 1e4, '萬': 1e4, '만': 1e4
};
function parseCount(v) {
    const t = String(v == null ? '' : v).toLowerCase().replace(/\p{Nd}/gu, ch => {
        const c = ch.codePointAt(0);
        for (const b of COUNT_DIGIT_BASES) if (c >= b && c < b + 10) return String(c - b);
        return ch;
    });
    const m = t.match(/(\d{1,3}(?:[\s\u00a0\u202f\u066c.,'’]\d{3})+(?!\d)|\d+(?:[.,]\d+)?)\s?(\p{L}+)?/u);
    if (!m) return NaN;
    const raw = m[1], word = (m[2] || '').replace(/\.$/, '');
    let mul = COUNT_SUF[word];
    if (!mul && word && '万萬만千천'.includes(word[0])) mul = COUNT_SUF[word[0]];
    const grouped = /^\d{1,3}([\s\u00a0\u202f\u066c.,'’]\d{3})+$/.test(raw);
    let n;
    if (mul) n = parseFloat(raw.replace(/[\s\u00a0\u202f'’]/g, '').replace(',', '.')) * mul;
    else if (grouped) n = parseInt(raw.replace(/\D/g, ''), 10);
    else n = parseFloat(raw.replace(',', '.'));
    return Number.isFinite(n) ? Math.round(n) : NaN;
}
function fmtCount(n) {
    n = Math.floor(+n);
    if (!Number.isFinite(n) || n < 0) return '';
    const lang = String(AppState.lang || 'EN').toUpperCase();
    let div = 1, suf = '';
    const cjk = { JA: ['万', '億'], ZH: ['万', '亿'], KO: ['만', '억'] }[lang];
    if (cjk) {
        if (n >= 1e8) { div = 1e8; suf = cjk[1]; } else if (n >= 1e4) { div = 1e4; suf = cjk[0]; }
    } else {
        const cyr = lang === 'RU' || lang === 'UK';
        if (n >= 1e6) { div = 1e6; suf = cyr ? 'М' : 'M'; } else if (n >= 1e3) { div = 1e3; suf = cyr ? 'к' : 'k'; }
    }
    if (!suf) return String(n);
    const v = Math.floor(n / div * 10) / 10;
    if (v >= 100) return Math.floor(v) + suf;
    let s;
    try { s = v.toLocaleString(lang.toLowerCase() + '-u-nu-latn', { maximumFractionDigits: 1 }); } catch { s = String(v); }
    return s + suf;
}

const BC_RE = /bongacams|bongocams|webcamsluts/;
const siteKey = h => BC_RE.test(h) ? 'bc' : /stripchat/.test(h) ? 'sc' : /chaturbate/.test(h) ? 'cb' : h;
const STORE_SITE = siteKey(location.hostname);
const SHARED_KEY = /^(bc_bookmarks|bc_hist|bc_bm_|bc_cal_|bc_plat$|bc_lang|bc_bk_parts$|bc_mig_|bc_previews$|bc_prev_spd$|bc_favnav$)/;
const siteKeyOf = k => (SHARED_KEY.test(k) ? k : `${STORE_SITE}:${k}`);
const Store = {
    key: siteKeyOf,
    get: (k, def) => {
        try {
            const sk = siteKeyOf(k);
            let v = GM_getValue(sk);
            if (v == null && sk !== k) v = GM_getValue(k);
            if (v == null) return def;
            const p = JSON.parse(v);
            return (Array.isArray(def) && !Array.isArray(p)) || p === null ? def : p;
        } catch { return def; }
    },
    set: (k, v) => {
        try {
            const sk = siteKeyOf(k);
            const str = JSON.stringify(v);
            GM_setValue(sk, str);
            try { const back = GM_getValue(sk); if (back != null && back !== str) return false; } catch (e) { dbg(e, 'set'); }
            return true;
        } catch { return false; }
    }
};
const storeOn = k => Store.get(k, true) !== false;

const clamp = (v, min, max) => Math.max(min, Math.min(v, max));
const pad2 = n => String(n).padStart(2, '0');
const gmMenu = (caption, fn, id) => {
    if (typeof GM_registerMenuCommand !== 'function') return;
    try { GM_registerMenuCommand(caption, fn, id ? { id } : undefined); } catch { try { GM_registerMenuCommand(caption, fn); } catch (e) { dbg(e, 'gmMenu'); } }
};
const hardMute = v => { v.muted = true; v.volume = 0; };
const stopTracks = s => s?.getTracks?.().forEach(t => t.stop());
const unloadMedia = v => { v.pause(); v.removeAttribute('src'); v.load(); };
const mkStyle = (parent, css, id) => { const st = document.createElement('style'); if (id) st.id = id; st.textContent = css; parent.appendChild(st); return st; };
const wait = ms => new Promise(r => setTimeout(r, ms));
let bcPtrDown = false;
['pointerdown'].forEach(t => window.addEventListener(t, () => { bcPtrDown = true; }, { capture: true, passive: true }));
['pointerup', 'pointercancel'].forEach(t => window.addEventListener(t, () => { bcPtrDown = false; }, { capture: true, passive: true }));
const isObj = o => !!o && typeof o === 'object';
const isPlain = o => isObj(o) && !Array.isArray(o);
const axisK = (grow, shrink, size, d) => (grow ? (size + d) / size : shrink ? (size - d) / size : null);
const isPlaying = v => !!v && !v.paused && !v.ended && v.readyState > 2;
const escHTML = s => String(s).replace(/[&<>"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
const safeDecode = s => { try { return decodeURIComponent(s); } catch { return s; } };

const gmReq = o => new Promise(resolve => {
    const { method = 'GET', url, data, headers, type = 'text', timeout = 6000, signal, credentials, viaFetch } = o;
    let tm = 0;
    const onAbort = () => { try { req?.abort?.(); } catch (e) { dbg(e, 'gmReq.onAbort'); } try { ctrl?.abort(); } catch (e) { dbg(e, 'gmReq.onAbort'); } done(0, null, 'abort'); };
    const done = (status, body, fail = '') => {
        clearTimeout(tm);
        signal?.removeEventListener('abort', onAbort);
        resolve({ status, body, fail });
    };
    let req, ctrl;
    if (signal?.aborted) return done(0, null, 'abort');
    if (!viaFetch && typeof GM_xmlhttpRequest === 'function') {
        try {
            req = GM_xmlhttpRequest({
                method, url, headers: headers || {}, timeout,
                ...(data !== undefined && { data }),
                ...(type !== 'text' && { responseType: type }),
                onload: r => done(r.status, type === 'text' ? (r.responseText || '') : r.response),
                onerror: () => done(0, null, 'error'),
                ontimeout: () => done(0, null, 'timeout')
            });
            signal?.addEventListener('abort', onAbort, { once: true });
        } catch { done(0, null, 'error'); }
        return;
    }
    ctrl = new AbortController();
    let timedOut = false;
    if (timeout > 0) tm = setTimeout(() => { timedOut = true; ctrl.abort(); }, timeout);
    signal?.addEventListener('abort', onAbort, { once: true });
    fetch(url, {
        method, cache: 'no-store', signal: ctrl.signal,
        ...(headers && { headers }), ...(data !== undefined && { body: data }), ...(credentials && { credentials })
    })
        .then(async r => done(r.status, type === 'arraybuffer' ? await r.arrayBuffer() : type === 'blob' ? await r.blob() : await r.text()))
        .catch(() => done(0, null, timedOut ? 'timeout' : 'error'));
});
const bytesOf = r => (r.status >= 200 && r.status < 300 && r.body ? new Uint8Array(r.body) : null);
const gmBytes = async (url, referer, gmFirst, range, signal) => {
    const hasGm = typeof GM_xmlhttpRequest === 'function';
    const rh = range ? { Range: `bytes=${range.o}-${range.o + range.n - 1}` } : null;
    const viaFetch = () => gmReq({ url, type: 'arraybuffer', timeout: 30000, viaFetch: true, signal, ...(rh && { headers: rh }) });
    const viaGm = () => gmReq({ url, type: 'arraybuffer', timeout: 20000, signal, headers: { Referer: referer || `${location.origin}/`, ...rh } });
    const gone = r => r.status === 404 || r.status === 410;
    if (gmFirst) {
        if (!hasGm) return bytesOf(await viaFetch());
        const g = await viaGm();
        return bytesOf(g) || (gone(g) || g.fail === 'timeout' ? null : bytesOf(await viaFetch()));
    }
    const f = await viaFetch();
    return bytesOf(f) || (gone(f) || !hasGm ? null : bytesOf(await viaGm()));
};

const MINI_SHIFT_X = 168, MINI_SHIFT_Y = 5;
const BM_HOST = /(^|\.)(bongacams2?\.com|bongocams\.com|webcamsluts\.ru|stripchat\.com|chaturbate\.com)$/;
const isSafeUrl = u => { const x = parseUrl(u); return !!x && x.protocol === 'https:' && BM_HOST.test(x.hostname); };
const urlSite = u => { const x = parseUrl(u); return x ? siteKey(x.hostname) : ''; };
const SITE = siteKey(location.hostname), IS_BC = SITE === 'bc';
const toHttps = u => {
    u = typeof u === 'string' ? u : '';
    if (u.startsWith('//')) u = `https:${u}`;
    return u.startsWith('https://') ? u : '';
};
const isStr = v => typeof v === 'string';
const hasUrl = b => isObj(b) && isStr(b.url);
const clip = (v, n) => isStr(v) ? v.slice(0, n) : '';
const validBm = b => !!b && isStr(b.name) && isStr(b.url) && !!b.name.trim() && isSafeUrl(b.url);
const cleanBm = b => {
    if (!validBm(b)) return null;
    const out = {
        name: clip(b.name, 100),
        url: normUrl(b.url),
        avatar: toHttps(b.avatar),
        note: clip(b.note, 500),
        g: clip(isStr(b.g) && b.g.trim(), 40),
        seen: Number.isFinite(b.seen) && b.seen > 0 ? b.seen : 0
    };
    return Object.assign(out, cleanBmExtras(b));
};
const BP_MAX_G = 14, BP_MAX_R = 40, BP_VAL = 1200;
const bpClean = bp => {
    if (!isPlain(bp) || !Array.isArray(bp.g)) return null;
    const g = [];
    for (const x of bp.g.slice(0, BP_MAX_G)) {
        if (!isPlain(x) || !Array.isArray(x.r)) continue;
        const r = [];
        for (const row of x.r) {
            if (!Array.isArray(row) || r.length >= BP_MAX_R) continue;
            const l = clip(row[0], 80).trim(), v = clip(row[1], BP_VAL).trim();
            if (l && v) r.push(row[2] ? [l, v, 1] : [l, v]);
        }
        if (r.length) g.push({ h: clip(x.h, 80).trim(), r });
    }
    return g.length ? { g, ...(isStr(bp.s) ? { s: clip(bp.s, 24) } : {}), ...(Number.isFinite(bp.t) && bp.t > 0 ? { t: bp.t } : {}) } : null;
};
const INF_TRACK = ['g', 'a', 'lg', 'ii', 'bt', 'et', 'hc', 'ec', 'sc', 'sp', 'co', 'rg', 'ci'];
const INF_STR = ['g', 'ii', 'bt', 'et', 'hc', 'ec', 'sc', 'co', 'rg', 'ci', 'ds', 'st'];
const INF_ARR = ['lg', 'sp', 'it'];
const cleanInf = i => {
    if (!isPlain(i) || !(i.t > 0)) return null;
    const x = { t: i.t };
    for (const k of INF_STR) if (isStr(i[k]) && i[k]) x[k] = clip(i[k], k === 'ds' ? 400 : k === 'st' ? 200 : 60).replace(/[\ud800-\udbff]$/, '');
    for (const k of INF_ARR) {
        if (!Array.isArray(i[k])) continue;
        const a = i[k].filter(isStr).map(v => clip(v, 48)).slice(0, 40);
        if (a.length) x[k] = a;
    }
    if (Number.isFinite(i.a) && i.a > 0 && i.a < 130) x.a = Math.round(i.a);
    return x;
};
const cleanBmExtras = b => {
    const x = {};
    if (b.pin === true) x.pin = true;
    if (Array.isArray(b.ptags)) {
        const keep = [];
        b.ptags.forEach((t, i) => { const c = clip(t, 40); if (c && keep.length < 30) keep.push([c, b.ptic?.[i]]); });
        x.ptags = keep.map(p => p[0]);
        if (keep.some(p => p[1])) x.ptic = keep.map(p => (isStr(p[1]) && /^[a-z0-9_\/-]{1,60}$/i.test(p[1]) ? p[1] : ''));
    }
    if (Array.isArray(b.sch) && b.sch.length === 7) x.sch = b.sch.map(s => clip(s, 60));
    if (Array.isArray(b.alts)) {
        const a = b.alts.filter(isStr).map(v => clip(v, 60).trim()).filter(Boolean).slice(0, 10);
        if (a.length) x.alts = a;
    }
    if (isPlain(b.altd)) {
        const d = {};
        for (const [k, v] of Object.entries(b.altd).slice(0, 10)) if (Number.isFinite(v) && v > 0) d[clip(k, 60)] = v;
        if (Object.keys(d).length) x.altd = d;
    }
    if (Array.isArray(b.alte)) {
        const e = b.alte.filter(isStr).map(v => clip(v, 60)).slice(0, 10);
        if (e.length) x.alte = e;
    }
    const inf = cleanInf(b.inf);
    if (inf) x.inf = inf;
    const bp = bpClean(b.bp);
    if (bp) x.bp = bp;
    if (Array.isArray(b.infh)) {
        const h = b.infh.filter(v => isPlain(v) && isStr(v.k) && (INF_TRACK.includes(v.k) || /^bp:/.test(v.k)) && v.t > 0).slice(-30).map(v => (v.k.startsWith('bp:')
            ? { k: clip(v.k, 90), o: isStr(v.o) ? clip(v.o, BP_VAL) : null, n: isStr(v.n) ? clip(v.n, BP_VAL) : null, t: v.t }
            : { k: v.k, o: v.o ?? null, n: v.n ?? null, t: v.t }));
        if (h.length) x.infh = h;
    }
    return x;
};
const HASHTAG_RE = /#[\p{L}\p{M}\p{N}_]+/giu;
const noteHashTags = b => b.note?.match(HASHTAG_RE) || [];
const bmTagMatch = (b, tag) => !tag || (tag[0] === '#' ? noteHashTags(b).some(t => t.toLowerCase() === tag) : (b.ptags || []).some(t => t.toLowerCase() === tag));
const countInto = (m, x) => { const k = x.toLowerCase(); m.set(k, (m.get(k) || 0) + 1); };

const pultVars = sel => `@supports(color:color-mix(in srgb,red 10%,blue)) {
  ${sel} {
    --pb:color-mix(in srgb,var(--th) 7%,#0b0c0f);
    --pc:color-mix(in srgb,var(--th) 11%,#15171b);
    --pl:color-mix(in srgb,var(--th) 38%,#3a3e46);
    --pt:color-mix(in srgb,var(--th) 14%,#22252b)
  }
}
@supports(color:oklch(from red l c h)) {
  ${sel} {
    --pon:oklch(from var(--th) calc((0.7 - l) * 1000) 0 0 / 1)
  }
}`;
const RS_PULT_CSS = `.rs-pult {
  --pb:#0e0f12;
  --pc:#17191d;
  --pl:#2a2d33;
  --pt:#22252b;
  --pon:#fff;
  display: flex!important;
  flex-direction: column;
  font-family: Onest,system-ui,-apple-system,sans-serif;
  color: #fff;
  border: 1px solid var(--pl)!important;
  border-radius: 18px!important;
  box-shadow: 0 14px 36px rgba(0,0,0,.6)!important;
  -webkit-backdrop-filter: none!important;
  backdrop-filter: none!important;
  padding: 0!important;
  overflow: hidden!important;
  width: 460px;
  max-width: 94vw;
  max-height: 92vh;
  height: auto
}
${pultVars('.rs-pult')}.rs-pult * {
  box-sizing: border-box
}
.rs-pult .bm-list {
  order: 1;
  display: flex!important;
  flex-direction: column;
  gap: 8px;
  padding: 8px!important;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  grid-template-columns: none!important;
  align-content: stretch;
  scrollbar-width: thin;
  scrollbar-color: var(--pl) transparent
}
.rs-pult .bm-hdr {
  order: 2;
  position: static!important;
  display: flex!important;
  flex-wrap: nowrap!important;
  align-items: center;
  gap: 8px;
  flex: none;
  padding: 8px 8px calc(8px + env(safe-area-inset-bottom,0px))!important;
  margin: 0!important;
  background: var(--pc)!important;
  border: 0!important;
  border-top: 1px solid var(--pl)!important
}
.rs-pult .bm-title {
  flex: 1 1 auto;
  min-width: 0;
  display: flex!important;
  align-items: center;
  gap: 10px;
  font-size: 15px;
  font-weight: 500;
  color: #fff
}
.rs-pult .rs-ic {
  flex: none;
  width: 40px;
  height: 40px;
  border-radius: 12px;
  background: var(--th);
  color: var(--pon);
  display: flex;
  align-items: center;
  justify-content: center
}
.rs-pult .rs-ic svg {
  display: block;
  margin: 0!important;
  width: 22px;
  height: 22px
}
.rs-pult .bm-close {
  position: static!important;
  inset: auto!important;
  transform: none!important;
  flex: 0 0 44px;
  width: 44px;
  height: 44px;
  margin: 0!important;
  padding: 0;
  border-radius: 12px!important;
  background: var(--pb)!important;
  border: 1px solid var(--pl)!important;
  color: #fff!important;
  display: flex;
  align-items: center;
  justify-content: center
}
.rs-pult .bm-close:hover {
  background: rgba(231,76,60,.9)!important;
  border-color: transparent!important
}
.rs-pult .rs-sum {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  border-radius: 14px;
  background: var(--pc);
  border: 1px solid var(--pl);
  font-size: 13px;
  font-weight: 500;
  color: #fff
}
.rs-pult .rs-sum svg {
  flex: none;
  width: 18px;
  height: 18px;
  margin: 0!important;
  color: var(--th);
  filter: brightness(1.4)
}
.rs-pult .rs-g {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px;
  border-radius: 14px;
  background: rgba(0,0,0,.34)
}
.rs-pult .hd-sec {
  display: block!important;
  padding: 2px 4px 0!important;
  margin: 0;
  font-size: 11px!important;
  font-weight: 600!important;
  letter-spacing: .4px!important;
  text-transform: uppercase!important;
  color: #fff!important;
  opacity: .62
}
.rs-pult .rs-g .hd-sec:not(:first-child) {
  margin-top: 6px
}
.rs-pult .rs-qs {
  display: grid!important;
  grid-template-columns: repeat(2,minmax(0,1fr));
  gap: 6px
}
.rs-pult .rs-qs .bm-btn {
  display: flex!important;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  min-height: 78px;
  padding: 10px 6px!important;
  margin: 0!important;
  text-align: center!important;
  border-radius: 12px!important;
  border: 1px solid var(--pl)!important;
  background: var(--pt)!important;
  color: #fff!important;
  box-shadow: none!important;
  transition: background .12s,transform .12s
}
.rs-pult .rs-qs .bm-btn b {
  font-size: 17px;
  font-weight: 600
}
.rs-pult .rs-qs .bm-btn span {
  font-size: 12px
}
.rs-pult .rs-qs .bm-btn small {
  font-size: 11px;
  opacity: .7;
  margin-top: 2px
}
.rs-pult .rs-qs .bm-btn:active,
.rs-pult .rs-chips .bm-tbtn:active {
  transform: scale(.97)
}
.rs-pult .rs-qs .bm-btn.sel {
  background: var(--th)!important;
  border-color: transparent!important;
  color: var(--pon)!important
}
.rs-pult .rs-qs .bm-btn.sel small {
  opacity: .85
}
.rs-pult .rs-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px
}
.rs-pult .rs-chips .bm-tbtn {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 44px;
  margin: 0!important;
  padding: 0 12px!important;
  border-radius: 12px!important;
  border: 1px solid var(--pl)!important;
  background: var(--pt)!important;
  color: #fff!important;
  font-size: 13px!important;
  font-weight: 500;
  line-height: 1.2;
  box-shadow: none!important;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  transition: background .12s,transform .12s
}
.rs-pult .rs-chips .bm-tbtn.on {
  background: var(--th)!important;
  border-color: transparent!important;
  color: var(--pon)!important;
  font-weight: 600
}
.rs-pult .rs-src .bm-tbtn {
  flex: 1 1 0;
  padding: 6px 10px!important
}
:host .bm-modal.rs-pult .rs-note,
:host .bm-modal.rs-pult .rs-info {
  padding: 0 6px;
  font-size: 11px;
  line-height: 1.5;
  color: #fff!important;
  opacity: .62
}
:host .bm-modal.rs-pult .rs-info {
  font-size: 12px;
  opacity: .85
}
.rs-pult .rs-chips:empty {
  display: none
}
.rs-pult .bm-btn:focus-visible,
.rs-pult .bm-tbtn:focus-visible,
.rs-pult .bm-close:focus-visible {
  outline: 2px solid var(--th);
  outline-offset: 2px
}
@media (max-width:520px) {
  .bm-overlay:has(.rs-pult) {
    align-items: stretch
  }
  .bm-modal.rs-pult {
    height: 100%!important;
    max-height: 100%!important;
    width: 100vw!important;
    max-width: 100vw!important;
    border-radius: 0!important;
    border-left: 0!important;
    border-right: 0!important;
    border-bottom: 0!important;
    align-self: stretch!important
  }
}`;
const buildModalShell = (shadow, modalCls, bodyCls = 'bm-list') => {
    const overlay = mk('div', 'bm-overlay');
    const modal = mk('div', modalCls);
    const hdr = mk('div', 'bm-hdr');
    const title = mk('span', 'bm-title');
    const closeBtn = htmlEl('bm-close', '×', 'button');
    hdr.append(title, closeBtn);
    const body = mk('div', bodyCls);
    modal.append(hdr, body);
    overlay.appendChild(modal);
    shadow.appendChild(overlay);
    return { overlay, modal, title, closeBtn, body };
};

const bindOutsideClose = (owner, overlay, close) => { overlay.onclick = e => { if (e.target === overlay && Date.now() - (owner._opAt || 0) > 500) close(); }; };
const pultTitle = (el, icon, text) => { el.innerHTML = `<span class="rs-ic">${icon}</span><span>${escHTML(text)}</span>`; };
const buildPultShell = (shadow, css, extraCls = '') => {
    const sh = buildModalShell(shadow, 'bm-modal rs-modal rs-pult' + extraCls);
    sh.modal.style.setProperty('background', 'var(--pb)', 'important');
    sh.closeBtn.innerHTML = ICON_CLOSE;
    mkStyle(sh.overlay, css);
    return sh;
};

const bmTbtn = (html, active, onClick, title = '') => {
    const btn = mk('button', `bm-tbtn${active ? ' on' : ''}`);
    btn.innerHTML = html;
    btn.title = title;
    btn.onclick = onClick;
    return btn;
};
const chipBtn = (text, on, fn) => bmTbtn(escHTML(text), on, fn);
const RESORT_SORTS = new Set(['online', 'last', 'viewers']);
const stripBm = ({ _updateItemDOM, _topicExp, _lastImg, _lastData, _chk, _isUpdating, _avFix, _avK, _live, _mob, _pf, _sv, label, ...rest }) => rest;

const BC_CONTAINERS = ['#stream-container', '#ResizablePane', '#EmbedSWFHolder', '.player-container', '.performer-video-container', '.chat-video-container', '.video-panel', '#video-panel-wrap'];

const HIDE_DECL = 'display:none!important;opacity:0!important;pointer-events:none!important;height:0!important;margin:0!important;padding:0!important';

const HIDE_CATS = [
    { id: 'hdr_m', plat: 'm', def: true, sel: ['.bc-header', '.bc-header-main', '.h_container', '#mainbar_container', '.join_button_container', '.js-chat_join_button', '[data-role="header"]'] },
    { id: 'hdr_d', plat: 'd', def: false, sel: ['.js-page_header', '.js-header_bar'] },
    { id: 'tabs', plat: 'm', def: true, sel: ['.live_tabs', '#mls_filter_result_container', '.bc_alternate_header_live_tabs'] },
    { id: 'seotitle', plat: 'b', def: true, sel: ['.mls_title'] },
    { id: 'chat_tips', plat: 'b', def: false, sel: ['.history .system_msg.tip_success', '.system_msg.tip_success'] },
    { id: 'chat_topic', plat: 'b', def: false, sel: ['.history .system_msg:has(.bc_notice.__topic)', '.bc_notice.__topic'] },
    { id: 'chat_boost', plat: 'b', def: false, sel: ['.history [class*="boost"]', '.history [class*="Boost"]'] },
    { id: 'ftr', plat: 'b', def: true, sel: ['.bc-footer', '#footer_container', '.bcm_footer', '.bcm_socials', '.m_socials', 'footer.footer', '.footer_copy', '.footer_seo', '.footer_mobile_guide', '#jsx-ls_seo_custom_text', '#jsx-ls_footer_container'] },
    { id: 'promo', plat: 'b', def: true, sel: ['.get_app_banner', '.js-get_app_banner', '.gab_wrp', '.banner_wrp', '.js-index_banner', '.sb_topmodel', '.free_tokens_panel', '#top_banner_above_chat', '.banner_top_chat', '.hb_item.__promo', '.js-free_tokens', '.hb_contest_item', '.holiday_discount_alert', '.js-top_banner', 'a[rel="buy_tokens"].holiday_discount_alert', '.stream_join_btn', '.join_button_container', '.bc_popalert.__give_away', '.bc_popalert.__member_bonus', '.stream_social_links', '.fancybox-overlay', '.fp_outer', '.fp_outer_overlay', '[class*="promo-banner"]', '[class*="age-gate"]', '[class*="age_gate"]', '[class*="cookie-banner"]', '[class*="notification-bar"]'] },
    { id: 'lastview', plat: 'b', def: false, sel: ['.lst_bage.__last_viewed'] },
    { id: 'joinpop', plat: 'b', def: false, sel: ['#member_join_popup', '.member_join_popup', '.fp_outer', '.fp_outer_overlay', '.fp_inner_wrap', '*:has(> .fp_inner_wrap)'] },
    { id: 'tips', plat: 'b', def: true, sel: ['#firstChatTooltipContainer', '.chat_bar_alert_container', '.chat_alert_container', '#chat_prompt_container', '#alerts_above_chat', '.js-bottom_alerts_area', '#bFontSizeTipHolder'] },
    { id: 'modelchat', plat: 'b', def: true, sel: ['[data-bc-mpop]'] },
    { id: 'games', plat: 'm', def: true, sel: ['#games_button'] },
    { id: 'popups', plat: 'm', def: false, sel: ['#popupChatSelection', '#popupGroup', '#popupVoyeur', '#gamecontrol_popup_container', '.ui-popup-container', '.cam2cam_modal'] },
    { id: 'actions', plat: 'b', def: false, sel: ['#chat_actions_bar', '.bcm_chat_footer', '#private_btn_cell', '.cf_cell.__private', 'a.cf_item.__tip', 'button.cf_item.__tip_menu', '.private_btn_wrp', '#bPaidButtonTopRow', '#bPaidButtonGroup', '.paid_button_group', '#bGotoGroup', '#bGotoPrivate', '#bGotoVoyeur', '#bTip'] },
    { id: 'balance', plat: 'd', def: false, sel: ['.memberBalance', '.memberNavigation'] },
    { id: 'chathdr', plat: 'b', def: false, sel: ['#id-chat-header', '.bc_chat_header'] },
    { id: 'tipking', plat: 'b', def: false, sel: ['.tip_king_wrp', '.bc_new_tip_modal'] },
    { id: 'below', plat: 'd', def: false, sel: ['.stream-controls'] },
    { id: 'wm', plat: 'd', def: false, sel: ['#layover-logo', '.layover-logo'] },
    { id: 'age18', plat: 'b', def: true, sel: ['#popup_18_plus', '#popup_18_plus-screen', '#popup_18_plus-popup'] }
];
HIDE_CATS.forEach(c => { c.site = 'bc'; });
const SC_CATS = [
    { id: 'sc_hdr', def: true, sel: ['.header-top-wrapper'] },
    { id: 'sc_sub', def: false, sel: ['.view-cam-header-sub'] },
    { id: 'sc_promo', def: true, sel: ['[class*="SidebarBanners"]', '.tokens-giveaway-banner', '.tokens-giveaway-banner-container', '.player-join-button', '.player-controls-user__join-btn'] },
    { id: 'sc_cookie', def: true, sel: ['[class*="CookieReminder"]', '.cookies-banner', '[data-testid="CookiesReminder"]'] },
    { id: 'sc_footer', def: true, sel: ['#MAIN_FOOTER_ID', '[class*="footer-container"]', '[class*="Footer__inner"]'] },
    { id: 'sc_widgets', def: true, sel: ['.plugin-panel', '.viewcam-widget-panel'] },
    { id: 'sc_goal', def: false, sel: ['.view-cam-info-goal', '.epic-goal-progress__wrap'] },
    { id: 'sc_topic', def: false, sel: ['.view-cam-info-topic'] },
    { id: 'sc_king', def: false, sel: ['[data-testid="king-wrapper-is-king"]', '[data-testid="view-cam-info-king-content"]'] },
    { id: 'sc_wm', def: false, sel: ['[class*="Watermark"]', '.watermark'] },
    { id: 'sc_about', def: false, sel: ['[data-testid="viewcam-model-sections"]'] }
];
const CB_CATS = [
    { id: 'cb_hdr', def: false, sel: ['.MobileHeader', '#header-links'] },
    { id: 'cb_alerts', def: true, sel: ['.LanguageMismatchAlert', '.dismissible_notice', '.MobileDismissibleMessages', '#ie-support-notice'] },
    { id: 'cb_footer', def: true, sel: ['#footer', '#mobile-social-media-icons'] },
    { id: 'cb_sort', def: false, sel: ['.MobileSort', '#mobile-sort-container', '#mobile-sort-form-container'] },
    { id: 'cb_tags', def: false, sel: ['.TopTagsBanner'] },
    { id: 'cb_tips', def: false, sel: ['.roomNotice.isTip'] },
    { id: 'cb_status', def: false, sel: ['.roomStatusNotifier'] },
    { id: 'cb_actions', def: false, sel: ['.sendTipButton', '.tipForm'] },
    { id: 'cb_info', def: false, sel: ['.HeaderRoomInfo'] }
];
SC_CATS.forEach(c => HIDE_CATS.push({ ...c, site: 'sc', plat: 'b' }));
CB_CATS.forEach(c => HIDE_CATS.push({ ...c, site: 'cb', plat: 'b' }));

const CB_GEO_KEY = 'cb_geo_block';
const CB_FOLLOW_CLS = 'zGQx7GmMy4m1dlBUX9Rw';
const CB_GEO_POP = ['ru', 'ua', 'by', 'kz', 'co', 'us', 'br', 'ro', 'pl', 'de', 'it', 'es'];
const cbGeoList = () => {
    const a = Store.get(CB_GEO_KEY, []);
    return Array.isArray(a) ? [...new Set(a.filter(x => isStr(x) && /^[a-z]{2}$/.test(x)))] : [];
};
const cbGeoSeen = () => {
    const out = new Set();
    document.querySelectorAll('.thumbnail_flag [class*="fi-"]').forEach(e => {
        const m = String(e.className).match(/(?:^|\s)fi-([a-z]{2})(?:\s|$)/);
        if (m) out.add(m[1]);
    });
    return [...out];
};

const HIDE_TEXT = {
    hdr_m:   ['Верх сайта',              'Логотип, меню, кнопки входа',                 'Site top bar',          'Logo, menu, login buttons'],
    hdr_d:   ['Шапка сайта (ПК)',        'Верхняя панель на компьютере',                'Site header (PC)',      'Top bar on desktop'],
    seotitle:['Заголовок-реклама',       'Строка «Безкоштовний секс в онлайн порно-чаті» над списком', 'Ad headline', 'The "Free sex in online porn chat" line above the list'],
    chat_tips:['Чаевые в чате',          'Сообщения «подарил токены», «Меню чаевых», тип-меню', 'Tips in chat', '\"Tipped N tokens\" and tip-menu messages'],
    chat_topic:['Тема комнаты',          'Длинное описание «Тема: …» в начале чата',     'Room topic',            'Long \"Topic: …\" block at the top of chat'],
    chat_boost:['Бусты в чате',          'Плашки «поднял модель на N секунд» (FAN BOOSTED)', 'Boosts in chat',   '\"Boosted the model\" cards'],
    tabs:    ['Вкладки и сортировка',    'Девушки/пары, фильтры, значок сортировки',    'Tabs & sorting',        'Category tabs, filters, sort icon'],
    ftr:     ['Низ страницы',            'Подвал, соцсети, SEO-текст',                  'Page bottom',           'Footer, socials, SEO text'],
    promo:   ['Реклама и баннеры',       'Баннеры, промо, плашка «Присоединяйся» на плеере, приложение',          'Ads & banners',         'Banners, promos, "Join me" strip on the player, get-the-app'],
    lastview:['Плашка «Продолжить просмотр»', 'Кнопка на превью последней модели',     '"Continue watching" badge', 'Button on the last viewed thumbnail'],
    joinpop: ['Окно регистрации',        'Всплывающая форма «Создайте аккаунт»',        'Sign-up popup',         '"Create an account" popup'],
    tips:    ['Подсказки в чате',        'Всплывающие подсказки и уведомления',         'Chat hints',            'Tooltips and alerts'],
    modelchat:['Всплывающий чат модели', 'Розовое окошко «Привет! Любишь виртуальный секс?» внизу', 'Model popup chat', 'Pink pop-up with the model\'s auto-greeting at the bottom'],
    games:   ['Кнопка «Игры»',           'Иконка геймпада в красной панели',            '"Games" button',        'Gamepad icon in the red bar'],
    popups:  ['Всплывающие окна',        'Выбор чата, группа, подглядывание',           'Popups',                'Chat choice, group, voyeur'],
    actions: ['Кнопки «Приват / Чаевые»','Синие (на ПК зелёные) кнопки под красной панелью',            '"Private / Tips" buttons','Blue (green on PC) buttons under the red bar'],
    balance: ['Баланс и меню (ПК)',      'Баланс и навигация на компьютере',            'Balance & menu (PC)',   'Balance and navigation on desktop'],
    chathdr: ['Плашка «Чат с …»',        'Строка «Бесплатный чат с …» и король чаевых', 'Title bar "Chat with …"','"Free chat with …" strip and tip king'],
    tipking: ['Король чаевых',           'Строка короля и окна чаевых',                 'Tip king',              'Tip king strip and tip popups'],
    below:   ['Панель под плеером (ПК)', 'Блок кнопок под видео',                       'Under-player bar (PC)', 'Button row under the video'],
    wm:      ['Водяной знак (ПК)',       'Логотип поверх видео',                        'Watermark (PC)',        'Logo over the video'],
    age18:   ['Окно «Старше 18»',        'Заставка «Заходи ТОЛЬКО если ты уже старше 18»', '18+ notice',          'The "18+ only" entry screen']
};
Object.assign(HIDE_TEXT, {
    sc_hdr:    ['Верх сайта',            'Шапка с логотипом, поиском и кнопками входа',   'Site top bar',     'Header with logo, search and login buttons'],
    sc_sub:    ['Строка модели',         'Аватар, подписка и меню над плеером',           'Model bar',        'Avatar, follow and menu row above the player'],
    sc_promo:  ['Реклама и баннеры',     'Розыгрыши токенов, кнопка «Присоединиться» на плеере', 'Ads & banners', 'Token giveaways, Join button on the player'],
    sc_cookie: ['Плашка cookies',        'Уведомление о cookie внизу',                    'Cookie notice',    'Cookie reminder at the bottom'],
    sc_footer: ['Низ страницы',          'Подвал и юридический текст',                    'Page bottom',      'Footer and legal text'],
    sc_widgets:['Виджеты на плеере',     'Опросы и реакции поверх видео',                 'Player widgets',   'Polls and reactions over the video'],
    sc_goal:   ['Цель (гол)',            'Полоса прогресса цели',                         'Goal bar',         'Goal progress bar'],
    sc_topic:  ['Тема комнаты',          'Текст темы над и внутри плеера',                'Room topic',       'Topic text over and inside the player'],
    sc_king:   ['Король чаевых',         'Плашка короля чаевых',                          'Tip king',         'Tip king strip'],
    sc_wm:     ['Водяной знак',          'Логотип поверх видео',                          'Watermark',        'Logo over the video'],
    sc_about:  ['Блоки под плеером',     'Обо мне, расписание, похожие модели',           'Below the player', 'About, schedule, similar models'],
    cb_hdr:    ['Верх сайта',            'Шапка с логотипом и меню',                      'Site top bar',     'Header with logo and menu'],
    cb_alerts: ['Уведомления',           'Предупреждение о языке и закрываемые заметки',   'Alerts',           'Language alert and dismissible notes'],
    cb_footer: ['Низ страницы',          'Подвал и соцсети',                              'Page bottom',      'Footer and socials'],
    cb_sort:   ['Сортировка',            'Панель сортировки в мобильной версии',          'Sorting',          'Mobile sorting panel'],
    cb_tags:   ['Полоса тегов',          'Популярные теги над списком',                   'Tag strip',        'Top tags above the list'],
    cb_tips:   ['Чаевые в чате',         'Сообщения о токенах и чаевых',                  'Tips in chat',     'Tip and token messages'],
    cb_status: ['Статус комнаты',        'Плашка про оффлайн и приват с подсказками',     'Room status',      'Offline and private notifier'],
    cb_actions:['Кнопка чаевых',         'Кнопка и форма отправки токенов',               'Tip button',       'Send-tip button and form'],
    cb_info:   ['Название комнаты',      'Имя и счётчик зрителей в шапке',                'Room info',        'Name and viewers in the header']
});
HIDE_CATS.forEach(c => { const t = HIDE_TEXT[c.id]; if (t) { c.ru = t[0]; c.dru = t[1]; c.en = t[2]; c.den = t[3]; } });

const HIDE_PRESETS = {
    min: ['promo', 'joinpop', 'tips', 'modelchat', 'popups', 'seotitle', 'sc_promo', 'sc_cookie', 'sc_widgets', 'cb_alerts'],
    clean: HIDE_CATS.filter(c => c.site === 'bc' ? !/^chat_/.test(c.id) : c.def).map(c => c.id)
};

const catOn = (c, cfg) => cfg[c.id] === undefined ? c.def : !!cfg[c.id];
const detectPlatform = () => document.body?.classList.contains('ui-mobile-viewport') ? 'm' : 'd';
const customAll = () => { const o = Store.get('bc_hide_custom', {}); return isPlain(o) ? o : {}; };
const getCustom = () => { const a = customAll()[siteKey(location.hostname)]; return Array.isArray(a) ? a.filter(x => isStr(x)) : []; };
const setCustom = list => { const o = customAll(); o[siteKey(location.hostname)] = list; Store.set('bc_hide_custom', o); };
const addCustom = sel => { const l = getCustom(); if (!l.includes(sel)) { l.push(sel); setCustom(l); } };
const removeCustom = sel => setCustom(getCustom().filter(x => x !== sel));

const buildHideCSS = skipId => {
    const out = [];
    {
        const cfg = Store.get('bc_hide_cfg', {});
        const hdr = HIDE_CATS.find(x => x.id === 'hdr_m');
        if (IS_BC && hdr && !catOn(hdr, cfg)) out.push('.bc-header,.bc_header.bc-header-main,.bc-header__fixed,.bc-header.hidden,.bc-header__fixed.hidden{display:flex!important;visibility:visible!important;opacity:1!important;transform:none!important;pointer-events:auto!important;position:fixed!important;top:0!important;z-index:1000!important}.bc-header.hidden:before,.bc-header.hidden .ui-title,.bc-header.hidden .lbtn,.bc-header.hidden .rbtn,.bc-header.hidden .ui-title.logo{transform:none!important}');
        const scope = { m: 'body.ui-mobile-viewport ', d: 'body:not(.ui-mobile-viewport) ' };
        HIDE_CATS.forEach(c => {
            if (c.site !== SITE || c.id === skipId || !catOn(c, cfg)) return;
            const pre = c.site === 'bc' ? (scope[c.plat] || '') : '';
            out.push(`${c.sel.map(x => pre + x).join(',')}{${HIDE_DECL}}`);
        });
    }
    if (SITE === 'cb') cbGeoList().forEach(c => out.push(`.RoomCard:not(:has(.RoomCard__followStar.${CB_FOLLOW_CLS})):has(.thumbnail_flag .fi-${c}){display:none!important}`));
    getCustom().forEach(x => out.push(`${x}{${HIDE_DECL}}`));
    return out.join('\n');
};
const applyHideCSS = skipId => {
    let el = document.getElementById('bc-unchained-hide-css');
    if (!el) {
        el = mkStyle(document.head || document.documentElement, '', 'bc-unchained-hide-css');
    }
    el.textContent = buildHideCSS(isStr(skipId) ? skipId : undefined);
};
const ensureHideCSS = () => { if (!document.getElementById('bc-unchained-hide-css')) applyHideCSS(); };

const cssEsc = x => (window.CSS && CSS.escape) ? CSS.escape(x) : String(x).replace(/[^\w-]/g, '\\$&');
const stableCls = c => c.length < 40 && !/^(ui-|__|bcu-|bc-(?:theater|thtr|unlock))/.test(c) && !/\d{3,}/.test(c) && !/^(active|open|opened|hidden|visible|selected|current|hover|focus|loaded|loading|playing|disabled|on|off|hd)$/i.test(c);
const selUniq = x => { try { return document.querySelectorAll(x).length === 1; } catch { return false; } };
const pathSelector = el => {
    const parts = [];
    for (let cur = el; cur && cur.nodeType === 1 && cur !== document.documentElement; cur = cur.parentElement) {
        if (cur.id && selUniq(`#${cssEsc(cur.id)}`)) { parts.unshift(`#${cssEsc(cur.id)}`); break; }
        const par = cur.parentElement;
        const idx = par ? [...par.children].indexOf(cur) + 1 : 1;
        parts.unshift(`${cur.tagName.toLowerCase()}:nth-child(${idx})`);
        if (cur === document.body) break;
    }
    return parts.join(' > ');
};
const isPickable = n => n?.nodeType === 1 && n !== document.body && n !== document.documentElement;
const idSelector = c => {
    if (!c.id || /\d{3,}/.test(c.id)) return '';
    const s = `#${cssEsc(c.id)}`;
    return selUniq(s) ? s : '';
};
const classSelector = c => c.tagName.toLowerCase() + [...c.classList].filter(stableCls).slice(0, 2).map(x => `.${cssEsc(x)}`).join('');
const buildSelector = el => {
    const parts = [];
    let cur = el;
    for (let i = 0; i < 6 && isPickable(cur); i++, cur = cur.parentElement) {
        const id = idSelector(cur);
        if (id) {
            parts.unshift(id);
            const full = parts.join(' > ');
            return selUniq(full) ? full : pathSelector(el);
        }
        parts.unshift(classSelector(cur));
        const sel = parts.join(' > ');
        if (selUniq(sel)) return sel;
    }
    return pathSelector(el);
};

const BC_CSS = `a.htlp_icon_wrp.bg-head {
  display: none!important
}
#header_login {
  display: block!important;
  opacity: 1!important;
  pointer-events: auto!important;
  visibility: visible!important
}
#header_login .join_btn,
#header_login .jbt_inner {
  opacity: 1!important;
  pointer-events: auto!important;
  visibility: visible!important
}
.performer-frame,
.blurred-performer-frame {
  pointer-events: none!important
}
#video-panel-wrap,
#stream-container,
[class*="video-container"],
[class*="player-wrap"] {
  pointer-events: auto!important;
  touch-action: manipulation
}
.chat_panel_wrap {
  height: 100%!important;
  flex: 1
}
body:not(.page_index):not(.__page_main),
body:not(.page_index):not(.__page_main) .ui-page,
body:not(.page_index):not(.__page_main) .js-page,
body:not(.page_index):not(.__page_main) .bc-content {
  padding-top: 0!important;
  margin-top: 0!important
}
body:not(.__page_main) .bc_alternate_header_live_tabs,
body:not(.__page_main) .mls_hash_tag,
body:not(.__page_main) .__fixed_top {
  position: static!important;
  margin: 0!important
}
.bc_sticky_bar {
  position: fixed!important;
  top: var(--bc-sticky-y,80px)!important;
  left: var(--bc-sticky-x,50%)!important;
  transform: var(--bc-sticky-tf,translateX(-50%))!important;
  margin: 0!important;
  bottom: auto!important;
  z-index: 9990!important;
  pointer-events: none!important
}
.js-filter_button {
  pointer-events: auto!important;
  display: flex;
  justify-content: center
}
#chat_bar_v2_container {
  display: block!important;
  opacity: 1!important;
  pointer-events: auto!important;
  z-index: 2147483605!important
}
#chat_bar_v2,
#chatProfileButton,
#chatBarChatButton,
#switch_between_chat {
  display: flex!important;
  opacity: 1!important;
  pointer-events: auto!important;
  z-index: 2147483605!important
}
@media (min-width:1024px) {
  *:fullscreen video,
  *:-webkit-full-screen video,
  video:fullscreen,
  video:-webkit-full-screen {
    object-fit: contain!important;
    width: 100%!important;
    height: 100%!important;
    max-width: 100vw!important;
    max-height: 100vh!important;
    margin: auto!important
  }
}`;

const firstSegment = () => location.pathname.split('/').filter(Boolean)[0] || '';
const slugName = (reserved, valid) => () => {
    const f = firstSegment();
    return f && !reserved.test(f) && valid.test(f) ? f : '';
};
const BC_SYS = /^(?:ru|uk|ua|en|es|de|fr|pt|it|pl|tr|nl|cs|ro|hu|bg|el|ja|zh|ko|he|ar|hi|id|vi|th|tags?|categor(?:y|ies)|free-cams|all|search|girls|couples?|male|men|guys|female|trans|transsexual|new|top|models|favou?rites|live|cams|blog|help|support|settings|terms|privacy|faq|contacts|login|signup|register|apps|mobile|profile)$/i;
const bcDisplayName = () => {
    const d = Helpers.getModelSecretData();
    return d?.chatHost?.displayName
        || document.querySelector('#bChatRoomTitle, .chat-header-inner h1 a')?.textContent?.trim() || '';
};
const bcFallbackName = () => {
    const d = Helpers.getModelSecretData();
    return bcDisplayName() || d?.chatHost?.username || '';
};

const SC_SYS = /^(?:girls|men|guys|couples|trans|tags?|favorites|favourites|login|signup|search|blog|cams|explore|top|api|help|support|terms|privacy|models|profile|settings|videos|photos|pricing|studios|category|categories|new|vr|contest|announcement|user|guests|latest|about|contact|dmca|2257|tokens|messages|notifications|affiliates|shop|rewards|community)$/i;
const CB_SYS = /^(?:b|tags?|.+-cams|accounts|auth|tipping|supporter|affiliates|api|contest|apps|security|terms|privacy|dmca|support|billing|photo_videos|store|login|signup|register|search|explore|hosting|location|followed|follow|female|male|couple|trans|featured|2257|tokens|purchase|help|about|press|contact|sitemap|faq|studios|business|broadcast|messages|notifications|settings|cams|chatbot)$/i;
const FS_CSS = '@media (min-width:1024px){*:fullscreen video,*:-webkit-full-screen video,video:fullscreen,video:-webkit-full-screen{object-fit:contain!important;width:100%!important;height:100%!important;max-width:100vw!important;max-height:100vh!important;margin:auto!important}}';

const currentSite = (() => {
    const hostname = window.location.hostname;
    const configs = {
        bongacams: {
            match: BC_RE,
            videoSelectors: BC_CONTAINERS.map(s => `${s} video`).concat('video'),
            containerSelectors: BC_CONTAINERS,
            getName: () => {
                const [first = '', second = ''] = location.pathname.split('/').filter(Boolean);
                const raw = !BC_SYS.test(first) ? first : /^profile$/i.test(first) ? second : '';
                return bcDisplayName() || safeDecode(raw) || bcFallbackName();
            },
            css: BC_CSS
        },
        stripchat: {
            match: /stripchat|strpst/,
            videoSelectors: ['video.video-element', '.player video', '.web-rtc-player video', 'video'],
            containerSelectors: ['.player-wrapper', '[class*="ViewCamPlayer"]', '.web-rtc-player-wrapper', '.video-element-wrapper'],
            getName: slugName(SC_SYS, /^[\w.-]+$/),
            css: FS_CSS
        },
        chaturbate: {
            match: /chaturbate/,
            videoSelectors: ['#chat-player_html5_api', '#VideoPanel video', '.videoPlayerDiv video', '.video-js video', 'video'],
            containerSelectors: ['#TheaterModePlayer', '#VideoPanel', '.videoPlayerDiv', '#chat-player', '.video-js', '#video_container'],
            getName: slugName(CB_SYS, /^\w+$/),
            css: FS_CSS
        }
    };
    return Object.values(configs).find(c => c.match.test(hostname)) || configs.bongacams;
})();

const LANGS = ['RU', 'EN', 'UK', 'ES', 'DE', 'FR', 'PT', 'IT', 'PL', 'TR', 'NL', 'CS', 'RO', 'HU', 'JA', 'ZH', 'KO', 'HE', 'AR', 'HI', 'ID', 'VI', 'TH'];
const RTL_LANGS = ['HE', 'AR'];
const LANG_NAMES = { RU: 'Русский', EN: 'English', UK: 'Українська', ES: 'Español', DE: 'Deutsch', FR: 'Français', PT: 'Português', IT: 'Italiano', PL: 'Polski', TR: 'Türkçe', NL: 'Nederlands', CS: 'Čeština', RO: 'Română', HU: 'Magyar', JA: '日本語', ZH: '中文', KO: '한국어', HE: 'עברית', AR: 'العربية', HI: 'हिन्दी', ID: 'Bahasa Indonesia', VI: 'Tiếng Việt', TH: 'ไทย' };
const detectLang = () => {
    let list = [];
    try { list = [...(navigator.languages?.length ? navigator.languages : [navigator.language]), document.documentElement?.lang]; } catch (e) { dbg(e, 'detectLang'); }
    for (const c of list) {
        const k = String(c || '').slice(0, 2).toUpperCase().replace('IW', 'HE');
        if (LANGS.includes(k)) return k;
    }
    return 'EN';
};
const savedLang = Store.get('bc_lang', null);
const langAuto0 = Store.get('bc_lang_auto', savedLang == null) === true;

const DEFAULT_THEME = 'rgba(220, 20, 60, 0.9)';

const AppState = {
    bgPlay: Store.get('bc_bg', true),
    isMinimized: Store.get('bc_min', false),
    isHidden: Store.get('bc_hid', false),
    scale: parseFloat(Store.get('bc_scale', 1)),
    posX: Store.get('bc_x', null),
    posY: Store.get('bc_y', null),
    hasPos: Store.get('bc_pos', false),
    theme: Store.get('bc_theme', DEFAULT_THEME),
    langAuto: langAuto0,
    lang: langAuto0 ? detectLang() : (LANGS.includes(savedLang) ? savedLang : 'RU'),
    gmute: Store.get('bc_gmute', false) === true,
    blurMute: Store.get('bc_blur_mute', false) === true,
    bookmarks: Store.get('bc_bookmarks', []).filter(isObj),
    dark: Store.get('bc_dark', false) === true
};
const FavNav = {
    get on() { return Store.get('bc_favnav', false) === true; },
    set(v) { Store.set('bc_favnav', !!v); this.apply(); },
    apply() { try { this.hook && this.hook(this.on); } catch (e) { dbg(e, 'apply'); } }
};

const bmTomb = new Set();
const bmPersisted = new Set();
const BM_BAK = 'bc_bookmarks_bak';
let bmSaveFailed = false, bmDirty = false, bmNeedSave = false, bmRetryT = 0, bmRetries = 0, bmPersistedInit = false, bmBakAt = 0, bmBakLen = -1;
const bmRuntimeKey = k => k[0] === '_' || k === 'label';
const bmEnsurePersisted = () => {
    if (bmPersistedInit) return;
    bmPersistedInit = true;
    try {
        const a = Store.get('bc_bookmarks', []);
        if (Array.isArray(a)) for (const r of a) if (validBm(r)) bmPersisted.add(bmKey(r.url));
    } catch (e) { dbg(e, 'bmEnsurePersisted'); }
};
const saveBookmarks = () => {
    try {
        const have = new Set(AppState.bookmarks.filter(b => hasUrl(b)).map(b => bmKey(b.url)));
        const stored = Store.get('bc_bookmarks', []);
        if (Array.isArray(stored)) for (const r of stored) {
            if (!validBm(r)) continue;
            const k = bmKey(r.url);
            if (have.has(k) || bmTomb.has(k)) continue;
            have.add(k);
            AppState.bookmarks.push({ ...stripBm(r) });
        }
    } catch (e) { dbg(e, 'saveBookmarks'); }
    const out = AppState.bookmarks.filter(isObj).map(stripBm);
    bmSaveFailed = !Store.set('bc_bookmarks', out);
    if (!bmSaveFailed) {
        bmTomb.clear();
        bmDirty = false; bmRetries = 0;
        bmPersistedInit = true;
        bmPersisted.clear();
        for (const r of out) if (isStr(r.url)) bmPersisted.add(bmKey(r.url));
        if (out.length && (out.length !== bmBakLen || Date.now() - bmBakAt > 60000)) { Store.set(BM_BAK, out); bmBakLen = out.length; bmBakAt = Date.now(); }
    } else if (!bmRetryT && bmRetries < 3) {
        bmRetries++;
        bmRetryT = setTimeout(() => { bmRetryT = 0; saveBookmarks(); }, 2500);
    }
};
const incomingBookmarks = arr => {
    const m = new Map();
    for (const r of arr) if (validBm(r)) m.set(bmKey(r.url), r);
    return m;
};
const BM_LIVE = ['status', 'viewers', 'frame', 'topic'];
const mergeInto = (b, src, skip) => {
    for (const f of Object.keys(b)) if (!bmRuntimeKey(f) && !BM_LIVE.includes(f) && !(f in src) && !skip.includes(f)) delete b[f];
    for (const f of Object.keys(src)) if (!skip.includes(f)) b[f] = src[f];
};
const mergeLocalBookmark = (b, incoming, seen, skip) => {
    if (!isStr(b.url)) return true;
    const k = bmKey(b.url), r = incoming.get(k);
    if (seen.has(k)) return false;
    if (!r && !validBm(b)) return false;
    if (!r && bmPersisted.has(k) && !bmSaveFailed) return false;
    seen.add(k);
    if (r) mergeInto(b, stripBm(r), skip); else bmNeedSave = true;
    return true;
};
const mergeRemoteBookmarks = (arr, skip = []) => {
    if (!Array.isArray(arr)) return false;
    bmEnsurePersisted();
    bmNeedSave = false;
    const incoming = incomingBookmarks(arr), out = [], seen = new Set();
    for (const b of AppState.bookmarks) {
        if (isObj(b) && mergeLocalBookmark(b, incoming, seen, skip)) out.push(b);
    }
    incoming.forEach((r, k) => { bmPersisted.add(k); if (!seen.has(k)) out.push({ ...stripBm(r) }); });
    AppState.bookmarks = out;
    if (bmNeedSave) { bmNeedSave = false; bmDirty = true; setTimeout(saveBookmarks, 0); }
    return true;
};
const syncBookmarks = () => {
    try { const a = Store.get('bc_bookmarks', null); if (Array.isArray(a)) mergeRemoteBookmarks(a); } catch (e) { dbg(e, 'syncBookmarks'); }
};
const bmDropHooks = [];
const dropBookmark = b => {
    const k = isStr(b.url) ? bmKey(b.url) : null;
    if (k) bmTomb.add(k);
    AppState.bookmarks = AppState.bookmarks.filter(x => x !== b && !(k && hasUrl(x) && bmKey(x.url) === k));
    saveBookmarks();
    if (!AppState.bookmarks.length) { Store.set(BM_BAK, []); bmBakLen = -1; }
    if (k) for (const h of bmDropHooks) { try { h(k); } catch (e) { dbg(e, 'dropBookmark'); } }
};
try {
    if (!AppState.bookmarks.length) {
        const bak = Store.get(BM_BAK, []);
        const good = Array.isArray(bak) ? bak.filter(validBm).map(stripBm) : [];
        if (good.length) { AppState.bookmarks = good; bmDirty = true; setTimeout(() => saveBookmarks(), 1500); }
    }
} catch (e) { dbg(e, 'top'); }
const flushBookmarks = () => { if (bmDirty) { try { saveBookmarks(); } catch (e) { dbg(e, 'flushBookmarks'); } } };
window.addEventListener('pagehide', flushBookmarks, { capture: true });
document.addEventListener('visibilitychange', () => {
    let hid; try { hid = typeof realHidden === 'function' ? realHidden() : document.hidden; } catch { hid = document.hidden; }
    if (hid) flushBookmarks();
}, true);
const setGMute = v => { AppState.gmute = !!v; Store.set('bc_gmute', !!v); };
const sameBm = (b, name) => String(b.name).toLowerCase() === String(name).toLowerCase() && urlSite(b.url) === SITE;

const mergeBookmarks = arr => {
    const seen = new Set(AppState.bookmarks.map(b => bmKey(b.url)));
    let added = 0;
    for (const raw of arr) {
        const b = cleanBm(raw);
        if (!b) continue;
        const k = bmKey(b.url);
        if (seen.has(k)) continue;
        seen.add(k);
        bmTomb.delete(k);
        AppState.bookmarks.push(b);
        added++;
    }
    saveBookmarks();
    return added;
};

const DARK_PLAYER = '#TheaterModePlayer,.video-js:not(#TheaterModePlayer .video-js,#basePlayer .video-js)';
const DARK_PLAYER_MEDIA = 'video,img,picture,canvas,iframe,[style*="background-image"]';
const DARK_FB_CSS = `html.bc-dark-fb {
  filter: invert(1) hue-rotate(180deg)!important;
  background: #fff
}
html.bc-dark-fb :is(video,img,picture,canvas,iframe,[style*="background-image"],#bc-unchained-host,${DARK_PLAYER}) {
  filter: invert(1) hue-rotate(180deg)!important
}
html.bc-dark-fb :is(${DARK_PLAYER}) :is(${DARK_PLAYER_MEDIA}) {
  filter: none!important
}
html.bc-dark-fb:not(.bc-theater-html):not(.bc-fs-on) .RoomCardThumbnail::after,
html.bc-dark-fb:not(.bc-theater-html):not(.bc-fs-on) .RoomCardThumbnail__bottomBar {
  filter: invert(1) hue-rotate(180deg)!important
}
html.bc-dark-fb.bc-theater-html,
html.bc-dark-fb.bc-theater-html :is(video,img,picture,canvas,iframe,[style*="background-image"],#bc-unchained-host,${DARK_PLAYER}) {
  filter: none!important
}
html.bc-dark-fb.bc-fs-on,
html.bc-dark-fb.bc-fs-on *,
html.bc-dark-fb.bc-fs-on :is(video,img,picture,canvas,iframe,[style*="background-image"],#bc-unchained-host,${DARK_PLAYER}) {
  filter: none!important
}
html.bc-dark-fb :fullscreen,
html.bc-dark-fb :fullscreen *,
html.bc-dark-fb :fullscreen :is(video,img,picture,canvas,iframe,[style*="background-image"],#bc-unchained-host,${DARK_PLAYER}),
html.bc-dark-fb :is(video,img,picture,canvas,iframe,[style*="background-image"],#bc-unchained-host,${DARK_PLAYER}):fullscreen {
  filter: none!important
}`;
const DARK_FB_CSS_WK = `html.bc-dark-fb :-webkit-full-screen,
html.bc-dark-fb :-webkit-full-screen *,
html.bc-dark-fb :-webkit-full-screen :is(video,img,picture,canvas,iframe,[style*="background-image"],#bc-unchained-host,${DARK_PLAYER}),
html.bc-dark-fb :is(video,img,picture,canvas,iframe,[style*="background-image"],#bc-unchained-host,${DARK_PLAYER}):-webkit-full-screen {
  filter: none!important
}`;
const syncFsDark = () => {
    try { document.documentElement.classList.toggle('bc-fs-on', !!(document.fullscreenElement || document.webkitFullscreenElement)); } catch (e) { dbg(e, 'syncFsDark'); }
};
document.addEventListener('fullscreenchange', syncFsDark, true);
document.addEventListener('webkitfullscreenchange', syncFsDark, true);
const DARK_M_CSS = () => `html.bc-dark-m {
  color-scheme: dark;
  background: #121216
}
html^body,
html^.ui-page,
html^.ui-body-c,
html^.ui-content,
html^.ui-panel-content-wrap,
html^.bc-content {
  background: #121216!important;
  color: #dedede;
  text-shadow: none
}
html^#chat_bar_v2_container,
html^.inputContainer,
html^.video-panel-wrap {
  background: #18181c!important
}
html^.history,
html^.history p.msg_wrp,
html^.history .msg,
html^.history dfn:after {
  color: #dedede
}
html^.history p:not(.model) .author_name {
  color: #e8e8ec
}
html^.np_title,
html^.nt_message_title,
html^.bc_notice.__topic {
  color: #ee5b78
}
^.cp_purchase_popup {
  --bc-thm-purchasePopup-bg:#121213;
  --bc-thm-purchasePopup-accent-color:#e17085;
  --bc-thm-purchasePopup-avatar-bg:#212124;
  --bc-thm-purchasePopup-desc-divider-bg:#212124;
  --bc-thm-purchasePopup-title-color:#bcbcbc;
  --bc-thm-purchasePopup-desc-color:#d4d4d4
}
^.bc_confirm_popup_wrp {
  --bc-thm-confirmPopup_error-color:#f65a61;
  --bc-thm-confirmPopup_action_column-confirmBtn_decline-bg:#252527;
  --bc-thm-confirmPopup_action_column-confirmBtn_decline-bg-active:#323235;
  --bc-thm-confirmPopup_fanBoostWhat-bg:#121213;
  --bc-thm-confirmPopup_fanBoostWhat-color:#d8d8d8;
  --bc-thm-confirmPopup_fanBoostWhat-accent-color:#d67b87;
  --bc-thm-confirmPopup_fanBoostWhat-benefits-bg:#151517;
  --bc-thm-confirmPopup_fanBoostWhat-statuses-item-borderColor:#3b3b3f
}
^.bc_confirm_popup.__confirmed_light {
  --bc-thm-confirmPopup_confirmLight-bg:#151516;
  --bc-thm-confirmPopup_confirmLight-color:#d8d8d8;
  --confirmPopup_profile_media-confirm-title-color:#e17085;
  --confirmPopup_profile_media-confirmBtn_decline-color:#cecece
}
^.bc_confirm_popup_wrp.__vertical_orientation {
  --confirmPopup_vertical_orientation-color:#d8d8d8
}
^.bc_confirm_popup.__qoq_contest {
  --bc-thm-confirmPopup_qoqContest-color:#f5f5f5
}
^.bc_confirm_popup.__gift_preview {
  --confirmPopup_giftPreview-body-bg:#121213
}
^.bc_confirm_popup.__private_close {
  --bcm-privateClose-title-color:#e17085
}
^.mbc_btn {
  --bcm-btn-color:#cecece;
  --bcm-btn-bg:#252527;
  --bcm-btn-bg-active:#1d1d1f;
  --bcm-btn-bg-hover:#1b1b1d;
  --bcm-btn_secondary-color:#cecece;
  --bcm-btn_secondary-bg:#1c1c1e;
  --bcm-btn_secondary-bg-active:#323235;
  --bcm-btn_secondary-bg-hover:#1b1b1d;
  --bcm-btn_outline-borderColor:#3e3e43
}
^.bc_static_page {
  --bcm-static-page-color:#d8d8d8;
  --bcm-static-page-striped-content-div-even-text-shadow-color:rgba(0,0,0,0);
  --bcm-static-page-striped-content-div-even-bg-color:#1a1a1c;
  --bcm-static-page-header-text-shadow-color:rgba(0,0,0,0);
  --bcm-static-page-header-bg-color:#1a1a1c
}
^#mainbar {
  --bcm-mainbar-btn-color:#d8d8d8;
  --bcm-mainbar-btn-bg-active:#19191b;
  --bcm-mainbar-menuItemList-subItem-color:#e17085
}
^#mainbar .holiday_discount {
  --bcm-sidebar-holidayDiscount-accent-color:#e17085
}
^.bc_alternate_header_live_tabs {
  --alternateHeaderLiveTabs-base-color:#d8d8d8;
  --alternateHeaderLiveTabs-bg:#18181a;
  --alternateHeaderLiveTabs-scrollShadow-bg:linear-gradient(270deg, #18181a 0%, #f2f2f200 100%)
}
^.bc_skeleton_item {
  --bcm-skeleton-item-bg-color:#18181a
}
^.bc_skeleton_item:before {
  --bcm-skeleton-item-pseudo-bg-color:#121213
}
^.bcm_alphabet_filter {
  --bcm-alphabetFilter-base-accent-color:#e17085;
  --bcm-alphabetFilter-base-borderColor:#404044;
  --bcm-alphabetFilter-dropdown-bg:#121213;
  --bcm-alphabetFilter-dropdown-item-color:#d8d8d8;
  --bcm-alphabetFilter-select-bg:#121213;
  --bcm-alphabetFilter-select-caption-color:#d8d8d8
}
^.bcm_pagination {
  --bcm-pagination-item-color:#f3f3f3;
  --bcm-pagination-item-bg-color:#18181a;
  --bcm-pagination-item-bg-color-active:#1b1b1d;
  --bcm-pagination-item-border-color:#3e3e42
}
^.bcm_fp_features {
  --bcm-filter-results-features-item-color:#f5f5f5;
  --bcm-filter-results-features-item-bg-color:#161618
}
^.bcm_checkbox {
  --bcm-checkbox-mark-bg-color:#252527;
  --bcm-checkbox-mark-border-color:#4c4c52;
  --bcm-checkbox-mark-icon-fill:#bcbcbc;
  --bcm-checkbox-text-color:#bcbcbc;
  --bcm-checkbox-disabled-mark-bg-color:#1b1b1d;
  --bcm-checkbox-disabled-mark-border-color:#3c3c40;
  --bcm-checkbox-checked-text-color:#f5f5f5
}
^.bcm_radio {
  --bcm-radio-icon-color:#e17085
}
^.bcm_range {
  --bcm-range-values-color:#bcbcbc;
  --bcm-range-track-bg-color:#1a1a1c;
  --bcm-range-handle-bg-color:#121213;
  --bcm-range-handle-border-color:#45454a
}
^.bcm_form_alert {
  --bcm-form-alert-error-color:#ff5252;
  --bcm-form-alert-error-bg-color:#19191b
}
^.bcm_accordion {
  --bcm-accordion-title-color:#c1c1c1;
  --bcm-accordion-arrow-fill:#f5f5f5
}
^.bc_nav_tabs_wrp {
  --navigationTabs-item-color:#d8d8d8;
  --navigationTabs-inner-bg:#18181a;
  --navigationTabs-item-borderColor:#38383c;
  --navigationTabs_tags-after-bg:linear-gradient(270deg, #18181a, #f2f2f200)
}
^.bc_fade_scroll {
  --fadeScroll-fade-bg:linear-gradient(90deg, #18181a, rgba(24,24,26,0)
}
^.bc_fade_scroll.__listing_tags {
  --fadeScroll-fade-bg:linear-gradient(90deg, #121213, rgba(18,18,19,0)
}
^.smileysContainer {
  --mobile-smiles-panel-container-background-color:#121213;
  --mobile-smiles-panel-container-border-color:#4c4c52;
  --mobile-smiles-panel-close-wrapper-color:#e36e84;
  --mobile-smiles-panel-title-wrapper-color:#e2e2e2;
  --mobile-smiles-panel-title-shortcut-color:#c0c0c0;
  --mobile-smiles-panel-tab-color:#d2d2d2;
  --mobile-smiles-panel-tab-background-color:#151516;
  --mobile-smiles-panel-tab-background-color-hover:#121213;
  --mobile-smiles-panel-tab-border-color:#353539;
  --mobile-smiles-panel-tab-active-background-color:#121213
}
^.bcm_socials {
  --bcm-thm-socials-counter-color:#bcbcbc;
  --bcm-thm-socials-counter-bg:#121213;
  --bcm-thm-socials-counter-borderColor:#45454a
}
^.bcm_switcher {
  --bcm-switcher-bg:#222225
}
^.bc_notice {
  --bcm-notice-color:#e6e6e6;
  --bcm-notice-bg:linear-gradient(#121213, #121213);
  --bcm-notice-close-iconColor-hover:#dedede;
  --bcm-notice_secondary-paragraph-color:#d8d8d8;
  --bcm-notice_membershipError-close-iconColor-hover:#a960f0;
  --bcm-notice_sexToy-avatar-boxShadowColor:rgba(0,0,0,0);
  --bc-thm-notice_attention-bg:#181819;
  --bc-thm-notice_attention-color:#a866eb;
  --bc-thm-notice_topic-bg:#161618;
  --bc-thm-notice_topic-color:#e17085
}
^.bc_picture_stub {
  --bcm-thm-pictureStub-iconColor:linear-gradient(180deg, #e07184 0%, #e36d84 100%)
}
^.bc_carousel_bar {
  --carouselBar-item_active-inner-bg:#121213
}
^.mbc_header {
  --mob-header-menuBtn-shadow-color:rgba(0,0,0,0);
  --mob-header-menuBtn-bg:#121213
}
^.mbc_header_menu {
  --mob-header-menu-border-color:#4c4c52;
  --mob-header-menu-item-bg:#141416;
  --mob-header-menu-action-color:#f5f5f5
}
^.mbc_header_menu.__sorting {
  --mob-header-menu-item-border-color:#49494e
}
^.bcm_footer {
  --bcm-thm-footer-bg:#18181a;
  --bcm-thm-footer-borderColor:#2b2b2e;
  --bcm-thm-footer-details-color:#d8d8d8;
  --bcm-thm-footer-details-nav-link-color:#d8d8d8;
  --bcm-thm-footer-details-nav-link-bg-active:#1d1d1f;
  --bcm-thm-copyright-color:#d8d8d8;
  --bcm-thm-copyright-link-color:#df7187
}
^.mls_item {
  --bcm-thm-listingThumb-stub-bg:#161617;
  --bcm-thm-listingThumb-stub-img-bg:#1d1d1f
}
^.mls_tag_badge {
  --bcm-listing-tagBadge-bg:#18181a;
  --bcm-listing-tagBadge-borderColor:#3e3e42;
  --bcm-listing-tagBadge-color:#f3f3f3
}
^.lscl_item {
  --listing-category-item-color:#f3f3f3;
  --listing-category-item-bg-color:#1c1c1e;
  --listing-category-item-firstRow-color:#ececec;
  --listing-category-item-firstRow-bg:#18181a;
  --listing-category-item-firstRow-bg-active:#212124
}
^.listing_age_verification_banner {
  --ageVerificationBanner-icon-color:linear-gradient(#e26e84, #e17084)
}
^.mls_hash_tag {
  --mlsHashTag-tag-color:#ececec;
  --mlsHashTag-tag-bg:#18181a;
  --mlsHashTag-tag-bg-active:#212124;
  --mlsHashTag-tag_record-icon-color:#bdbdbd;
  --mlsHashTag-removeFilterLink-close-iconColor:#d4d4d4
}
^.instant_buy {
  --bcm-instant-buy-bar-border-color:#4c4c52;
  --bcm-instant-buy-success-wrp-circle-border-color:#4c4c52;
  --bcm-instant-buy-success-wrp-block-bg:#121213;
  --bcm-instant-buy-menu-bg:#161617;
  --bcm-instant-buy-menu-block-bg-active:#1e1e20;
  --bcm-instant-buy-menu-item-color:#eeeeee;
  --bcm-instant-buy-menu-item-desc-color:#c5c5c5
}
^.bc_chat_topic {
  --bcm-chatTopic-bg:#121213;
  --bcm-chatTopic-inner-bg:rgba(22,22,24,0.9);
  --bcm-chatTopic-content-color:#e6e6e6;
  --bcm-chatTopic-title-color:#e17085
}
^.chat_notice {
  --bcm-chatNotice-color:#e6e6e6;
  --bcm-chatNotice-inner-bg:#121213;
  --bcm-chatNotice-link-color:#52a1ff;
  --bcm-chatNotice-author-name-color:#e6e6e6
}
^.chat-panel .history .service_list {
  --sysMsg-serviceList-bg:#121213;
  --sysMsg-serviceList-authorName-color:#e6e6e6;
  --sysMsg-serviceList-title-color:#f5f5f5;
  --sysMsg-serviceList-services-item-bg-hover:#181819;
  --sysMsg-serviceList-item-title-color:#e6e6e6;
  --sysMsg-serviceList-showBtn-color:#d8d8d8;
  --sysMsg-serviceList-showBtn-bg:#121213;
  --sysMsg-serviceList-showBtn-borderColor:#38383c;
  --sysMsg-serviceList-showBtn-color-active:#f5f5f5;
  --sysMsg-serviceList-showBtn-borderColor-active:#49494e;
  --sysMsg-serviceList-showBtn-bg-active:#121213
}
^.bcm_chat_footer {
  --bcm-chat-footer-item-border-color:#323235;
  --bcm-chat-footer-item_tipMenu_disabled-bg:#38383c
}
^.bcm_history_scroll_up {
  --bcm-historyScrollUp-bg:#252527;
  --bcm-historyScrollUp-link-color:#e17085
}
^.bcm_chat_auto_message {
  --bcm-chat-member-notification-borderColor:#45454a;
  --bcm-chat-member-notification-title-color:#e2e2e2
}
^.video-panel-wrap {
  --videoPanelWrap-bg:#121213
}
^.mbc_tabs {
  --bcm-tabs-item-color:#d8d8d8;
  --bcm-tabs-item-bg:#18181a;
  --bcm-tabs-item_selected-color:#d8d8d8;
  --bcm-tabs-item_selected-bg:#18181a
}
^.bcm_nav_panel {
  --bcm-navPanel-bg:#18181a;
  --bcm-navPanel-item-color:#d8d8d8;
  --bcm-navPanel-item_active-color:#e36e84
}
^.f_rptfmm .buy_tokens,
^.f_rptsmm .buy_tokens {
  --bcm-base-buyTokens-color:#d8d8d8;
  --bcm-base-buyTokens-borderColor:#36363a;
  --bcm-base-buyTokens-error-color:#ff5272;
  --bcm-buyTokens-bg:#121213;
  --bcm-buyTokens-text_faded-color:#bcbcbc;
  --bcm-buyTokens-decline-text-bg:#161617;
  --bcm-buyTokens-paymentMethodCards-card-bankCounter-bg:#121213;
  --bcm-buyTokens-packages-checkbox_off-borderColor:#4a4a50;
  --bcm-buyTokens-packages-error-bg:#19191b;
  --bcm-buyTokens-confirmCode-title-accent-color:#e17085;
  --bcm-buyTokens-amountField-bg:#121213;
  --bcm-buyTokens-amountField_onlyLocalCurrenct-localCurrency-color:#d8d8d8;
  --bcm-buyTokens-choice-bg:#121213;
  --bcm-buyTokens-choice-iconRadioOff-bg:#313134;
  --bcm-buyTokens-select-iconWrp-bg:#131315;
  --bcm-buyTokens-stickyFooter-bg:#121213
}
^.f_rptsmm .buy_tokens {
  --bcm-buyTokens-paymentMethodCards-moreBtn-iconWrp-bg:#161617;
  --bcm-buyTokens-morePaymentMethodsPopup-bg:#121213;
  --bcm-buyTokens-morePaymentMethodsPopup-listWrp-shadow-bg:linear-gradient(180deg, #161617 35%, rgba(22,22,23,0.8) 50%, #ebfff000 100%);
  --bcm-buyTokens-morePaymentMethodsPopup-item-bg:#161617;
  --bcm-buyTokens-morePaymentMethodsPopup-item_active-bg:#121213;
  --bcm-buyTokens-morePaymentMethodsPopup-item-bankCounter-bg:#121213
}
^.bcm_popup {
  --bcm-popup-body-bg-color:#121213
}
^.categories_popup {
  --bcm-popup-categories-main-bg-color:#121213;
  --bcm-popup-categories-list-wrp-border-color:#45454a;
  --bcm-popup-categories-item-icon-color:#e17085;
  --bcm-popup-categories-item-color:#d8d8d8;
  --bcm-popup-categories-item-bg-color-active:#1b1b1d;
  --bcm-popup-categories-item-counter-bg-active:#121213
}
^.filter_popup {
  --bcm-popup-filter-main-bg:#121213;
  --bcm-popup-filter-features-title-color:#c1c1c1;
  --bcm-popup-filter-footer-borderColor:#45454a;
  --bcm-popup-filter-notice-color:#c1c1c1;
  --bcm-popup-filter-button-outline-bg:#2b2b2e;
  --bcm-popup-filter-button-outline-borderColor:#3f3f44
}
^.search_popup {
  --bcm-popup-search-header-bg-color:#18181a
}
^.mbc_search_block {
  --bcm-search-block-input-bg-color:#121213;
  --bcm-search-block-input-borderColor:#404044;
  --bcm-search-block-input-placeholder-color:#d8d8d8;
  --bcm-search-block-header-close-icon-color:#d8d8d8;
  --bcm-search-block-reset-button-bg-color:#1a1a1c;
  --bcm-search-block-reset-button-border-color:#45454a;
  --bcm-search-block-result-empty-color:#e17085;
  --bcm-search-block-result-recent-title-color:#e17085;
  --bcm-search-block-result-name-color:#f5f5f5;
  --bcm-search-block-result-border-color:#333337;
  --bcm-search-block-result-tag-color:#ececec;
  --bcm-search-block-result-tag-name-bg-color:#18181a;
  --bcm-search-block-result-tag-symbol-color:#e17085
}
^#pop-sorting-popup {
  --bcm-thm-popSortingPopup-sortingItem-color:#f5f5f5
}
^.categories {
  --bcm-categories-skeleton-bg:#151516
}
^.contact_support_page {
  --bcm-contact-support-page-title-color:#e17085;
  --bcm-contact-support-page-text-color:#d4d4d4;
  --bcm-contact-support-page-form-label-color:#f5f5f5;
  --bcm-contact-support-page-item-border-color:#353539
}
^.join {
  --bcm-join-base-color:#d8d8d8;
  --join-bg:#121213;
  --join_login-flashAlert-bg:#19191b;
  --join_login-redirect-bg:#212124;
  --join_login-redirect-borderColor:#45454a;
  --join_login-redirect-text-color:#d8d8d8;
  --join_login-redirect-text-shadowColor:rgba(0,0,0,0)
}
^.join_form {
  --bcm-joinForm-color:#d8d8d8;
  --bcm-joinForm-termsOfUse-bg:#151516;
  --bcm-joinForm-termsOfUse-color:#cecece;
  --bcm-joinForm-termsOfUse-checkbox-borderColor:#45454a;
  --bcm-joinForm-termsOfUse-checkbox-bg:#121213;
  --bcm-joinForm-input-color:#f5f5f5;
  --bcm-joinForm-input-bg:#151516;
  --bcm-joinForm-input-borderColor:#4c4c52;
  --bcm-joinForm-formLink-color:#b6b6b6
}
^.invisible_mode_overview {
  --bc-thm-invisibleModeOverview-content-color:#9099c1;
  --bc-thm-invisibleModeOverview-promoBlock-feature-bg:#151517;
  --bc-thm-invisibleModeOverview-switchBlock-box-bg:linear-gradient(180deg, #121213 0%, #141416 100%);
  --bc-thm-invisibleModeOverview-switchBlock-box-borderColor:#38383c;
  --bc-thm-invisibleModeOverview-switchBlock-title-color:#9099c1;
  --bc-thm-invisibleModeOverview-switcher-wrp-bg:linear-gradient(180deg, #b6bfd1 0%, #121213 100%)
}
^.invisible_mode_overview_plank {
  --bc-thm-invisibleModeOverviewPlank-color:#cacaca;
  --bc-thm-invisibleModeOverviewPlank-bg:#151516
}
^.im_conversations .imc_reply {
  --bcm-im-reply-bg:#121213;
  --bcm-im-reply-borderColor:#323235;
  --bcm-im-reply-quickSmiles-bg:#121213;
  --bcm-im-reply-quickSmiles-borderColor:#3d3d41
}
^.im_conversations .imc_rf_textarea_wrapper {
  --bcm-im-replyForm-textareaWrapper-control-color:#c4c4c4;
  --bcm-im-replyForm-textareaWrapper-control-textShadowColor:rgba(0,0,0,0);
  --bcm-im-replyForm-textareaWrapper-placeholder-color:#c4c4c4
}
^.im_conversations .imc_rf_textarea_error {
  --bcm-im-replyForm-textareaError-color:#ff5252
}
^.im_conversations .imc_r_button {
  --bcm-im-replyButton_smilepicker-trigger-icon-stroke:#c4c4c4;
  --bcm-im-replyButton_smilepicker-trigger_active-icon-stroke:#f5f5f5
}
^.im_conversations .imc_balance_restriction_alert {
  --im-balanceRestrictionAlert-color:#d4d4d4;
  --im-balanceRestrictionAlert-bg:#151516;
  --im-balanceRestrictionAlert-title-color:#e17085
}
^.im_conversations .imc_kicked_alert {
  --im-kickedAlert-color:#d4d4d4;
  --im-kickedAlert-acent-color:#e17085;
  --im-kickedAlert-bg:#121213;
  --im-kickedAlert-borderColor:#45454a
}
^.im_conversations .imc_notification {
  --im-notification-color:#e6e6e6;
  --im-notification_pink-accent-color:#ea67ce
}
^.im_conversations .imch_contact_card {
  --im-contactCard-color:#d4d4d4;
  --im-contactCard-accent-color:#e17085;
  --im-contactCard-checkbox-color:#d8d8d8;
  --im-contactCard-bg-color:#121213;
  --im-contactCard-borderColor:#45454a
}
^.imch_reactions {
  --im-reactions-item-bg:#121213;
  --im-reactions-item_own-bg:#161617;
  --im-reactions-item-focus-bg:#161617;
  --im-reactions-counter-color:#d2d2d2
}
^.bc_switch_lang_popup {
  --bcm-thm-switchLangPopup-bg:#121213;
  --bcm-thm-switchLangPopup-close-color:#e17085;
  --bcm-thm-switchLangPopup-title-color:#e17085;
  --bcm-thm-switchLangPopup-noteText-color:#bdbdbd;
  --bcm-thm-switchLangPopup-note-bg:#141415;
  --bcm-thm-switchLangPopup-note-borderColor:#45454a;
  --bcm-thm-switchLangPopup-flagsGroup-itemCaption-color:#cecece
}
^.mbc_alert {
  color: #d6d6d6;
  background: #121213
}
^.mbc_alert.__info {
  color: #c9c9c9;
  background: #151516;
  border: 1px solid #3f3f43
}
^.mbc_alert.__warning {
  color: #ff5253;
  background: #171718
}
^.bc_btn {
  color: #cecece;
  background: #252527
}
^.bc_btn:active {
  background: #303033
}
^.bc_btn.bc_btn_white {
  background: #121213;
  border: 1px solid #3f3f44;
  color: #cecece !important
}
^.bc_btn.bc_btn_white:active:not(.__disabled),
^.bc_btn.bc_btn_white:hover:not(.__disabled) {
  background: linear-gradient(#131315,#18181a)
}
^.bc_btn.bc_btn_white.__disabled,
^.bc_btn.bc_btn_blue.__disabled {
  background: #18181a;
  border-color: #36363a
}
^.bc_btn.bc_btn_light {
  background: linear-gradient(#151516,#1a1a1c);
  border: 1px solid #3f3f44;
  color: #cecece !important
}
^.bc_btn.bc_btn_light:active:not(.__disabled),
^.bc_btn.bc_btn_light:hover:not(.__disabled),
^.bc_btn.bc_btn_light.__disabled {
  background: linear-gradient(#1a1a1c,#151516)
}
^.bc_confirm_popup_wrp.__hint,
^.bc_confirm_popup_wrp .bc_confirm_popup.__dialog .dlg_subTitle,
^.bc_confirm_popup_wrp .account_deleted_popup,
^.bc_confirm_popup_wrp .questions_popup,
^.bc_confirm_popup_wrp .bc_confirm_popup.__paid_chat_hint .bc_cp_body,
^.bc_confirm_popup_wrp .cancel_account_confirm,
^.bc_confirm_popup_wrp .bc_confirm_popup.__auto_message_chat_hint .bc_cp_body,
^.bcm_radio.__background .bcm_r_input:checked~.bcm_r_text,
^.bcm_input_row,
^.forward_to_remote,
^.im_conversations .pm_confirm_email .pmc_body,
^.im_conversations .pm_confirm_email .pmc_body .pmc_item,
^.im_conversations .pm_offering .pmob_item,
^.im_conversations .pm_offering .pmol_item {
  color: #d4d4d4
}
^.bc_confirm_popup_wrp.__ask_again .bcc_header .bcc_title,
^.bc_confirm_popup_wrp .bc_confirm_popup.__error .bcc_container_info,
^.bc_confirm_popup_wrp .bcc_no_header_popup .nh_title,
^.bc_confirm_popup_wrp .bc_confirm_popup.__dialog.__d_alert .dlg_title,
^.bc_confirm_popup_wrp .account_deleted_popup .adp_title,
^.bc_confirm_popup_wrp .questions_popup .qp_title,
^.bc_confirm_popup_wrp .questions_popup .qp_accent,
^.bc_confirm_popup_wrp .delete_msg .dm_accent,
^.bc_confirm_popup_wrp .cancel_account_confirm .cac_title,
^.bcm_input_row .bcm_i_label_required,
^.mls_title .mlst_icon.__category,
^.vip-payment .inner-vip .vip-blocks,
^.suspect_login_page .slp_desc,
^.bc_cmn_confirm_wrp.__danger .bc_confirm_popup .bcc_title,
^.head_confirm_box .hcb_close_wrp .hcb_close,
^.aChat .message.admin strong,
^#admin_chat_popup .admin_info .details .label,
^.buy_tokens h4 strong,
^.verify_info_box p b,
^.confirm_box .line b,
^.confirm_box .note span,
^.bt_first_step span,
^.buy_tokens .discount_gift_card .new_price,
^.loyalty_box.leaving .lb_blue,
^.account_preview .acp_alert,
^.forward_to_remote .ftr_highlight,
^.imch_reply .imch_r_author,
^.imc_footer_reply .imc_fr_username,
^.imc_footer_reply .imc_fr_close_icon,
^.im_conversations .pm_valentine_day_themed_gift .pmvdtgd_accent,
^.cg_vibe_with_me .vwm_s_danger {
  color: #e17085
}
^.bc_confirm_popup_wrp.__ask_again .cgr_label,
^.bc_confirm_popup_wrp.__ask_again .cg_label,
^.bc_confirm_popup_wrp.__ask_again .tknt_item,
^.bc_confirmer .bcc_alert .ca_text,
^.bcm_filter_result .fr_title,
^.chat-panel .msg_chat_game .cg_msg_s_time,
^.bc_cmn_confirm_wrp .bc_confirm_popup .bcc_title,
^.confirm_box .example_box,
^.cg_overlay .cg_options_list .ol_item .cgo_label,
^.cg_overlay .cg_accordion .cga_item .ai_title,
^.cg_overlay .cg_adj_empty_playlist .ep_message,
^.cg_overlay .cg_adj_list .it_title,
^.cg_overlay .cg_adj_notice,
^.cg_overlay .cg_adj_title,
^.cg_overlay .cg_select_subtitle .ss_chosen {
  color: #c1c1c1
}
^.bc_confirm_popup_wrp.__ask_again .cgr_label .bc_capitalize,
^.bc_confirm_popup_wrp.__ask_again .cg_label .bc_capitalize,
^.bc_confirm_popup_wrp.__ask_again .tknt_item .bc_capitalize,
^.bc_confirm_popup_wrp .bc_confirm_popup.__display_name,
^.bc_confirm_popup_wrp .bcc_header .bcc_title,
^.bc_confirm_popup_wrp .bc_confirm_popup.__change_displayname .bcm_input_row .bcm_form_input,
^.mbc_menu .mbcm_item.__default,
^.translator_settings .bcf_check,
^#mainbar .mm_icon,
^#mainbar.__lang_list_opened .change-lang>.change-lang-btn,
^.bcm_radio .bcm_r_input:checked~.bcm_r_text,
^.mls_container.is_history .mlshiw_icon,
^.vip-payment .head-vip i,
^.vip-payment .head-vip,
^.buy-descr,
^#bChat .member,
^.history p.model,
^.history dfn:after,
^.bcm_chat_selection .bcm_cs_title,
^.bcm_chat_selection .bcm_cs_arrow,
^.chat-panel .history .system_msg.msg_activity .mat_accent.__member,
^.chat-panel .msg_chat_game.__vibe_with_me .cg_vwm_text,
^.chat-panel .msg_chat_game .gc_msg_label,
^.ic_menu_wrp .iw_translator,
^.ic_menu_wrp .iw_font_picker,
^.bc_cmn_confirm_wrp .bc_confirm_popup .bcc_title .bc_capitalize,
^.head_confirm_box .hcb_text,
^.buy_tokens .buy_tokens_form p.form_notice,
^.buy_tokens h4,
^.processors .ui-btn-inner,
^.buy_tokens .choice.discount_box .discount_sum,
^.buy_tokens label.choice.discount_box .discount_sum,
^.buy_tokens .bt_payment_title,
^.buy_tokens .sp_current_package .spcp_local_currency,
^.buy_invisible_package .choice_title,
^.buy_invisible_package .choice_footer,
^.bcm_popup .bcm_p_content,
^.mbc_search_block .sb_input,
^.mbc_search_block .sb_result .sbr_title,
^.account_preview .acp_name,
^.im_conversations .pm_confirm_email,
^.smilePanelTabs .sm_t_label,
^.cg_overlay .cg_options_list .ol_item .cgo_label .ol_accent,
^.cg_overlay .cg_result .cgr_preview,
^.cg_overlay .cg_accordion .cga_item .ai_arrow,
^.cg_overlay .cg_adj_form .djf_title,
^.cg_overlay .cg_adj_list .pll_item.__playing .it_title,
^.cg_overlay .cg_select_subtitle,
^.cg_overlay .cg_select_subtitle .ss_chosen .ssc_amount,
^.cg_vibe_with_me.__vwm_success,
^.cg_vibe_with_me.__vwm_success .vwm_s_desc,
^.cg_vibe_with_me .vwm_text {
  color: #f5f5f5
}
^.bc_confirm_popup_wrp .bc_confirm_popup,
^.bc_confirm_popup_wrp .bc_confirm_popup.__dialog .bc_cp_body,
^.mbc_fullscreen_popup,
^.what_view .what_view_close:before,
^.bongacams_popup_v2 .popup_close:before,
^.mls .mls_pager_header .mls_header_content,
^.footer .support_group li.ui-btn-up-c,
^.padded.white,
^.padded.stripe:nth-child(2n),
^.vip-payment .inner-vip,
^#conversations .notfound,
^#bChatFullScreenHistory p,
^#chat_bar_v2_container,
^.chat_prompt_wrp .cp_msg .cp_msg_decline_close:after,
^.chat_prompt_wrp .cp_msg .cp_msg_decline_close:before,
^.tap_model_popup_container .tap_container_inner,
^.fancybox-tip-popup-wrap .fancybox-inner,
^.bc_cmn_confirm_wrp .bc_confirm_popup,
^.promo_in_banner_popup .pibp_body,
^#admin_chat_popup .admin_chat_popup_content,
^.buy_tokens .section .box,
^.buy_tokens .bt_anim_icon_success:after,
^.profile_avatar_popup,
^.ac_change_photo .ac_cp_inner,
^.imc_footer,
^.imc_footer_alerts,
^.im_conversations .imc_avatar .imc_avatar_img_wrap.__moderator,
^.cg_overlay .cg_body,
^.cg_keno_cube.__selected,
^.bc_token_keno_field.__win .tkf_cube:hover .fc_body,
^.bc_token_keno_field .tkf_cube.__checked:hover .fc_body,
^.bc_token_keno_field .tkf_cube.__checked .fc_body,
^.cg_overlay .cg_prize_list.__keno .pl_item.__open {
  background-color: #121213
}
^.bc_confirm_popup_wrp .bc_confirm_popup.__tip .bc_cp_body .bcf_check,
^.bcm_radio.__background,
^.bcm_checkbox_row .cr_label,
^.cg_overlay .cg_input_wrp .cgi_label {
  background: #1c1c1e
}
^.bc_confirm_popup_wrp .bc_confirm_popup.__tip .bc_cp_body .bcf_text,
^.cg_overlay .cg_input_wrp .cgi_text {
  color: #cecece;
  text-shadow: 0 1px transparent
}
^.bc_confirm_popup_wrp .bc_confirm_popup.__tip .bc_cp_body .bcf_mark,
^.cg_overlay .cg_input_wrp .cgi_mark {
  background: #2a2a2d
}
^.bc_confirm_popup_wrp .bc_confirm_popup.__tip .bc_cp_body .bcf_mark:before,
^.bc_confirm_popup_wrp .bc_confirm_popup.__buy_fb .bc_cp_close:before,
^.bc_confirm_popup_wrp .bc_confirm_popup.__buy_fb .bc_cp_close:after,
^.bc_confirm_popup_wrp .bc_confirm_popup.__thank .bc_cp_close:before,
^.bc_confirm_popup_wrp .bc_confirm_popup.__thank .bc_cp_close:after,
^.bc_confirm_popup_wrp .bc_confirm_popup.__feedback .bc_cp_close:before,
^.bc_confirm_popup_wrp .bc_confirm_popup.__feedback .bc_cp_close:after,
^.bc_confirm_popup_wrp .account_deleted_popup .bcc_content,
^#mainbar .mm_icon.__free,
^.bc_confirmer .bcc_alert,
^.holiday_discount_alert .hda_perc,
^.bcm_skeleton:before,
^.bcm_toggle_switcher_holder .ts_switcher:before,
^.bcm_switcher.__on .bcm_s_slider:before,
^.bcm_switcher .bcm_s_slider:before,
^.info_part .icon_membership.icon_free,
^.close_icon_wr span:before,
^.close_icon_wr span:after,
^.chat_prompt_wrp .cp_msg,
^.chat_tooltip .close-btn:before,
^.chat_tooltip .close-btn:after,
^.bcm_oops_popup_layover .opl_body,
^.popup_c2c_no_access .c2c_na_content,
^.popup_c2c_no_access .c2c_na_close:before,
^.popup_c2c_no_access .c2c_na_close:after,
^.head_confirm_box .hcb_links .input_wrapper .ui-input-text,
^.head_confirm_box .hcb_links .input_wrapper .hcb_input.ui-input-text,
^.phone_confirm_box,
^.sp_dropdown,
html.bc-dark-m,
^.buy_tokens .packages_badge .pb_icon,
^#page_loader_overlay,
^.loyalty_box.__personal .lb_holder,
^.buy_tokens_header_board,
^.bt_select_modal .slm_item,
^.fancybox-wrap.__shemale_gold_slider_wrap .fancybox-close:before,
^.fancybox-wrap.__shemale_gold_slider_wrap .fancybox-close:after,
^.mbc_search_block .sb_result,
^.popHint,
^.vip_overview_stock_page .spp_list .pl_item,
^.invisible_mode_overview .imsw_switcher:before,
^.page_im.__settings .im_conversations,
^.im_conversations .imc_actions .mbc_btn,
^.im_conversations .imc_empty .imce_icon_wrp,
^.im_conversations .imci_wrap,
^.im_conversations .imc_followers .imcf_item,
^.im_conversations .imc_search.__active .search_input,
^.im_conversations .imc_search .search_input:focus,
^.imch_reply,
^.pm_activation_2fa .pmaf_alert,
^.im_conversations .pm_upgraded_membership.__diamond .pmuh_logo_plank,
^.im_conversations .pm_downgrade_membership .dmh_logo_plank,
^.notice_need_tokens.__alert,
^.smilePanel.smilePanelMobile .smilePanelContent,
^.cg_overlay .gc_btn.__white,
^.cg_overlay .cg_input_wrp .cgi_mark:before,
^.cg_overlay .cg_game_field.__dice:before,
^.bc_token_keno_field.__win .tkf_cube .fc_body,
^.cg_vibe_with_me.__vwm_app .vwm_ai_title:after {
  background: #121213
}
^.bc_confirm_popup_wrp .bc_confirm_popup.__tip .bc_cp_body .bcf_control {
  color: #d8d8d8;
  border: 1px solid #4c4c52
}
^.bc_confirm_popup_wrp .bc_confirm_popup.__tip .bc_cp_body .form_error,
^.bc_confirm_popup_wrp .bc_confirm_popup.__social_buy .bc_cp_body .form_error,
^.form .form_errors_row .form_error,
^#bChat .msg_wrp .ch_m_tr_error,
^.confirm_code_form strong.hl,
^.im_conversations .imc_history_box .imch_item.__translate.__error .imch_msg_translate,
^.cg_overlay .cg_result .cgr_prize .rp_text {
  color: #ff5252
}
^.bc_confirm_popup_wrp .bc_confirm_popup.__social_buy.__profile_lg .bc_cp_body .bcf_input_block {
  background-color: #151516;
  border-color: #4c4c52
}
^.bc_confirm_popup_wrp .bc_confirm_popup.__social_buy .bc_cp_body .bcf_label_wrp,
^.ui-btn-hover-c:visited,
^.ui-btn-hover-c:hover,
^.ui-btn-hover-c a.ui-link-inherit,
^.ui-btn-down-c:visited,
^.ui-btn-down-c:hover,
^.ui-btn-down-c a.ui-link-inherit,
^.ui-btn-active:visited,
^.ui-btn-active:hover,
^.ui-btn-active a.ui-link-inherit,
^.what_view.__pin .what_view_inner,
^.bcm_checkbox_row .cr_text,
^.phone_confirm_box .hcb_text,
^.password_changed_box,
^.loyalty_box.speaker .lb_first,
^.loyalty_box.leaving,
^.mbc_search_block .sb_result .sbr_delete_icon,
^.profile_avatar_popup .account_actions .mbc_btn.ac_a_btn_cancel,
^.account_actions .mbc_btn.ac_a_btn_cancel {
  color: #cecece
}
^.bc_confirm_popup_wrp .bc_confirm_popup.__social_buy .bc_cp_body .bcf_input_block,
^.live_tabs .tabs_list .tabsl_item.ui-btn-active {
  background-color: #1c1c1e
}
^.bc_confirm_popup_wrp .bc_confirm_popup.__error .bcc_button {
  color: #cecece;
  background-color: #1a1a1c
}
^.bc_confirm_popup_wrp .bcc_actions {
  border-top: 1px solid #49494e
}
^.bc_confirm_popup_wrp .bcc_no_header_popup {
  color: #d4d4d4;
  background: #151516
}
^.bc_confirm_popup .cp_signup_wrap {
  color: #d8d8d8;
  background-color: #212124;
  border-top: 1px solid #45454a
}
^.bc_confirm_popup .cp_signup_title,
^.ui-bar-e,
^.ui-body-e,
^.ui-overlay-e,
^.video-item .date,
^.model_status .author,
^.im_conversations .gift_card_promo_wrapper .gift_card_promo .gift_card_promo_code_wrapper .gift_card_promo_code_valid,
^.im_conversations .gift_card_promo_wrapper .gift_card_promo .gift_card_promo_bonuses .gift_card_promo_bonus {
  text-shadow: 0 1px transparent
}
^.bc_confirm_popup_wrp .bc_confirm_popup.__dialog .dlg_title,
^.bc_confirm_popup_wrp .bc_confirm_popup.__change_displayname .bcm_i_label_txt,
^.ui-body-b .ui-link-inherit,
^.ui-body-c .ui-link-inherit,
^.ui-bar-e .ui-link-inherit,
^#mainbar .menu_item .mi_list .ui-btn,
^#mainbar .menu_item .mi_list .mil_item.__collapsed .mil_sub_item,
^#mainbar .combobox-lang,
^#mainbar .combobox-lang .mm_arrow,
^#mainbar .home .mm_live,
^.auto_purchase_form,
^.input_wrapper.__field_password .fp_icon_wrp,
^.mls_title,
^.notfound p,
^.chat-panel .history .system_msg.msg_activity,
^.bc_cmn_confirm_wrp .bc_confirm_popup .bcc_content,
^.bt_first_step,
^.buy_tokens .buy_tokens_auto_purchase,
^.loyalty_box.leaving .lb_second,
^.f_rptfmm .buy_tokens_sms_dimoco_package .input_phone.__extended_description .action_block,
^.f_rptsmm .buy_tokens_sms_dimoco_package .input_phone.__extended_description .action_block,
^.f_rptfmm .buy_tokens_sms_dimoco_package .input_phone.__extended_description .sdp_phone_title,
^.f_rptsmm .buy_tokens_sms_dimoco_package .input_phone.__extended_description .sdp_phone_title,
^.buy_tokens .bt_decline .btd_desc,
^.ac_change_photo,
^.ac_change_photo .ac_cp_sub_title,
^.im_conversations .imc_history_box .imch_item.__translate.__error,
^.im_conversations .imc_history_box .imch_item.__tip .imch_msg_text,
^.im_conversations .imc_history_box .imch_msg_translate,
^.im_conversations .pm_valentine_day_themed_gift .pmvdtg_name,
^.im_conversations .pm_valentine_day_themed_gift .pmvdtgd_text,
^.cg_vibe_with_me.__vwm_app .vwm_a_qr_icon,
^.cg_vibe_with_me.__vwm_app .vwm_a_qr_icon_wrp {
  color: #d8d8d8
}
^.bc_confirm_popup_wrp .questions_popup .qp_title.__warn {
  color: #f06171
}
^.bc_confirm_popup_wrp .questions_popup .qp_accent_info,
^.mls_container.is_history .mls_history_icon_wrp,
^.chat_user_menu .cum_content,
^.buy_tokens_sms_dimoco_package .input_phone.__extended_description .description_block,
^.buy_tokens .bt_extened_description_block,
^.im_conversations .imc_history_box .imch_badge_unread,
^.notice_need_tokens .nnt_icon_wrp {
  background: #161617
}
^.bc_confirm_popup_wrp .questions_popup .qp_accent_info .ai_title {
  color: #5ff281
}
^.bc_confirm_popup_wrp .questions_popup.__support .qp_title,
^.ui-body-e .ui-link-inherit,
^.what_view .what_view_inner .title,
^.msg_style_panel .msp_subtitle,
^.bcm_chat_invite.__decline .bcm_ci_badge_icon {
  color: #e2e2e2
}
^.bc_confirm_popup_wrp .delete_msg .dm_text,
^.seo_box h2,
^.seo_box h3,
^.mls_title .mlst_icon.__recordable,
^.tap_model_popup_container .tip_after_private .tap_title_block {
  color: #bdbdbd
}
^.bc_confirm_popup_wrp .bc_confirm_popup.__up_status .bc_cp_body,
^.popup_c2c_no_access .c2c_na_text,
^.buy_tokens_redirect .btr_text.__action,
^.im_conversations .imc_actions,
^.im_conversations .imc_actions .mbc_btn.__btn_msg.__active .imca_text.__new_msg,
^.im_conversations .imc_actions .mbc_btn.__btn_msg:active .imca_text.__new_msg,
^.im_conversations .imc_actions .imca_text.__online,
^.empty_conversation .ec_inner,
^.im_conversations .imc_search.__active .imcs_icon,
^.im_conversations .imc_search .search_input:focus+.imcs_icon,
^.notice_need_tokens .nnt_text {
  color: #d6d6d6
}
^.bc_confirm_popup_wrp .bc_confirm_popup.__up_status .ups_desc {
  background-color: #141416
}
^.bc_confirm_popup_wrp .bc_confirm_popup.__help .bcc_button {
  color: #cecece;
  background: linear-gradient(#151516,#1a1a1c);
  border: 1px solid #3f3f44
}
^.bc_confirm_popup_wrp .bc_confirm_popup.__pin_list .bc_cp_body,
^.bc_online_notification .ont_avatar_wrp,
^.bc_online_notification .ont_info_wrp,
^.bc_context_menu_list .cml_item:active,
^.bcm_skeleton .bcms_inner,
^.bc_body.tablet.android .chat_panel_area:after,
^.bcm_chat_selection .bcm_cs_link:hover,
^.bcm_chat_selection .bcm_cs_link:active,
^.cg_overlay .cg_footer {
  background: #18181a
}
^.ui_floating_tooltip .ft_arrow {
  fill: rgba(245,245,245,0.8)
}
^.mbc_menu {
  background-color: #121213;
  border: 1px solid #38383c
}
^.mbc_menu .mbcm_item {
  border-bottom: 1px solid #38383c
}
^.mbc_menu .mbcm_item:active,
^.im_conversations .imc_history_box .imch_item.__selected,
^.im_conversations .pm_upgraded_membership.__diamond .pmu_holder.__filled,
^.im_conversations .pm_upgraded_membership.__diamond .pmu_footer {
  background: #1b1b1d
}
^.ui-body-b,
^.ui-overlay-b {
  color: #e2e2e2;
  text-shadow: 0 1px transparent;
  background: #18181a;
  background-image: -moz-linear-gradient(#232325,#2b2b2e);
  background-image: -ms-linear-gradient(#232325, #2b2b2e);
  background-image: linear-gradient(#232325,#2b2b2e);
  border: 1px solid #4c4c52
}
^.ui-bar-c {
  color: #d2d2d2;
  text-shadow: 0 1px transparent;
  background: #1a1a1c;
  background-image: -moz-linear-gradient(#19191b,#232325);
  background-image: -ms-linear-gradient(#19191b, #232325);
  background-image: linear-gradient(#19191b,#232325);
  border: 1px solid #4c4c52
}
^.ui-bar-c .ui-link-inherit {
  color: #d2d2d2
}
^.ui-body-c,
^.ui-overlay-c {
  color: #d8d8d8;
  text-shadow: 0 1px transparent;
  background: #151516;
  background-image: -moz-linear-gradient(#151516,#1a1a1c);
  background-image: -ms-linear-gradient(#151516, #1a1a1c);
  background-image: linear-gradient(#151516,#1a1a1c);
  border: 1px solid #4c4c52
}
^.ui-btn-up-c {
  color: #cecece;
  background: #141416
}
^.ui-btn-hover-c,
^#mainbar .li_shadow.ui-btn-active,
^.ui-btn-active,
^#mainbar .ui-btn-up-c.ui-btn-active,
^#mainbar .ui-btn-active.ui-btn-hover-c,
^#mainbar .ui-btn-active:focus,
^#mainbar .ui-btn-active:active,
^#mainbar .ui-btn-active:hover,
^#mainbar .ui-btn-active,
^#mainbar a.join-btn.ui-btn-active,
^#mainbar .ui-btn-up-c.ui-btn-active .btn_ico.ui-link-inherit,
^.buy_tokens .processors_btn_disabled {
  color: #cecece;
  background: #1c1c1e
}
^.ui-btn-down-c {
  text-shadow: 0 1px transparent;
  color: #cecece;
  background: #303033
}
^.ui-btn-up-c,
^.ui-btn-hover-c,
^.ui-btn-down-c {
  background-color: #1c1c1e;
  color: #cecece !important
}
^.ui-btn-active.ui-btn-up-c span.ui-btn-up-c,
^.ui-btn-active.ui-btn-up-c a.ui-link-inherit,
^.ui-btn-active.ui-btn-up-c,
^.ui-btn-active.ui-btn-up-c:visited,
^.ui-btn-active.ui-btn-hover-c:hover,
^.ui-btn-active.ui-btn-down-c a.ui-link-inherit {
  background: #1c1c1e;
  color: #cecece !important
}
^.ui-checkbox .ui-btn-up-c,
^.choice.ui-btn-up-c,
^.ui-listview .ui-btn-up-c,
^.ac_settings .ac_delete_link_wrp {
  color: #cecece;
  background: #141416;
  border: 1px solid #45454a
}
^.ui-collapsible-heading-toggle.ui-btn-up-c {
  color: #cecece;
  border: 1px solid #45454a;
  background: #141416 !important
}
^.ui-checkbox .ui-btn-hover-c,
^.choice.ui-btn-hover-c,
^.ui-listview .ui-btn-hover-c,
^.ui-collapsible-heading-toggle.ui-btn-hover-c {
  color: #cecece;
  background-color: #1c1c1e;
  border: 1px solid #45454a
}
^.ui-body-d,
^.ui-overlay-d,
^#mainbar .bc_social_networks .sn_count,
^.aChat,
^#admin_chat_popup .admin_info .admin_avatar_wrp,
^.loyalty_box,
^.buy_tokens_sms_dimoco_package .input_phone,
^.buy_tokens_sms_dimoco_package .input_phone.__extended_description .sdp_phone_input,
^.buy_tokens .sp_input,
^.im_conversations .pm_confirm_email .pmc_form,
^.cg_overlay .cg_adj_list .djpl_list {
  border: 1px solid #45454a
}
^.ui-btn-up-e:visited,
^.ui-btn-up-e a.ui-link-inherit,
^.ui-btn-up-e,
^.ui-btn-hover-e,
^.ui-btn-down-e,
^.ui-btn-active.ui-btn-up-e:visited,
^.ui-btn-active.ui-btn-hover-e:hover,
^.ui-btn-active.ui-btn-down-e a.ui-link-inherit {
  color: #feb953
}
^.ui-icon-alt .ui-icon,
^.ui-icon-alt .ui-icon-searchfield:after {
  background-color: rgba(18,18,19,0.3)
}
^.buy_tokens label.selected,
^.live_tabs .ui-btn-active.ui-btn-up-c .ui-btn-up-c,
^.categories .ui-btn-active.ui-btn-up-c .ui-btn-up-c,
^.footer .support_group li.ui-btn-up-c.support_message,
^.im_conversations .imc_actions .bcf_selectbox:active,
^.im_conversations .imc_actions .mbc_btn.__active,
^.im_conversations .imc_actions .mbc_btn:active {
  background: #141416
}
^.form div.ui-input-text input.ui-input-text,
^.input_wrapper.__field_password .ui-input-text,
^.bcm_input_row .bcm_form_input,
^.bcm_input_row .bcm_form_textarea,
^.tap_model_popup_container .tip_after_private .radio_list li .custom_price input,
^.head_confirm_box .hcb_links .input_wrapper {
  border: 1px solid #4c4c52
}
^.what_view {
  color: #d4d4d4;
  background-color: #121213;
  border: 1px solid #45454a
}
^.what_view .what_view_inner {
  background-color: #151516
}
^.what_view .what_view_corner {
  background: #121213;
  border-color: #0000 #0000 #45454a #45454a
}
^.translator_settings .mbc_select {
  border: 1px solid #3c3c40
}
^.msg_style_panel .preview_content,
^.mbc_search_block.__filter .sb_input,
^.mbc_search_block.__filter .sb_result {
  border: 1px solid #3f3f43
}
^.msg_style_panel .msp_p_item.__selected,
^.f_rptfmm .buy_tokens_sms_dimoco_package .input_phone.__extended_description .sdp_phone_input,
^.f_rptsmm .buy_tokens_sms_dimoco_package .input_phone.__extended_description .sdp_phone_input {
  border-color: #3d3d41
}
^.bongacams_popup_v2 {
  background-color: #121213;
  border: 1px solid #4c4c52
}
^#mainbar,
^#mainbar .ui-panel-inner,
^body,
^.ui-page,
^.ui-content,
^.ui-panel-content-wrap {
  background: #121213 !important
}
^#mainbar .ui-listview .mc_followers .btn_icon .mc_icon_follower {
  fill: #d8d8d8;
  stroke: #d8d8d8
}
^#mainbar .ui-li-divider,
^.ic_menu_wrp .icm_item,
^.buy_tokens_sms_form h3,
^.buy_tokens_sms_sale_form h3,
^.buy_tokens_wire_form h3,
^.cg_overlay .cg_accordion .cga_item,
^.cg_overlay .cg_adj_list.__no_btn .pll_item:last-child {
  border-bottom: 1px solid #45454a
}
^#mainbar .ui-li {
  border-color: #3e3e43
}
^#mainbar .ui-btn-active,
^#mainbar .mainbar-control.__end_position,
^#bChat .historyContainer,
^.buy_tokens_wire_first h4 {
  border-top: 1px solid #45454a
}
^#mainbar .ui-btn-active.ui-btn-up-c .ui-link-inherit .mm_icon.__free {
  border-color: #47474c
}
^#mainbar .btn_icon {
  border-top: 1px solid #3e3e43
}
^#mainbar .menu_item,
^.footer_content .copy a {
  color: #f5f5f5 !important
}
^#mainbar .menu_item.__collapsed,
^.bc_header_live_tabs .hlt_link:visited,
^.bc_header_live_tabs .hlt_link,
^.im_conversations .pm_valentine_day_themed_gift .pmvdtg_text,
^.im_conversations .pm_valentine_day_themed_gift .pmvdtg_signature {
  color: #d8d8d8 !important
}
^#mainbar .menu_item .mi_list .mil_sub_item .mm_arrow,
^.head_confirm_box .hcb_close_wrp.__bubble .hcb_close_icon {
  fill: #e17085
}
^#mainbar .language-dropdown {
  background: #121213;
  border-top: 1px solid #45454a
}
^#mainbar .language-dropdown .change-lang-btn.__selected,
^#mainbar .language-dropdown .change-lang-btn:active,
^.im_conversations .pm_upgraded_membership.__sapphire .pmu_holder.__filled,
^.im_conversations .pm_upgraded_membership.__sapphire .pmu_footer,
^.im_conversations .pm_upgraded_membership.__titan .pmu_holder.__filled,
^.im_conversations .pm_upgraded_membership.__titan .pmu_footer,
^.im_conversations .pm_upgraded_membership .pmu_holder.__filled,
^.im_conversations .pm_upgraded_membership .pmu_footer {
  background: #19191b
}
^.bc_online_notification .ont_close_wrp .ont_close,
^.bc_online_notification .ont_info .ont_i_text,
^.chat-content .bcm_paid_show_countdown .bcm_psc_text,
^.bcm_chat_footer .cf_item,
^.bcm_chat_footer .cf_item:hover .cfi_inner,
^.buy_tokens .bt_cashback_nft_wrp,
^.buy_tokens_sms_dimoco_package .input_phone.__extended_description,
^.buy_tokens_sms_dimoco_package .input_phone.__extended_description .sdp_phone_title,
^#buy_tokens_sms_confirm_code_form_tele2 .cct_form,
^.buy_tokens .sp_label {
  color: #c4c4c4
}
^.bc_confirmer .bcc_alert .caa_button,
^.bc_cmn_confirm_wrp .bc_confirm_popup .bcc_actions .bcc_button.__light {
  color: #cecece;
  background: #1a1a1c
}
^.bc_header_live_tabs .hlt_inner,
^.smileButton.ui-btn-hover-e,
^.tap_model_popup_container .tip_after_private .form_actions,
^.forward_to_remote .ftr_header {
  background-color: #18181a
}
^.holiday_discount_alert.__world_sexual_health_day_2026 .hda_buy {
  color: #596ef8
}
^.bc_ms_icon.__diamond {
  fill: #f65d5b
}
^.bc_ms_icon.__exclusive {
  fill: #efefef
}
^.bc_ms_icon.__sapphire {
  fill: #9452ff
}
^.bc_ms_icon.__white_bg.__diamond {
  color: #f65d5b
}
^.bc_ms_icon.__white_bg.__exclusive {
  color: #efefef
}
^.bc_ms_icon.__white_bg.__sapphire {
  color: #9452ff
}
^.bc_context_menu_list,
^.im_conversations .imc_actions .bcf_selectbox .bcsb_dropdown,
^.bcsb_dropdown.__in_portal,
^.im_conversations .imc_history_box .imch_item.__action_btns .imch_msg_actions {
  background: #121213;
  border: 1px solid #38383c
}
^.bc_context_menu_list .cml_item,
^.im_conversations .imc_actions .bcf_selectbox .bcsb_item,
^.bcsb_dropdown.__in_portal .bcsb_item {
  color: #f5f5f5;
  border-bottom: 1px solid #38383c
}
^.auto_purchase_form .bcf_control {
  background: #151516;
  border: 1px solid #4c4c52
}
^.bc_dropdown.__reactions .mbcm_item+.mbcm_item {
  border-left: 1px solid #38383c
}
^.bcm_radio .bcm_r_input:disabled~.bcm_r_mark {
  background: #1b1b1d;
  border-color: #3c3c40
}
^.bcm_radio .bcm_r_input:disabled~.bcm_r_mark:before,
^.bcm_toggle_switcher_holder .ts_switcher {
  background: #222225
}
^.bcm_radio .bcm_r_mark {
  background: #252527;
  border: 1px solid #4c4c52
}
^.bcm_radio .bcm_r_text,
^.mls_item_online.actions a.chat,
^.bcm_filter_result .fr_value,
^.purchase_vip label.choice.discount .eur-vip s,
^.history .service.__group .msgs_desc,
^.chat_prompt_wrp .cp_msg.__group_join .cp_msg_s_text,
^.chat_prompt_wrp .cp_msg.__group_join .cp_msg_arrow .cp_msg_arrow_icon,
^.bcm_chat_selection .bcm_cs_desc,
^.bcm_chat_selection .bcm_cs_price,
^.buy_tokens_wire_form p,
^.buy_tokens_sms_dimoco_form p,
^.buy_tokens_digiseller_form p,
^#buy_tokens_payment_type .ui-collapsible .package.__simple .brbw_input:disabled~.ui-btn .ui-btn-text,
^#buy_tokens_payment_type .bt_radio_btn_wrp.__simple .brbw_input:disabled~.ui-btn .ui-btn-text,
^.buy_tokens .choice .ui-btn-inner,
^.buy_tokens .choice .foreign_currency,
^.buy_tokens .bt_price_old,
^.buy_tokens .pkg_cybersale .bt_strike_price,
^.buy_tokens .bt_payment_description,
^.buy_tokens_sms_dimoco_package .input_phone.__sending .status,
^.buy_tokens_sms_dimoco_package .input_phone .sdp_phone_title,
^.buy_tokens_sms_dimoco_package .form_note_btn,
^.f_rptfmm .buy_tokens_sms_dimoco_package .package+p,
^.f_rptsmm .buy_tokens_sms_dimoco_package .package+p,
^.f_rptfmm .buy_tokens_sms_dimoco_package .package+p+p,
^.f_rptsmm .buy_tokens_sms_dimoco_package .package+p+p,
^.buy_tokens .sp_current_package .spcp_approx,
^.buy_tokens .bt_custom_amount_field,
^.buy_tokens .bt_custom_amount_field .caf_input_custom_field,
^.buy_invisible_package .choice_description,
^.im_conversations .imc_history_box .imch_item.__translate,
^.im_conversations .imc_history_box .imch_item.__action_btns .imch_action_btn,
^.cg_overlay .cg_options_list .ol_item.__checked .cgo_radio {
  color: #bcbcbc
}
^.bcm_radio.__background .bcm_r_text {
  color: #d4d4d4;
  text-shadow: 0 1px transparent
}
^.bc_phone_select_box .bcsb_input_wrp {
  color: #c4c4c4;
  border: 1px solid #444449;
  background: #161617
}
^.bc_phone_select_box .bcsb_input {
  color: #c4c4c4;
  background: #121213;
  border: 1px solid #444449
}
^.bc_phone_select_box .bcsb_input::placeholder,
^.bcm_input_row .bcm_form_input::placeholder,
^.bcm_input_row .bcm_form_textarea::placeholder,
^.purchase_vip label.choice.discount_gift_card .eur-vip s,
^.purchase_vip label.choice.discount .eur-vip s,
^.mbc_search_block .sb_input::placeholder,
^.im_conversations .imc_search .imcs_icon,
^.im_conversations .imc_search .search_input::placeholder,
^.imch_reply .imch_r_msg,
^.cg_vibe_with_me.__vwm_app .vwm_a_qr_code {
  color: #b3b3b3
}
^.bcm_checkbox_row .cr_mark {
  background: linear-gradient(#2f2f33,#252528)
}
^.bc_social_networks .sn_count {
  color: #bcbcbc;
  background: #121213
}
^.range_slider .rs_track,
^.im_conversations .imc_history_box .imch_date_wrp:before,
^.im_conversations .imc_history_box .imch_date_wrp:after,
^.cg_vibe_with_me.__vwm_app .vwm_a_qr_icon_wrp.__vwm_active {
  background: #252527
}
^.range_slider .rs_progress.__zero:after {
  background: #121213;
  border: 1px solid #404045
}
^.mls .mls_pager_header {
  color: #bcbcbc;
  border-top: 1px solid #45454a
}
^.bcm_filter_btn_wrp .bcm_fbw_btn {
  color: #cecece;
  background: #121213;
  border: 1px solid #3f3f44
}
^.bcm_filter_btn_wrp .bcm_fbwb_icon {
  color: #dadada
}
^.mls_tabs_container {
  background: linear-gradient(#121213,#1a1a1c);
  border-bottom: 1px solid #38383c
}
^ul.mls_tabs li {
  text-shadow: 1px -1px transparent;
  background-color: #19191b;
  border: 1px solid #45454a
}
^ul.mls_tabs li:hover,
^.phone_confirm_box .confirm_phone_form .bc_phone_select_box.__focused .bcsb_input,
^.phone_confirm_box .confirm_phone_form .bc_phone_select_box.__focused .bcsb_input_wrp {
  border-color: #4c4c52
}
^ul.mls_tabs li a,
^.im_conversations .pm_offering p {
  color: #d4d4d4 !important
}
^.pager>div {
  background-image: linear-gradient(#29292c,#222224)
}
^.forget_info,
^.history p.undefined,
^.history p.undefined dfn,
^.aChat .message .col1,
^.buy_tokens .gray_desc,
^.verification_failed p,
^.credit_box_success .gray_desc {
  color: #b2b2b2
}
^.live_tabs .tabs_list .tabsl_item.__new .tabsl_count:before {
  background: linear-gradient(#141416,#141416) padding-box padding-box,linear-gradient(270deg,#ff8201,#fca710) border-box
}
^.form .form_errors_row {
  background-color: #19191b
}
^.footer_content {
  background-color: #222225
}
^.footer_content .separator {
  background-color: #343438;
  border-top: 1px solid #333336
}
^.center-wrapper .descr_block {
  border: 1px dashed #4c4c52
}
^.icon_membership.icon_free {
  background: #121213;
  border: 2px solid #47474c
}
^.mc_status_icon.__icon_free {
  background-color: #121213;
  border: 2px solid #47474c
}
^.padded.grey,
^.padded.stripe,
^.buy_tokens_wire_form .odd,
^.page_im .im_conversations .imc_action_bar {
  background-color: #1a1a1c
}
^.vip-payment .ui-btn-up-c {
  border: 2px solid #45454a !important
}
^.vip-payment .head-vip {
  background-color: #141415;
  border-bottom: 1px solid #45454a
}
^.vip-payment.pkg_cybersale .eur-vip,
^.vip-payment.pkg_cybersale .inner-vip .vip-blocks,
^.maroon_strong,
^.buy_tokens .pkg_cybersale .bt_highlight,
^.buy_tokens .pkg_cybersale .foreign_currency,
^.buy_tokens .bt_custom_amount_alert .caa_cell.__accent,
^.buy_tokens.__holiday .bt_highlight {
  color: #dd7488
}
^.vip-payment .eur-vip {
  color: #e17085;
  border-left: 1px solid #444449
}
^.vip-payment .eur-vip b,
^.fancybox-error,
^.buy_tokens .mobile_pay_form .rich,
^.loyalty_box.speaker,
^.loyalty_box.speaker .lb_white,
^a {
  color: #cfcfcf
}
^.vip-payment .head-vip b,
^.notfound .nt_des,
^.notfound p.nf_bottom a,
^.notfound p.nf_bottom {
  color: #b4b4b4
}
^.vip-payment .foot-vip {
  color: #b4b4b4;
  background-color: #141415;
  border-top: 1px solid #45454a
}
^.vip-exist,
^.buy_tokens_warning,
^.f_rptfmm .buy_tokens_sms_dimoco_package .input_phone.__extended_description,
^.f_rptsmm .buy_tokens_sms_dimoco_package .input_phone.__extended_description {
  border: 1px solid #36363a
}
^.votes_percent_line,
^#bChat .msg_wrp .ch_m_translator_link.__source,
^#bChat .msg_wrp .ch_m_translator_link.__translate,
^.cg_vibe_with_me.__vwm_app .vwm_a_qr_code_wrp {
  background: #121213;
  border: 1px solid #45454a
}
^.bitcoin_paypal_popup .close_button {
  color: #f5f5f5;
  background: #121213
}
^.suspect_login_page form,
^.vip_overview_page .vo_badge,
^.im_conversations .imc_item.__selected .imci_wrap {
  background: #151516
}
^.suspect_login_page .form_error {
  color: #ff5252;
  background-color: #19191b
}
^#bChat .history p.deleted .msg {
  color: #b6b6b6 !important
}
^#bChatFullScreenHistory p.groupChatWelcome,
^.history p.groupChatWelcome {
  color: #5252ff !important
}
^.history .service.__group {
  background: #141415;
  border: 1px solid #38383c
}
^.history p.topicChange {
  color: #e17085 !important
}
^.history p.model .icon_avatar,
^.imc_footer_reply .imc_fr_inner {
  background-color: #161618
}
^.smileButton {
  background-color: #18181a;
  border: 1px solid #3d3d41
}
^.smileButton .smile_btn_icon,
^.chat-content #bChatInputContainer .smile_btn_icon {
  stroke: #c4c4c4
}
^.smileButton.__active .smile_btn_icon {
  stroke: #f5f5f5
}
^#scroll-chat-btn .inner:after,
^.popHint.topLeft:after,
^.popHint.topRight:after,
^.popHint.topCenter:after {
  border-top-color: #2b2b2e
}
^.chat_prompt_wrp .cp_msg_after {
  background: linear-gradient(#202023 0%,#fff0 25%)
}
^.chat-content .chat_panel_wrap>.bcm_ps_countdown_container .bcm_paid_show_countdown {
  border-top: 1px solid #36363a
}
^.chat-content .bcm_paid_show_countdown {
  background: #18181a;
  border-bottom: 1px solid #36363a
}
^.bcm_chat_selection {
  background: #121213;
  border: 1px solid #3d3d41
}
^.bcm_chat_selection .bcm_cs_list_item {
  border-bottom: 1px solid #3d3d41
}
^.chat-panel .history .system_msg.tip_success.__tip_menu .msg_clear_btn {
  border: 1px solid #2b2b2e
}
^.chat-panel .history .system_msg.follow_plank .fp_container {
  color: #e6e6e6;
  background: rgba(27,27,29,0.7)
}
^.chat-panel .history .system_msg.msg_activity .ma_avatar_wrp:before {
  border: 1px solid rgba(43,43,46,0.8)
}
^.chat-panel .msg_chat_game,
^#buy_tokens_payment_type .ui-collapsible .package .ui-btn.selected .ui-btn-inner,
^#buy_tokens_payment_type .bt_radio_btn_wrp .ui-btn.selected .ui-btn-inner,
^.bt_select_modal .slm_i_input:checked~.slm_i_label {
  background: #141415
}
^.chat-content .cp_navigation .cpnl_item,
^.im_conversations .imc_history_box .imch_item_wrp.__highlight:before {
  background-color: #212124
}
^.chat-content #bChatInputContainer {
  background-color: #121213;
  border-top: 1px solid #323235;
  border-bottom: 1px solid #323235
}
^.chat-content #bChatInputContainer .sendInput {
  color: #c4c4c4;
  background-color: #18181a
}
^.chat-content #bChatInputContainer .sendInput::placeholder,
^.chat-content #bChatInputContainer .sendInput.__placeholder:before {
  color: #c4c4c4 !important
}
^.ic_menu_wrp .ic_menu {
  background: #151516;
  border: 1px solid #45454a
}
^#bChat .msg_wrp.__tr_enabled.__tr_show_tooltip {
  background: #171718
}
^#bChat .msg_wrp .ch_m_translator_link.__empty_translate {
  color: #cecece;
  background: #171718
}
^.chat_user_menu .cum_header {
  color: #d8d8d8;
  border-bottom: 1px solid #38383c
}
^.chat_user_menu .cum_info .mi_content,
^.chat_user_menu .cum_action .ma_text {
  color: #b5b5b5
}
^.bc_new_message_separator:before,
^.bc_new_message_separator:after {
  background-color: #262628
}
^.bcm_chat_footer {
  background: #18181a;
  border-top: 1px solid #323235;
  border-bottom: 1px solid #36363a
}
^.bcm_chat_footer .cf_balance {
  color: #c4c4c4;
  background: #18181a
}
^.bcm_chat_invite .bcm_ci_badge_icon,
^.bcm_chat_invite .bcm_ci_toggle_icon {
  fill: rgba(245,245,245,0.2)
}
^.video-panel .layover .layoverText .layoverIns,
^.ru_buy_tokens_mobile_pay p.provider_attention {
  color: #c5c5c5
}
^.video-panel .layover .layoverLimitText {
  color: #b2b2b2 !important
}
^.layover_before_private .lbp_content {
  background-color: rgba(18,18,19,0.4)
}
^.layover_before_private .lbp_item,
^.bc_confirm_popup.fan_boosted_confirm .bcc_container_info {
  color: #d5d5d5
}
^.bcm_oops_popup_layover .opl_message {
  color: #d8d8d8;
  background: #161618
}
^.quality_settings_block .qsb_item.__active {
  background-color: rgba(18,18,19,0.2)
}
^.cam2cam_modal {
  border: 2px solid #444449
}
^.tap_model_popup_container .tip_after_private .tap_title .tap_model_name {
  color: #dc748a
}
^.tap_model_popup_container .tip_after_private .radio_list li {
  background-color: #121213;
  border: 1px solid #45454a
}
^.tap_model_popup_container .tip_after_private .radio_list li.selected {
  background: #131315
}
^.tap_model_popup_container .tip_after_private .radio_list li .custom_price input:disabled {
  background-color: #121213;
  border-color: #3c3c41
}
^.tap_model_popup_container .tip_after_private .radio_list li .radio_help {
  color: #a1a3b0
}
^.tap_model_popup_container .tip_after_private .form_actions .tap_note {
  color: #e3e3e3
}
^.tap_model_popup_container .tip_after_private .form_actions .tap_note .tap_red {
  color: #dc758a
}
^.tap_model_popup_container .tip_after_private .form_error_list {
  color: #dc758a;
  background-color: #18181a
}
^.fan_boosted_animation.__stopping .fba_timer {
  stroke: #ff527b
}
^.bc_confirm_popup.fan_boosted_confirm .bcc_header .bcc_title {
  color: #f4f4f4
}
^.fancybox-skin {
  color: #cfcfcf;
  background: #151516
}
^.head_confirm_box {
  background-color: #131315
}
^.head_confirm_box .hcb_close_wrp:hover .hcb_close {
  color: #e46d89
}
^.head_confirm_box .hcb_close_wrp.__bubble:hover .hcb_close_icon {
  fill: #e46d89
}
^.phone_confirm_box .fcb_not_show .ns_mark {
  border: 2px solid #4c4c52
}
^.phone_confirm_box .confirm_phone_form {
  color: #c9c9c9
}
^.phone_confirm_box .confirm_phone_form .cpf_code .bcf_control {
  color: #cfcfcf;
  background: #121213;
  border: 1px solid #4c4c52
}
^.promo_in_banner_popup .pibp_name {
  color: #e17084 !important
}
^#admin_chat_popup .admin_info .details h4 {
  border-bottom: 1px solid #4c4c52
}
^.buy_tokens .bt_email_wrp,
^.buy_tokens .sp_form {
  color: #c4c4c4;
  border: 1px solid #414146
}
^.buy_tokens .btc_input_warning {
  color: #f65a61
}
^.buy_tokens .limit_alert,
^.buy_tokens .buy_tokens_mbway_form .bt_input_wrp .err,
^.buy_tokens .buy_tokens_wasabigate_form .bt_input_wrp .err,
^.buy_tokens .buy_tokens_hkpay_form .bt_input_wrp .err,
^.buy_tokens .coinpayments_form .bt_input_wrp .err.err_show,
^.buy_tokens .mobile_pay_form .bt_input_wrp .err.err_show,
^.buy_tokens .bt_custom_amount_field .caf_input_warning {
  color: #ff5272
}
^.buy_vip_sms_form .price span,
^.buy_vip_sms_form .price em,
^.ru_buy_tokens_sms_pay .heading h4,
^.ru_buy_tokens_mobile_pay .heading h4,
^.ru_buy_tokens_sms_pay ul a {
  color: #db7688
}
^.buy_tokens .mobile_pay_form input,
^.confirm_code_form input {
  border: 1px solid #45454a;
  border-color: #4c4c52 #45454a #45454a #4c4c52
}
^.buy_tokens .mobile_pay_form input.disabled {
  color: #bcbcbc;
  background-color: #1a1a1c
}
^.confirm_code_form {
  color: #bcbcbc;
  border: 1px solid #45454a
}
^table.wire-tab td {
  color: #f5f5f5;
  border: 1px solid #45454a
}
^.buy_tokens .foreign_currency span {
  color: #5297ff
}
^.verify_info_box p,
^.confirm_box {
  color: #d9d9d9
}
^.confirm_box .example_box .ex {
  background-color: #121213;
  border: 1px solid #3e3e43
}
^.confirm_box .example_box .ex .amount span,
^.confirm_box .example_box .info {
  color: #e86969
}
^#buy_tokens_payment_type .ui-collapsible .package .ui-btn:after,
^#buy_tokens_payment_type .bt_radio_btn_wrp .ui-btn:after,
^.buy_tokens .bt_select_change:after {
  border: 1px solid #414146
}
^#buy_tokens_payment_type .payment_icon,
^.bt_select_modal .payment_icon {
  background: #121213;
  border: 1px solid #3e3e42
}
^.buy_tokens .choice.discount_box,
^.buy_tokens label.choice.discount_box,
^.buy_tokens .sp_current_package {
  color: #bcbcbc;
  background: #131315;
  border: 1px solid #45454a
}
^.buy_tokens .sms_amount_error,
^.buy_tokens .bitcoin_form .bitcoin_error,
^.buy_tokens .bitcoin_form .info_text {
  color: #f25e5e
}
^.buy_tokens .discount_box .original_sum,
^.buy_tokens label.discount_box .original_sum,
^.show_hide_packages {
  color: #b9b9b9
}
^.gift_card_section .gift_card_box {
  background: #141416;
  border: 1px solid #45454a
}
^.buy_tokens .ui-radio label {
  background-color: #141416 !important
}
^.buy_tokens .pkg_cybersale .ui-radio label,
^.buy_tokens .bt_select_change {
  color: #bcbcbc !important
}
^.buy_tokens_redirect .btr_text.__wait,
^.buy_tokens_header_board .hbt_paragraph,
^.im_conversations .imc_actions .imca_text.__new_msg,
^.im_conversations .imc_empty .imce_text,
^.im_conversations .imci_message,
^.im_typing {
  color: #bababa
}
^.loyalty_box.speaker:before {
  border-top: 5px solid #2b2b2e
}
^.loyalty_box.speaker:after {
  border-top: 6px solid #45454a
}
^.loyalty_box.bit {
  color: #cbcbcb;
  background: #141416
}
^.loyalty_box.bit .lb_title {
  color: #eaeaea
}
^.loyalty_box.bit .lb_green {
  color: #92ff52
}
^.loyalty_box.bit .lb_warn:before {
  border-top: 1px solid #3a3a3e
}
^.loyalty_box.leaving .lb_warn:before,
^.loyalty_box.leaving .lb_warn:after {
  background: #2b2b2e
}
^.buy_tokens .confirm_code_form_resend {
  background: #131315;
  border: 1px solid #45454a
}
^.buy_tokens_header_board .hbt_headline_block,
^.cg_service_list .cg_sub_header {
  border-bottom: 1px solid #333336
}
^.buy_tokens_header_board .hbt_headline {
  color: #e8e8e8
}
^.buy_tokens .bt_custom_amount_alert {
  color: #bcbcbc;
  background: #131315;
  border: 1px solid #414146
}
^.buy_tokens .bt_custom_amount_field .caf_input_currency {
  color: #bcbcbc;
  background: #141415
}
^.show_hide_packages .sh_label .sh_icon {
  color: #c5c5c5;
  background: #141416;
  border: 1px solid #45454a
}
^.buy_tokens .bt_anim_icon_success .ais_item:after {
  border: 6px solid #2b2b2e
}
^.bt_select_modal {
  background: #121213;
  border: 1px solid #404044
}
^.bt_select_modal .slm_i_label {
  background: #121213;
  color: #bcbcbc !important
}
^#discount_green_purple .dgp_clock_hand {
  color: #c58bb5
}
^.gc_popup {
  color: #9d78d8;
  background: #121213
}
^.gc_popup .gc_title .gc_red {
  color: #e07185
}
^#gc_popup_vip .gc_title .gc_red,
^.ac_change_photo .ac_cp_desc.__error {
  color: #e36e84
}
^.gc_popup.gc_popup_hor .gc_title {
  color: #9d78d8
}
^.fancybox-item.fancybox-close.pl_un_default_close,
^.fancybox-item.fancybox-close.di_ex_close {
  color: #f5f5f5;
  background: #121213 !important
}
^.mbc_search_block.__filter .sb_result .sbr_head.__recent {
  border-bottom: 1px solid #3f3f43
}
^.mbc_search_block .sb_result .sbr_count {
  color: #c2c2c2
}
^body {
  color: #cfcfcf;
  background: #242426
}
^.popHint .popHintFooter {
  background: #141416;
  border-top: 1px solid #3c3c41
}
^.popHint.bottomLeft:after,
^.popHint.bottomRight:after,
^.popHint.bottomCenter:after {
  border-bottom-color: #2b2b2e
}
^.popHint.leftTop:after,
^.popHint.leftBottom:after,
^.popHint.leftCenter:after {
  border-left-color: #2b2b2e
}
^.popHint.rightTop:after,
^.popHint.rightBottom:after,
^.popHint.rightCenter:after {
  border-right-color: #2b2b2e
}
^#bChat.bChatModel .popHint:after {
  border-top-color: #2f2f33
}
^.profile_avatar_popup .account_actions,
^.account_actions {
  background-color: #141415;
  border-top: 1px solid #49494e
}
^.profile_avatar_popup .account_actions .mbc_btn.ac_a_btn_cancel:active,
^.account_actions .mbc_btn.ac_a_btn_cancel:active {
  background-color: #323235
}
^.account_preview .acp_img_wrp {
  border: 2px solid #2b2b2e
}
^.account_preview .acp_btn {
  color: #cecece;
  text-shadow: 0 1px transparent;
  background: #252527
}
^.ac_change_photo .ac_cp_line {
  background-color: #2b2b2e
}
^.vip_overview_page,
^.vip_overview_page .vo_ttl {
  color: #c3c3c3
}
^.page_content .cancel_subscription.__dashed {
  border: 1px dashed #45454a
}
^.vip_overview_page_badge {
  color: #c3c3c3;
  background: #151516
}
^.vip_overview_stock_page {
  color: #e0e0e0;
  border: 1px solid #313134
}
^.vip_overview_stock_page .sp_privilege {
  border-top: 1px solid #37373a
}
^.invisible_mode_overview_plank {
  color: #9099c1;
  background: #151516
}
^.page_im .im_conversations,
^.smilePanelTabs {
  background: #1a1a1c
}
^.im_conversations .imc_actions .bcf_selectbox,
^.im_conversations .gift_card_promo_wrapper {
  background: #121213;
  border: 1px solid #3e3e43
}
^.im_conversations .imc_actions .mbc_btn.__btn_msg.__disabled {
  background: #2e2e31
}
^.im_conversations .imc_actions .imca_icon {
  color: #d6d6d6;
  fill: #d6d6d6
}
^.im_conversations .imc_item.__new .imci_wrap .imci_message,
^.im_conversations .imci_user_name,
^.im_conversations .imc_followers .imcf_name {
  color: #d7d7d7
}
^.im_conversations .mbc_alert.__tip,
^.im_conversations .imc_history_box .imch_group.__own .imch_item.__tip,
^.im_conversations .pm_upgraded_membership.__unlimited .pmu_footer {
  background: #171719
}
^.im_conversations .imc_history_box .imch_msg_check.__unsent .imch_mc_icon,
^.im_conversations .imc_search .imcs_reset .imcs_r_icon {
  fill: #e36d84
}
^.im_conversations .imc_history_box .imch_group.__own .imch_item,
^.pm_activation_2fa .pmaf_body,
^.im_conversations .pm_upgraded_membership.__exclusive .pmu_holder.__filled {
  background: #161618
}
^.im_conversations .imc_history_box .imch_group.__own .imch_item:first-of-type:after {
  border-left: 6px solid #303033
}
^.im_conversations .imc_history_box .imch_group.__own .imch_item.__tip:first-of-type:after {
  border-left-color: #313134
}
^.im_conversations .imc_history_box .imch_item {
  background: #151517
}
^.im_conversations .imc_history_box .imch_item:first-of-type:after {
  border-right: 6px solid #2f2f32
}
^.im_conversations .imc_history_box .imch_item.__selected:first-of-type:after {
  border-right-color: #353538
}
^.im_conversations .imc_history_box .imch_item.__sq .imc_question_link,
^.im_conversations .imch_item.__default a {
  color: #52b0ff
}
^.im_conversations .imc_history_box .imch_item.__sq_error {
  color: #e17085;
  background: #171719
}
^.im_conversations .imc_history_box .imch_item.__sq_error:first-of-type:after {
  border-right-color: #313134
}
^.im_conversations .imc_history_box .imch_item.__action_btns .imch_action_btn.__active {
  background: #202022
}
^.im_conversations .imc_history_box .imch_context_menu,
^.cg_service_list .cg_sl_price {
  border: 1px solid #38383c
}
^.im_conversations .imc_history_box .imch_context_menu .imch_cm_btn {
  color: #f5f5f5;
  background: #121213;
  border-bottom: 1px solid #38383c
}
^.im_conversations .imc_history_box .mbc_btn.__fab {
  background: #121213;
  border-color: #38383c
}
^.im_conversations .imc_search .search_input {
  color: #d6d6d6;
  background: #1b1b1d;
  border: 1px solid #47474c
}
^.imc_footer_reply {
  color: #d8d8d8;
  background: #121213;
  border-top: 1px solid #3d3d41
}
^.imc_tip_before_start .tbs_content {
  background: #121213;
  border: 2px dashed rgba(74,74,79,0.5)
}
^.im_conversations .imch_contact_card {
  border: 2px dashed #4a4a4f
}
^.im_typing .imt_dots .imtd_item {
  background: #3d3d41
}
^.pm_activation_2fa .pmaf_title {
  color: #cacaca
}
^.pm_activation_2fa .pmaf_info_wrp {
  border-top: 1px solid #37373b
}
^.pm_activation_2fa .pmaf_info {
  color: #e7e7e7
}
^.pm_activation_2fa .pmaf_a_text {
  color: #c6c6c6
}
^.pm_activation_2fa .pmaf_footer {
  background: #1e1e20
}
^.im_conversations .pm_upgraded_membership {
  color: #f5f5f5;
  background: #121213;
  border: 2px solid #333336
}
^.im_conversations .pm_upgraded_membership.__platinum {
  border-color: #37373b
}
^.im_conversations .pm_upgraded_membership.__platinum .pmu_holder.__filled,
^.im_conversations .pm_upgraded_membership.__platinum .pmu_footer,
^.im_conversations .pm_upgraded_membership.__unlimited .pmu_holder.__filled,
^.cg_overlay .gc_btn.__white:active {
  background: #1d1d1f
}
^.im_conversations .pm_upgraded_membership.__unlimited {
  border-color: #313134
}
^.im_conversations .pm_upgraded_membership.__sapphire,
^.im_conversations .pm_upgraded_membership.__titan {
  border-color: #323236
}
^.im_conversations .pm_upgraded_membership.__sapphire .pmu_highlight {
  color: #8b52ff
}
^.im_conversations .pm_upgraded_membership.__diamond {
  border-color: #343438
}
^.im_conversations .pm_upgraded_membership.__diamond .pmu_highlight {
  color: #ff527b
}
^.im_conversations .pm_upgraded_membership.__exclusive {
  border-color: #303033
}
^.im_conversations .pm_upgraded_membership .pmu_updated_status,
^.im_conversations .pm_upgraded_membership .pmu_current_status {
  color: #94a2bc
}
^.im_conversations .gift_card_promo_wrapper .gift_card_promo .gift_card_promo_code_wrapper {
  color: #a079d8
}
^.im_conversations .gift_card_promo_wrapper .gift_card_promo .gift_card_promo_code_wrapper p span {
  color: #e17087
}
^.im_conversations .gift_card_promo_wrapper .gift_card_promo .gift_card_promo_code_wrapper .gift_card_promo_code_ribbon_left:before {
  border-left: 10px solid #2b2b2e
}
^.im_conversations .gift_card_promo_wrapper .gift_card_promo .gift_card_promo_code_wrapper .gift_card_promo_code_ribbon_right:before {
  border-right: 10px solid #2b2b2e
}
^.im_conversations .pm_offering .pmo_bonus {
  color: #e26f85
}
^.im_conversations .pm_downgrade_membership {
  border: 2px solid #303033
}
^.im_conversations .pm_downgrade_membership .dmh_content .hc_status {
  color: #91afbf;
  background-color: #121213
}
^.im_conversations .pm_downgrade_membership .dm_title .dmt_username,
^.im_conversations .pm_downgrade_membership .dm_title .dmt_desc {
  color: #949dbc
}
^.im_conversations .pm_downgrade_membership .dm_title .dmt_desc .td_accent {
  color: #e76978
}
^.im_conversations .pm_valentine_day_themed_gift .pmvdtg_card {
  background: #121213;
  border: 1px solid #3c3c41
}
^.smilePanelTabs:before {
  background: linear-gradient(#29292c,#efefef00)
}
^.cg_overlay .cg_options_list .ol_item.__checked {
  border-color: #3f3f44
}
^.cg_overlay .gc_btn {
  color: #cecece;
  border: 1px solid #3f3f44
}
^.cg_overlay .gc_btn.__gray {
  background: linear-gradient(#151516,#1a1a1c)
}
^.cg_overlay .gc_btn.__gray:active {
  background: #323235
}
^.cg_overlay .gc_btn.__disabled {
  background: #121213;
  border: 1px solid #3f3f44
}
^.cg_overlay .cg_prize_list .li_text,
^.cg_overlay .cg_prize_list.__wheel .li_text,
^.bc_token_keno_field .tkf_cube .fc_body .fc_number {
  color: #bd9494
}
^.cg_overlay .cg_input_wrp .cgi_input_amount {
  color: #d8d8d8;
  background: #151516;
  border: 1px solid #4c4c52
}
^.cg_overlay .cg_accordion .cga_item:active {
  background-color: #1d1d1f
}
^.cg_overlay .cg_adj_empty_playlist {
  background-color: #131315;
  border: 1px solid #45454a
}
^.cg_overlay .cg_adj_form .djf_link {
  color: #f5f5f5;
  border: 1px solid #3c3c40
}
^.cg_overlay .cg_adj_form .djf_premium.__checked {
  background-color: #141415
}
^.cg_auto_dj_box .adjb_icon,
^.cg_overlay .cg_prize_list.__slot .pli_icon_wrp .iw_icon,
^.cg_slm_slot .slms_item.__active .slms_icon {
  fill: #bd9494
}
^.cg_overlay .cg_adj_icon_premium .ip_icon {
  fill: #cfcfcf
}
^.cg_overlay .cg_adj_list .pll_item {
  color: #c1c1c1;
  background-color: #131315;
  border-bottom: 1px solid #45454a
}
^.cg_overlay .cg_game_field .sm_body {
  background-image: linear-gradient(#1b1b1c 0%,#232326 100%)
}
^.cg_overlay .cg_game_field .si_item:after {
  background: #212123
}
^.cg_overlay .cg_game_field .si_item .sii_icon {
  fill: #f5f5f5
}
^.cg_vibe_with_me.__vwm_app .vwm_a_list {
  color: #f5f5f5;
  background: #171719
}
^.cg_vibe_with_me.__vwm_app .vwm_ai_container:before {
  background: rgba(43,43,46,0.5)
}
^.cg_service_list .cg_sl_item {
  color: #d8d8d8;
  border-bottom: 1px solid #2e2e31
}
@media (any-hover:hover) and (pointer:fine) {
  ^.mbc_menu .mbcm_item:hover,
  ^.cg_overlay .gc_btn.__white:hover,
  ^.cg_overlay .gc_btn.__gray:hover {
    background: #1b1b1d
  }
  ^.profile_avatar_popup .account_actions .mbc_btn.ac_a_btn_cancel:hover,
  ^.account_actions .mbc_btn.ac_a_btn_cancel:hover {
    background-color: #1b1b1d
  }
  ^.im_conversations .imc_actions .bcf_selectbox:hover {
    background: #141416
  }
  ^.im_conversations .imc_actions .mbc_btn.__btn_msg:hover .imca_text.__new_msg {
    color: #d6d6d6
  }
}
@media screen and (max-width:480px) {
  ^.vip-payment .inner-vip .vip-blocks .cont-vip {
    border-bottom: 1px solid #45454a
  }
}
@media (max-width:40em) {
  ^.notfound p {
    color: #d8d8d8
  }
}
@media (min-width:46em) {
  ^.aChat .message .col1 {
    color: #b2b2b2
  }
}
@media (max-width:780px) {
  ^.im_conversations .pm_upgraded_membership.__gold .pmup_icon,
  ^.im_conversations .pm_upgraded_membership.__sapphire .pmup_icon,
  ^.im_conversations .pm_upgraded_membership.__titan .pmup_icon {
    background-color: #19191b
  }
  ^.im_conversations .pm_upgraded_membership.__platinum .pmup_icon {
    background-color: #1d1d1f
  }
  ^.im_conversations .pm_upgraded_membership.__unlimited .pmup_icon {
    background-color: #171719
  }
  ^.im_conversations .pm_upgraded_membership.__diamond .pmup_icon {
    background-color: #1b1b1d
  }
  ^.im_conversations .pm_upgraded_membership.__exclusive .pmup_icon {
    background-color: #161618
  }
}`.replaceAll('^', '.bc-dark-m ');
const DARK_FIX_CSS = () => `^.bc_fade_scroll:before,
^.bc_fade_scroll:after,
^.bc_fade_scroll.__listing_tags:before,
^.bc_fade_scroll.__listing_tags:after {
  background: linear-gradient(90deg, #121216, rgba(18,18,22,0))!important
}
^.mls_hash_tag {
  --bc-thm-hashTag-shadow-bg:#121216;
  --bc-thm-hashTag-shadow-bg-alpha0:rgba(18,18,22,0);
  --bc-thm-hashTag-viewAllLink-wrp-bg:#121216
}
^.bc_notice {
  --bc-thm-notice-color:#e6e6e6;
  --bc-thm-notice-bg:linear-gradient(#121213, #121213);
  --bc-thm-notice-borderColor:linear-gradient(180deg, #3a3a3f, #3a3a3f);
  --bc-thm-notice-close-iconColor-hover:#dedede;
  --bc-thm-notice-link-color:#46c2ff;
  --bc-thm-notice_secondary-paragraph-color:#d8d8d8;
  --bc-thm-notice_countdownGoal-bg:#18181c;
  --bc-thm-notice_countdownGoal-borderColor:#8a28eb80;
  --bc-thm-notice_follow-borderColor:linear-gradient(90deg, #31c4ee, #20b8e3);
  --bc-thm-notice_pin-borderColor:linear-gradient(90deg, #e75ec9, #d331b0);
  --bc-thm-notice_love-borderColor:linear-gradient(90deg, #fe4668, #fe254d)
}
^.bc_notice.__countdown_goal,
^.bc_notice .nt_message {
  background-color: #18181c;
  color: #e6e6e6
}
^.bc_notice .nt_message,
^.bc_notice .nt_message p,
^.bc_notice .nt_message span:not(.nt_accent) {
  color: #e6e6e6
}
^.chat-tab,
^.chat-panel,
^.history,
^#bChat {
  --bc-thm-sysMsg_game-resultWrp-bg:#18181c;
  --bc-thm-sysMsg_game-label-color:#e6e6e6;
  --bc-thm-sysMsg_game-content-color:#ee5b78;
  --bc-thm-sysMsg_game-time-color:#a0a0a8;
  --bc-thm-sysMsg_topicChange-color:#ee5b78;
  --bc-thm-sysMsg_modelNote-color:#5fd97f;
  --bc-thm-sysMsg_serviceGroup-bg:#18181c;
  --bc-thm-sysMsg_serviceGroup-borderColor:#2b2b2e;
  --bc-thm-sysMsg_serviceGroup-boxShadowColor:#0000;
  --bc-thm-sysMsg_serviceGroup-boxShadowColor-inset:#2b2b2e;
  --bc-thm-sysMsg_serviceGroup-desc-color:#bcbcbc;
  --bc-thm-sysMsg_serviceGroup-title-color:#5fd97f;
  --bc-thm-sysMsg_friend-bg:#18181c;
  --bc-thm-sysMsg_friend-borderColor:#3a3a3f;
  --bc-thm-sysMsg_friend-color:#e6e6e6;
  --bc-thm-sysMsg_activity-color:#d8d8d8;
  --bc-thm-sysMsg_activity-matAccent_member-color:#f5f5f5;
  --bc-thm-sysMsg_service-bg:#2b2b2e;
  --bc-thm-sysMsg-serviceList-bg:#18181c;
  --bc-thm-sysMsg-serviceList-authorName-color:#e6e6e6;
  --bc-thm-sysMsg-serviceList-title-color:#f5f5f5;
  --bc-thm-sysMsg-serviceList-services-item-title-color:#e6e6e6;
  --bc-thm-sysMsg-serviceList-services-item-borderColor:#ffffff1f;
  --bc-thm-sysMsg-serviceList-services-item-bg-hover:#24242a;
  --bc-thm-sysMsg-serviceList-showBtn-bg-hover:#24242a;
  --bc-thm-sysMsg-serviceList-showBtn-color-hover:#fff;
  --bc-thm-game-autoDJ-title-color:#f5f5f5
}
^.history .msg_chat_game .cg_msg_wrp,
^.history .system_msg.msg_chat_game {
  background: #18181c!important
}
^.history .msg_chat_game .cg_content,
^.history .msg_chat_game .gc_msg_label,
^.history .msg_chat_game .cg_msg_s_time {
  background: transparent
}
^.history .msg_chat_game .gc_msg_label,
^.history .msg_chat_game .gc_msg_label * {
  color: #e6e6e6
}
^.history .msg_chat_game .cg_msg_s_time {
  color: #a0a0a8
}
^.history .msg_chat_game .mbc_btn,
^.history .msg_chat_game .bc_btn,
^.history .msg_chat_game button,
^.history .msg_chat_game a[class*="btn"] {
  color: #e6e6e6!important;
  background: transparent!important;
  border-color: #4c4c52!important
}
^.chat_control_panel,
^.send-input-container_wrap,
^.ccp_inner,
^#chat_control_panel {
  background-color: #121213!important;
  border-color: #2b2b2e!important;
  box-shadow: none!important
}
^.chat_control_panel:before,
^.chat_control_panel:after,
^.ccp_inner:before,
^.ccp_inner:after,
^#bChatInputContainer:before,
^#bChatInputContainer:after {
  border-color: #2b2b2e!important
}
^.bc_fade_scroll {
  --fadeScroll-fade-bg:linear-gradient(90deg, #121216, rgba(18,18,22,0))
}
^.bcm_nav_panel,
^.bcm_nav_panel.ui-bar-c,
^.bcm_nav_panel.ui-footer,
^.bcm_nav_panel .np_inner,
^.bcm_nav_panel .np_block {
  background: #18181a!important;
  background-image: none!important;
  border-color: #2b2b2e!important
}
^.bcm_nav_panel .np_item {
  color: #d8d8d8!important;
  background: transparent!important
}
^.bcm_nav_panel .np_item.__active,
^.bcm_nav_panel .np_item:active {
  color: #e36e84!important
}
^.bc_fade_scroll.__listing_tags {
  --fadeScroll-fade-bg:linear-gradient(90deg, #121216, rgba(18,18,22,0))
}
^.profile_info_lg {
  --profileInfo-color:#e6e6e6;
  --profileInfo-base-accentTitle-color:#ee5b78;
  --profileInfo-section-bg:#1b1b1f;
  --profileInfo-detailsWrp-bg:#27272c;
  --profileInfo-detailsMore-color:#e6e6e6;
  --profileInfo-flag-borderColor:#3a3a3f;
  --profileInfo-shelf-broderColor:#3a3a3f;
  --profileInfo-shelf-badge-bg:#27272c;
  --profileInfo-section_shedule-text_highlight-color:#ee5b78;
  --profileInfo-qoqContests-chat-bg:#1b1b1f;
  --profileInfo-qoqContests-title-color:#ee5b78;
  --profileInfo-qoqContests-text-color:#e6e6e6;
  --profileInfo-gifts-emptyHead-imgWrp-bg:#27272c;
  --profileInfo-gifts-emptyHead-imgWrp-borderColor:#3a3a3f;
  --profileInfo-comments-content-bg:#27272c;
  --profileInfo-comments-content-text-color:#e6e6e6;
  --profileInfo-comments-btn-color:#e6e6e6;
  --profileInfo-comments-btn-bg:#27272c;
  --profileInfo-section_tags-stlItem-color:#e6e6e6;
  --profileInfo-section_tags-stlItem-bg:#27272c;
  --profileInfo_section_wish-listItem-bg:#27272c;
  --profileInfo-listWrp-bg:#27272c;
  --profileInfo-listItem-bg:#1b1b1f;
  --profileInfo-listItemCaption-color:#9a9aa2;
  --profileInfo-listItemHighlight-color:#e6e6e6
}`.replaceAll('^', '.bc-dark-m ');

const DARK_ALL_CSS = () => `^.bc_notice,
^.bc_notice.__countdown_goal,
^.bc_notice.__primary,
^.bc_notice.__secondary {
  --bcm-notice-color:#e6e6e6;
  --bcm-notice-bg:linear-gradient(#121213, #121213);
  --bcm-notice-borderColor:linear-gradient(#2e2e33, #2e2e33);
  --bcm-notice-link-color:#46c2ff;
  --bcm-notice-close-iconColor:#a0a0a8;
  --bcm-notice_countdownGoal-bg:linear-gradient(#121213, #121213);
  --bcm-notice_countdownGoal-borderColor:linear-gradient(#2e2e33, #2e2e33);
  --bcm-notice_membershipError-bg:linear-gradient(#121213, #121213);
  --bcm-notice_membershipError-borderColor:linear-gradient(#2e2e33, #2e2e33);
  --bcm-notice_notEnoughtTokens-bg:linear-gradient(#121213, #121213);
  --bc-thm-notice_topic-bg:#18181c;
  --bc-thm-notice_topic-color:#ee5b78;
  --bc-thm-notice_attention-bg:#1d1828;
  --bc-thm-notice_attention-color:#c291f2
}
^.bc_notice.__countdown_goal,
^.bc_notice.__primary {
  background: linear-gradient(#121213, #121213) padding-box, linear-gradient(#2e2e33, #2e2e33) border-box!important;
  border-color: transparent!important;
  box-shadow: none!important
}
^.bc_notice.__countdown_goal .nt_message,
^.bc_notice.__countdown_goal .nt_message *:not(.nt_accent):not(.nt_tip_btn) {
  color: #e6e6e6
}
^.mls_hash_tag {
  --mlsHashTag-tag-color:#e6e6e6;
  --mlsHashTag-tag-bg:#26262b;
  --mlsHashTag-tag-bg-active:#34343a;
  --mlsHashTag-tag_record-icon-color:#a0a0a8;
  --mlsHashTag-viewAllLink-color:#46c2ff;
  --mlsHashTag-removeFilterLink-close-iconColor:#c4c4c4
}
^.mls_hash_tag .ht_item,
^.mls_hash_tag .ht_item.ui-link {
  background: #26262b!important;
  color: #e6e6e6!important
}
^.mls_tag_badge {
  --bcm-listing-tagBadge-bg:#26262b;
  --bcm-listing-tagBadge-borderColor:#3a3a3f;
  --bcm-listing-tagBadge-color:#e6e6e6;
  --bcm-listing-tagBadge-button-bg:#4c4c52;
  --bcm-listing-tagBadge-button-iconColor:#fff
}
^.bcm_popup,
^.filter_popup,
^.search_popup,
^.mbc_search_block {
  --bcm-popup-body-bg-color:#121213;
  --bcm-popup-filter-main-bg:#121213;
  --bcm-popup-filter-features-title-color:#c1c1c1;
  --bcm-popup-filter-footer-borderColor:#2e2e33;
  --bcm-popup-filter-notice-color:#c1c1c1;
  --bcm-popup-filter-button-outline-color:#d8d8d8;
  --bcm-popup-filter-button-outline-color-active:#fff;
  --bcm-popup-filter-button-outline-bg:#1b1b1f;
  --bcm-popup-filter-button-outline-borderColor:#3e3e43;
  --bcm-popup-search-header-bg-color:#18181a;
  --bcm-search-block-input-bg-color:#1b1b1f;
  --bcm-search-block-input-borderColor:#3e3e43;
  --bcm-search-block-input-placeholder-color:#8e8e96;
  --bcm-search-block-header-close-icon-color:#d8d8d8;
  --bcm-search-block-reset-button-bg-color:#26262b;
  --bcm-search-block-reset-button-border-color:#3e3e43;
  --bcm-search-block-result-name-color:#e6e6e6;
  --bcm-search-block-result-border-color:#2b2b2e;
  --bcm-search-block-result-tag-color:#e6e6e6;
  --bcm-search-block-result-tag-name-bg-color:#26262b
}
^.bcm_popup .bcm_popup_body,
^.bcm_popup .bp_body,
^.filter_popup .fp_main,
^.filter_popup .fp_body,
^.filter_popup .fp_footer,
^.filter_popup .fp_content,
^.search_popup .sp_body,
^.mbc_search_block {
  background-color: #121213!important;
  color: #d8d8d8
}
^.bcm_range {
  --bcm-range-values-color:#bcbcbc;
  --bcm-range-track-bg-color:#2b2b2e;
  --bcm-range-handle-bg-color:#d8d8d8;
  --bcm-range-handle-border-color:#4c4c52
}
^.bcm_accordion {
  --bcm-accordion-box-shadow-color:#2b2b2e;
  --bcm-accordion-title-color:#bcbcbc;
  --bcm-accordion-arrow-fill:#d8d8d8
}
^.bcm_form_alert {
  --bcm-form-alert-error-bg-color:#2a1a1d;
  --bcm-form-alert-error-color:#ff7a85;
  --bcm-form-alert-success-bg-color:#173320;
  --bcm-form-alert-success-color:#b6f0c4
}
^.bcm_switcher {
  --bcm-switcher-bg:#3a3a3f
}
^.bcm_socials {
  --bcm-thm-socials-counter-color:#bcbcbc;
  --bcm-thm-socials-counter-bg:#18181c;
  --bcm-thm-socials-counter-borderColor:#3a3a3f
}
^.bcm_footer {
  --bcm-thm-footer-bg:#18181a;
  --bcm-thm-footer-borderColor:#2b2b2e;
  --bcm-thm-footer-details-color:#d8d8d8;
  --bcm-thm-footer-details-nav-link-color:#d8d8d8;
  --bcm-thm-footer-details-nav-link-bg-active:#26262b;
  --bcm-thm-copyright-bg:#121213;
  --bcm-thm-copyright-color:#bcbcbc
}
^.bcm_chat_footer {
  --bcm-chat-footer-item-border-color:#2b2b2e
}
^.bcm_history_scroll_up {
  --bcm-historyScrollUp-bg:#26262b
}
^.bcm_chat_auto_message {
  --bcm-chat-member-notification-borderColor:#3a3a3f;
  --bcm-chat-member-notification-title-color:#e6e6e6
}
^input:not([type=checkbox]):not([type=radio]):not([type=range]):not([type=button]):not([type=submit]):not([type=reset]):not([type=file]):not([type=color]):not([type=image]):not([type=hidden]),
^textarea,
^.mbc_search_block .sb_input {
  color: #f0f0f0!important;
  -webkit-text-fill-color: #f0f0f0!important;
  caret-color: #f0f0f0!important
}`.replaceAll('^', '.bc-dark-m ');
const darkOpaqueCss = () => '.panel,.bm-modal{background:' + 'rgb(18,18,22)' + '!important;-webkit-backdrop-filter:none!important;backdrop-filter:none!important}';
const syncOpaque = on => {
    const sr = document.getElementById('bc-unchained-host')?.shadowRoot;
    const st = sr?.getElementById('bc-dark-opaque');
    if (!sr || on === !!st) return;
    if (st) return st.remove();
    mkStyle(sr, darkOpaqueCss(), 'bc-dark-opaque');
};
const rafOnce = fn => { let q = 0; return () => { if (q) return; q = requestAnimationFrame(() => { q = 0; fn(); }); }; };
const setCls = (el, c, on) => { if (el.classList.contains(c) !== on) el.classList.toggle(c, on); };
const addStyle = (id, css, root) => {
    if (document.getElementById(id)) return;
    mkStyle(document.head || root, typeof css === 'function' ? css() : css, id);
};
const darkBcDesktop = (b, on) => {
    const cl = b.classList;
    if (on) {
        if (b.dataset.bcTheme === undefined && !cl.contains('theme_dark')) b.dataset.bcTheme = cl.contains('theme_light') ? '1' : '0';
        setCls(b, 'theme_light', false);
        setCls(b, 'theme_dark', true);
    } else if (b.dataset.bcTheme !== undefined) {
        const wasLight = b.dataset.bcTheme === '1';
        delete b.dataset.bcTheme;
        setCls(b, 'theme_dark', false);
        if (wasLight) setCls(b, 'theme_light', true);
    }
};
const isOwnNode = n => n.nodeType === 1 && (n.classList.contains('bcu-star') || n.classList.contains('bcu-ring'));
const onlyOwnNodes = recs => {
    for (const r of recs) {
        if (r.type !== 'childList') return false;
        for (const n of r.addedNodes) if (!isOwnNode(n)) return false;
        for (const n of r.removedNodes) if (!isOwnNode(n)) return false;
    }
    return true;
};
const DomBus = (() => {
    const subs = [];
    let mo = null;
    const start = () => {
        if (mo || !document.documentElement) return;
        mo = new MutationObserver(recs => {
            let own;
            const isOwn = () => own === undefined ? (own = onlyOwnNodes(recs)) : own;
            for (const fn of subs) { try { fn(recs, isOwn); } catch (e) { dbg(e, 'start'); } }
        });
        mo.observe(document.documentElement, { childList: true, subtree: true });
    };
    return { subscribe(fn) { subs.push(fn); start(); } };
})();
const DarkSweep = (() => {
    const SKIP = '.video-panel-wrap,video,canvas,svg,img,#bc-unchained-host';
    const INK = '#e6e6e6';
    const BORDERS = [['Top', 0.55], ['Bottom', 0.55], ['Left', 0.75], ['Right', 0.75]];
    let on = false, mo = null, tm = 0, seen = new WeakSet(), gen = 0, lastSweep = 0;
    const touched = [];
    let touchedCap = 4000;
    const pruneTouched = () => {
        const live = touched.filter(r => r.deref());
        touched.length = 0;
        for (const r of live) touched.push(r);
        touchedCap = Math.max(4000, live.length * 2);
    };
    const rgba = s => s.match(/[\d.]+/g)?.map(Number) ?? null;
    const solid = (c, a) => !!c && (c.length < 4 || c[3] > a);
    const lumOf = c => (0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2]) / 255;
    const spread = c => Math.max(c[0], c[1], c[2]) - Math.min(c[0], c[1], c[2]);
    const darkInk = c => solid(c, 0.3) && lumOf(c) < 0.33;
    const styleOf = el => { try { return getComputedStyle(el); } catch { return null; } };
    const put = (el, prop, val) => {
        if (!el.__bcdm) {
            el.__bcdm = {};
            touched.push(new WeakRef(el));
            if (touched.length > touchedCap) pruneTouched();
        }
        if (!(prop in el.__bcdm)) el.__bcdm[prop] = el.style.getPropertyValue(prop);
        if (defer) defer.push(el, prop, val);
        else el.style.setProperty(prop, val, 'important');
    };
    let defer = null;
    const batched = fn => {
        if (defer) return fn();
        defer = [];
        try { fn(); } finally {
            const d = defer;
            defer = null;
            for (let i = 0; i < d.length; i += 3) d[i].style.setProperty(d[i + 1], d[i + 2], 'important');
        }
    };
    const REJECT_SEL = '.video-panel-wrap,video,canvas,img,#bc-unchained-host';
    const walkFilter = { acceptNode: n => (n.ownerSVGElement || n.matches(REJECT_SEL)) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT };
    const walk = (root, cb) => {
        const tw = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, walkFilter);
        for (let n = tw.nextNode(); n; n = tw.nextNode()) cb(n);
    };
    const fixSvg = el => {
        const cs = styleOf(el);
        if (!cs) return;
        if (darkInk(rgba(cs.color))) put(el, 'color', INK);
        for (const k of ['fill', 'stroke']) {
            const v = cs[k];
            if (v && v !== 'none' && !v.startsWith('url') && darkInk(rgba(v))) put(el, k, INK);
        }
    };
    const hasText = el => Array.from(el.childNodes).some(n => n.nodeType === 3 && n.nodeValue.trim());
    const nestedBg = el => {
        for (let a = el.parentElement; a; a = a.parentElement) {
            if (a.__bcdm && 'background-color' in a.__bcdm) return true;
            if (a.id === 'bChatContent') break;
        }
        return false;
    };
    const fixBg = (el, cs) => {
        const bg = rgba(cs.backgroundColor);
        if (solid(bg, 0.6) && lumOf(bg) > 0.88) put(el, 'background-color', nestedBg(el) ? '#27272c' : '#1b1b1f');
    };
    const fixBorders = (el, cs) => {
        for (const [side, lim] of BORDERS) {
            const c = rgba(cs[`border${side}Color`]);
            if (parseFloat(cs[`border${side}Width`]) > 0 && solid(c, 0.3) && lumOf(c) > lim && spread(c) < 40) put(el, `border-${side.toLowerCase()}-color`, '#3a3a3f');
        }
    };
    const isRed = c => c[0] > 100 && c[1] < 90 && c[2] < 100 && c[0] > c[1] * 1.8;
    const textTone = c => {
        const L = lumOf(c), sat = spread(c);
        if (isRed(c)) return L < 0.33 ? '#ee5b78' : null;
        return (sat < 40 && L < 0.45) || (L < 0.33 && sat < 90) ? INK : null;
    };
    const fixText = (el, cs) => {
        const c = hasText(el) && rgba(cs.color);
        const tone = c && textTone(c);
        if (tone) put(el, 'color', tone);
    };
    const fix = el => {
        const cs = styleOf(el);
        if (!cs) return;
        fixBg(el, cs);
        fixBorders(el, cs);
        fixText(el, cs);
    };
    const inBox = el => !!el.closest && !el.closest('#bc-unchained-host');
    const visit = (el, trusted) => {
        if (el.nodeType !== 1 || seen.has(el)) return;
        seen.add(el);
        if (el.localName === 'svg') { if (trusted || !el.parentElement?.closest(SKIP)) fixSvg(el); }
        else if (trusted || !el.closest(SKIP)) fix(el);
    };
    const visitTree = el => {
        if (el.nodeType !== 1 || !inBox(el)) return;
        visit(el);
        if (el.localName === 'svg' || el.ownerSVGElement || el.closest(SKIP)) return;
        walk(el, n => visit(n, true));
    };
    const inViewport = e => {
        const r = e.getBoundingClientRect(), vh = window.innerHeight + 80, vw = window.innerWidth;
        return r.width > 0 && r.height > 0 && r.bottom > -80 && r.top < vh && r.right > 0 && r.left < vw;
    };
    const sweep = () => {
        tm = 0;
        const box = document.body;
        if (!on || !box) return;
        lastSweep = Date.now();
        const my = ++gen;
        const first = [], list = [];
        walk(box, e => { if (!seen.has(e)) (inViewport(e) ? first : list).push(e); });
        batched(() => { for (const e of first) visit(e, true); });
        let i = 0;
        const step = () => {
            if (!on || my !== gen) return;
            const t0 = performance.now();
            if (i < list.length) batched(() => { do visit(list[i++], true); while (i < list.length && performance.now() - t0 < 8); });
            if (i < list.length) requestAnimationFrame(step);
        };
        step();
    };
    const pendAttr = new Set(), pendTree = new Set();
    let pendRaf = 0, pendTo = 0;
    const flushPend = () => {
        cancelAnimationFrame(pendRaf); clearTimeout(pendTo); pendRaf = pendTo = 0;
        const trees = [...pendTree], attrs = [...pendAttr];
        pendTree.clear(); pendAttr.clear();
        if (!on) return;
        batched(() => {
            for (const n of trees) if (n.isConnected) visitTree(n);
            for (const t of attrs) if (t.isConnected) fix(t);
        });
    };
    const schedPend = () => {
        if (pendRaf || pendTo) return;
        pendRaf = requestAnimationFrame(flushPend);
        pendTo = setTimeout(flushPend, 250);
    };
    const onAttr = ({ target: t, attributeName }) => {
        if (t.nodeType !== 1 || !inBox(t) || t.closest(SKIP)) return;
        if (attributeName === 'style' && (t.__bcdm || !t.closest('#bChatContent'))) return;
        pendAttr.add(t);
    };
    const onMut = recs => {
        if (!on) return;
        for (const r of recs) {
            if (r.type === 'childList') r.addedNodes.forEach(n => { if (!isOwnNode(n)) pendTree.add(n); });
            else if (r.type === 'attributes') onAttr(r);
        }
        if (pendTree.size || pendAttr.size) schedPend();
    };
    const queue = () => { if (!tm) tm = requestAnimationFrame(sweep); };
    const revert = () => {
        for (const ref of touched) { const el = ref.deref(); if (!el || !el.__bcdm) continue; for (const k in el.__bcdm) { const v = el.__bcdm[k]; v ? el.style.setProperty(k, v) : el.style.removeProperty(k); } delete el.__bcdm; }
        touched.length = 0; seen = new WeakSet();
    };
    const start = () => {
        if (!mo && document.body) { mo = new MutationObserver(onMut); mo.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'hidden', 'style'] }); }
        queue();
    };
    const stop = () => { gen++; mo?.disconnect(); mo = null; pendTree.clear(); pendAttr.clear(); revert(); };
    return {
        set(v) {
            v = !!v;
            if (v === on) { if (on && Date.now() - lastSweep > 10000) queue(); return; }
            on = v;
            on ? start() : stop();
        }
    };
})();
const markHostDfb = on => {
    try { document.getElementById('bc-unchained-host')?.toggleAttribute('data-dfb', !!on); } catch (e) { dbg(e, 'markHostDfb'); }
};
const DARK_M_SHEETS = [['bc-unchained-dark-m', DARK_M_CSS], ['bc-unchained-dark-fix', DARK_FIX_CSS], ['bc-unchained-dark-all', DARK_ALL_CSS]];
const darkBcMobile = (root, on) => {
    DarkSweep.set(on);
    markHostDfb(false);
    if (on) DARK_M_SHEETS.forEach(([id, css]) => addStyle(id, css, root));
    setCls(root, 'bc-dark-m', on);
    setCls(root, 'bc-dark-fb', false);
    syncOpaque(false);
};
const darkGeneric = (root, on) => {
    if (on) addStyle('bc-unchained-dark-css', DARK_FB_CSS, root);
    if (on) addStyle('bc-unchained-dark-css-wk', DARK_FB_CSS_WK, root);
    setCls(root, 'bc-dark-fb', on);
    markHostDfb(on);
    syncOpaque(on);
};
const PLAT_UA = /Android|iPhone|iPad|iPod|Mobile|Opera Mini|IEMobile/i;
let platFinal = false;
const platGuess = () => {
    const h = Store.get('bc_plat', null);
    return h === 'm' || h === 'd' ? h : (PLAT_UA.test(navigator.userAgent) ? 'm' : 'd');
};
const platNow = () => (document.body?.classList.contains('ui-mobile-viewport') ? 'm' : platFinal ? 'd' : platGuess());
const rememberPlat = p => { if (Store.get('bc_plat', null) !== p) Store.set('bc_plat', p); };
const applyDark = () => {
    const b = document.body, root = document.documentElement;
    if (!b || !root) return;
    const on = AppState.dark;
    if (!BC_RE.test(location.hostname)) return darkGeneric(root, on);
    const p = platNow();
    if (platFinal || b.classList.contains('ui-mobile-viewport')) rememberPlat(p);
    if (p === 'd') {
        if (root.classList.contains('bc-dark-m')) darkBcMobile(root, false);
        darkBcDesktop(b, on);
    } else darkBcMobile(root, on);
};
(() => {
    const start = () => {
        try { applyDark(); } catch (e) { dbg(e, 'start'); }
        let q = 0;
        const mo = new MutationObserver(() => {
            if (!AppState.dark || q) return;
            q = requestAnimationFrame(() => { q = 0; if (AppState.dark) try { applyDark(); } catch (e) { dbg(e, 'start'); } });
        });
        mo.observe(document.body, { attributes: true, attributeFilter: ['class'] });
        mo.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
        const settle = () => { platFinal = true; try { applyDark(); } catch (e) { dbg(e, 'start.settle'); } };
        if (document.readyState === 'complete') settle();
        else window.addEventListener('load', settle, { once: true });
    };
    if (document.body) return start();
    const mo = new MutationObserver(() => { if (document.body) { mo.disconnect(); start(); } });
    mo.observe(document.documentElement, { childList: true });
})();

const REC_RES = { hi: '1080p', mid: '720p', low: '480p', min: '360p' };
const REC_Q = { hi: [6000000, 128000], mid: [3500000, 128000], low: [2000000, 96000], min: [1000000, 64000] };
const REC_PART = [50, 100, 150, 300];
const REC_TOTAL = [0, 512, 1024, 2048, 4096];
const REC_H = { hi: Infinity, mid: 720, low: 480, min: 360 };
const recNorm = c => {
    c = isPlain(c) ? c : {};
    return { q: REC_Q[c.q] ? c.q : 'hi', part: REC_PART.includes(c.part) ? c.part : 150, total: REC_TOTAL.includes(c.total) ? c.total : 0, fmt: c.fmt === 'webm' ? 'webm' : 'mp4', src: c.src === 'cap' ? 'cap' : 'hls' };
};
const recCfg = () => recNorm(Store.get('bc_rec', {}));
const fmtSize = (b, mb, gb) => { const m = b / 1048576; return m >= 1024 ? `${(m / 1024).toFixed(2)}${gb}` : `${m.toFixed(1)}${mb}`; };
const fmtShort = b => fmtSize(b, 'M', 'G');
const fmtBytes = b => { const l = L(); return fmtSize(b, ` ${l.u_mb}`, ` ${l.u_gb}`); };

const BK_CHECK = {
    cols: v => Number.isInteger(v) && v >= 1 && v <= 2,
    b: v => typeof v === 'boolean',
    scale: v => typeof v === 'number' && v >= 0.6 && v <= 1.5,
    css: v => isStr(v) && /^-?\d+(\.\d+)?(px|%)$/.test(v),
    color: v => isStr(v) && v.length < 60 && /^(rgba?|hsla?)\([\d\s.,%]+\)$/.test(v),
    lang: v => LANGS.includes(v),
    sort: v => isStr(v) && /^[a-z]{1,12}$/.test(v),
    spd: v => v === 0 || v === 500 || v === 1000,
    wf: v => v === -1 || v === 0 || v === 1,
    lay: v => Number.isInteger(v) && v >= 0 && v <= 3,
    ctext: v => isStr(v) && v.length <= 80,
    keys: v => Array.isArray(v) && v.length <= 2000 && v.every(x => isStr(x) && x.length <= 160),
    hsort: v => v === 'last' || v === 'time' || v === 'visits',
    mvdvr: v => Array.isArray(MV_DVR_OPTS) && MV_DVR_OPTS.includes(v),
    vol: v => typeof v === 'number' && v >= 0 && v <= 1,
    cbmode: v => v === 'off' || v === 'anim' || v === 'video',
    cbh: v => v === 240 || v === 360 || v === 480 || v === 720
};
const SETTINGS_SCHEMA = {
    bc_bg: 'b', bc_min: 'b', bc_hid: 'b', bc_pos: 'b', bc_gmute: 'b', bc_dark: 'b',
    bc_bm_gal: 'b', bc_bm_hide_topic: 'b', bc_bm_hide_grp: 'b', bc_bm_hide_ptags: 'b', bc_bm_hide_note: 'b', bc_bm_hide_priv: 'b', bc_bm_hide_group: 'b', bc_bm_hide_vip: 'b', bc_bm_hide_away: 'b', bc_bm_hide_off: 'b', bc_bm_sort_rev: 'b', bc_previews: 'b', bc_favnav: 'b',
    bc_scale: 'scale', bc_x: 'css', bc_y: 'css', bc_fx: 'css', bc_fy: 'css', bc_fb: 'css',
    bc_theme: 'color', bc_lang: 'lang', bc_bm_sort_type: 'sort', bc_prev_spd: 'spd',
    bc_hide_cfg: 'hcfg', bc_hide_custom: 'hcustom', bc_rec: 'rec', bc_lang_auto: 'b',
    bc_hist_ch: 'b', bc_btnhide: 'btns', bc_star: 'b', bc_hist_on: 'b', bc_bm_cols_g: 'cols', bc_bm_cols_l: 'cols',
    bc_bm_wf: 'wf', bc_cal_wf: 'wf', bc_cal_lay: 'lay', bc_cal_grp: 'ctext', bc_cal_tag: 'ctext', bc_cal_models: 'keys', bc_hist_sort: 'hsort',
    bc_mv_auto: 'b', bc_mv_bg: 'b', bc_mv_hide_off: 'b', bc_mv_hide_priv: 'b', bc_mv_hide_group: 'b', bc_mv_hide_vip: 'b', bc_mv_hide_away: 'b', bc_rad: 'b', bc_rad_x: 'css', bc_rad_y: 'css',
    bc_mv_abr: 'b', bc_mv_dvr: 'mvdvr', bc_mv_vol: 'vol', bc_mv: 'mvlist',
    cbhp_mode: 'cbmode', cbhp_sound: 'b', cbhp_maxh: 'cbh',
    bc_blur_mute: 'b'
};
const BACKUP_SCHEMA = { ...SETTINGS_SCHEMA, bc_hist: 'hist' };
const SETTINGS_RECOMMENDED = {
    bc_bg: true, bc_gmute: false, bc_blur_mute: false,
    bc_previews: true, bc_star: true, bc_hist_on: true, bc_lang_auto: true,
    bc_mv_abr: true, bc_rec: { q: 'hi', part: 150, total: 0, fmt: 'mp4', src: 'hls' },
    cbhp_mode: 'anim', cbhp_sound: false, cbhp_maxh: 360
};
const safeSel = x => {
    if (typeof x !== 'string' || !x.trim() || x.length > 400 || /[{};@]|\/\*/.test(x)) return false;
    try { document.querySelector(x); return true; } catch { return false; }
};
const cleanHideCfg = o => {
    if (!isPlain(o)) return null;
    const out = {};
    HIDE_CATS.forEach(c => { if (typeof o[c.id] === 'boolean') out[c.id] = o[c.id]; });
    return out;
};
const cleanHideCustom = o => {
    if (!isPlain(o)) return null;
    const out = {};
    Object.keys(o).forEach(k => {
        if (!/^[a-z0-9.-]{1,40}$/i.test(k) || !Array.isArray(o[k])) return;
        out[k] = o[k].filter(safeSel).slice(0, 200);
    });
    return out;
};
const cleanHist = o => {
    if (!isPlain(o)) return null;
    const m = {}, list = [];
    const src = isPlain(o.m) ? o.m : {};
    Object.keys(src).forEach(k => {
        const e = src[k];
        if (!/^[a-z0-9.-]{1,40}\|.{1,120}$/i.test(k) || !e || typeof e !== 'object' || !isStr(e.u) || !isSafeUrl(e.u)) return;
        m[k] = { n: clip(e.n, 100), u: normUrl(e.u), a: toHttps(e.a), s: histPos(e.s), v: histPos(e.v), f: histPos(e.f), l: histPos(e.l) };
    });
    (Array.isArray(o.s) ? o.s : []).forEach(x => {
        if (isObj(x) && isStr(x.k) && m[x.k]) list.push({ k: x.k, n: clip(x.n, 100), t: histPos(x.t), d: histPos(x.d) });
    });
    return { m, s: list };
};
const cleanBtns = o => {
    if (!isPlain(o)) return null;
    const out = {};
    Object.keys(o).forEach(k => { if (/^[A-Za-z]{2,12}$/.test(k) && o[k] === true) out[k] = true; });
    return out;
};
const cleanMvList = a => Array.isArray(a) ? a.filter(e => isObj(e) && (e.t === 'bm' || e.t === 'url') && isStr(e.url) && e.url.length <= 600 && isSafeUrl(e.url)).slice(0, MV_MAX).map(e => ({ t: e.t, name: clip(e.name, 100), url: e.url })) : null;
const BK_CLEAN = { mvlist: cleanMvList, hist: cleanHist, btns: cleanBtns, hcfg: cleanHideCfg, hcustom: cleanHideCustom, rec: o => isPlain(o) ? recNorm(o) : null };
const bkValue = (kind, v) => BK_CLEAN[kind] ? BK_CLEAN[kind](v) : (BK_CHECK[kind](v) ? v : null);
const inspectSettings = src => {
    const r = { ok: {}, bad: [], unknown: [] };
    if (!isPlain(src)) return r;
    Object.keys(src).forEach(k => {
        if (!Object.prototype.hasOwnProperty.call(BACKUP_SCHEMA, k)) { r.unknown.push(k); return; }
        const v = bkValue(BACKUP_SCHEMA[k], src[k]);
        if (v == null) r.bad.push(k); else r.ok[k] = v;
    });
    return r;
};
const restoreSettings = (src, rep = inspectSettings(src)) => Object.entries(rep.ok).reduce((n, [k, v]) => {
    if (k === 'bc_hist') histMerge(v); else Store.set(k, v);
    return n + 1;
}, 0);
const Backup = {
    parts() {
        const p = Store.get('bc_bk_parts', {});
        const o = { s: p.s !== false, f: p.f !== false, h: p.h !== false };
        return o.s || o.f || o.h ? o : { s: true, f: true, h: true };
    },
    setPart(k, on) {
        const p = this.parts();
        p[k] = on;
        if (!p.s && !p.f && !p.h) return false;
        Store.set('bc_bk_parts', p);
        return true;
    },
    build() {
        try { syncBookmarks(); } catch (e) { dbg(e, 'build'); }
        const p = this.parts();
        const out = { app: 'Cams Unchained', ver: 2, date: new Date().toISOString() };
        if (p.s) {
            const settings = {};
            Object.keys(BACKUP_SCHEMA).forEach(k => { if (k === 'bc_hist') return; const v = Store.get(k, null); if (v !== null) settings[k] = v; });
            out.settings = settings;
        }
        if (p.f) out.bookmarks = AppState.bookmarks.map(stripBm);
        if (p.h) out.history = histLoad();
        return out;
    },
    export() {
        const blob = new Blob([JSON.stringify(this.build(), null, 2)], { type: 'application/json' });
        Helpers.download(blob, `BC_Backup_${Helpers.getTimestamp().slice(0, 10)}.json`);
    },
    exportFavCsv() {
        const rows = [['name', 'url', 'group', 'note', 'pinned', 'last_seen'],
            ...AppState.bookmarks.map(b => [b.name, b.url, b.g || '', b.note || '', b.pin ? 1 : 0, b.seen ? fmtStamp(b.seen) : ''])];
        Helpers.download(new Blob([csvText(rows)], { type: 'text/csv;charset=utf-8' }), `BC_Favorites_${Helpers.getTimestamp().slice(0, 10)}.csv`);
    },
    inspect(data) {
        const p = this.parts();
        if (Array.isArray(data)) return { p, legacy: true, set: inspectSettings(null), favs: data.length, hist: 0, date: '' };
        if (!isPlain(data) || (data.app !== 'Cams Unchained' && data.app !== 'BongaCams Unchained')) throw new Error('bad backup');
        const hm = isPlain(data.history) && isPlain(data.history.m) ? Object.keys(data.history.m).length : 0;
        return {
            p, legacy: false,
            set: inspectSettings(isPlain(data.settings) ? data.settings : null),
            favs: Array.isArray(data.bookmarks) ? data.bookmarks.length : 0,
            hist: hm,
            date: isStr(data.date) ? data.date.slice(0, 10) : ''
        };
    },
    summary(rep) {
        const t = ruT, p = rep.p, lines = [t('Импорт резервной копии', 'Import backup') + (rep.date ? ` · ${rep.date}` : '')];
        const okN = Object.keys(rep.set.ok).length, bad = rep.set.bad, unk = rep.set.unknown;
        if (p.s && !rep.legacy) {
            let ln = `${t('Настройки', 'Settings')}: ${okN}`;
            if (bad.length) ln += ` · ${t('отклонено', 'rejected')}: ${bad.length} (${bad.slice(0, 4).join(', ')}${bad.length > 4 ? '…' : ''})`;
            if (unk.length) ln += ` · ${t('неизвестно', 'unknown')}: ${unk.length}`;
            lines.push(ln);
        }
        if (p.f && rep.favs) lines.push(`${t('Закладки', 'Bookmarks')}: ${rep.favs}`);
        if (p.h && rep.hist) lines.push(`${t('История', 'History')}: ${rep.hist}`);
        lines.push(t('Данные объединятся с текущими, страница перезагрузится.', 'Data is merged with the current one, the page will reload.'));
        return lines.join('\n');
    },
    useful(rep) {
        const p = rep.p;
        return (p.s && Object.keys(rep.set.ok).length > 0) || (p.f && rep.favs > 0) || (p.h && rep.hist > 0);
    },
    apply(data, rep = this.inspect(data)) {
        const p = rep.p;
        const favs = list => (p.f && Array.isArray(list) ? mergeBookmarks(list) : 0);
        if (rep.legacy) return { n: 0, added: favs(data), bad: 0, unknown: 0 };
        const n = p.s ? restoreSettings(null, rep.set) : 0;
        const added = favs(data.bookmarks);
        if (p.h && isObj(data.history)) { const hc = cleanHist(data.history); if (hc) histMerge(hc); }
        return { n, added, bad: rep.set.bad.length, unknown: rep.set.unknown.length };
    },
    async read(file) {
        if (!file || file.size > 3e7) throw new Error('too big');
        return JSON.parse(await file.text());
    },
    isJsonFile: f => !!f && (/\.json$/i.test(f.name || '') || /json/i.test(f.type || '')),
    pick(onFile) {
        const input = document.createElement('input');
        input.type = 'file';
        input.accept = '.json,application/json';
        input.style.display = 'none';
        (document.body || document.documentElement).appendChild(input);
        input.oncancel = () => input.remove();
        input.onchange = e => { const f = e.target.files?.[0]; input.remove(); if (f) onFile(f); };
        input.click();
    },
    applyRecommended() {
        const rep = inspectSettings(SETTINGS_RECOMMENDED);
        return restoreSettings(null, rep);
    }
};

const LANG_SEG = /^(?:ru|uk|ua|en|es|de|fr|pt|it|pl|tr|nl|cs|ro|hu|bg|el|ja|zh|ko|he|ar|hi|id|vi|th)$/i;
const bcSlug = seg => {
    while (seg.length > 1 && LANG_SEG.test(seg[0])) seg.shift();
    if (/^profile$/i.test(seg[0] || '')) seg.shift();
    const s = seg[0] || '';
    return BC_SYS.test(s) ? '' : s.toLowerCase();
};
const plainSlug = (sys, valid) => seg => {
    const s = seg[0] || '';
    return sys.test(s) || !valid.test(s) ? '' : s;
};
const SLUG_BY_SITE = new Map([['bc', bcSlug], ['sc', plainSlug(SC_SYS, /^[\w.-]+$/)], ['cb', plainSlug(CB_SYS, /^\w+$/)]]);
const firstSeg = seg => seg[0] || '';
const modelSlug = u => {
    try {
        const x = new URL(u);
        const seg = x.pathname.split('/').filter(Boolean).map(safeDecode);
        return (SLUG_BY_SITE.get(siteKey(x.hostname)) || firstSeg)(seg);
    } catch { return ''; }
};
const bmKeyRaw = u => { const s = modelSlug(u); return s ? `${urlSite(u)}|${s.toLowerCase()}` : String(u); };
const bmKeyCache = new Map();
const bmKey = u => {
    if (typeof u !== 'string') return bmKeyRaw(u);
    let k = bmKeyCache.get(u);
    if (k === undefined) {
        if (bmKeyCache.size > 3000) bmKeyCache.clear();
        k = bmKeyRaw(u);
        bmKeyCache.set(u, k);
    }
    return k;
};
const bmByKey = k => AppState.bookmarks.find(x => hasUrl(x) && bmKey(x.url) === k);
const pageKey = () => { const s = modelSlug(location.href); return s ? `${SITE}|${s.toLowerCase()}` : ''; };
const normUrl = (u, host) => {
    try {
        if (!isSafeUrl(u)) return u;
        const x = new URL(u), s = modelSlug(u);
        return s ? `https://${host || x.host}/${encodeURIComponent(s)}` : x.href;
    } catch { return u; }
};
const linkFor = u => {
    try {
        const host = urlSite(u) === siteKey(location.hostname) ? location.host : new URL(u).host;
        return normUrl(u, host);
    } catch { return u; }
};
const isFavKey = k => !!k && AppState.bookmarks.some(b => bmKey(b.url) === k);
const isFavName = name => !!name && (AppState.bookmarks.some(b => sameBm(b, name)) || isFavKey(pageKey()));
const BM_FILL_KEYS = ['note', 'g', 'avatar'];
const absorbBm = (prev, b) => {
    BM_FILL_KEYS.forEach(k => { if (!prev[k] && b[k]) prev[k] = b[k]; });
    if (b.pin) prev.pin = true;
    prev.seen = Math.max(prev.seen || 0, b.seen || 0);
};
const migrateBookmarks = () => {
    const map = new Map();
    const before = AppState.bookmarks.length;
    let changed = false;
    AppState.bookmarks.forEach((b, i) => {
        if (!b || !isStr(b.url)) { map.set(`#${i}`, b); return; }
        const nu = normUrl(b.url);
        if (nu !== b.url) { b.url = nu; changed = true; }
        const k = bmKey(b.url), prev = map.get(k);
        if (!prev) { map.set(k, b); return; }
        changed = true;
        absorbBm(prev, b);
    });
    if (changed) {
        AppState.bookmarks = [...map.values()].filter(Boolean);
        saveBookmarks();
    }
    return before - AppState.bookmarks.length;
};
migrateBookmarks();

const readFb = () => {
    const fb = Store.get('bc_fb', null);
    if (fb) return fb;
    const fy = Store.get('bc_fy', null);
    if (!fy || window.innerHeight < 200) return null;
    const v = `${Math.max(0, Math.round(window.innerHeight - parseFloat(fy) - 50))}px`;
    Store.set('bc_fb', v);
    return v;
};
const injectSiteCSS = () => {
    if (document.getElementById('bc-unchained-site-css')) return;
    const fx = Store.get('bc_fx', null), fb = readFb();
    let css = `${currentSite.css || ''} .js-filter_button,
.bc_sticky_bar {
  touch-action: none !important;
  user-select: none !important;
  -webkit-user-drag: none !important;
}
.bc_sticky_bar .bc_filter_button,
.bc_sticky_bar .js-filter_button {
  animation: none!important;
  transition: none!important;
  transform: none!important;
  translate: none!important;
  opacity: 1!important;
  top: auto!important;
  bottom: auto!important;
  inset-block: auto!important
}
html.bc-theater-html {
  background: #000!important;
  overflow: hidden!important
}
body.bc-theater-mode {
  overflow: hidden!important;
  background: #000!important;
  margin: 0!important;
  padding: 0!important
}
body.bc-theater-mode :is(.chat_panel_wrap,.player_footer,[class*="chat-panel"],[class*="chat_panel"],[class*="player-footer"],#chat_bar_v2_container,#chat_bar_v2,#switch_between_chat,.chat-tabs,[class*="chat_nav"],[class*="chat_bar"],#games_button,.mls_title,.live_tabs,.page_header,.header_bar,.chat-wrapper,.bc_chat_header,.memberBalance,.memberNavigation,.private_btn_wrp,#bPaidButtonTopRow,#profile-content,.stream-controls,#top_banner_above_chat,[class*="footer"]:not(#bc-unchained-host *)) {
  display: none!important
}
body.bc-theater-mode .bc-thtr-anc {
  transform: none!important;
  translate: none!important;
  scale: none!important;
  rotate: none!important;
  filter: none!important;
  backdrop-filter: none!important;
  perspective: none!important;
  contain: none!important;
  overflow: visible!important;
  clip-path: none!important;
  mask: none!important;
  will-change: auto!important
}
body.bc-theater-mode .bc-thtr-wrap {
  position: fixed!important;
  inset: 0!important;
  width: 100vw!important;
  height: 100vh!important;
  width: 100dvw!important;
  height: 100dvh!important;
  max-width: none!important;
  max-height: none!important;
  z-index: 2147483644!important;
  background: #000!important;
  margin: 0!important;
  padding: 0!important;
  transform: none!important;
  display: block!important
}
body.bc-theater-mode video.bc-thtr-video {
  position: fixed!important;
  inset: 0!important;
  width: 100vw!important;
  height: 100vh!important;
  width: 100dvw!important;
  height: 100dvh!important;
  max-width: none!important;
  max-height: none!important;
  aspect-ratio: auto!important;
  z-index: 2147483646!important;
  object-fit: contain!important;
  background: #000!important;
  margin: 0!important;
  padding: 0!important;
  transform: none!important;
  translate: none!important;
  rotate: none!important;
  scale: none!important
}`;
    if (fx && fb) {
        css += `.bc_sticky_bar:not([data-bcu]) {
  visibility: hidden!important
}
.bc_sticky_bar {
  left: ${fx}!important;
  top: auto!important;
  bottom: ${fb}!important;
  transform: none!important;
  margin: 0!important;
  right: auto!important;
  width: max-content!important;
  transition: none!important;
  animation: none!important
}`;
    }
    addStyle('bc-unchained-site-css', css, document.documentElement);
};
injectSiteCSS();
applyHideCSS();

const LOCK_SEL = '.fp_outer,.fp_outer_overlay,.fp_inner_wrap';
const unlockScroll = () => {
    if (!BC_RE.test(location.hostname) || !document.documentElement) return;
    const root = document.documentElement;
    const locked = !!document.querySelector(LOCK_SEL);
    if (!locked) { root.classList.remove('bc-unlock'); return; }
    addStyle('bc-unchained-unlock-css', 'html.bc-unlock,html.bc-unlock body{overflow-y:auto!important;overflow-x:hidden!important;position:relative!important;top:auto!important;height:auto!important;touch-action:pan-y pinch-zoom!important;overscroll-behavior:auto!important}', root);
    root.classList.add('bc-unlock');
    [root, document.body].forEach(el => {
        if (!el) return;
        [...el.classList].forEach(c => { if (/^(fp_|__fp|noscroll|no_scroll|no-scroll|scroll_lock|scroll-lock|modal_open|modal-open|popup_open|has_popup)/i.test(c)) el.classList.remove(c); });
    });
};
if (!window.__bcUnlockBound) {
    window.__bcUnlockBound = true;
    const guard = e => {
        if (!document.documentElement.classList.contains('bc-unlock')) return;
        const path = e.composedPath ? e.composedPath() : [];
        if (path.some(n => n && (n.id === 'bc-unchained-host' || n.id === 'bc-cb-qual'))) return;
        e.stopImmediatePropagation();
    };
    ['touchmove', 'wheel', 'scroll'].forEach(ev => window.addEventListener(ev, guard, { capture: true, passive: true }));
}
const MPOP_ATTR = 'data-bc-mpop';
const MPOP_INPUTS = 'input.sendInput,textarea.sendInput,input.js-chat_msg_input,input.ccp_input';
const MPOP_SKIP = '#bChat,#bChatInputContainer,.ccp_wrapper,.chat_panel_wrap,#chat_bar_v2_container,[' + MPOP_ATTR + ']';
const isRootEl = p => p === document.body || p === document.documentElement;
const findFixedAncestor = inp => {
    for (let p = inp.parentElement; p && !isRootEl(p); p = p.parentElement) {
        if (getComputedStyle(p).position === 'fixed') return p;
    }
    return null;
};
const findBodyChild = inp => {
    let p = inp;
    while (p.parentElement && p.parentElement !== document.body) p = p.parentElement;
    return p.parentElement === document.body ? p : null;
};
const popupTarget = inp => {
    if (inp.id === 'send-input-container' || inp.closest(MPOP_SKIP)) return null;
    return findFixedAncestor(inp) || findBodyChild(inp);
};
const isModelPopup = (target, vw, vh) => {
    if (!target || target === document.body) return false;
    if (target.querySelector('video,#bChat,.chat_panel_wrap,#stream-container')) return false;
    const r = target.getBoundingClientRect();
    return !(r.width > vw * 0.9 && r.height > vh * 0.9);
};
const tagModelPopups = () => {
    if (!BC_RE.test(location.hostname) || !document.body) return;
    const inputs = document.querySelectorAll(MPOP_INPUTS);
    if (!inputs.length) return;
    const vw = window.innerWidth, vh = window.innerHeight;
    const found = new Set();
    inputs.forEach(inp => { const t = popupTarget(inp); if (isModelPopup(t, vw, vh)) found.add(t); });
    if (found.size) requestAnimationFrame(() => found.forEach(t => t.setAttribute(MPOP_ATTR, '1')));
};
(() => {
    let t = 0;
    const run = () => {
        clearTimeout(t); t = 0;
        if (realHidden()) return;
        try { tagModelPopups(); } catch (e) { dbg(e, 'run'); }
        try { unlockScroll(); } catch (e) { dbg(e, 'run'); }
    };
    const sched = () => { if (!t) t = setTimeout(run, 150); };
    DomBus.subscribe((recs, isOwn) => { if (!isOwn()) sched(); });
    onPageEvent('visibilitychange', () => { if (!realHidden()) sched(); });
    setInterval(run, 5000);
    sched();
})();

const COLS_KEY = 'bc_bm_cols_g';
const colsGet = () => (Store.get(COLS_KEY, 1) === 2 ? 2 : 1);
const colsToggle = () => Store.set(COLS_KEY, colsGet() === 2 ? 1 : 2);
const pSvg = (d, s = 20, fill = 'none', stroke = 'currentColor') => `<svg viewBox="0 0 24 24" width="${s}" height="${s}" stroke="${stroke}" fill="${fill}" stroke-width="2">${d}</svg>`;
const COLS_ICONS = [
    pSvg('<rect x="4" y="5" width="16" height="14" rx="2.5"/>', 18),
    pSvg('<rect x="3" y="5" width="7.5" height="14" rx="2"/><rect x="13.5" y="5" width="7.5" height="14" rx="2"/>', 18)
];
const icons = {
    coffee: pSvg('<g stroke-linecap="round" stroke-linejoin="round"><path d="M8 3a2.4 2.4 0 0 0-1 2 2.4 2.4 0 0 0 1 2"/><path d="M12 3a2.4 2.4 0 0 0-1 2 2.4 2.4 0 0 0 1 2"/><path d="M3 10h14v5a6 6 0 0 1-6 6H9a6 6 0 0 1-6-6v-5z"/><path d="M16.75 16.73a3 3 0 1 0 .25-5.56"/></g>', 16),
    color: pSvg('<g stroke-linecap="round" stroke-linejoin="round"><path class="colb" d="M12 3a9 9 0 0 0 0 18c1.2 0 2-.9 2-2 0-.6-.3-1-.6-1.4-.3-.4-.4-.8-.4-1.2 0-1.1.9-2 2-2H17a4 4 0 0 0 4-4c0-4.5-4-7.4-9-7.4z"/><circle cx="7.5" cy="11" r="1" fill="currentColor"/><circle cx="10" cy="7" r="1" fill="currentColor"/><circle cx="15" cy="7" r="1" fill="currentColor"/></g>', 14).replace('<svg ', '<svg class="col-i" '),
    menu: pSvg('<circle cx="12" cy="12" r="1"/><circle cx="12" cy="5" r="1"/><circle cx="12" cy="19" r="1"/>', 24),
    eye: pSvg('<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/>', 14),
    close: pSvg('<g stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M9 9l6 6M15 9l-6 6"/></g>', 16),
    home: pSvg('<g stroke-linecap="round" stroke-linejoin="round"><path d="M4 10.5V19a1.5 1.5 0 0 0 1.5 1.5H9v-5.5a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v5.5h3.5A1.5 1.5 0 0 0 20 19v-8.5"/><path d="M2.5 11.5L12 3.5l9.5 8"/></g>', 16),
    rel: pSvg('<g stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-2.64-6.36"/><path d="M21 4.5V9h-4.5"/></g>', 16),
    min: pSvg('<g stroke-linecap="round" stroke-linejoin="round"><path d="M6 9.5l6 6 6-6"/></g>', 16),
    bgOn: pSvg('<g stroke-linecap="round" stroke-linejoin="round"><circle class="slp-trk" cx="12" cy="12" r="9.5"/><circle class="slp-arc" cx="12" cy="12" r="9.5" pathLength="100" stroke-dasharray="100" stroke-dashoffset="0" stroke-opacity="0" transform="rotate(-90 12 12)"/><polygon points="10 8.5 10 15.5 16 12" fill="currentColor"/></g>'),
    bgOff: pSvg('<g stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9.5"/><polygon points="10 8.5 10 15.5 16 12" fill="currentColor"/><line x1="3" y1="3" x2="21" y2="21"/></g>'),
    vol: pSvg('<g stroke-linecap="round" stroke-linejoin="round"><path d="M3 9.5h3.2L11 6v12l-4.8-3.5H3z"/><path d="M15 9.5v5M18.5 7v10M22 10.5v3"/></g>'),
    mute: pSvg('<g stroke-linecap="round" stroke-linejoin="round"><path d="M3 9.5h3.2L11 6v12l-4.8-3.5H3z"/><path d="M16 9.5l5 5M21 9.5l-5 5"/></g>'),
    muteLock: pSvg('<g stroke-linecap="round" stroke-linejoin="round"><path d="M3 9.5h3.2L11 6v12l-4.8-3.5H3z"/><path d="M16 9.5l5 5M21 9.5l-5 5" style="stroke:var(--th,#ff4d5e)" stroke-width="2.6"/></g>'),
    volLow: pSvg('<g stroke-linecap="round" stroke-linejoin="round"><path d="M3 9.5h3.2L11 6v12l-4.8-3.5H3z"/><path d="M15 9.5v5"/></g>'),
    volMed: pSvg('<g stroke-linecap="round" stroke-linejoin="round"><path d="M3 9.5h3.2L11 6v12l-4.8-3.5H3z"/><path d="M15 9.5v5M18.5 7v10"/></g>'),
    snap: pSvg('<g stroke-linecap="round" stroke-linejoin="round"><path class="snc" d="M3 8V5a2 2 0 0 1 2-2h3M16 3h3a2 2 0 0 1 2 2v3M21 16v3a2 2 0 0 1-2 2h-3M8 21H5a2 2 0 0 1-2-2v-3"/><circle class="snl" cx="12" cy="12" r="3.5"/></g>').replace('<svg ', '<svg class="snap-i" '),
    rec: pSvg('<g><rect x="2" y="6" width="20" height="12" rx="6"/><circle cx="12" cy="12" r="3.5" fill="currentColor" stroke="none"/></g>'),
    qual: pSvg('<rect x="2" y="5" width="20" height="14" rx="3"/><path d="M6.5 9v6M11 9v6M6.5 12H11M14.5 9v6h1.8a2.7 2.7 0 0 0 2.7-2.7v-.6A2.7 2.7 0 0 0 16.3 9z"/>'),
    pip: pSvg('<g stroke-linecap="round" stroke-linejoin="round"><rect x="2.5" y="4.5" width="19" height="15" rx="2.5"/><rect x="12" y="11.5" width="6" height="4.5" rx="1" fill="currentColor" stroke="none"/></g>'),
    fs: pSvg('<g stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3M16 3h3a2 2 0 0 1 2 2v3M8 21H5a2 2 0 0 1-2-2v-3M16 21h3a2 2 0 0 0 2-2v-3"/></g>'),
    thtr: pSvg('<g stroke-linecap="round" stroke-linejoin="round"><rect class="thf" x="2" y="6" width="20" height="12" rx="2" stroke-width="1.6" vector-effect="non-scaling-stroke"/><g class="thc" stroke-width="1.7"><path d="M3 8V5a2 2 0 0 1 2-2h3" style="--tx:2px;--ty:2px"/><path d="M16 3h3a2 2 0 0 1 2 2v3" style="--tx:-2px;--ty:2px"/><path d="M21 16v3a2 2 0 0 1-2 2h-3" style="--tx:-2px;--ty:-2px"/><path d="M8 21H5a2 2 0 0 1-2-2v-3" style="--tx:2px;--ty:-2px"/></g></g>').replace('<svg ', '<svg class="thtr-i" '),
    search: pSvg('<g stroke-linecap="round" stroke-linejoin="round"><circle cx="10.5" cy="10.5" r="7"/><path d="M16 16l5 5M10.5 7.5v6M7.5 10.5h6"/></g>'),
    copy: pSvg('<g stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></g>'),
    fav: pSvg('<g stroke-linecap="round" stroke-linejoin="round"><path d="M6 3h12a1 1 0 0 1 1 1v17l-7-4.5L5 21V4a1 1 0 0 1 1-1z"/><polygon points="12.00 7.30 13.00 9.82 15.71 9.99 13.62 11.73 14.29 14.36 12.00 12.90 9.71 14.36 10.38 11.73 8.29 9.99 11.00 9.82" fill="currentColor" stroke="currentColor" stroke-width="0.8"/></g>'),
    favPrev: pSvg('<polygon points="19 20 9 12 19 4 19 20"/><line x1="5" y1="19" x2="5" y2="5"/>'),
    favNext: pSvg('<polygon points="5 4 15 12 5 20 5 4"/><line x1="19" y1="5" x2="19" y2="19"/>'),
    del: pSvg('<polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/>', 16),
    hls: pSvg('<g stroke-linecap="round" stroke-linejoin="round"><path d="M2 12h4.5l2.5-6 4 12 3-8 2 2H22"/></g>'),
    get ok() { return pSvg('<polyline points="20 6 9 17 4 12"/>', 16, 'none', '#2ecc71'); },
    arrowUp: pSvg('<line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/>', 12),
    arrowDown: pSvg('<line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/>', 12),
    back: pSvg('<polyline points="15 18 9 12 15 6"/>', 18),
    info: pSvg('<circle cx="12" cy="12" r="9"/><path d="M12 11v5.5M12 7.6v.1" stroke-linecap="round"/>', 22),
    cam: pSvg('<path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/>', 12),
    clock: pSvg('<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>', 12),
    name: pSvg('<path d="M4 20l5-12 5 12"/><path d="M6 15h6"/>', 12),
    get dot() { const c = '#2ecc71'; return pSvg('<circle cx="12" cy="12" r="7"/>', 12, c, c); },
    list: pSvg('<line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="18" x2="20" y2="18"/>', 12),
    grid: pSvg('<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/>', 12)
};
icons.sleep = pSvg('<circle cx="12" cy="13" r="8"/><polyline points="12 9 12 13 15 15"/><line x1="9" y1="2" x2="15" y2="2"/>');
icons.cal = pSvg('<rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/>', 15);
icons.circle = pSvg('<circle cx="12" cy="12" r="9"/>', 16);
icons.check = pSvg('<polyline points="20 6 9 17 4 12"/>', 16);
icons.eyeNo = pSvg('<g stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><path d="M9 9l6 6M15 9l-6 6"/></g>', 16);
icons.eyeOk = pSvg('<g stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><path d="M8.3 12.2l2.7 2.7 4.8-5.4"/></g>', 16);
icons.tag = pSvg('<path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"/><circle cx="7" cy="7" r="1"/>', 12);
icons.folder = pSvg('<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>', 12);
icons.chevL = pSvg('<polyline points="15 18 9 12 15 6"/>', 18);
icons.chevR = pSvg('<polyline points="9 18 15 12 9 6"/>', 18);
icons.boxOn = pSvg('<rect x="3" y="3" width="18" height="18" rx="4"/><polyline points="8 12 11 15 16 9"/>', 16);
icons.boxOff = pSvg('<rect x="3" y="3" width="18" height="18" rx="4"/>', 16);
icons.undo = pSvg('<polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>', 14);

icons.eyeOff = pSvg('<g stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3.6-7 10-7 10 7 10 7-3.6 7-10 7S2 12 2 12z"/><circle cx="12" cy="12" r="3"/><line x1="4" y1="20" x2="20" y2="4"/></g>', 14);
const THEME_GOLD = '#F2B93B';
const THEME_MOON = 'M11.08 3.85A8.2 8.2 0 1 0 19.8 14.52A7 7 0 0 1 11.08 3.85z';
const THEME_RAYS = Array.from({ length: 8 }, (_, i) => `<line x1="12" y1="2.3" x2="12" y2="4.5" transform="rotate(${i * 45} 12 12)"/>`).join('');
const THEME_STARS = '<path d="M18.2 3.6l.55 1.45 1.45.55-1.45.55-.55 1.45-.55-1.45-1.45-.55 1.45-.55z"/><path d="M21 10.2l.32.83.83.32-.83.32-.32.83-.32-.83-.83-.32.83-.32z"/>';
const themeSvg = (inner, s = 14) => `<svg viewBox="0 0 24 24" width="${s}" height="${s}" stroke="none">${inner}</svg>`;
icons.sun = themeSvg(`<circle cx="12" cy="12" r="4.4" fill="${THEME_GOLD}"/><g stroke="${THEME_GOLD}" stroke-width="1.9" stroke-linecap="round">${THEME_RAYS}</g>`);
icons.moon = themeSvg(`<path d="${THEME_MOON}" fill="currentColor"/><g fill="currentColor">${THEME_STARS}</g>`);
let themeMorphId = 0;
const themeMorph = toDark => {
    const id = `tm${++themeMorphId}`;
    const t = 'dur="0.55s" begin="0s" fill="freeze" calcMode="spline" keySplines=".4 0 .2 1"';
    const v = (a, b) => (toDark ? `${a};${b}` : `${b};${a}`);
    return themeSvg(`<defs><mask id="${id}"><rect width="24" height="24" fill="#fff"/><circle r="7"><animate attributeName="cx" values="${v(26, 16.4)}" ${t}/><animate attributeName="cy" values="${v(5.5, 8.4)}" ${t}/></circle></mask></defs>`
        + `<circle cx="12" cy="12" mask="url(#${id})"><animate attributeName="r" values="${v(4.4, 8.2)}" ${t}/><animate attributeName="fill" values="${v(THEME_GOLD, 'currentColor')}" dur="0.55s" begin="0s" fill="freeze"/></circle>`
        + `<g stroke="${THEME_GOLD}" stroke-width="1.9" stroke-linecap="round"><animate attributeName="opacity" values="${v(1, 0)}" dur="0.3s" begin="0s" fill="freeze"/><animateTransform attributeName="transform" type="rotate" values="${v('0 12 12', '90 12 12')}" ${t}/>${THEME_RAYS}</g>`
        + `<g fill="currentColor" opacity="${toDark ? 0 : 1}"><animate attributeName="opacity" values="${v(0, 1)}" dur="0.3s" begin="${toDark ? '0.35s' : '0s'}" fill="freeze"/>${THEME_STARS}</g>`);
};
icons.dl = pSvg('<g stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9.5"/><path d="M12 7.5v8M8.5 12.5l3.5 3.5 3.5-3.5"/></g>', 14);
icons.ul = pSvg('<g stroke-linecap="round" stroke-linejoin="round"><rect x="2.5" y="2.5" width="19" height="19" rx="5"/><path d="M12 16.5v-8M8.5 12l3.5-3.5 3.5 3.5"/></g>', 14);
icons.target = pSvg('<circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/>', 14);
icons.globe = pSvg('<circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/>', 15);
icons.dice = pSvg('<rect x="3" y="3" width="18" height="18" rx="3"/><circle cx="8.5" cy="8.5" r="1"/><circle cx="15.5" cy="8.5" r="1"/><circle cx="8.5" cy="15.5" r="1"/><circle cx="15.5" cy="15.5" r="1"/>', 15);
icons.gear = pSvg('<g stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18M3 12h18M3 18h18"/><circle cx="15" cy="6" r="2.2" fill="currentColor"/><circle cx="8" cy="12" r="2.2" fill="currentColor"/><circle cx="17" cy="18" r="2.2" fill="currentColor"/></g>', 15);
const STAR_D = 'M12 17.27l4.15 2.51c.76.46 1.69-.22 1.49-1.08l-1.1-4.72 3.67-3.18c.67-.58.31-1.68-.57-1.75l-4.83-.41-1.89-4.46c-.34-.81-1.5-.81-1.84 0L9.19 8.63l-4.83.41c-.88.07-1.24 1.17-.57 1.75l3.67 3.18-1.1 4.72c-.2.86.73 1.54 1.49 1.08l4.15-2.5z';
const STAR_GOLD = '#F2B93B';
const STAR_GOLD_URI = 'data:image/svg+xml,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="' + STAR_GOLD + '" stroke-width="2" stroke-linejoin="round" stroke-linecap="round"><path d="' + STAR_D + '"/></svg>');
icons.star = pSvg('<path stroke-linejoin="round" stroke-linecap="round" d="' + STAR_D + '"/>', 16);
icons.history = pSvg('<path d="M3 12a9 9 0 1 0 3-6.7L3 8"/><polyline points="3 3 3 8 8 8"/><polyline points="12 7 12 12 15 14"/>', 15);
icons.bookmark = pSvg('<path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"/>', 15);
icons.dots = pSvg('<circle cx="5" cy="12" r="1.4"/><circle cx="12" cy="12" r="1.4"/><circle cx="19" cy="12" r="1.4"/>', 18);
icons.table = pSvg('<rect x="3" y="4" width="18" height="16" rx="2"/><line x1="3" y1="10" x2="21" y2="10"/><line x1="3" y1="15" x2="21" y2="15"/><line x1="9" y1="4" x2="9" y2="20"/>', 15);

const SUPPORT_REF_URL = 'https://chaturbate.com/in/?tour=g4pe&campaign=opzS6&track=default';
const supT = k => (SUPPORT_TXT[AppState.lang] || SUPPORT_TXT.EN)[k];
const openSupportLink = () => { try { window.open(SUPPORT_REF_URL, '_blank', 'noopener,noreferrer'); } catch (e) { dbg(e, 'openSupportLink'); } };

const L_MERGED = {};
const L = () => { const k = i18n[AppState.lang] ? AppState.lang : 'EN'; return L_MERGED[k] || (L_MERGED[k] = { ...i18n.EN, ...i18n[k] }); };
const ruT = (a, b) => (/^(RU|UK|BE)$/i.test(AppState.lang) ? a : b);
const fmtAgo = ts => {
    const l = L(), m = Math.max(0, Date.now() - ts) / 60000;
    if (m < 1) return l.seen_now;
    const n = m < 60 ? `${Math.floor(m)} ${l.u_m}` : m < 1440 ? `${Math.floor(m / 60)} ${l.u_h}` : `${Math.floor(m / 1440)} ${l.u_d}`;
    return l.ago.replace('{n}', n);
};

const CSS_NOSEL = `* {
  -webkit-tap-highlight-color: transparent
}
.panel,
.panel * {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none
}
.panel button,
.panel [role=button],
.panel div,
.panel span {
  outline: 0
}
.panel input,
.panel textarea {
  -webkit-user-select: text;
  user-select: text
}`;
const CSS_UI = `:host {
  --th:${AppState.theme};
  pointer-events: none;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  top: auto;
  width: 100%;
  height: 0;
  z-index: 2147483647;
  font-family: system-ui,-apple-system,sans-serif
}
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0
}
.h-btn,
.btn,
.restore,
.ghost,
.vico,
.z-btn,
.bm-close,
.bm-del,
.bm-act-btn,
.resize-handle {
  display: flex;
  align-items: center;
  justify-content: center
}
.panel {
  user-select: none;
  touch-action: none;
  position: absolute;
  width: 304px;
  padding: 12px;
  border-radius: 14px;
  pointer-events: auto;
  background: rgba(15,15,18,.95);
  border: 1px solid rgba(255,255,255,.1);
  box-shadow: 0 10px 30px rgba(0,0,0,.7),inset 0 1px 1px rgba(255,255,255,.1);
  backdrop-filter: blur(24px) saturate(150%);
  transform-origin: top left;
  will-change: transform,left,top
}
.panel.mini {
  width: 48px;
  height: 48px;
  padding: 0;
  background: transparent;
  border: none;
  box-shadow: none;
  backdrop-filter: none
}
.panel.hidden {
  display: none!important
}
.col {
  display: flex;
  flex-direction: column;
  gap: 8px
}
.grid-4 {
  display: grid;
  grid-template-columns: repeat(4,minmax(0,1fr));
  grid-auto-rows: 56px;
  gap: 6px
}
.grid-2 {
  display: grid;
  grid-template-columns: repeat(2,1fr);
  gap: 6px;
  margin-top: 6px
}
.sep {
  height: 1px;
  background: linear-gradient(90deg,transparent,rgba(255,255,255,.15),transparent);
  margin: 2px 0
}
.hdr {
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: rgba(0,0,0,.35);
  border-radius: 8px;
  padding: 6px 8px;
  margin-bottom: 6px
}
.drag {
  flex: 0 0 0;
  width: 0;
  align-self: stretch;
  min-height: 0;
  min-width: 0;
  padding: 0!important;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: rgba(255,255,255,.6);
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 1px;
  text-transform: uppercase;
  cursor: grab;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  padding-left: 4px;
  user-select: none;
  touch-action: none!important;
  margin-right: auto
}
.drag:active {
  cursor: grabbing
}
.stats {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  background: rgba(0,0,0,.5);
  color: #fff;
  font-size: 9px;
  min-width: 30px;
  height: 26px;
  padding: 2px 4px;
  border-radius: 4px;
  font-weight: bold;
  margin-right: 2px;
  box-sizing: border-box;
  line-height: 1;
  flex-shrink: 0
}
.hdr-btns {
  display: flex;
  gap: 5px;
  align-items: center;
  flex: 1 1 auto;
  margin-left: 0;
  justify-content: space-between
}
.h-btn {
  width: 24px;
  height: 24px;
  background: rgba(255,255,255,.05);
  border: none;
  border-radius: 4px;
  color: #fff;
  cursor: pointer;
  font-size: 12px;
  transition: .2s
}
.h-btn:hover {
  background: rgba(255,255,255,.2);
  transform: translateY(-1px)
}
.h-btn.close:hover {
  background: #e74c3c;
  transform: none
}
.h-btn.lang {
  width: 28px;
  font-weight: bold;
  background: rgba(255,255,255,.1);
  font-size: 10px
}
.btn {
  color: #fff;
  background: rgba(255,255,255,.06);
  border: 1px solid rgba(255,255,255,.05);
  border-radius: 8px;
  padding: 8px 2px 6px;
  font-size: 18px;
  cursor: pointer;
  flex-direction: column;
  width: 100%;
  min-width: 0;
  height: 56px;
  min-height: 56px;
  max-height: 56px;
  overflow: hidden;
  transition: all .2s ease;
  outline: none
}
.btn:hover {
  background: rgba(255,255,255,.15);
  transform: translateY(-2px);
  border-color: rgba(255,255,255,.2)
}
.btn:active {
  transform: scale(.92)
}
.btn.on {
  background: var(--th);
  box-shadow: 0 4px 10px rgba(0,0,0,.3);
  border-color: transparent
}
.btn sub {
  font-size: 8px;
  font-weight: 600;
  margin-top: 4px;
  letter-spacing: .5px;
  text-transform: uppercase;
  opacity: .8;
  pointer-events: none;
  text-align: center;
  white-space: nowrap;
  word-break: normal;
  line-height: 1
}
.restore {
  width: 48px;
  height: 48px;
  border-radius: 22px;
  font-size: 22px;
  background: var(--th);
  border: 2px solid rgba(255,255,255,.3);
  display: none;
  cursor: pointer;
  pointer-events: auto;
  box-shadow: 0 4px 15px rgba(0,0,0,.6);
  transition: .2s;
  touch-action: none!important
}
.restore:hover {
  transform: scale(1.05)
}
.ghost {
  position: absolute;
  bottom: 80px;
  right: 16px;
  width: 44px;
  height: 44px;
  border-radius: 20px;
  font-size: 20px;
  background: var(--th);
  border: 2px solid rgba(255,255,255,.35);
  display: none;
  cursor: pointer;
  pointer-events: auto;
  box-shadow: 0 4px 15px rgba(0,0,0,.6);
  transition: transform .2s,opacity .2s;
  touch-action: manipulation;
  z-index: 2147483647
}
.ghost:hover,
.ghost:active {
  transform: scale(1.1)
}
.v-cap {
  display: flex;
  align-items: center;
  gap: 8px;
  background: rgba(0,0,0,.4);
  border-radius: 10px;
  padding: 8px 12px;
  border: 1px solid rgba(255,255,255,.05)
}
.vico {
  color: #fff;
  font-size: 16px;
  cursor: pointer;
  flex-shrink: 0;
  filter: drop-shadow(0 2px 4px rgba(0,0,0,.5))
}
.vslider {
  -webkit-appearance: none;
  appearance: none;
  flex: 1;
  height: 3px;
  border-radius: 1px;
  outline: none;
  cursor: pointer;
  background: linear-gradient(to right,var(--th) 0%,var(--th) var(--p,100%),rgba(255,255,255,.1) var(--p,100%),rgba(255,255,255,.1) 100%)
}
.vslider::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 5px rgba(0,0,0,.5);
  border: 2px solid var(--th);
  cursor: pointer;
  transition: .2s
}
.vslider::-webkit-slider-thumb:hover {
  transform: scale(1.3)
}
.vval {
  font-size: 10px;
  font-weight: 600;
  color: #ccc;
  min-width: 32px;
  text-align: right
}
.row-4 {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px
}
.scale-ctrl {
  display: flex;
  align-items: center;
  gap: 4px;
  background: rgba(0,0,0,.3);
  border-radius: 4px;
  padding: 2px
}
.z-btn {
  width: 20px;
  height: 20px;
  background: rgba(255,255,255,.05);
  border: none;
  border-radius: 3px;
  color: #fff;
  cursor: pointer;
  font-size: 14px;
  transition: .2s;
  outline: none
}
.z-btn:hover {
  background: rgba(255,255,255,.2)
}
.s-val {
  font-size: 9px;
  color: #ccc;
  min-width: 24px;
  text-align: center;
  font-weight: bold
}
.hotkeys {
  font-size: 9px;
  color: rgba(255,255,255,.4);
  text-align: center;
  font-weight: 500;
  letter-spacing: .5px;
  padding-top: 2px
}
.bm-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,.8);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
  transition: .3s;
  backdrop-filter: blur(5px);
  z-index: 2147483647
}
.bm-overlay.open {
  opacity: 1;
  pointer-events: auto
}
.bm-overlay:not(.open),
.bm-overlay:not(.open) * {
  pointer-events: none!important
}
.bm-modal {
  width: 370px;
  max-width: 96vw;
  height: 90vh;
  max-height: 90vh;
  min-height: 450px;
  background: rgb(20,20,25);
  border: 1px solid rgba(255,255,255,.1);
  border-radius: 14px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  box-shadow: 0 10px 40px rgba(0,0,0,.8)
}
.bm-hdr {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 12px 16px;
  background: rgba(0,0,0,.4);
  border-bottom: 1px solid rgba(255,255,255,.05);
  flex-shrink: 0
}
.bm-title {
  color: #fff;
  font-weight: bold;
  font-size: 13px;
  white-space: nowrap;
  flex-shrink: 0
}
.bm-close {
  background: none;
  border: none;
  color: #aaa;
  font-size: 26px;
  cursor: pointer;
  padding: 2px 8px;
  line-height: 1;
  min-width: 32px;
  min-height: 32px
}
.bm-close:hover {
  color: #fff
}
.bm-list {
  flex: 1;
  min-height: 0;
  overflow-y: scroll;
  scrollbar-gutter: stable;
  padding: 12px;
  display: flex;
  flex-direction: column;
  gap: 10px
}
.bm-item {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px;
  background: rgba(255,255,255,.05);
  border-radius: 10px;
  transition: .2s
}
.bm-item:hover {
  background: rgba(255,255,255,.08)
}
.bm-item-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px
}
.bm-link {
  display: flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  color: #fff;
  flex: 1;
  overflow: hidden
}
.bm-link:hover .bm-name {
  color: var(--th)
}
.bm-avatar {
  width: 42px;
  height: 42px;
  border-radius: 50%;
  object-fit: cover;
  aspect-ratio: 1/1;
  background: #333;
  border: 1px solid rgba(255,255,255,.1);
  flex-shrink: 0
}
.bm-name {
  font-size: 15px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: .2s
}
.bm-del {
  width: 30px;
  height: 30px;
  border-radius: 4px;
  background: rgba(231,76,60,.2);
  color: #e74c3c;
  border: none;
  cursor: pointer;
  transition: .2s;
  flex-shrink: 0
}
.bm-del:hover {
  background: #e74c3c;
  color: #fff
}
.bm-del svg,
.bm-modal .bm-del svg {
  width: 14px;
  height: 14px;
  margin: 0!important;
  display: block;
  vertical-align: baseline
}
.bm-act-btn {
  height: 28px;
  padding: 0 8px;
  border-radius: 4px;
  background: rgba(255,255,255,.06);
  color: #ccc;
  border: 1px solid rgba(255,255,255,.08);
  gap: 4px;
  cursor: pointer;
  font-size: 11px;
  font-weight: 600;
  transition: .2s
}
.bm-act-btn:hover {
  background: rgba(255,255,255,.15);
  color: #fff;
  border-color: rgba(255,255,255,.2)
}
.bm-act-btn svg {
  width: 13px;
  height: 13px;
  flex-shrink: 0
}
.bm-note {
  width: 100%;
  background: rgba(0,0,0,.4);
  border: 1px solid rgba(255,255,255,.1);
  color: #fff;
  border-radius: 6px;
  padding: 8px 10px;
  font-size: 13px;
  font-family: inherit;
  outline: none;
  transition: .2s
}
.bm-note:focus {
  border-color: var(--th);
  background: rgba(0,0,0,.6);
  box-shadow: 0 0 5px rgba(255,255,255,.1)
}
.bm-foot {
  padding: 12px;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  background: rgba(0,0,0,.4);
  border-top: 1px solid rgba(255,255,255,.05);
  flex-shrink: 0
}
.bm-add {
  grid-column: span 2;
  padding: 12px;
  border-radius: 6px;
  border: none;
  background: var(--th);
  color: #fff;
  font-weight: bold;
  cursor: pointer;
  font-size: 14px;
  box-shadow: 0 2px 10px rgba(0,0,0,.3);
  transition: .2s
}
.bm-add:hover {
  filter: brightness(1.2)
}
.bm-add:disabled {
  background: #555;
  cursor: not-allowed;
  opacity: .5
}
.bm-btn {
  padding: 10px;
  border-radius: 6px;
  border: 1px solid rgba(255,255,255,.1);
  background: rgba(255,255,255,.05);
  color: #ddd;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: .2s
}
.bm-btn:hover {
  background: rgba(255,255,255,.15);
  color: #fff
}
.bm-empty {
  text-align: center;
  padding: 30px 10px;
  color: #777;
  font-size: 14px;
  grid-column: 1 / -1
}
.bm-gallery-mode {
  display: grid!important;
  grid-template-columns: repeat(2,minmax(0,1fr));
  align-content: start;
  gap: 10px;
  padding: 10px
}
.bm-item {
  min-width: 0
}
.bm-gallery-mode .bm-item {
  min-width: 0;
  padding: 10px;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  background: rgba(255,255,255,.05);
  border: 1px solid rgba(255,255,255,.1);
  border-radius: 10px;
  position: relative;
  gap: 6px
}
.bm-gallery-mode .bm-item:hover {
  transform: translateY(-2px);
  z-index: 10;
  border-color: var(--th);
  background: rgba(255,255,255,.08);
  box-shadow: 0 4px 12px rgba(0,0,0,.5)
}
.bm-gallery-mode .bm-topic,
.bm-gallery-mode .bm-note {
  display: none!important
}
.bm-gallery-mode .bm-item-top {
  display: contents
}
.bm-gallery-mode .bm-link {
  display: contents
}
.bm-gallery-mode .bm-link>div {
  order: 1;
  display: flex;
  align-items: center;
  width: calc(100% - 32px);
  max-width: calc(100% - 32px);
  pointer-events: none
}
.bm-gallery-mode .bm-del {
  order: 2;
  position: absolute;
  top: 6px;
  right: 6px;
  width: 24px;
  height: 24px;
  z-index: 2;
  margin: 0;
  border-radius: 4px
}
.bm-gallery-mode .bm-avatar {
  order: 3;
  width: 100%!important;
  height: auto!important;
  aspect-ratio: 16/9!important;
  border-radius: 6px;
  object-fit: cover;
  object-position: center;
  border: 1px solid rgba(0,0,0,.3);
  pointer-events: none;
  margin: 0;
  display: block!important;
  filter: brightness(.75)
}
.bm-gallery-mode .bm-item:has(img[data-frame]) .bm-avatar {
  display: none!important
}
.bm-gallery-mode img[data-frame] {
  order: 3;
  width: 100%!important;
  height: auto!important;
  aspect-ratio: 16/9!important;
  border-radius: 6px;
  object-fit: cover;
  border: 1px solid rgba(0,0,0,.3)!important;
  margin: 0!important;
  pointer-events: none;
  display: block!important
}
.bm-gallery-mode .bm-actions-row {
  order: 4;
  display: grid!important;
  width: 100%;
  grid-template-columns: repeat(3,1fr);
  gap: 6px;
  margin-top: auto;
  padding-top: 2px
}
.bm-gallery-mode .bm-act-btn {
  height: 28px;
  padding: 0;
  background: rgba(0,0,0,.4);
  border-radius: 4px;
  border: 1px solid rgba(255,255,255,.05)
}
.bm-gallery-mode .bm-act-btn span {
  display: none
}
.bm-gallery-mode .bm-act-btn svg {
  margin: 0;
  width: 14px;
  height: 14px
}
.bm-gallery-mode .bm-name {
  font-size: 12px;
  color: #eee;
  font-weight: bold;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap
}
@media (min-width:768px) {
  .bm-modal {
    width: 780px!important;
    max-width: 92vw!important;
    height: 85vh!important;
    max-height: 85vh!important
  }
  .bm-list {
    display: grid!important;
    grid-template-columns: repeat(2,minmax(0,1fr))!important;
    align-content: start!important;
    gap: 12px!important
  }
  .bm-gallery-mode {
    grid-template-columns: repeat(3,minmax(0,1fr))!important;
    gap: 12px!important
  }
}
@media (min-width:1200px) {
  .bm-modal {
    width: 920px!important
  }
  .bm-gallery-mode {
    grid-template-columns: repeat(4,minmax(0,1fr))!important
  }
}
.bm-list::-webkit-scrollbar {
  width: 6px
}
.bm-list::-webkit-scrollbar-thumb {
  background: rgba(255,255,255,.2);
  border-radius: 2px
}
@keyframes pulseRec {
  0%,
  100% {
    background: rgba(255,255,255,.06);
    box-shadow: none
  }
  50% {
    background: #c0392b;
    box-shadow: 0 2px 8px rgba(0,0,0,.5)
  }
}
@keyframes recRing {
  0%,
  100% {
    box-shadow: 0 0 0 0 rgba(255,77,94,0),inset 0 0 0 2px #ff4d5e
  }
  50% {
    box-shadow: 0 0 0 4px rgba(255,77,94,.25),inset 0 0 0 5px rgba(255,77,94,.45)
  }
}
.rec-active {
  animation: recRing 1.4s infinite!important;
  background: transparent!important;
  border-color: #ff4d5e!important;
  color: #ff4d5e!important
}
.rec-active sub {
  color: #fff!important
}
.resize-handle {
  position: absolute;
  right: 2px;
  bottom: 2px;
  width: 16px;
  height: 16px;
  cursor: nwse-resize;
  touch-action: none!important;
  opacity: .4;
  transition: opacity .2s;
  z-index: 10
}
.resize-handle:hover {
  opacity: 1
}
.resize-handle::after {
  content: none
}
.panel.mini .resize-handle {
  display: none!important
}
.rs-edge {
  position: absolute;
  z-index: 9;
  touch-action: none!important
}
.rs-n {
  top: -5px;
  left: 14px;
  right: 14px;
  height: 12px;
  cursor: ns-resize
}
.rs-s {
  bottom: -5px;
  left: 14px;
  right: 14px;
  height: 12px;
  cursor: ns-resize
}
.rs-w {
  left: -5px;
  top: 14px;
  bottom: 14px;
  width: 12px;
  cursor: ew-resize
}
.rs-e {
  right: -5px;
  top: 14px;
  bottom: 14px;
  width: 12px;
  cursor: ew-resize
}
.rs-nw {
  top: -5px;
  left: -5px;
  width: 18px;
  height: 18px;
  cursor: nwse-resize
}
.rs-ne {
  top: -5px;
  right: -5px;
  width: 18px;
  height: 18px;
  cursor: nesw-resize
}
.rs-sw {
  bottom: -5px;
  left: -5px;
  width: 18px;
  height: 18px;
  cursor: nesw-resize
}
.panel.mini .rs-edge {
  display: none!important
}
@media(pointer:coarse) {
  .rs-n,
  .rs-s {
    height: 22px
  }
  .rs-n {
    top: -8px
  }
  .rs-s {
    bottom: -8px
  }
  .rs-w,
  .rs-e {
    width: 22px
  }
  .rs-w {
    left: -8px
  }
  .rs-e {
    right: -8px
  }
  .rs-nw,
  .rs-ne,
  .rs-sw {
    width: 28px;
    height: 28px
  }
  .rs-nw {
    top: -8px;
    left: -8px
  }
  .rs-ne {
    top: -8px;
    right: -8px
  }
  .rs-sw {
    bottom: -8px;
    left: -8px
  }
}
.bm-prev:not([data-frame]) {
  display: none!important
}
.bm-prev.bm-bad {
  visibility: hidden
}
.bm-stat {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #888;
  margin-right: 8px;
  flex-shrink: 0;
  box-shadow: 0 0 4px rgba(0,0,0,.5);
  transition: .3s
}
.bm-nwrap {
  display: flex;
  align-items: center;
  min-width: 0;
  flex: 1
}
.bm-acts {
  display: grid;
  grid-template-columns: repeat(3,1fr);
  gap: 6px;
  margin-top: 4px
}
.bm-topic {
  font-size: 11px;
  color: #ddd;
  background: rgba(0,0,0,.35);
  padding: 6px 10px;
  border-radius: 4px;
  margin: 6px 0;
  word-break: break-word;
  white-space: pre-wrap;
  display: none;
  border: 1px solid rgba(255,255,255,.08)
}
.bm-prev {
  width: 100%;
  max-height: 180px;
  object-fit: cover;
  border-radius: 3px;
  margin-top: 8px;
  display: none;
  border: 1px solid rgba(255,255,255,.1);
  cursor: pointer
}
.bm-tbtn {
  display: flex;
  align-items: center;
  font-size: 11px;
  font-weight: bold;
  padding: 4px 10px;
  border-radius: 10px;
  border: 1px solid rgba(255,255,255,.2);
  background: rgba(255,255,255,.05);
  color: #fff;
  cursor: pointer;
  transition: .2s;
  margin-right: 4px;
  flex-shrink: 0;
  box-sizing: border-box
}
.bm-tbtn.on {
  background: var(--th)
}
.bm-hbtn {
  font-size: 11px;
  cursor: pointer;
  background: rgba(255,255,255,.1);
  border: 1px solid rgba(255,255,255,.15);
  color: #fff;
  border-radius: 4px;
  display: flex;
  align-items: center;
  padding: 3px 7px
}
.bm-sort-wrap {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-left: auto;
  margin-right: 6px
}
.bm-tags-wrap {
  display: none;
  flex-wrap: wrap;
  gap: 6px;
  padding: 4px 12px 10px;
  background: rgba(0,0,0,.4);
  flex-shrink: 0;
  border-bottom: 1px solid rgba(255,255,255,.05)
}
.bm-modal button svg {
  vertical-align: middle
}
.bm-title svg {
  margin-right: 4px;
  vertical-align: -2px
}
.bm-twrap {
  display: flex;
  align-items: center;
  gap: 2px;
  flex: 1
}
.hd-sec {
  grid-column: 1 / -1;
  color: #888;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  padding: 6px 4px 0
}
.hd-hint {
  grid-column: 1 / -1;
  color: #777;
  font-size: 11px;
  padding: 0 4px
}
.bm-overlay.peek {
  opacity: 0!important;
  background: transparent!important;
  backdrop-filter: none!important;
  transition: opacity .12s!important
}
.pick-box {
  position: fixed;
  display: none;
  pointer-events: none;
  border: 2px solid #e74c3c;
  background: rgba(231,76,60,.12);
  box-sizing: border-box;
  z-index: 2147483646
}
.pick-bar {
  position: fixed;
  left: 50%;
  bottom: 16px;
  transform: translateX(-50%);
  display: none;
  flex-direction: column;
  gap: 8px;
  width: min(92vw,360px);
  padding: 10px;
  background: rgba(15,15,18,.95);
  border: 1px solid rgba(255,255,255,.15);
  border-radius: 12px;
  box-shadow: 0 8px 30px rgba(0,0,0,.7);
  pointer-events: auto;
  z-index: 2147483647
}
.pick-bar.open {
  display: flex
}
.pick-info {
  color: #ddd;
  font-size: 11px;
  font-family: monospace;
  word-break: break-all;
  max-height: 56px;
  overflow: hidden
}
.pick-btns {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 8px
}
.bm-grp {
  width: 100%;
  background: rgba(0,0,0,.4);
  border: 1px solid rgba(255,255,255,.1);
  color: #ddd;
  border-radius: 6px;
  padding: 7px 8px;
  font-size: 12px;
  font-family: inherit;
  outline: none
}
.bm-grp option {
  background: #1b1b1f;
  color: #fff
}
.bm-gallery-mode .bm-grp {
  display: none!important
}
.bm-seen {
  flex-shrink: 0;
  margin-left: 8px;
  font-size: 10px;
  color: #8a8a90;
  white-space: nowrap
}
.bm-modal.rs-modal {
  width: 380px!important;
  max-width: 94vw!important;
  height: auto!important;
  min-height: 0!important;
  max-height: 90vh!important
}
.rs-modal .bm-list {
  display: flex!important;
  flex-direction: column!important;
  flex: 0 1 auto;
  overflow-y: auto;
  scrollbar-gutter: auto;
  gap: 8px
}
.rs-qs {
  display: flex;
  flex-direction: column;
  gap: 6px
}
.rs-qs .bm-btn {
  text-align: left
}
.rs-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px
}
.rs-chips .bm-tbtn {
  margin-right: 0;
  padding: 7px 12px;
  font-size: 12px
}
.rs-info {
  font-size: 12px;
  color: #ddd;
  min-height: 16px;
  padding: 2px 4px
}
.rs-note {
  font-size: 11px;
  color: #8a8a90;
  padding: 0 4px;
  line-height: 1.35
}
.bm-btn.sel {
  background: var(--th);
  color: #fff;
  border-color: transparent
}
.bm-overlay.fav-ov {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  background: rgba(0,0,0,.8);
  transition: opacity .18s ease
}
.fav-modal {
  width: min(980px,96vw)!important;
  max-width: 96vw!important;
  height: 88vh!important;
  max-height: 88vh!important;
  min-height: 0!important;
  border-radius: 18px;
  background: linear-gradient(180deg,rgba(30,30,37,.97),rgba(15,15,19,.98));
  border: 1px solid rgba(255,255,255,.08);
  box-shadow: 0 24px 70px rgba(0,0,0,.7)
}
.fav-modal .bm-hdr {
  position: relative;
  flex-wrap: wrap;
  justify-content: flex-start;
  gap: 8px;
  padding: 14px 16px 12px;
  padding-inline-end: 58px;
  background: rgba(255,255,255,.03);
  border-bottom: 1px solid rgba(255,255,255,.06)
}
.fav-modal .bm-title {
  display: flex;
  align-items: center;
  font-size: 17px;
  font-weight: 800;
  letter-spacing: .2px
}
.fav-modal .bm-close {
  position: absolute;
  top: 10px;
  inset-inline-end: 12px;
  width: 36px;
  height: 36px;
  min-width: 0;
  min-height: 0;
  padding: 0;
  border-radius: 50%;
  background: rgba(255,255,255,.07);
  font-size: 22px;
  color: #ccc
}
.fav-modal .bm-close:hover {
  background: rgba(231,76,60,.85);
  color: #fff
}
.fav-modal .bm-hdr>.bm-hbtn {
  margin-left: 0!important
}
.fav-modal .bm-hdr .bm-title {
  flex: 1 1 auto;
  min-width: 0
}
.fav-modal .bm-tools {
  order: 3;
  flex: 1 0 100%;
  display: flex;
  align-items: center;
  gap: 8px;
  justify-content: center;
  justify-content: safe center;
  overflow-x: auto;
  scrollbar-width: none
}
.fav-modal .bm-tools::-webkit-scrollbar {
  display: none
}
.fav-modal .bm-tools>* {
  flex: 0 0 auto;
  margin: 0!important
}
.fav-modal .bm-tools .bm-hbtn {
  height: 36px;
  min-width: 40px;
  justify-content: center
}
.fav-modal .bm-tools .bm-hbtn.on {
  background: var(--th);
  border-color: transparent
}
.fav-modal .bm-tools .bm-sort-wrap .bm-hbtn.on {
  background: transparent
}
.fav-modal .bm-hbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1
}
.fav-modal .bm-hbtn svg {
  display: block;
  flex: none;
  margin: 0!important
}
.fav-modal .bm-hdr {
  padding-inline-end: 16px!important
}
.fav-modal .bm-grp-btn {
  display: flex!important;
  align-items: center;
  justify-content: space-between!important;
  gap: 8px;
  width: 100%;
  text-align: start!important
}
.fav-modal .bm-grp-btn>span:first-child {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap
}
.fav-modal .bm-grp-arr {
  flex: none
}
.fav-modal .bm-tags-wrap {
  flex-direction: column!important;
  flex-wrap: nowrap!important;
  align-items: stretch;
  gap: 10px!important;
  max-height: none!important;
  overflow: visible!important;
  padding: 12px 16px!important
}
.fav-modal .bm-seg {
  display: flex;
  border: 1px solid rgba(255,255,255,.12);
  border-radius: 8px;
  overflow: hidden;
  background: rgba(255,255,255,.05)
}
.fav-modal .bm-seg .bm-tbtn {
  flex: 1 1 0;
  min-width: 0;
  height: 36px;
  margin: 0;
  padding: 0 8px;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis
}
.fav-modal .bm-seg .bm-tbtn+.bm-tbtn {
  border-left: 1px solid rgba(255,255,255,.12)
}
.fav-modal .bm-seg .bm-tbtn.on {
  background: var(--th)
}
.fav-modal .bm-seg svg {
  display: block;
  flex: none;
  margin: 0;
  width: 16px;
  height: 16px
}
.fav-modal .bm-chips {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  scrollbar-width: none;
  margin: 0 -16px;
  padding: 0 16px
}
.fav-modal .bm-chips::-webkit-scrollbar {
  display: none
}
.fav-modal .bm-chips .bm-tbtn {
  flex: none;
  padding: 6px 14px
}
.fav-modal .bm-hdr .bm-title {
  padding-inline-end: 48px;
  min-height: 38px
}
.fav-modal .bm-hdr {
  row-gap: 10px
}
.fav-modal .bm-tools {
  overflow: visible;
  gap: 10px
}
.fav-modal .bm-tools>.bm-sort-wrap {
  flex: 1 1 auto!important;
  display: flex
}
.fav-modal .bm-tools .bm-sort-wrap .bm-hbtn:first-child {
  flex: 1 1 auto
}
.fav-modal .bm-tools .bm-sort-wrap .bm-hbtn:last-child {
  width: 46px
}
.fav-modal .bm-setp {
  padding: 2px 14px;
  gap: 0;
  border: 1px solid rgba(255,255,255,.08)
}
.fav-modal .bm-setrow+.bm-setrow {
  border-top: 1px solid rgba(255,255,255,.06)
}
.fav-modal .bm-tools .bm-gear {
  width: 48px;
  flex: none
}
.fav-modal .bm-gear {
  width: 48px;
  padding: 0
}
.fav-modal .bm-gear svg {
  width: 18px;
  height: 18px
}
.fav-modal .bm-setp {
  display: none;
  flex: 1 0 100%;
  order: 4;
  flex-direction: column;
  gap: 2px;
  padding: 4px 10px;
  border-radius: 10px;
  background: rgba(0,0,0,.28)
}
.fav-modal .bm-setp.open {
  display: flex
}
.fav-modal .bm-setrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 44px;
  font-size: 14px;
  color: #ddd
}
.fav-modal .bm-sw {
  position: relative;
  flex: none;
  width: 46px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 11px;
  background: rgba(255,255,255,.18);
  cursor: pointer;
  transition: .2s
}
.fav-modal .bm-sw::after {
  content: "";
  position: absolute;
  top: 3px;
  left: 3px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #fff;
  transition: .2s
}
.fav-modal .bm-sw.on {
  background: var(--th)
}
.fav-modal .bm-sw.on::after {
  left: 23px
}
.fav-modal .bm-spd {
  height: 30px;
  min-width: 52px
}
.fav-modal.bm-no-topic .bm-topic {
  display: none!important
}
.fav-modal.bm-no-grp .bm-grp-btn {
  display: none!important
}
.fav-modal .bm-sort-wrap {
  gap: 0;
  margin: 0;
  margin-inline-start: auto;
  border: 1px solid rgba(255,255,255,.12);
  border-radius: 8px;
  overflow: hidden;
  background: rgba(255,255,255,.05)
}
.fav-modal .bm-hbtn {
  height: 30px;
  padding: 0 10px;
  gap: 4px;
  border-radius: 8px;
  font-weight: 600;
  background: rgba(255,255,255,.06);
  border: 1px solid rgba(255,255,255,.12);
  transition: .2s
}
.fav-modal .bm-hbtn:hover {
  background: rgba(255,255,255,.14)
}
.fav-modal .bm-sort-wrap .bm-hbtn {
  border: 0;
  border-radius: 0;
  background: transparent
}
.fav-modal .bm-sort-wrap .bm-hbtn:hover {
  background: rgba(255,255,255,.1)
}
.fav-modal .bm-sort-wrap .bm-hbtn+.bm-hbtn {
  border-left: 1px solid rgba(255,255,255,.12)
}
.fav-modal .bm-sort-wrap .bm-hbtn:last-child {
  padding: 0 9px
}
.fav-modal .bm-sort-wrap .bm-hbtn:last-child svg {
  margin: 0
}
.fav-modal .bm-tags-wrap {
  gap: 6px;
  padding: 10px 16px;
  max-height: 92px;
  overflow-y: auto;
  background: transparent;
  border-bottom: 1px solid rgba(255,255,255,.06)
}
.fav-modal .bm-tbtn {
  margin: 0;
  padding: 6px 12px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  background: rgba(255,255,255,.06);
  border: 1px solid rgba(255,255,255,.12);
  transition: .2s
}
.fav-modal .bm-tbtn:hover {
  background: rgba(255,255,255,.14)
}
.fav-modal .bm-tbtn.on {
  background: var(--th);
  border-color: transparent;
  box-shadow: 0 2px 10px rgba(0,0,0,.35)
}
.fav-modal .bm-list {
  grid-template-columns: repeat(auto-fill,minmax(min(300px,100%),1fr))!important;
  gap: 12px!important;
  padding: 14px!important;
  scrollbar-width: thin;
  scrollbar-color: rgba(255,255,255,.2) transparent
}
.fav-modal .bm-list.bm-gallery-mode {
  grid-template-columns: repeat(auto-fill,minmax(min(150px,100%),1fr))!important;
  gap: 10px!important
}
@media(min-width:760px) {
  .fav-modal .bm-list.bm-gallery-mode {
    grid-template-columns: repeat(auto-fill,minmax(min(200px,100%),1fr))!important
  }
}
.fav-modal .bm-item {
  position: relative;
  padding: 12px 12px 12px 16px;
  gap: 10px;
  border-radius: 14px;
  background: linear-gradient(180deg,rgba(255,255,255,.065),rgba(255,255,255,.03));
  border: 1px solid rgba(255,255,255,.08);
  transition: transform .2s,border-color .2s,background .2s,box-shadow .2s
}
@media (hover:hover) and (pointer:fine) {
  .fav-modal .bm-item:hover {
    background: linear-gradient(180deg,rgba(255,255,255,.09),rgba(255,255,255,.05));
    border-color: rgba(255,255,255,.18)
  }
}
.fav-modal .bm-item,
.fav-modal button {
  -webkit-tap-highlight-color: transparent
}
.fav-modal .bm-item[data-st="0"] {
  --st:rgba(255,255,255,.14)
}
.fav-modal .bm-item[data-st="1"] {
  --st:#2ecc71
}
.fav-modal .bm-item[data-st="2"] {
  --st:#f39c12
}
.fav-modal .bm-item[data-st="3"] {
  --st:#9b59b6
}
.fav-modal .bm-item::before {
  content: "";
  position: absolute;
  left: 0;
  top: 14px;
  bottom: 14px;
  width: 3px;
  border-radius: 0 2px 2px 0;
  background: var(--st,rgba(255,255,255,.14));
  transition: background .3s
}
.fav-modal .bm-list:not(.bm-gallery-mode) .bm-avatar {
  box-sizing: border-box;
  width: 48px;
  height: 48px;
  padding: 0;
  border: 2px solid var(--st,rgba(255,255,255,.14));
  background-clip: padding-box;
  box-shadow: none
}
.fav-modal .bm-avatar {
  transition: filter .3s,border-color .3s,box-shadow .3s
}
.fav-modal .bm-item[data-st="0"] .bm-avatar {
  filter: grayscale(.8) brightness(.8)
}
.fav-modal .bm-name {
  font-size: 15px;
  font-weight: 700;
  letter-spacing: .1px
}
.fav-modal .bm-seen {
  font-size: 11px;
  color: #9a9aa2
}
.fav-modal .bm-list .bm-stat {
  display: none
}
.fav-modal .bm-list:not(.bm-gallery-mode) .bm-act-btn {
  background: rgba(0,0,0,.4);
  border-color: rgba(255,255,255,.05)
}
.fav-modal .bm-list:not(.bm-gallery-mode) .bm-act-btn:hover {
  background: rgba(0,0,0,.25);
  border-color: rgba(255,255,255,.15)
}
.fav-modal .bm-list:not(.bm-gallery-mode) .bm-pin:not(.on),
.fav-modal .bm-list:not(.bm-gallery-mode) .bm-del {
  background: rgba(0,0,0,.55);
  border-color: rgba(255,255,255,.1)
}
.fav-modal .bm-list:not(.bm-gallery-mode) .bm-del:hover {
  background: #e74c3c
}
.fav-modal .bm-list:not(.bm-gallery-mode) .bm-nwrap .bm-view:not(:empty) {
  margin-inline-start: 8px;
  padding: 3px 6px;
  gap: 3px;
  border-radius: 7px;
  border: 1px solid rgba(255,255,255,.12);
  background: rgba(255,255,255,.06);
  color: #cfcfd6;
  font-size: 11px
}
.fav-modal .bm-stat {
  width: 9px;
  height: 9px
}
.fav-modal .bm-del {
  width: 32px;
  height: 32px;
  border-radius: 8px;
  background: rgba(255,255,255,.05);
  border: 1px solid rgba(255,255,255,.08);
  color: #d9707e
}
.fav-modal .bm-del:hover {
  background: #e74c3c;
  border-color: transparent;
  color: #fff
}
.fav-modal .bm-acts {
  gap: 6px
}
.fav-modal .bm-act-btn {
  height: 32px;
  border-radius: 8px;
  background: rgba(255,255,255,.07);
  border: 1px solid rgba(255,255,255,.1);
  color: #d4d4da;
  font-size: 12px
}
.fav-modal .bm-act-btn:hover {
  background: var(--th);
  border-color: transparent;
  color: #fff
}
.fav-modal .bm-note,
.fav-modal .bm-grp {
  background: rgba(0,0,0,.32);
  border: 1px solid rgba(255,255,255,.08);
  border-radius: 8px;
  padding: 8px 10px;
  font-size: 13px;
  color: #eee
}
.fav-modal .bm-note:focus,
.fav-modal .bm-grp:focus {
  border-color: var(--th);
  background: rgba(0,0,0,.5);
  box-shadow: 0 0 0 3px rgba(255,255,255,.06)
}
.fav-modal .bm-topic {
  margin: 0;
  padding: 8px 10px;
  border-radius: 8px;
  border: 1px solid rgba(255,255,255,.06);
  border-left: 3px solid var(--th);
  background: rgba(0,0,0,.28);
  font-size: 12px;
  line-height: 1.4;
  color: #d0d0d6
}
.fav-modal .bm-prev {
  margin-top: 0;
  border-radius: 3px;
  width: 100%;
  aspect-ratio: 16/9;
  max-height: none;
  object-fit: cover;
  background: rgba(255,255,255,.05)
}
.fav-modal .bm-gallery-mode .bm-item {
  padding: 8px;
  border-radius: 14px;
  gap: 8px
}
.fav-modal .bm-gallery-mode .bm-item::before {
  display: none
}
.fav-modal .bm-gallery-mode .bm-item[data-st="1"] {
  border-color: rgba(46,204,113,.55)
}
.fav-modal .bm-gallery-mode .bm-item[data-st="2"] {
  border-color: rgba(243,156,18,.55)
}
.fav-modal .bm-gallery-mode .bm-item[data-st="3"] {
  border-color: rgba(155,89,182,.6)
}
.fav-modal .bm-gallery-mode .bm-avatar {
  border-radius: 10px
}
.fav-modal .bm-gallery-mode .bm-name {
  font-size: 12.5px
}
.fav-modal .bm-gallery-mode .bm-del {
  width: 26px;
  height: 26px;
  border-radius: 6px;
  background: rgba(0,0,0,.55);
  border-color: rgba(255,255,255,.1)
}
.fav-modal .bm-gallery-mode .bm-del:hover {
  background: #e74c3c
}
.fav-modal .bm-gallery-mode .bm-act-btn {
  height: 30px;
  padding: 0;
  background: rgba(0,0,0,.4);
  border-radius: 6px
}
.fav-modal .bm-gallery-mode .bm-act-btn:hover {
  background: var(--th)
}
.fav-modal .bm-foot {
  padding: 12px 16px;
  background: rgba(255,255,255,.03);
  border-top: 1px solid rgba(255,255,255,.06)
}
.fav-modal .bm-add {
  padding: 13px;
  border-radius: 10px;
  font-size: 14px;
  box-shadow: 0 6px 18px rgba(0,0,0,.35)
}
@supports(background:color-mix(in srgb,red,blue)) {
  .fav-modal .bm-add {
    background: linear-gradient(135deg,var(--th),color-mix(in srgb,var(--th) 65%,#000))
  }
}
.fav-modal .bm-add:disabled {
  background: rgba(255,255,255,.08);
  box-shadow: none;
  color: #8a8a90;
  opacity: 1
}
.fav-modal .bm-empty {
  padding: 70px 12px;
  color: #8a8a90;
  font-size: 15px
}
.fav-modal .bm-empty::before {
  content: "★";
  display: block;
  font-size: 44px;
  line-height: 1;
  margin-bottom: 10px;
  opacity: .35
}
.fav-ov,
.fav-modal,
.fav-modal .bm-list,
.fav-modal .hs-pane,
.fav-modal .cal-pane,
.fav-modal .bm-tags-wrap {
  overscroll-behavior: contain
}
@media(max-width:520px) {
  .fav-modal {
    width: 100vw!important;
    max-width: 100vw!important;
    height: 100%!important;
    max-height: 100%!important;
    border-radius: 0;
    border: 0
  }
  .fav-modal .bm-foot {
    padding-bottom: calc(12px + env(safe-area-inset-bottom,0px))
  }
}
.panel .hdr {
  background: rgba(255,255,255,.04);
  border: 1px solid rgba(255,255,255,.06);
  border-radius: 10px;
  padding: 6px 8px;
  margin-bottom: 8px
}
.panel .drag {
  color: #9a9aa2;
  font-weight: 700
}
.panel .stats {
  background: rgba(255,255,255,.06);
  border: 1px solid rgba(255,255,255,.08);
  border-radius: 6px
}
.panel .stats circle {
  stroke: var(--th)
}
.panel .h-btn {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: rgba(255,255,255,.07)
}
.panel .h-btn.lang {
  width: 30px;
  border-radius: 8px;
  background: rgba(255,255,255,.1)
}
.hdr-btns .h-btn svg {
  width: 19px!important;
  height: 19px!important
}
.hdr-btns .h-btn.lang {
  border: 1px solid var(--th)!important
}
.hdr-btns .stats {
  flex-direction: row!important;
  width: 62px!important;
  min-width: 62px!important;
  max-width: 62px!important;
  flex: 0 0 62px!important;
  justify-content: center!important;
  height: 26px!important;
  gap: 4px!important;
  padding: 0 6px!important;
  font-size: 12px!important;
  overflow: hidden!important
}
.hdr-btns .stats svg {
  width: 15px!important;
  height: 15px!important
}
.hdr-btns .stats span {
  font-size: 12px!important;
  line-height: 1!important
}
.row-2 .z-btn svg,
.row-4 .z-btn svg {
  width: 17px!important;
  height: 17px!important
}
.panel .h-btn.close:hover {
  background: rgba(231,76,60,.9);
  transform: none
}
.favnav {
  display: none;
  grid-template-columns: repeat(2,1fr);
  gap: 6px;
  margin-top: 6px
}
.favnav.on {
  display: grid
}
.favnav .btn {
  height: 32px!important;
  min-height: 0!important;
  padding: 0!important
}
.favnav .btn sub {
  display: none!important
}
.favnav .btn svg {
  width: 16px;
  height: 16px
}
.panel .btn.on {
  background: var(--th);
  border-color: transparent;
  color: #fff;
  box-shadow: 0 4px 12px rgba(0,0,0,.35)
}
@supports(background:color-mix(in srgb,red,blue)) {
  .panel .btn.on {
    background: linear-gradient(135deg,var(--th),color-mix(in srgb,var(--th) 65%,#000))
  }
}
.thtr-i .thf {
  transform-box: fill-box;
  transform-origin: center;
  fill: currentColor;
  fill-opacity: .16;
  transition: transform .5s cubic-bezier(.4,0,.2,1),fill-opacity .5s cubic-bezier(.4,0,.2,1)
}
.thtr-i .thc path {
  opacity: 0;
  transform: translate(var(--tx),var(--ty));
  transition: transform .5s cubic-bezier(.4,0,.2,1),opacity .5s cubic-bezier(.4,0,.2,1)
}
.snap-i .snc,
.snap-i .snl {
  transform-box: view-box;
  transform-origin: 50% 50%
}
.snap-i .snl {
  fill: currentColor;
  fill-opacity: 0
}
.btn.snap-go .snap-i .snc {
  animation: bcSnapC .6s cubic-bezier(.4,0,.2,1)
}
.btn.snap-go .snap-i .snl {
  animation: bcSnapL .6s cubic-bezier(.4,0,.2,1)
}
@keyframes bcSnapC {
  0% {
    transform: scale(1)
  }
  30% {
    transform: scale(.7)
  }
  65% {
    transform: scale(1.06)
  }
  100% {
    transform: scale(1)
  }
}
@keyframes bcSnapL {
  0% {
    transform: scale(1);
    fill-opacity: 0
  }
  22% {
    transform: scale(.4);
    fill-opacity: 0
  }
  42% {
    transform: scale(1.25);
    fill-opacity: 1
  }
  100% {
    transform: scale(1);
    fill-opacity: 0
  }
}
@media(prefers-reduced-motion:reduce) {
  .btn.snap-go .snap-i .snc,
  .btn.snap-go .snap-i .snl {
    animation: none
  }
}
.slp-arc {
  transition: stroke-dashoffset 1s linear
}
.btn.slp .slp-trk {
  stroke-opacity: .3
}
.btn.slp .slp-arc {
  stroke-opacity: 1
}
.col-i .colb {
  fill: var(--th);
  transition: fill .6s ease
}
.col-i circle {
  fill: #fff;
  stroke: none
}
.col-i {
  transform-box: view-box;
  transform-origin: 50% 50%
}
.col-go .col-i {
  animation: bcColPop .5s cubic-bezier(.4,0,.2,1)
}
@keyframes bcColPop {
  0% {
    transform: scale(1)
  }
  35% {
    transform: scale(1.3)
  }
  100% {
    transform: scale(1)
  }
}
@media(prefers-reduced-motion:reduce) {
  .col-i .colb {
    transition: none
  }
  .col-go .col-i {
    animation: none
  }
}
.btn.on .thtr-i .thf {
  transform: scale(.7);
  fill-opacity: .3
}
.btn.on .thtr-i .thc path {
  opacity: 1;
  transform: translate(0,0)
}
@media(prefers-reduced-motion:reduce) {
  .thtr-i .thf,
  .thtr-i .thc path {
    transition: none
  }
}
.panel .btn.fav-on {
  position: relative;
  color: #F2B93B
}
.panel .btn.fav-on svg polygon {
  fill: none!important
}
.panel .z-btn {
  width: 24px;
  height: 24px;
  border-radius: 6px;
  background: rgba(255,255,255,.1)
}
.panel:not(.mini)::before {
  content: '';
  position: absolute;
  top: -1px;
  left: 16px;
  right: 16px;
  height: 2px;
  border-radius: 0 0 1px 1px;
  background: linear-gradient(90deg,transparent,var(--th),transparent);
  pointer-events: none
}
.panel .sep {
  background: linear-gradient(90deg,transparent,var(--th),transparent);
  opacity: .6
}
.bm-overlay:not(.fav-ov) {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  background: rgba(0,0,0,.8);
  transition: opacity .18s ease
}
.bm-modal:not(.fav-modal) {
  border-radius: 18px;
  background: linear-gradient(180deg,rgb(30,30,37),rgb(15,15,19))!important;
  border: 1px solid rgba(255,255,255,.08);
  box-shadow: 0 24px 70px rgba(0,0,0,.7)
}
.bm-modal:not(.fav-modal) .bm-hdr {
  position: relative;
  gap: 8px;
  padding: 14px 16px 12px;
  padding-inline-end: 58px;
  background: rgba(255,255,255,.03);
  border-bottom: 1px solid rgba(255,255,255,.06)
}
.bm-modal:not(.fav-modal) .bm-title {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 17px;
  font-weight: 800;
  letter-spacing: .2px
}
.bm-modal:not(.fav-modal) .bm-close {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  inset-inline-end: 12px;
  width: 36px;
  height: 36px;
  min-width: 0;
  min-height: 0;
  padding: 0;
  border-radius: 50%;
  background: rgba(255,255,255,.07);
  font-size: 22px;
  color: #ccc;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: .2s
}
.bm-modal:not(.fav-modal) .bm-close:hover {
  background: rgba(231,76,60,.85);
  color: #fff
}
.bm-modal:not(.fav-modal) .bm-hbtn {
  border-radius: 8px;
  background: rgba(255,255,255,.06);
  border: 1px solid rgba(255,255,255,.12);
  transition: .2s
}
.bm-modal:not(.fav-modal) .bm-list {
  padding: 14px;
  gap: 10px;
  scrollbar-width: thin;
  scrollbar-color: rgba(255,255,255,.2) transparent
}
.bm-modal:not(.fav-modal) .bm-btn {
  border-radius: 10px;
  background: rgba(255,255,255,.065);
  border: 1px solid rgba(255,255,255,.1);
  color: #e2e2e6
}
.bm-modal:not(.fav-modal) .bm-btn:hover {
  background: rgba(255,255,255,.12);
  border-color: rgba(255,255,255,.18)
}
.bm-modal:not(.fav-modal) .bm-btn:active {
  background: var(--th);
  border-color: transparent;
  color: #fff
}
.bm-modal:not(.fav-modal) .bm-btn.sel,
.bm-modal:not(.fav-modal) .bm-btn.sel:hover {
  background: var(--th);
  border-color: transparent;
  color: #fff
}
.bm-modal:not(.fav-modal) .bm-tbtn {
  border-radius: 999px;
  font-weight: 600;
  background: rgba(255,255,255,.06);
  border: 1px solid rgba(255,255,255,.12);
  transition: .2s
}
.bm-modal:not(.fav-modal) .bm-tbtn.on {
  background: var(--th);
  border-color: transparent;
  box-shadow: 0 2px 10px rgba(0,0,0,.35)
}
.bm-modal:not(.fav-modal) .bm-note {
  background: rgba(0,0,0,.32);
  border: 1px solid rgba(255,255,255,.08);
  border-radius: 8px
}
.bm-modal:not(.fav-modal) .bm-note:focus {
  border-color: var(--th);
  background: rgba(0,0,0,.5);
  box-shadow: 0 0 0 3px rgba(255,255,255,.06)
}
.bm-modal:not(.fav-modal) .hd-sec {
  color: #9a9aa2
}
.bm-modal:not(.fav-modal) .bm-foot {
  padding: 12px 16px;
  background: rgba(255,255,255,.03);
  border-top: 1px solid rgba(255,255,255,.06)
}
@media(max-width:520px) {
  .bm-modal:not(.fav-modal) {
    border-radius: 16px
  }
}
.bm-modal .bm-close {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0!important;
  line-height: 0;
  text-align: center
}
.bm-modal .bm-close svg {
  display: block;
  flex: none;
  margin: 0!important;
  width: 16px;
  height: 16px
}
.bm-modal .bm-close.bm-back {
  inset-inline-end: 56px!important
}
.bm-modal:not(.fav-modal).has-back .bm-hdr {
  padding-inline-end: 104px!important
}
.fav-modal.has-back .bm-hdr .bm-title {
  padding-inline-end: 96px!important
}
:host([data-rtl]) .bm-back svg {
  transform: scaleX(-1)
}
.bm-overlay.bm-nofade {
  transition: none!important
}
.fav-modal .bm-search-wrap {
  display: none;
  padding: 10px 16px 0;
  flex-shrink: 0
}
.fav-modal .bm-search-box {
  position: relative
}
.fav-modal .bm-search-ico {
  position: absolute;
  inset-inline-start: 12px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  color: #8a8a90;
  pointer-events: none
}
.fav-modal .bm-search-ico svg {
  width: 16px;
  height: 16px
}
.fav-modal .bm-search-in {
  width: 100%;
  height: 38px;
  padding: 0 40px;
  border-radius: 10px;
  background: rgba(0,0,0,.32);
  border: 1px solid rgba(255,255,255,.1);
  color: #f0f0f0;
  -webkit-text-fill-color: #f0f0f0;
  font-size: 14px;
  font-family: inherit;
  outline: none;
  transition: .2s
}
.fav-modal .bm-search-in:focus {
  border-color: var(--th);
  background: rgba(0,0,0,.5);
  box-shadow: 0 0 0 3px rgba(255,255,255,.06)
}
.fav-modal .bm-search-in::placeholder {
  color: #8a8a90
}
.fav-modal .bm-hdr {
  padding: 8px 12px 8px!important;
  padding-inline-end: 50px!important;
  gap: 6px!important;
  align-items: center
}
.fav-modal .bm-title {
  font-size: 16px!important;
  flex: 0 0 auto!important
}
.fav-modal .bm-close {
  top: 9px!important;
  inset-inline-end: 8px!important;
  width: 32px!important;
  height: 32px!important;
  font-size: 20px!important
}
.fav-modal .bm-title {
  flex: 1 1 auto!important
}
.fav-modal .bm-tools {
  order: 2!important;
  flex: 0 0 auto!important;
  gap: 6px!important;
  justify-content: flex-end!important
}
.fav-modal .bm-search-wrap {
  align-items: center;
  gap: 8px
}
.fav-modal .bm-search-box {
  flex: 1 1 0;
  min-width: 0
}
.fav-modal .bm-search-wrap>.bm-sort-wrap {
  flex: 0 0 auto!important;
  margin: 0!important;
  height: 34px
}
.fav-modal .bm-search-wrap>.bm-sort-wrap .bm-hbtn {
  height: 34px!important;
  padding: 0 10px!important
}
.fav-modal .bm-search-wrap>.bm-sort-wrap .bm-hbtn:last-child {
  width: 38px!important;
  padding: 0!important
}
.fav-modal .bm-tools .bm-hbtn {
  height: 32px!important;
  min-width: 36px!important
}
.fav-modal .bm-tools .bm-gear {
  width: 38px!important
}
.fav-modal .bm-tools>.bm-sort-wrap {
  min-width: 0!important
}
.fav-modal .bm-setp {
  order: 4
}
.fav-modal .bm-search-wrap {
  padding: 6px 12px 0!important
}
.fav-modal .bm-search-in {
  height: 34px!important;
  font-size: 13px!important
}
.fav-modal .bm-tags-wrap {
  flex-direction: row!important;
  flex-wrap: wrap!important;
  align-items: center!important;
  gap: 8px!important;
  padding: 6px 12px!important;
  max-height: none!important;
  overflow: hidden!important
}
.fav-modal .bm-seg {
  flex: 1 1 100%;
  min-width: 0;
  max-width: 100%
}
.fav-modal .bm-seg .bm-tbtn {
  height: 30px!important;
  padding: 0 12px!important;
  font-size: 12px!important
}
.fav-modal .bm-chips {
  flex: 1 1 140px;
  min-width: 0;
  margin: 0!important;
  padding: 0!important
}
.fav-modal .bm-chips .bm-tbtn {
  padding: 5px 12px!important;
  white-space: nowrap!important;
  flex: none!important;
  width: auto!important;
  max-width: none!important;
  line-height: 1.2!important
}
.fav-modal .bm-hbtn svg,
.fav-modal .bm-del svg,
.fav-modal .bm-pin svg {
  display: block!important;
  margin: 0 auto!important;
  flex: none
}
.fav-modal .bm-act-btn svg {
  display: block!important;
  margin: 0!important;
  flex: none
}
.fav-modal .bm-act-btn {
  justify-content: center;
  text-align: center
}
.fav-modal .bm-gear {
  display: inline-flex!important;
  align-items: center!important;
  justify-content: center!important;
  padding: 0!important
}
.fav-modal .bm-search-clr {
  position: absolute;
  inset-inline-end: 6px;
  top: 50%;
  transform: translateY(-50%);
  display: none;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(255,255,255,.1);
  color: #ddd;
  font-size: 17px;
  line-height: 1;
  cursor: pointer
}
.fav-modal .bm-search-clr:hover {
  background: rgba(255,255,255,.2);
  color: #fff
}
.fav-modal .bm-pin {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  flex-shrink: 0;
  border-radius: 8px;
  background: rgba(255,255,255,.05);
  border: 1px solid rgba(255,255,255,.08);
  color: #9a9aa2;
  cursor: pointer;
  transition: .2s
}
.fav-modal .bm-pin svg {
  width: 15px;
  height: 15px;
  margin: 0;
  display: block;
  vertical-align: baseline
}
.fav-modal .bm-pin:hover {
  background: rgba(245,197,24,.18);
  color: #f5c518
}
.fav-modal .bm-pin.on {
  color: #f5c518;
  background: rgba(245,197,24,.14);
  border-color: rgba(245,197,24,.4)
}
.fav-modal .bm-item.pinned {
  border-color: rgba(245,197,24,.3)
}
.fav-modal .bm-gallery-mode .bm-pin {
  position: absolute;
  top: 6px;
  right: 38px;
  order: 2;
  z-index: 2;
  width: 26px;
  height: 26px;
  border-radius: 6px;
  background: rgba(0,0,0,.55);
  border-color: rgba(255,255,255,.1)
}
.fav-modal .bm-gallery-mode .bm-pin.on {
  border-color: rgba(245,197,24,.6)
}
.fav-modal .bm-gallery-mode .bm-link>div {
  width: calc(100% - 66px);
  max-width: calc(100% - 66px)
}
.fav-modal .bm-gallery-mode .bm-grp-btn {
  order: 5;
  width: 100%;
  padding: 5px 8px;
  font-size: 12px;
  min-width: 0
}
.fav-modal .bm-gallery-mode .bm-grp-btn>span:first-child {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap
}
.stats,
.panel .stats {
  width: 42px;
  min-width: 42px;
  max-width: 42px;
  overflow: hidden;
  white-space: nowrap
}
.bm-spd:disabled {
  cursor: default
}
.fav-modal .bm-setrow-spd {
  flex-wrap: wrap;
  padding: 6px 0
}
.fav-modal .bm-setrow-spd>span {
  flex: 1 0 100%
}
.fav-modal .bm-spdseg {
  display: grid;
  grid-template-columns: repeat(4,minmax(0,1fr));
  gap: 6px;
  width: 100%;
  padding: 4px;
  border-radius: 12px;
  background: rgba(0,0,0,.28)
}
.fav-modal .bm-spdopt {
  height: 38px;
  border: 0;
  border-radius: 9px;
  background: transparent;
  color: inherit;
  font-size: 14px;
  cursor: pointer
}
.fav-modal .bm-spdopt.on {
  background: var(--pt-ac,#d0123f);
  color: var(--pt-on,#fff);
  font-weight: 600
}
:host([data-rtl]) .bm-overlay {
  direction: rtl
}
`;
const CSS_COMPACT = `.fav-modal .bm-hdr {
  padding-top: 6px!important;
  padding-bottom: 6px!important;
  padding-inline-end: 46px!important
}
.fav-modal .bm-close {
  top: 10px!important;
  inset-inline-end: 4px!important;
  width: 32px!important;
  height: 32px!important;
  min-width: 0!important;
  min-height: 0!important
}
.fav-modal .bm-tabs .bm-tbtn,
.fav-modal .bm-hdr .bm-tabs .bm-tbtn {
  height: 40px!important;
  min-height: 0!important
}
.fav-modal .bm-tools .bm-hbtn,
.fav-modal .bm-tools .bm-tbtn {
  height: 32px!important;
  min-height: 0!important
}
.fav-modal .bm-tools .bm-hbtn {
  min-width: 38px!important
}
.fav-modal .bm-seg .bm-tbtn,
.fav-modal .bm-seg.bm-wseg .bm-tbtn,
.fav-modal .mv-pane>.bm-seg.mv-seg .bm-tbtn {
  height: 30px!important;
  min-height: 0!important
}
.fav-modal .mv-pane>.bm-seg.mv-seg {
  min-height: 0!important
}
.fav-modal .bm-search-wrap>.bm-sort-wrap .bm-hbtn {
  height: 34px!important;
  min-height: 0!important
}
.fav-modal .mv-bar .bm-tbtn,
.fav-modal .mv-bar .bm-hbtn {
  height: 34px!important;
  min-height: 0!important
}
.fav-modal .mv-bar .bm-hbtn {
  min-width: 38px!important
}
.fav-modal .mv-bar {
  padding-bottom: 6px!important
}
.fav-modal .bm-search-wrap .bm-srch,
.fav-modal .bm-search-wrap input {
  min-height: 0!important
}
.fav-modal .bm-more {
  display: none!important
}
.fav-modal .bm-mpop {
  display: none;
  position: absolute;
  right: 8px;
  bottom: 54px;
  z-index: 5;
  min-width: 124px;
  max-width: calc(100% - 16px);
  padding: 4px;
  flex-direction: column;
  gap: 2px;
  border-radius: 12px;
  background: var(--pt-cd,#1b1b21);
  border: 1px solid var(--pt-ln,rgba(255,255,255,.12));
  box-shadow: 0 6px 18px rgba(0,0,0,.5)
}
.fav-modal .bm-mpop.open {
  display: flex
}
.fav-modal .bm-list:not(.bm-gallery-mode) .bm-mpop {
  display: none!important
}
.fav-modal .bm-mrow {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 36px;
  padding: 0 10px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: #fff;
  font-size: 13px;
  text-align: start;
  cursor: pointer;
  white-space: nowrap
}
.fav-modal .bm-mrow:active {
  background: rgba(255,255,255,.1)
}
.fav-modal .bm-mrow svg {
  width: 16px;
  height: 16px;
  flex: none;
  margin: 0
}
.fav-modal .bm-mrow-info {
  justify-content: center;
  padding: 0 10px;
  text-align: center
}
.fav-modal .bm-mrow-del {
  color: #fff
}
.fav-modal .bm-mrow-del svg {
  color: #ff9aa6
}
.fav-modal .bm-mrow-pin svg {
  color: #F2B93B!important
}`;
const PT_SEL = ':host .bm-overlay.fav-ov .bm-modal.fav-modal';
const PT_CTL = 'height:44px!important;min-height:0!important;min-width:0!important;padding:0!important;display:inline-flex!important;align-items:center;justify-content:center;gap:6px;border-radius:var(--pt-r)!important;border:1px solid var(--pt-ln)!important;background:var(--pt-cd)!important;color:var(--pt-tx)!important;box-shadow:none!important;font-size:12px!important;font-weight:500!important';
const PT_ON = 'background:var(--pt-ac)!important;color:var(--pt-on)!important;border-color:transparent!important;font-weight:600!important;box-shadow:none!important';
const PT_CHIP = 'height:36px!important;min-height:0!important;padding:0 12px!important;display:inline-flex!important;align-items:center;justify-content:center;border-radius:999px!important;border:1px solid var(--pt-ln)!important;background:transparent!important;color:var(--pt-dm)!important;font-size:12px!important;font-weight:500!important;box-shadow:none!important';
const PT_CHIP_ON = 'color:var(--pt-on)!important;border-color:transparent!important;background:var(--pt-ac)!important;font-weight:600!important;box-shadow:none!important';
const PT_BACK = ';background:rgba(255,255,255,.05)!important;border-radius:14px!important;padding:4px!important;gap:4px!important';
const PT_ROW = 'display:flex!important;gap:6px!important;border:0!important;border-radius:0!important;background:transparent!important;box-shadow:none!important;margin:0!important;padding:0!important';
const PT_RULES = [
    ['', 'display:flex;flex-direction:column;--pt-bg:#0e0f12;--pt-cd:#17191d;--pt-ln:#2a2d33;--pt-tx:#fff;--pt-dm:#fff;--pt-ac:var(--th);--pt-on:#fff;--pt-rd:#ff7a88;--pt-r:12px;background:var(--pt-bg)!important;color:var(--pt-tx);border:0!important;border-radius:0!important;box-shadow:none!important;font-family:Onest,system-ui,-apple-system,sans-serif'],
    [' .bm-hdr', 'order:90;position:static!important;display:flex!important;flex-wrap:nowrap!important;align-items:center;gap:6px!important;padding:6px 8px calc(8px + env(safe-area-inset-bottom,0px))!important;background:var(--pt-cd)!important;border:0!important;border-top:1px solid var(--pt-ln)!important;flex:none'],
    [' .bm-hdr .bm-title', 'flex:1 1 auto!important;min-width:0;display:block!important;padding:0!important;margin:0!important'],
    [' .bm-hdr .bm-tabs', PT_ROW + ';width:100%!important;max-width:100%!important;gap:2px!important;min-height:0!important;overflow:visible!important'],
    [' .bm-hdr .bm-tabs .bm-tbtn', 'position:relative;flex:1 1 0!important;width:0!important;height:52px!important;min-height:0!important;display:flex!important;flex-direction:column!important;align-items:center;justify-content:center;gap:4px!important;padding:4px 0 0!important;border:0!important;border-radius:var(--pt-r)!important;background:transparent!important;color:var(--pt-dm)!important;font-size:11px!important;font-weight:500!important;box-shadow:none!important'],
    [' .bm-hdr .bm-tabs .bm-tbtn.on', 'color:var(--pt-ac)!important;background:transparent!important'],
    [" .bm-hdr .bm-tabs .bm-tbtn::before", "content:'';position:absolute;top:-6px;left:50%;width:32px;height:3px;margin-left:-16px;border-radius:0 0 3px 3px;background:transparent"],
    [' .bm-hdr .bm-tabs .bm-tbtn.on::before', 'background:var(--pt-ac)'],
    [' .bm-hdr .bm-tabs .bm-tbtn svg', 'width:20px!important;height:20px!important;margin:0!important'],
    [' .bm-hdr .bm-tabs .bm-tl', 'display:block!important;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap'],
    [' .bm-hdr .bm-close', 'position:static!important;inset:auto!important;top:auto!important;transform:none!important;flex:none;width:44px!important;height:44px!important;margin:0!important;border-radius:var(--pt-r)!important;background:var(--pt-bg)!important;border:1px solid var(--pt-ln)!important;color:var(--pt-dm)!important'],
    [' .bm-search-wrap', 'order:1;padding:10px 12px 0!important'],
    [' .bm-tags-wrap', 'order:2;padding:8px 12px!important;border:0!important;background:transparent!important;gap:6px!important'],
    [' .bm-list', 'order:3;flex:1 1 auto!important;min-height:0;padding:4px 12px 12px!important;gap:12px!important;grid-auto-rows:max-content!important;align-content:start'],
    [' .bm-foot', 'order:70;padding:8px 12px!important;background:var(--pt-bg)!important;border:0!important'],
    [' .bm-tbar', 'order:80;display:flex;flex-direction:column-reverse;gap:8px;padding:8px 12px!important;background:var(--pt-bg)!important;border:0!important;border-top:1px solid var(--pt-ln)!important'],
    [' .bm-tbar .bm-tools', 'display:flex!important;width:100%!important;gap:6px!important;margin:0!important;justify-content:stretch!important;overflow:visible!important'],
    [' .bm-tbar .bm-tools>*', 'flex:1 1 0!important;min-width:0!important'],
    [' .bm-tbar .bm-tools>.bm-wseg-host', 'flex:2 1 0!important;display:flex!important'],
    [' .bm-tbar .bm-wseg', PT_ROW + ';flex:1;width:100%!important;max-width:none!important'],
    [' .bm-tbar .bm-hbtn, .bm-tbar .bm-wseg .bm-tbtn', PT_CTL + ';flex:1 1 0'],
    [' .bm-tbar .bm-hbtn.on, .bm-tbar .bm-wseg .bm-tbtn.on', PT_ON],
    [' .bm-tbar .bm-gear, .mv-bar .bm-gear', 'border-color:var(--pt-ac)!important;color:#fff!important'],
    [' .bm-tbar .bm-gear.on, .mv-bar .bm-gear.on', PT_ON],
    [' .bm-setp, .mv-setp', 'background:var(--pt-cd)!important;border:1px solid var(--pt-ln)!important;border-radius:var(--pt-r)!important'],
    [' .bm-setp', 'flex:0 0 auto!important;align-self:stretch;margin:0 12px 8px!important'],
    [' .bm-setrow-av', 'margin-top:10px!important;padding-top:6px!important'],
    [' .bm-setrow-av .bm-avbtn', 'flex:none!important;width:72px!important;height:40px!important;border-radius:10px!important'],
    [' .bm-setp, .mv-setp', 'background:transparent!important;border:0!important;border-radius:0!important;padding:0!important;gap:8px!important'],
    [' .bm-setrow', 'min-height:48px!important;padding:0 10px 0 12px!important;gap:10px!important;border:1px solid var(--pt-ln)!important;border-radius:12px!important;background:#22252b!important;background:color-mix(in srgb,var(--th) 14%,#22252b)!important;border-color:color-mix(in srgb,var(--th) 38%,#3a3e46)!important;color:#fff!important;font-size:14px!important;line-height:1.25'],
    [' .bm-setrow+.bm-setrow', 'border-top:1px solid var(--pt-ln)!important;border-top-color:color-mix(in srgb,var(--th) 38%,#3a3e46)!important'],
    [' .bm-setrow-spd', 'padding:8px 10px 8px 12px!important'],
    [' .bm-setrow-av', 'margin-top:0!important;padding-top:0!important'],
    [' .bm-setrow>.bm-tbtn, .bm-setrow>.bm-hbtn, .bm-setrow .bm-avbtn', 'flex:none!important;width:auto!important;min-width:44px!important;height:36px!important;min-height:0!important;padding:0 12px!important;border-radius:10px!important;border:1px solid var(--pt-ln)!important;border-color:color-mix(in srgb,var(--th) 38%,#3a3e46)!important;background:#0e0f12!important;background:color-mix(in srgb,var(--th) 7%,#0b0c0f)!important;color:#fff!important;font-size:13px!important;font-weight:500!important'],
    [' .bm-search-in', 'height:44px!important;border-radius:var(--pt-r)!important;background:var(--pt-cd)!important;border:1px solid var(--pt-ln)!important;color:var(--pt-tx)!important'],
    [' .bm-search-wrap>.bm-sort-wrap', 'height:44px!important;border-radius:var(--pt-r)!important;border:1px solid var(--pt-ln)!important;background:var(--pt-cd)!important'],
    [' .bm-search-wrap>.bm-sort-wrap .bm-hbtn', 'height:44px!important;background:transparent!important;border-color:var(--pt-ln)!important;color:var(--pt-tx)!important'],
    [' .bm-tags-wrap .bm-seg', PT_ROW + ';flex:1 1 100%' + PT_BACK],
    [' .bm-tags-wrap .bm-chips', 'flex:1 1 100%!important' + PT_BACK],
    [' .bm-tags-wrap .bm-seg .bm-tbtn, .bm-chips .bm-tbtn, .mv-pane>.bm-seg .bm-tbtn', PT_CHIP + ';flex:1 1 auto'],
    [' .bm-tags-wrap .bm-seg .bm-tbtn.on, .bm-chips .bm-tbtn.on, .mv-pane>.bm-seg .bm-tbtn.on', PT_CHIP_ON],
    [' .bm-chips', 'overflow-x:auto!important;flex-wrap:nowrap!important;align-items:center!important'],
    [' .bm-chips .bm-tbtn', 'flex:none!important;width:auto!important;max-width:none!important;white-space:nowrap!important;line-height:1.2!important;padding:0 14px!important'],
    [' .bm-chips .bm-tbtn .bm-ptic', 'flex:none!important;display:inline-block!important'],
    [' .bm-chips>.bm-tbtn:first-child', 'position:sticky!important;left:0;z-index:3;background-color:#1c1e22!important;background-image:linear-gradient(rgba(255,255,255,.06),rgba(255,255,255,.06))!important;box-shadow:-4px 0 0 #191a1d,6px 0 6px #191a1d!important'],
    [' .bm-chips>.bm-tbtn:first-child.on', 'background-color:var(--pt-ac)!important;background-image:none!important'],
    [' .bm-chips .bm-tbtn.bm-tall', 'background-color:#1c1e22!important;background-image:linear-gradient(rgba(255,255,255,.06),rgba(255,255,255,.06))!important'],
    [' .bm-chips .bm-tbtn.bm-tall.on', 'background-color:var(--pt-ac)!important;background-image:none!important'],
    [' .bm-list:not(.bm-gallery-mode) .bm-item', 'background:var(--pt-cd)!important;border:1.5px solid var(--st,var(--pt-ln))!important;border-radius:16px!important;padding:12px!important;box-shadow:none!important'],
    [' .bm-list:not(.bm-gallery-mode) .bm-item::before', 'display:none'],
    [' .bm-gallery-mode .bm-item', 'display:grid!important;grid-template-columns:minmax(0,1fr) auto auto auto;align-content:start;gap:0!important;padding:0!important;overflow:clip;align-self:start;min-height:min-content!important;background:var(--pt-cd)!important;border:1.5px solid var(--st,var(--pt-ln))!important;border-radius:20px!important;box-shadow:none!important'],
    [' .bm-gallery-mode .bm-item .bm-avatar, .bm-gallery-mode .bm-item .bm-prev', 'grid-column:1/-1;grid-row:1;width:100%!important;height:auto!important;aspect-ratio:16/9;object-fit:cover;margin:0!important;border:0!important;border-radius:0!important;transform:none!important'],
    [' .bm-gallery-mode .bm-item .bm-nwrap', 'display:contents!important'],
    [' .bm-gallery-mode .bm-item .bm-name', 'grid-column:1;grid-row:2;align-self:start;min-width:0;margin:0!important;padding:10px 0 10px 14px!important;font-size:16px!important;font-weight:700!important;line-height:40px;color:var(--pt-tx)!important;overflow:hidden;text-overflow:ellipsis;white-space:nowrap'],
    [' .bm-gallery-mode .bm-item .bm-seen', 'position:static!important;grid-column:1/-1;grid-row:1;align-self:end;margin:0 10px 10px!important;padding:3px 9px!important;border-radius:999px!important;z-index:2;font-size:12px'],
    [' .bm-gallery-mode .bm-item .bm-view', 'position:static!important;grid-column:2;grid-row:2;align-self:start;display:inline-flex;align-items:center;gap:5px;height:40px;margin:10px 0 0 8px!important;padding:0!important;border-radius:0!important;background:transparent!important;-webkit-backdrop-filter:none!important;backdrop-filter:none!important;color:#fff!important;font-size:14px;font-weight:600'],
    [' .bm-gallery-mode .bm-item .bm-view svg', 'width:16px;height:16px'],
    [' .bm-gallery-mode .bm-item .bm-seen', 'justify-self:start'],
    [' .bm-gallery-mode .bm-item .bm-topic', 'grid-column:1;grid-row:3;align-self:start;margin:-12px 0 0!important;padding:0 8px 12px 14px!important;border:0!important;border-radius:0!important;background:transparent!important;color:var(--pt-dm)!important;font-size:13px!important;line-height:1.35'],
    [' .bm-gallery-mode .bm-item .bm-pin, .bm-gallery-mode .bm-item .bm-del', 'position:static!important;grid-row:2;align-self:start;width:40px!important;height:40px!important;margin:10px 0 0!important;border:0!important;border-radius:var(--pt-r)!important;-webkit-backdrop-filter:none!important;backdrop-filter:none!important'],
    [' .bm-gallery-mode .bm-item .bm-pin', 'grid-column:3;margin-inline:8px 0!important;background:var(--pt-bg)!important;color:#fff!important'],
    [' .bm-gallery-mode .bm-item .bm-pin.on', 'color:#F2B93B!important;background:var(--pt-bg)!important'],
    [' .bm-gallery-mode .bm-item .bm-del', 'grid-column:4;margin-inline:8px 12px!important;background:rgba(255,122,136,.18)!important;color:#fff!important'],
    [' .bm-gallery-mode .bm-item .bm-actions-row', 'grid-column:1/-1;grid-row:4;display:grid!important;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px!important;margin:0!important;padding:0 12px 12px!important'],
    [' .bm-gallery-mode .bm-item .bm-act-btn', 'height:42px!important;border-radius:var(--pt-r)!important;background:var(--pt-bg)!important;border:1px solid var(--pt-ln)!important;color:var(--pt-tx)!important'],
    [' .bm-list:not(.bm-gallery-mode) .bm-act-btn', 'height:40px!important;border-radius:var(--pt-r)!important;background:var(--pt-bg)!important;border:0!important;color:#fff!important'],
    [' .bm-list:not(.bm-gallery-mode) .bm-pin', 'width:36px!important;height:36px!important;border-radius:10px!important;background:var(--pt-bg)!important;border:0!important;color:#fff!important'],
    [' .bm-list:not(.bm-gallery-mode) .bm-pin.on', 'color:#F2B93B!important'],
    [' .bm-list:not(.bm-gallery-mode) .bm-del', 'width:36px!important;height:36px!important;border-radius:10px!important;background:rgba(255,122,136,.18)!important;border:0!important;color:#fff!important'],
    [' .bm-list.bm-gallery-mode:not(.bm-c1) .bm-item', 'grid-template-columns:minmax(0,1fr) auto!important;border-radius:16px!important'],
    [' .bm-list.bm-gallery-mode:not(.bm-c1) .bm-item .bm-name', 'grid-column:1!important;grid-row:2!important;padding:7px 0 0 10px!important;font-size:13px!important;font-weight:700!important;line-height:18px!important'],
    [' .bm-item.pinned .bm-name::after', "content:'';display:inline-block;width:15px;height:15px;margin-inline-start:6px;vertical-align:-2px;background:url('" + STAR_GOLD_URI + "') center/contain no-repeat"],
    [' .bm-list.bm-gallery-mode:not(.bm-c1) .bm-item .bm-view', 'grid-column:2!important;grid-row:2!important;height:18px!important;margin:7px 10px 0 6px!important;gap:5px!important;font-size:12px!important;font-weight:600!important;line-height:18px!important;align-self:start!important'],
    [' .bm-list.bm-gallery-mode:not(.bm-c1) .bm-item .bm-view svg', 'width:13px!important;height:13px!important'],
    [' .bm-list.bm-gallery-mode:not(.bm-c1) .bm-item .bm-pin', 'display:none!important'],
    [' .bm-list.bm-gallery-mode:not(.bm-c1) .bm-item .bm-del', 'display:none!important'],
    [' .bm-list.bm-gallery-mode:not(.bm-c1) .bm-item .bm-topic', 'grid-column:1/-1!important;grid-row:3!important;margin:0!important;padding:6px 10px 0!important;font-size:12px!important'],
    [' .bm-list.bm-gallery-mode:not(.bm-c1) .bm-item .bm-actions-row', 'grid-column:1/-1!important;grid-row:4!important;gap:5px!important;padding:8px!important'],
    [' .bm-list.bm-gallery-mode:not(.bm-c1) .bm-item .bm-act-btn', 'height:36px!important;border-radius:10px!important'],
    [' .bm-list.bm-gallery-mode:not(.bm-c1) .bm-item .bm-act-btn:nth-child(3)', 'display:none!important'],
    [' .bm-list.bm-gallery-mode:not(.bm-c1) .bm-item .bm-act-btn.bm-more', 'display:flex!important'],
    [' .bm-list.bm-gallery-mode:not(.bm-c1) .bm-item .bm-act-btn.bm-more.on', 'background:var(--pt-cd)!important'],
    [' .bm-list.bm-gallery-mode.bm-c1 .bm-item .bm-pin', 'display:none!important'],
    [' .bm-list.bm-gallery-mode.bm-c1 .bm-item .bm-del', 'display:none!important'],
    [' .bm-list.bm-gallery-mode.bm-c1 .bm-item .bm-act-btn:nth-child(3)', 'display:none!important'],
    [' .bm-list.bm-gallery-mode.bm-c1 .bm-item .bm-act-btn.bm-more', 'display:flex!important'],
    [' .bm-list.bm-gallery-mode.bm-c1 .bm-item .bm-act-btn.bm-more.on', 'background:var(--pt-cd)!important'],
    [' .bm-gallery-mode .bm-item .bm-act-btn svg', 'width:17.6px!important;height:17.6px!important'],
    [' .mv-tb button svg', 'width:17.6px!important;height:17.6px!important'],
    [' .bm-list.bm-gallery-mode:not(.bm-c1) .bm-item .bm-act-btn svg', 'width:22px!important;height:22px!important'],
    [' .bm-list.bm-gallery-mode.bm-c1 .bm-item' + ' .bm-name', 'grid-column:1!important;grid-row:1!important;align-self:end!important;position:relative;z-index:2;display:block!important;box-sizing:border-box;margin:0!important;padding:0 0 2px 12px!important;font-size:15px!important;line-height:20px!important;color:#fff!important;background:none!important;text-shadow:0 1px 3px rgba(0,0,0,.7)'],
    [' .bm-list.bm-gallery-mode.bm-c1 .bm-item' + ' .bm-view', 'grid-column:2!important;grid-row:1!important;align-self:end!important;position:relative!important;bottom:auto!important;right:auto!important;top:auto!important;left:auto!important;z-index:2;display:inline-flex!important;align-items:center!important;gap:4px!important;box-sizing:border-box;height:auto!important;margin:0!important;padding:0 12px 2px 8px!important;font-size:13px!important;line-height:20px!important;color:#fff!important;background:none!important;text-shadow:0 1px 3px rgba(0,0,0,.7)'],
    [' .bm-list.bm-gallery-mode.bm-c1 .bm-item' + ' .bm-view svg', 'width:14px!important;height:14px!important;margin:0'],
    [' .bm-list.bm-gallery-mode.bm-c1 .bm-item' + ' .bm-view:empty', 'display:none!important'],
    [' .bm-list.bm-gallery-mode.bm-c1 .bm-item' + ' .bm-seen', 'grid-column:1/-1!important;grid-row:1!important;align-self:start!important;justify-self:start!important;margin:10px!important'],
    [' .bm-list.bm-gallery-mode.bm-c1 .bm-item' + ' .bm-topic', 'grid-row:2!important;grid-column:1/-1!important;margin:0!important;padding:10px 14px 0!important'],
    [' .bm-list.bm-gallery-mode.bm-c1 .bm-item' + ' .bm-actions-row', 'grid-row:3!important;gap:6px!important;padding:8px 10px 10px!important']
,
    [' .bm-add', 'height:48px;border-radius:var(--pt-r)!important;background:var(--pt-ac)!important;color:var(--pt-on)!important;box-shadow:none!important'],
    ['.mode-mv .mv-pane', 'padding:8px 0 0!important;gap:8px!important;background:transparent'],
    [' .mv-pane>.bm-seg', 'order:1;' + PT_ROW + ';margin:0 12px!important;width:auto!important;overflow-x:auto!important;scrollbar-width:none' + PT_BACK],
    [' .mv-pane>.mv-stage', 'order:2'],
    [' .mv-pane>.mv-setp', 'order:3;margin:0 12px!important'],
    [' .mv-pane>.mv-bar', 'order:4;margin:0!important;padding:8px 12px!important;gap:6px!important;background:var(--pt-bg);border:0!important;border-top:1px solid var(--pt-ln)!important'],
    [' .mv-bar .bm-hbtn, .mv-bar .bm-tbtn', PT_CTL],
    [' .mv-bar .bm-hbtn.on, .mv-bar>.bm-tbtn:first-child', PT_ON],
    [' .mv-bar .bm-gear', 'border-color:var(--pt-ac)!important;color:#fff!important'],
    [' .mv-bar .bm-gear.on', PT_ON],
    [' .mv-bar .mv-clr', 'color:var(--pt-rd)!important;border-color:rgba(255,122,136,.4)!important;background:transparent!important'],
    [' .mv-grid', 'padding:0 12px 12px!important;gap:12px!important'],
    [' .mv-tile', 'background:var(--pt-cd)!important;border:1.5px solid var(--pt-ln)!important;border-radius:16px!important;box-shadow:none!important'],
    [' .mv-tile.act', 'border-color:var(--pt-ac)!important;box-shadow:none!important'],
    [' .mv-tile:fullscreen, .mv-tile.act:fullscreen, .mv-tile:-webkit-full-screen, .mv-tile.act:-webkit-full-screen', 'border:0!important;border-radius:0!important;outline:0!important;box-shadow:none!important;background:#000!important'],
    [' .mv-lbl', 'background:transparent!important;border:0!important;padding:10px 12px!important;font-size:15px!important;font-weight:600!important'],
    [' .mv-vw', 'background:var(--pt-bg)!important;color:var(--pt-dm)!important'],
    [' .mv-tile .mv-stg', 'order:1'],
    [' .mv-tile .mv-lbl', 'order:2;position:relative;box-sizing:border-box;margin:0!important;padding:10px 14px 0!important;font-size:16px!important;font-weight:700!important;line-height:24px!important;color:var(--pt-tx)!important;border:0!important;background:none!important;text-shadow:none!important;justify-content:space-between'],
    [' .mv-tile:fullscreen .mv-lbl, .mv-tile:-webkit-full-screen .mv-lbl', 'position:absolute!important;top:0;left:0;right:0;z-index:3;height:auto!important;background:none!important;color:#fff!important;padding:calc(10px + env(safe-area-inset-top,0px)) calc(14px + env(safe-area-inset-right,0px)) 22px calc(14px + env(safe-area-inset-left,0px))!important;opacity:0;text-shadow:none!important'],
    [' .mv-tile.ui:fullscreen .mv-lbl, .mv-tile.ui:-webkit-full-screen .mv-lbl', 'opacity:1'],
    [' .mv-tile:fullscreen .mv-lbl, .mv-tile:-webkit-full-screen .mv-lbl', 'padding-right:calc(32px + env(safe-area-inset-right,0px))!important'],
    [' .mv-tile .mv-vw', 'padding:0!important;gap:5px!important;border-radius:0!important;background:none!important;color:#fff!important;font-size:14px!important;font-weight:600!important;line-height:24px!important'],
    [' .mv-tile .mv-vw svg', 'width:16px!important;height:16px!important'],
    [' .mv-tile .mv-nm', 'min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap'],
    [' .mv-tile .mv-tb', 'order:3'],
    [' .mv-grid:not(.multi) .mv-tile:not(:fullscreen):not(:-webkit-full-screen) .mv-lbl', 'height:44px;margin:-44px 0 0!important;padding:0 13px 0!important;align-items:flex-end;background:none!important;color:#fff!important;text-shadow:none!important;z-index:2;pointer-events:none;transition:opacity .2s'],
    [' .mv-grid:not(.multi) .mv-tile.ui:not(:fullscreen):not(:-webkit-full-screen) .mv-lbl', 'opacity:0'],
    [' .mv-grid.multi .mv-tile .mv-lbl', 'padding:7px 10px 0!important;font-size:13px!important;line-height:18px!important'],
    [' .mv-grid.multi .mv-tile .mv-vw', 'font-size:12px!important'],
    [' .mv-grid.multi .mv-tile .mv-vw svg', 'width:13px!important;height:13px!important'],
    [' .mv-tb', 'background:transparent!important;border:0!important;padding:8px 10px 10px!important;gap:6px!important'],
    [' .mv-tb button', 'height:42px!important;border-radius:var(--pt-r)!important;background:var(--pt-bg)!important;border:1px solid var(--pt-ln)!important;color:var(--pt-tx)!important'],
    [' .mv-grid:not(.multi) .mv-tile.act .mv-tb button:nth-child(3)', 'background:var(--pt-bg)!important;border-color:var(--pt-ln)!important;color:var(--pt-rd)!important'],
    [' .cal-pane', 'padding:10px 12px 0!important;gap:10px!important;background:transparent'],
    [' .cal-pane .cal-filters>.cal-frow:first-child', 'display:flex!important;flex-wrap:nowrap!important;gap:6px!important;align-items:center'],
    [' .cal-pane .cal-filters .bm-seg', PT_ROW + ';flex:none'],
    [' .cal-pane .cal-filters .bm-seg .bm-tbtn', PT_CTL + ';width:48px!important'],
    [' .cal-pane .cal-filters .bm-seg .bm-tbtn.on', PT_ON],
    [' .cal-pane .cal-filters .bm-seg .bm-tbtn>span', 'display:none!important'],
    [' .cal-pane .cal-filters .bm-seg.cal-sites', 'flex:1 1 auto!important;min-width:0'],
    [' .cal-pane .cal-filters .bm-seg.cal-sites .bm-tbtn', 'width:auto!important;flex:1 1 0!important;min-width:0;padding:0 10px!important;white-space:nowrap;overflow:hidden'],
    [' .cal-pane .cal-filters .bm-seg.cal-sites .bm-tbtn>span', 'display:inline!important;overflow:hidden;text-overflow:ellipsis'],
    [' .cal-pane .cal-pill', PT_CTL + ';flex:1 1 auto!important'],
    [' .cal-pane .cal-dd', PT_CTL + ';padding:0 12px!important'],
    [' .cal-pane .bm-tbtn.on, .cal-pane .cal-pill.on, .cal-pane .cal-dd.on, .cal-pane .cal-opt.on, .cal-pane .cal-mrow.on', PT_ON],
    [' .cal-pane .cal-day', 'background:var(--pt-cd)!important;border:1px solid var(--pt-ln)!important;border-radius:12px!important;color:var(--pt-tx)!important;font-weight:600;box-shadow:none!important'],
    [' .cal-pane .cal-day.past', 'color:var(--pt-dm)!important'],
    [' .cal-pane .cal-day.today', 'border-color:var(--pt-ac)!important'],
    [' .cal-pane .cal-day.sel', 'background:var(--pt-ac)!important;color:var(--pt-on)!important;border-color:transparent!important'],
    [' .cal-pane .cal-b', 'background:rgba(255,255,255,.12)!important;background:color-mix(in srgb,var(--pt-ac) 24%,transparent)!important;color:var(--pt-tx)!important'],
    [' .cal-pane .cal-b.s', 'background:rgba(90,150,255,.22)!important;color:#a9bcff!important'],
    [' .cal-pane .cal-day.sel .cal-b', 'background:rgba(0,0,0,.25)!important;color:var(--pt-on)!important'],
    [' .cal-pane .cal-det', 'flex:1 0 auto;background:var(--pt-cd)!important;border:0!important;border-radius:18px 18px 0 0!important;box-shadow:none!important;margin:0 -12px!important;padding:14px!important'],
    [' .cal-pane .cal-dock', 'order:5;position:sticky;bottom:0;z-index:3;margin:0 -12px!important;padding:8px 12px!important;gap:8px!important;background:var(--pt-bg);border-top:1px solid var(--pt-ln)'],
    [' .cal-pane .cal-head', 'display:flex;gap:6px;align-items:center'],
    [' .cal-pane .cal-title', 'flex:1;text-align:center;font-weight:700;font-size:15px;color:var(--pt-tx)'],
    [' .cal-pane .cal-nav, .cal-pane .cal-today', PT_CTL + ';min-width:44px!important;padding:0 12px!important'],
    [' .cal-pane .cal-dock .bm-seg', PT_ROW + ';width:100%!important'],
    [' .cal-pane .cal-dock .bm-seg .bm-tbtn', PT_CTL + ';flex:1 1 0'],
    [' .cal-pane .cal-dock .bm-seg .bm-tbtn.on', PT_ON],
    [' .hs-pane', 'padding:10px 12px!important;gap:8px!important'],
    [' .hs-pane .hs-row', 'background:var(--pt-cd)!important;border:0!important;border-radius:20px!important;box-shadow:none!important'],
    [' .hs-pane .hs-row.fav', 'box-shadow:inset 0 0 0 1px rgba(242,185,59,.35)!important'],
    [' .hs-pane .hs-btn', 'width:44px!important;height:44px!important;border-radius:14px!important;background:transparent!important;border:0!important;color:#F2B93B!important'],
    [' .hs-pane .hs-btn.on', 'color:#F2B93B!important;background:rgba(242,185,59,.14)!important'],
    [' .hs-pane .hs-btn.del', 'color:var(--pt-rd)!important;background:transparent!important'],
    [' .hs-pane .hs-div', 'background:var(--pt-ln)!important'],
    [' .hs-pane .bm-tbtn, .mv-add .mv-chips .bm-tbtn', PT_CHIP],
    [' .mv-add .mv-chips .bm-tbtn', 'flex:1 1 auto!important;padding:0 6px!important;white-space:nowrap!important'],
    [' .hs-pane .bm-tbtn.on, .mv-add .mv-chips .bm-tbtn.on', PT_CHIP_ON],
    [' .hs-pane .hs-trash', 'color:var(--pt-rd)!important'],
    [' .hs-pane .hd-hint, .hs-pane .bm-empty, .bm-list .bm-empty', 'color:#fff!important'],
    [' .hs-pane .ch-bar, .hs-pane .ch-fill', 'background:var(--pt-ac)!important']
];
const CSS_CALDOCK = '.cal-dock{order:-1;display:flex;flex-direction:column;gap:10px}\n';
const PT_DYN = `@supports(color:color-mix(in srgb,red 10%,blue)) {
  ${PT_SEL} {
    --pt-bg:color-mix(in srgb,var(--th) 7%,#0b0c0f);
    --pt-cd:color-mix(in srgb,var(--th) 11%,#15171b);
    --pt-ln:color-mix(in srgb,var(--th) 24%,#24272d);
    --pt-dm:#fff
  }
}
@supports(color:oklch(from red l c h)) {
  ${PT_SEL} {
    --pt-on:oklch(from var(--th) calc((0.7 - l) * 1000) 0 0 / 1)
  }
}
`;
const PT_WIDE = `@media(min-width:521px){${PT_SEL}{border:1px solid var(--pt-ln)!important;border-radius:22px!important;overflow:hidden}}
@media(min-width:760px){${[
    [' .bm-hdr', 'order:-1;border-top:0!important;border-bottom:1px solid var(--pt-ln)!important;padding:8px 12px!important'],
    [' .bm-hdr .bm-tabs .bm-tbtn', 'height:44px!important;flex-direction:row!important;gap:8px!important;padding:0!important'],
    [' .bm-hdr .bm-tabs .bm-tbtn::before', 'top:auto;bottom:-8px;border-radius:3px 3px 0 0'],
    [' .bm-tbar', 'border-top:1px solid var(--pt-ln)!important']
].map(([x, d]) => x.split(',').map(y => PT_SEL + y).join(',') + `{${d}}`).join('\n')}}
`;
const CSS_PULT_PANEL = `:host .panel:not(.mini) {
  --pb:#0e0f12;
  --pc:#17191d;
  --pl:#2a2d33;
  background: var(--pb)!important;
  border: 1px solid var(--pl)!important;
  border-radius: 18px!important;
  -webkit-backdrop-filter: none!important;
  backdrop-filter: none!important;
  box-shadow: 0 14px 36px rgba(0,0,0,.6)!important
}
@supports(color:color-mix(in srgb,red 10%,blue)) {
  :host .panel:not(.mini) {
    --pb:color-mix(in srgb,var(--th) 7%,#0b0c0f);
    --pc:color-mix(in srgb,var(--th) 11%,#15171b);
    --pl:color-mix(in srgb,var(--th) 24%,#24272c)
  }
}
:host .panel:not(.mini)::before {
  display: none!important
}
:host .panel:not(.mini) {
  padding: 8px!important
}
:host .panel .col {
  gap: 6px!important
}
:host .panel .sep {
  display: none!important
}
:host .panel .hdr,
:host .panel .grid-4,
:host .panel .row-4,
:host .panel .row-2 {
  background: rgba(255,255,255,.05)!important;
  border: 0!important;
  border-radius: 14px!important;
  padding: 4px!important;
  margin: 0!important;
  gap: 6px!important
}
:host .panel .btn {
  height: 56px!important;
  min-height: 56px!important;
  max-height: 56px!important;
  box-sizing: border-box;
  overflow: hidden;
  padding: 8px 2px 6px!important;
  border-radius: 12px!important;
  border: 1px solid var(--pl)!important;
  background: transparent!important;
  color: #fff!important;
  transform: none!important
}
:host .panel .btn:not(.fav-on) {
  color: #fff!important
}
:host .panel .btn:hover,
:host .panel .btn:not(.on):hover {
  background: rgba(255,255,255,.08)!important;
  border-color: var(--pl)!important;
  transform: none!important
}
:host .panel .btn:active {
  transform: scale(.97)!important
}
:host .panel .btn sub {
  font-size: 10.5px!important;
  font-weight: 500;
  letter-spacing: .2px;
  opacity: 1!important;
  color: #fff;
  white-space: nowrap;
  word-break: normal;
  max-width: none
}
:host .panel .btn.fav-on sub {
  color: #fff
}
:host .panel .btn.on {
  background: var(--th)!important;
  border-color: transparent!important;
  box-shadow: none;
  font-weight: 600
}
:host .panel .h-btn {
  border-radius: 12px!important;
  background: transparent!important;
  border: 1px solid var(--pl)!important;
  color: #fff!important
}
:host .panel .h-btn:hover {
  background: rgba(255,255,255,.1)!important;
  transform: none!important
}
:host .panel .h-btn.close:hover {
  background: rgba(231,76,60,.9)!important
}
:host .panel .h-btn.lang {
  border-radius: 12px!important
}
:host .panel .stats {
  background: transparent!important;
  border: 0!important;
  color: #fff!important
}
:host .panel .drag {
  color: #fff!important
}
:host .panel .hdr-btns .hsep {
  background: var(--pl)!important
}
:host .panel .hdr-btns {
  flex: 1 1 auto!important;
  min-width: 0
}
:host .panel .hdr-btns .stats {
  flex: 1 1 0!important;
  width: auto!important;
  min-width: 62px!important;
  max-width: none!important;
  height: 30px!important;
  justify-content: center!important;
  align-items: center!important;
  padding: 0 6px!important;
  margin: 0!important
}
:host .panel .v-cap {
  background: var(--pc)!important;
  border: 1px solid var(--pl)!important;
  border-radius: 14px!important
}
:host .panel .vval,
:host .panel .s-val {
  color: #fff!important
}
:host .panel .z-btn {
  border-radius: 12px!important;
  background: transparent!important;
  border: 1px solid var(--pl)!important;
  color: #fff!important
}
:host .panel .z-btn:hover {
  background: rgba(255,255,255,.1)!important
}
:host .panel .z-btn.on {
  background: var(--th)!important;
  border-color: transparent!important
}
:host .panel .scale-ctrl {
  background: transparent!important;
  border: 1px solid var(--pl);
  border-radius: 12px!important;
  padding: 2px 6px!important
}
:host .panel .scale-ctrl .z-btn {
  width: 24px;
  height: 24px!important;
  border: 0!important;
  border-radius: 8px!important;
  background: rgba(255,255,255,.08)!important
}
:host .panel .hotkeys {
  color: #fff!important;
  font-size: 11px!important;
  opacity: 1;
  font-weight: 500
}
:host .panel:not(.mini) {
  --pon:#fff
}
@supports(color:oklch(from red l c h)) {
  :host .panel:not(.mini) {
    --pon:oklch(from var(--th) calc((0.7 - l) * 1000) 0 0 / 1)
  }
}
:host .panel .btn,
:host .panel .z-btn,
:host .panel .h-btn {
  background: var(--pc)!important;
  font-weight: 500
}
:host .panel .btn:hover,
:host .panel .z-btn:hover,
:host .panel .h-btn:hover {
  background: color-mix(in srgb,var(--pc) 80%,#fff)!important
}
:host .panel .btn.on,
:host .panel .z-btn.on {
  background: var(--th)!important;
  color: var(--pon)!important;
  border-color: transparent!important;
  font-weight: 600!important
}
:host .panel .btn.on sub {
  color: var(--pon)!important
}
:host .panel .h-btn.lang {
  border-color: var(--th)!important;
  color: #fff!important
}
:host .panel .h-btn.close {
  color: #fff!important
}
:host .panel .h-btn.close:hover {
  background: rgba(255,122,136,.9)!important
}
:host .panel .scale-ctrl {
  background: var(--pc)!important
}
:host .panel .scale-ctrl .z-btn {
  background: rgba(255,255,255,.08)!important;
  border: 0!important
}
:host .panel .vslider {
  height: 4px!important;
  border-radius: 2px!important
}
:host .panel .vslider::-webkit-slider-thumb {
  width: 18px!important;
  height: 18px!important;
  border: 0!important;
  box-shadow: none!important;
  background: #fff!important
}
:host .panel .btn:focus-visible,
:host .panel .z-btn:focus-visible,
:host .panel .h-btn:focus-visible {
  outline: 2px solid var(--th)!important;
  outline-offset: 2px
}
:host .panel .btn.fav-on {
  color: #F2B93B!important;
  position: relative
}
:host .panel .btn.fav-on svg polygon {
  fill: none!important
}
:host .panel .btn.fav-on sub {
  color: #fff!important
}
:host .panel .btn.rec-active {
  animation: recRing 1.4s infinite!important;
  background: transparent!important;
  border-color: #ff4d5e!important;
  color: #ff4d5e!important
}
:host .panel .btn.rec-active sub {
  color: #fff!important
}
:host .panel:not(.mini) {
  --pl:#3a3e46;
  --pt:#22252b
}
@supports(color:color-mix(in srgb,red 10%,blue)) {
  :host .panel:not(.mini) {
    --pl:color-mix(in srgb,var(--th) 38%,#3a3e46);
    --pt:color-mix(in srgb,var(--th) 14%,#22252b)
  }
}
:host .panel .hdr,
:host .panel .grid-4,
:host .panel .row-4,
:host .panel .row-2 {
  background: rgba(0,0,0,.34)!important
}
:host .panel .btn,
:host .panel .z-btn,
:host .panel .h-btn {
  background: var(--pt)!important;
  border-color: var(--pl)!important;
  transition: background .12s,border-color .12s,transform .12s
}
:host .panel .scale-ctrl {
  background: var(--pt)!important;
  border-color: var(--pl)!important
}
:host .panel .v-cap {
  background: rgba(0,0,0,.34)!important;
  border-color: var(--pl)!important
}
:host .panel .btn:hover,
:host .panel .z-btn:hover,
:host .panel .h-btn:hover {
  background: color-mix(in srgb,var(--pt) 85%,#fff)!important
}
:host .panel .btn:not(.on):active,
:host .panel .z-btn:not(.on):active,
:host .panel .h-btn:active {
  background: color-mix(in srgb,var(--th) 38%,var(--pt))!important;
  border-color: var(--th)!important;
  transform: scale(.95)!important
}
:host .panel .btn.on:active,
:host .panel .z-btn.on:active {
  filter: brightness(.85);
  transform: scale(.95)!important
}
:host .panel .scale-ctrl .z-btn {
  background: rgba(255,255,255,.1)!important;
  border: 0!important
}`;
const CSS_PULT_SQ = [
    ' .bm-item', ' .bm-list:not(.bm-gallery-mode) .bm-item', ' .bm-gallery-mode .bm-item', ' .bm-list.bm-gallery-mode:not(.bm-c1) .bm-item', ' .bm-list.bm-gallery-mode.bm-c1 .bm-item', ' .mv-tile', ' .mv-tile:fullscreen'
].map(x => PT_SEL + x).join(',') + '{border-radius:4px!important}\n' + [' .mv-tile .mv-stg', ' .mv-tile video', ' .bm-item .bm-avatar', ' .bm-item .bm-prev', ' .bm-item img[data-frame]'].map(x => PT_SEL + x).join(',') + '{border-radius:0!important}\n'
    + ':host .panel .grid-4{grid-auto-rows:56px!important}\n'
    + ':host .panel .grid-4>.btn{width:100%!important;min-width:0!important;height:56px!important;min-height:56px!important;max-height:56px!important;overflow:hidden!important;padding:8px 2px 6px!important}\n'
    + ':host .panel .grid-4>.btn sub{white-space:nowrap!important;word-break:normal!important;max-width:100%}\n';
const CSS_PULT = CSS_CALDOCK + PT_RULES.map(([x, d]) => x.split(',').map(y => PT_SEL + y).join(',') + `{${d}}`).join('\n') + '\n' + PT_DYN + PT_WIDE + CSS_PULT_PANEL + CSS_PULT_SQ;
const CSS_X = `.off {
  display: none!important
}
.panel.dragging {
  -webkit-backdrop-filter: none!important;
  backdrop-filter: none!important
}
:host([data-dfb]) :is(.bm-overlay,.cf-ov,.dlg-ov,.fr-ov) {
  position: absolute!important;
  inset: auto 0 0 0!important;
  width: 100%!important;
  height: 100vh!important;
  height: 100dvh!important
}
.hd-sec {
  display: flex;
  align-items: center;
  gap: 6px
}
.cf-ov {
  position: fixed;
  inset: 0;
  z-index: 2147483647;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(0,0,0,.75);
  pointer-events: auto
}
.cf-card {
  width: 350px;
  max-width: 100%;
  box-sizing: border-box;
  padding: 24px;
  border-radius: 24px;
  background: #17191d;
  border: 1px solid #2a2d33;
  box-shadow: 0 20px 60px rgba(0,0,0,.7);
  color: #fff;
  font-family: Onest,system-ui,-apple-system,sans-serif;
  display: flex;
  flex-direction: column;
  gap: 20px
}
.cf-head {
  display: flex;
  align-items: center;
  gap: 14px
}
.cf-ic {
  flex: none;
  width: 48px;
  height: 48px;
  border-radius: 16px;
  background: rgba(255,122,136,.14);
  color: #ff7a88;
  display: flex;
  align-items: center;
  justify-content: center
}
.cf-ic svg {
  width: 24px;
  height: 24px
}
.cf-txt {
  font-size: 18px;
  font-weight: 700;
  line-height: 1.3
}
.cf-btns {
  display: flex;
  gap: 10px
}
.cf-btns button {
  flex: 1 1 0;
  height: 48px;
  padding: 0;
  border-radius: 999px;
  border: 1px solid #2a2d33;
  background: transparent;
  color: #fff;
  font: inherit;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer
}
.cf-btns .cf-yes {
  background: var(--th);
  border-color: transparent;
  color: #fff;
  font-weight: 700
}
.st-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px
}
.st-chips .bm-tbtn {
  flex: 0 0 auto;
  height: auto;
  margin: 0;
  padding: 7px 12px;
  font-size: 12px;
  border: 1px solid rgba(255,255,255,.2);
  border-radius: 10px;
  background: rgba(255,255,255,.05);
  white-space: normal;
  text-align: center
}
.st-chips .bm-tbtn.on {
  background: var(--th)
}
.bm-view {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  margin-left: 8px;
  font-size: 11px;
  color: #aaa;
  flex-shrink: 0
}
.bm-view svg {
  width: 12px;
  height: 12px
}
.hs-tools {
  display: flex;
  justify-content: flex-end
}
.hs-tools .bm-btn {
  padding: 8px 16px;
  font-size: 12px
}
.hs-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 6px 10px 12px;
  border-radius: 20px;
  background: rgba(255,255,255,.05);
  cursor: pointer;
  transition: .2s
}
.hs-row:hover {
  background: rgba(255,255,255,.09)
}
.hs-ava {
  flex: none;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  object-fit: cover;
  background: rgba(255,255,255,.1)
}
.hs-info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px
}
.hs-name {
  color: #fff;
  font-size: 16px;
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap
}
.hs-meta {
  color: #c9b4bc;
  font-size: 13px
}
.hs-btn {
  flex: none;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: 14px;
  background: transparent;
  color: #f2b93b;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center
}
.hs-btn:hover {
  background: rgba(255,255,255,.08)
}
.hs-btn.on {
  color: #f2b93b;
  background: rgba(242,185,59,.14)
}
.hs-btn.on svg {
  fill: currentColor
}
.hs-div {
  flex: none;
  width: 1px;
  height: 28px;
  background: rgba(255,255,255,.14)
}
.hs-row.fav {
  box-shadow: inset 0 0 0 1px rgba(242,185,59,.35)
}
:where(.bm-modal) button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 5px
}
.bm-modal button svg {
  flex: none;
  margin: 0
}
.hs-btn svg,
.bm-close svg,
.bm-del svg,
.bm-pin svg {
  display: block;
  margin: 0!important
}
.hs-btn svg {
  width: 22px;
  height: 22px
}
.bm-btn,
.bm-add {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px
}
.fav-modal .bm-tabs {
  flex: 0 1 auto;
  min-width: 0;
  max-width: 100%
}
.fav-modal .bm-hdr .bm-title {
  padding-inline-end: 0!important;
  flex: 1 1 auto!important;
  min-width: 0;
  min-height: 0
}
.fav-modal .bm-tools {
  order: 2!important;
  flex: 0 0 auto!important;
  margin-inline-start: auto!important;
  justify-content: flex-end!important;
  gap: 6px!important
}
.fav-modal .bm-tabs .bm-tbtn {
  height: 34px!important;
  padding: 0 12px!important;
  font-size: 13px!important;
  font-weight: 700;
  gap: 6px;
  color: #aaa;
  flex: 1 1 auto
}
.fav-modal .bm-tabs .bm-tbtn.on {
  color: #fff
}
.fav-modal .bm-tabs .bm-tbtn svg {
  width: 15px;
  height: 15px
}
.fav-modal .bm-hdr .bm-tabs {
  display: flex!important;
  width: 100%!important;
  flex: 1 1 auto!important;
  min-width: 0!important;
  max-width: 100%!important
}
.fav-modal .bm-hdr .bm-tabs .bm-tbtn {
  flex: 1 1 auto!important;
  width: auto;
  min-width: 0!important;
  padding: 0 3px!important;
  gap: 3px!important;
  font-size: 12.5px!important;
  overflow: hidden;
  justify-content: center
}
.fav-modal .bm-tabs .bm-tbtn .bm-tl {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap
}
.fav-modal .bm-tools {
  order: 3!important;
  flex: 1 0 100%!important;
  justify-content: flex-end!important
}
.fav-modal .bm-tools .bm-wseg-host {
  margin-inline-end: auto;
  display: flex;
  min-width: 0
}
.fav-modal.mode-hist .bm-search-wrap,
.fav-modal.mode-hist .bm-tags-wrap,
.fav-modal.mode-hist .bm-list,
.fav-modal.mode-hist .bm-foot,
.fav-modal.mode-hist .bm-tools,
.fav-modal.mode-hist .bm-setp {
  display: none!important
}
.fav-modal .hs-pane {
  display: none
}
.fav-modal.mode-hist .hs-pane {
  display: flex;
  flex-direction: column;
  gap: 8px;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 14px;
  scrollbar-width: thin;
  scrollbar-color: rgba(255,255,255,.2) transparent
}
.hs-pane>* {
  flex: none
}
.hs-btn.del {
  color: #ff7a88;
  background: transparent
}
.hs-btn.del:hover {
  background: rgba(255,122,136,.16);
  color: #ff7a88
}
.st-chips {
  align-items: center
}
.st-chips.hs-seg {
  flex-wrap: nowrap;
  gap: 4px;
  padding: 3px;
  border-radius: 12px;
  background: rgba(255,255,255,.05)
}
.st-chips.hs-seg .bm-tbtn {
  flex: 1 1 0;
  min-width: 0;
  padding: 8px 4px;
  border: 0;
  background: transparent;
  border-radius: 9px;
  box-shadow: none
}
.st-chips.hs-seg .bm-tbtn.on {
  background: var(--th)
}
.hs-top {
  justify-content: space-between;
  flex-wrap: nowrap
}
.hs-trash {
  margin-inline-start: auto;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  background: transparent;
  color: #ff8591;
  border-radius: 8px;
  cursor: pointer
}
.hs-trash:hover {
  background: rgba(229,97,111,.18)
}
.hs-trash svg {
  width: 18px;
  height: 18px;
  display: block;
  flex: none;
  margin: 0
}
.hs-sep {
  flex: none;
  width: 1px;
  height: 22px;
  margin: 0 6px;
  background: rgba(255,255,255,.22);
  border-radius: 1px
}
.ch-box {
  padding: 10px 12px;
  border-radius: 10px;
  background: rgba(255,255,255,.05);
  margin-bottom: 8px
}
.ch-ttl {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  font-size: 12px;
  color: rgba(255,255,255,.65);
  margin-bottom: 8px
}
.ch-ttl b {
  font-weight: 600;
  color: #fff
}
.ch-bars {
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 84px;
  direction: ltr
}
.ch-col {
  flex: 1 1 0;
  min-width: 0;
  height: 100%;
  display: flex;
  align-items: flex-end
}
.ch-bar {
  width: 100%;
  min-height: 2px;
  border-radius: 3px 3px 0 0;
  background: var(--th)
}
.ch-bar.zero {
  background: rgba(255,255,255,.12)
}
.ch-axis {
  display: flex;
  gap: 3px;
  margin-top: 4px;
  font-size: 9px;
  color: rgba(255,255,255,.5);
  direction: ltr
}
.ch-axis span {
  flex: 1 1 0;
  min-width: 0;
  text-align: center;
  white-space: nowrap
}
.ch-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  margin-top: 6px
}
.ch-name {
  flex: 0 0 32%;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap
}
.ch-track {
  flex: 1 1 auto;
  height: 8px;
  border-radius: 3px;
  background: rgba(255,255,255,.08);
  overflow: hidden
}
.ch-fill {
  height: 100%;
  border-radius: 3px;
  background: var(--th)
}
.ch-val {
  flex: 0 0 auto;
  color: rgba(255,255,255,.65);
  font-variant-numeric: tabular-nums
}
.bm-view {
  margin-left: 0;
  margin-inline-start: 8px
}
:host([data-rtl]) .fav-modal .bm-gallery-mode .bm-pin {
  right: auto;
  left: auto;
  inset-inline-end: 38px
}
:host([data-rtl]) .bm-gallery-mode .bm-del {
  right: auto;
  left: 6px
}
:host([data-rtl]) .fav-modal .bm-gallery-mode .bm-pin {
  left: 38px
}
:host([data-rtl]) .fav-modal .bm-gallery-mode .bm-link>div {
  padding-inline: 0
}
:host([data-rtl]) .fav-modal .bm-item {
  padding: 12px 16px 12px 12px
}
:host([data-rtl]) .fav-modal .bm-gallery-mode .bm-item {
  padding: 8px
}
:host([data-rtl]) .fav-modal .bm-item::before {
  left: auto;
  right: 0;
  border-radius: 2px 0 0 2px
}
:host([data-rtl]) .fav-modal .bm-topic {
  border-left: 0;
  border-right: 3px solid var(--th)
}
:host([data-rtl]) .st-val {
  text-align: left
}
:host([data-rtl]) .fav-modal .bm-sort-wrap .bm-hbtn+.bm-hbtn {
  border-left: 0;
  border-right: 1px solid rgba(255,255,255,.12)
}
:host([data-rtl]) .fav-modal .bm-seg .bm-tbtn+.bm-tbtn {
  border-left: 0;
  border-right: 1px solid rgba(255,255,255,.12)
}
.fav-modal .bm-seg .bm-tbtn {
  gap: 6px
}
.fav-modal .bm-chips .bm-tbtn svg {
  width: 12px;
  height: 12px;
  margin-inline-end: 4px;
  vertical-align: -1px
}
.fav-modal .bm-grp-btn svg {
  width: 12px;
  height: 12px;
  margin-inline-end: 4px;
  vertical-align: -1px
}
.dlg-txt {
  display: flex;
  align-items: center;
  gap: 8px
}
.dlg-txt svg {
  width: 14px;
  height: 14px;
  flex: none
}
.fav-modal .bm-ptags {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  width: 100%
}
.fav-modal .bm-ptag {
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid rgba(255,255,255,.12);
  background: rgba(255,255,255,.06);
  color: #cfcfd6;
  font-size: 11px;
  font-family: inherit;
  cursor: pointer;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap
}
.fav-modal .bm-ptag.on {
  background: var(--th);
  color: #fff;
  border-color: transparent
}
.fav-modal .bm-ptag.more {
  cursor: default;
  opacity: .7
}
.fav-modal .bm-gallery-mode .bm-ptags {
  order: 6
}
.fav-modal .bm-gallery-mode .bm-ptag {
  font-size: 10px;
  padding: 1px 6px
}
.fav-modal .bm-list.bm-gallery-mode .bm-item .bm-grp-btn {
  display: none!important
}
.fav-modal .bm-gallery-mode .bm-nwrap {
  flex-wrap: wrap;
  row-gap: 2px;
  align-content: flex-start
}
.fav-modal .bm-gallery-mode .bm-nwrap .bm-stat {
  flex: none;
  margin: 0 6px 0 0
}
.fav-modal .bm-gallery-mode .bm-nwrap .bm-name {
  flex: 1 0 calc(100% - 16px);
  min-width: 0
}
.fav-modal .bm-gallery-mode .bm-nwrap .bm-view {
  margin: 0 8px 0 0
}
.fav-modal .bm-gallery-mode .bm-nwrap .bm-view:not(:empty),
.fav-modal .bm-gallery-mode .bm-nwrap .bm-view:empty+.bm-seen {
  margin-inline-start: 15px
}
.fav-modal .bm-gallery-mode .bm-nwrap .bm-seen {
  margin-left: 0
}
.fav-modal .bm-tabs .bm-tbtn {
  padding: 0 8px!important;
  font-size: 12.5px!important
}
.fav-modal .cal-pane {
  display: none
}
.fav-modal.mode-cal .cal-pane {
  display: flex;
  flex-direction: column;
  gap: 10px;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 12px 14px;
  scrollbar-width: thin;
  scrollbar-color: rgba(255,255,255,.2) transparent
}
.fav-modal.mode-cal .bm-search-wrap,
.fav-modal.mode-cal .bm-tags-wrap,
.fav-modal.mode-cal .bm-list,
.fav-modal.mode-cal .bm-foot,
.fav-modal.mode-cal .bm-tools,
.fav-modal.mode-cal .bm-setp,
.fav-modal.mode-cal .hs-pane {
  display: none!important
}
.fav-modal.mode-hist .cal-pane {
  display: none!important
}
.cal-pane {
  color: #e6e6e6
}
.cal-pane>* {
  flex: none
}
.cal-pane {
  overflow-anchor: none
}
.cal-head {
  display: flex;
  align-items: center;
  gap: 8px
}
.cal-title {
  flex: 1;
  text-align: center;
  font-weight: 700;
  font-size: 15px;
  color: #f2f2f2
}
.cal-nav svg {
  display: block;
  margin: auto
}
.cal-nav,
.cal-today {
  height: 34px;
  min-width: 34px;
  padding: 0 10px;
  border-radius: 8px;
  border: 1px solid rgba(255,255,255,.12);
  background: rgba(255,255,255,.06);
  color: #eee;
  font-size: 16px;
  font-family: inherit;
  cursor: pointer
}
.cal-today {
  font-size: 12px
}
.cal-filters {
  display: flex;
  flex-direction: column;
  gap: 8px
}
.cal-frow {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  align-items: center
}
.cal-sel {
  flex: 1 1 130px;
  min-width: 0;
  height: 34px;
  border-radius: 8px;
  border: 1px solid rgba(255,255,255,.12);
  background: rgba(0,0,0,.35);
  color: #eee;
  padding: 0 8px;
  font: inherit;
  font-size: 13px
}
.cal-tl {
  display: inline-flex;
  align-items: center;
  gap: 6px
}
.cal-tl svg {
  width: 14px;
  height: 14px;
  flex: none
}
.cal-pill {
  flex: 1 1 100%;
  height: 36px;
  padding: 0 12px;
  text-align: center;
  border-radius: 8px;
  border: 1px solid rgba(255,255,255,.12);
  background: rgba(255,255,255,.06);
  color: #ddd;
  font: inherit;
  font-size: 13px;
  cursor: pointer
}
.cal-pill.on {
  background: var(--th);
  color: #fff;
  border-color: transparent
}
.cal-models {
  display: grid;
  grid-template-columns: repeat(2,minmax(0,1fr));
  gap: 6px;
  max-height: 280px;
  overflow-y: auto;
  padding: 8px;
  border-radius: 10px;
  background: rgba(0,0,0,.28);
  border: 1px solid rgba(255,255,255,.06);
  scrollbar-width: thin;
  scrollbar-color: rgba(255,255,255,.2) transparent
}
.cal-mrow {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 8px;
  min-width: 0;
  height: 42px;
  padding: 0 10px 0 7px;
  border-radius: 10px;
  border: 1px solid rgba(255,255,255,.08);
  background: rgba(255,255,255,.05);
  color: #d4d4da;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  text-align: start
}
.cal-mrow>span {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap
}
.cal-mrow svg {
  width: 14px;
  height: 14px;
  flex: none
}
.cal-mrow.on {
  background: var(--th);
  border-color: transparent;
  color: #fff
}
.cal-mrow.reset {
  grid-column: 1/-1;
  justify-content: center;
  height: 34px;
  font-weight: 500;
  color: #aaa
}
.cal-mrow.reset>span {
  flex: none
}
.cal-ava.sm {
  width: 26px;
  height: 26px
}
.cal-dd {
  flex: 1 1 130px;
  min-width: 0;
  height: 36px;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 12px;
  border-radius: 8px;
  border: 1px solid rgba(255,255,255,.12);
  background: rgba(255,255,255,.06);
  color: #ddd;
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  text-align: start
}
.cal-dd svg {
  width: 14px;
  height: 14px;
  flex: none;
  opacity: .8
}
.cal-dd>span {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap
}
.cal-dd::after,
.cal-fold::after {
  content: '';
  flex: none;
  width: 7px;
  height: 7px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg) translateY(-2px);
  opacity: .6;
  transition: transform .15s
}
.cal-dd.open::after,
.cal-fold.open::after {
  transform: rotate(225deg) translateY(-2px)
}
.cal-dd.on {
  background: var(--th);
  color: #fff;
  border-color: transparent
}
.cal-panel {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  max-height: 230px;
  overflow-y: auto;
  padding: 10px;
  border-radius: 10px;
  background: rgba(0,0,0,.28);
  border: 1px solid rgba(255,255,255,.06);
  scrollbar-width: thin;
  scrollbar-color: rgba(255,255,255,.2) transparent
}
.cal-panel .cal-sep {
  flex: 1 1 100%;
  height: 0;
  border-top: 1px solid rgba(255,255,255,.08);
  margin: 2px 0
}
.cal-opt {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 100%;
  height: 30px;
  padding: 0 11px;
  border-radius: 999px;
  border: 1px solid rgba(255,255,255,.1);
  background: rgba(255,255,255,.05);
  color: #cfcfd6;
  font: inherit;
  font-size: 12.5px;
  cursor: pointer;
  white-space: nowrap
}
.cal-opt svg {
  width: 13px;
  height: 13px;
  flex: none
}
.cal-opt>span {
  overflow: hidden;
  text-overflow: ellipsis
}
.cal-opt i {
  font-style: normal;
  font-size: 10.5px;
  opacity: .55
}
.cal-opt.all {
  font-weight: 700
}
.cal-opt.on {
  background: var(--th);
  color: #fff;
  border-color: transparent
}
.cal-wsec {
  display: flex;
  flex-direction: column;
  gap: 8px
}
.cal-dtitle+.cal-wsec>.cal-fold {
  margin-top: 0;
  padding-top: 0;
  border-top: 0
}
.cal-grid {
  display: grid;
  grid-template-columns: repeat(7,minmax(0,1fr));
  gap: 5px
}
.cal-wd {
  text-align: center;
  font-size: 11px;
  font-weight: 600;
  color: #8a8a90;
  padding: 2px 0;
  text-transform: capitalize;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap
}
.cal-day {
  position: relative;
  min-height: 54px;
  padding: 7px 2px 5px;
  border-radius: 10px;
  border: 1px solid rgba(255,255,255,.07);
  background: rgba(255,255,255,.04);
  color: #e6e6ea;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  gap: 4px;
  cursor: pointer;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  min-width: 0;
  transition: background .15s
}
.cal-day:hover {
  background: rgba(255,255,255,.09)
}
.cal-day.blank {
  visibility: hidden;
  pointer-events: none
}
.cal-day.past {
  color: #9a9aa2
}
.cal-day.today {
  border-color: var(--th);
  box-shadow: inset 0 0 0 1px var(--th)
}
.cal-day.sel {
  background: var(--th);
  color: #fff;
  border-color: transparent;
  box-shadow: 0 2px 10px rgba(0,0,0,.35)
}
.cal-n {
  line-height: 1
}
.cal-bs {
  display: flex;
  gap: 3px;
  justify-content: center;
  align-items: center;
  min-height: 15px
}
.cal-b {
  font-style: normal;
  font-size: 10px;
  font-weight: 700;
  line-height: 1;
  min-width: 16px;
  padding: 3px 4px;
  text-align: center;
  border-radius: 999px;
  background: rgba(46,204,113,.22);
  color: #7ee2a8
}
.cal-b.s {
  background: rgba(90,150,255,.22);
  color: #9cc0ff
}
.cal-day.past .cal-b.s {
  opacity: .6
}
.cal-day.sel .cal-b {
  background: rgba(0,0,0,.32);
  color: #fff
}
.cal-det {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px;
  border-radius: 12px;
  background: rgba(0,0,0,.28);
  border: 1px solid rgba(255,255,255,.06)
}
.cal-dtitle {
  font-weight: 700;
  font-size: 15px;
  color: #f2f2f2
}
.cal-sec {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .5px;
  text-transform: uppercase;
  color: #8a8a90;
  margin-top: 6px;
  padding-top: 10px;
  border-top: 1px solid rgba(255,255,255,.07)
}
.cal-dtitle+.cal-sec {
  margin-top: 0;
  padding-top: 0;
  border-top: 0
}
.cal-tbl {
  display: flex;
  flex-direction: column;
  border-radius: 10px;
  overflow: hidden;
  border: 1px solid rgba(255,255,255,.07)
}
.cal-trow {
  display: grid;
  grid-template-columns: minmax(0,1fr) 52px 78px 54px;
  align-items: center;
  gap: 6px;
  padding: 7px 10px;
  font-size: 12.5px;
  font-variant-numeric: tabular-nums
}
.cal-trow>span {
  text-align: end;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: #cfcfd6
}
.cal-trow+.cal-trow {
  border-top: 1px solid rgba(255,255,255,.05)
}
.cal-trow:nth-child(even) {
  background: rgba(255,255,255,.03)
}
.cal-thead {
  background: rgba(255,255,255,.08)!important;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .4px;
  text-transform: uppercase
}
.cal-thead>span {
  color: #9a9aa2
}
.cal-tfoot {
  background: rgba(255,255,255,.08)!important;
  font-weight: 700
}
.cal-tfoot>span {
  color: #fff
}
.cal-tfoot>span:first-child {
  text-align: start
}
.cal-who {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0
}
.cal-who a {
  min-width: 0;
  color: #fff;
  text-decoration: none;
  font-weight: 600;
  font-size: 13px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap
}
.cal-thead>span:first-child {
  text-align: start
}
.cal-trow>span:nth-child(2),
.cal-trow>span:nth-child(3) {
  text-align: center
}
.cal-trow>span:nth-child(4) {
  text-align: end
}
.cal-axis {
  display: flex;
  justify-content: space-between;
  margin-left: 38px;
  direction: ltr;
  font-size: 10px;
  color: #6f6f76;
  font-variant-numeric: tabular-nums
}
.cal-row {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0
}
.cal-ava {
  flex: none;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  object-fit: cover;
  background: rgba(255,255,255,.08)
}
.cal-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6px
}
.cal-line {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  font-size: 13px
}
.cal-line a {
  flex: 1;
  min-width: 0;
  color: #fff;
  text-decoration: none;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap
}
.cal-line a:hover {
  color: var(--th)
}
.cal-t {
  flex: none;
  color: #aab;
  font-size: 12px;
  font-variant-numeric: tabular-nums
}
.cal-bar {
  position: relative;
  height: 6px;
  border-radius: 2px;
  background: rgba(255,255,255,.08);
  direction: ltr
}
.cal-seg {
  position: absolute;
  top: 0;
  bottom: 0;
  border-radius: 2px;
  background: #5a96ff
}
.cal-now {
  position: absolute;
  top: -3px;
  bottom: -3px;
  width: 2px;
  margin-left: -1px;
  border-radius: 1px;
  background: #fff
}
.cal-heat {
  position: relative;
  direction: ltr
}
.cal-hg {
  display: grid;
  grid-template-columns: repeat(24, 1fr);
  gap: 1px;
  height: 8px
}
.cal-hg>i {
  border-radius: 1px;
  background: #5a96ff
}
.cal-hg>i.nd {
  background: rgba(255,255,255,.07)
}
.cal-pres-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid rgba(255,255,255,.1);
  font-size: 14px
}
.cal-sites .bm-tbtn {
  white-space: nowrap
}
.cal-dot {
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #2ecc71;
  box-shadow: 0 0 6px #2ecc71
}
.cal-muted {
  color: #8a8a90;
  font-size: 12px
}
.cal-fold {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  margin-top: 6px;
  padding: 10px 0 0;
  border: 0;
  border-top: 1px solid rgba(255,255,255,.07);
  background: none;
  font-family: inherit;
  cursor: pointer;
  text-align: start
}
.cal-fold>span {
  flex: 1;
  min-width: 0
}
.fav-modal .bm-list:not(.bm-gallery-mode) .bm-ptags {
  gap: 5px;
  margin-top: 4px;
  padding-top: 8px;
  border-top: 1px solid rgba(255,255,255,.06)
}
.fav-modal .bm-ptags .bm-ptag {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 9px 2px 6px;
  border: 0;
  border-radius: 999px;
  background: rgba(255,255,255,.05);
  color: #9aa0b2;
  font-size: 11.5px
}
.fav-modal .bm-ptic {
  width: 15px;
  height: 15px;
  flex: none;
  object-fit: contain
}
.fav-modal .bm-chips .bm-tbtn .bm-ptic {
  width: 15px;
  height: 15px;
  margin-inline-end: 5px;
  vertical-align: -3px
}
.cal-tic {
  width: 16px;
  height: 16px;
  flex: none;
  object-fit: contain
}
.fav-modal .bm-ptags .bm-ptag.more {
  background: transparent;
  padding: 2px 4px
}
.fav-modal .bm-ptags .bm-ptag.on {
  background: var(--th);
  color: #fff
}
.fav-modal.bm-no-ptags .bm-ptags {
  display: none!important
}
.fav-modal.bm-no-note .bm-item .bm-note {
  display: none!important
}
.fav-modal .bm-chips .bm-chips-sep {
  flex: none;
  width: 1px;
  align-self: stretch;
  margin: 4px 2px;
  background: rgba(255,255,255,.14)
}
.fav-modal .bm-chips>.bm-tbtn:first-child {
  position: sticky;
  left: 0;
  z-index: 3;
  background-color: #1b1b21;
  background-image: linear-gradient(rgba(255,255,255,.06),rgba(255,255,255,.06));
  box-shadow: 5px 0 6px #1b1b21
}
.fav-modal .bm-chips>.bm-tbtn:first-child.on {
  background-image: linear-gradient(var(--th),var(--th))
}
.fav-modal .bm-chips .bm-tbtn.bm-tall {
  background-color: #1b1b21;
  background-image: linear-gradient(rgba(255,255,255,.06),rgba(255,255,255,.06))
}
.fav-modal .bm-chips .bm-tbtn.bm-tall.on {
  background-color: var(--th);
  background-image: none
}
.fav-modal .bm-gallery-mode .bm-ptags {
  display: none!important
}
.fav-modal .bm-gallery-mode .bm-link>div {
  width: 100%;
  max-width: 100%;
  pointer-events: auto
}
.fav-modal .bm-gallery-mode .bm-link {
  position: relative
}
.fav-modal .bm-gallery-mode .bm-nwrap {
  align-items: center;
  flex-wrap: nowrap;
  row-gap: 0;
  min-height: 26px;
  padding-inline-end: 58px
}
.fav-modal .bm-gallery-mode .bm-nwrap .bm-stat {
  display: none
}
.fav-modal .bm-gallery-mode .bm-nwrap .bm-name {
  order: 1;
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap
}
.fav-modal .bm-gallery-mode .bm-nwrap .bm-view,
.fav-modal .bm-gallery-mode .bm-nwrap .bm-seen {
  position: absolute;
  bottom: 51px;
  margin: 0!important;
  padding: 2px 7px;
  border-radius: 999px;
  background: rgba(0,0,0,.6);
  color: #fff;
  font-size: 11px;
  line-height: 1.3;
  white-space: nowrap;
  z-index: 1;
  pointer-events: none
}
.fav-modal .bm-gallery-mode .bm-nwrap .bm-view {
  right: 12px
}
.fav-modal .bm-gallery-mode .bm-nwrap .bm-seen {
  left: 12px
}
.fav-modal .bm-gallery-mode .bm-nwrap .bm-view:empty,
.fav-modal .bm-gallery-mode .bm-nwrap .bm-seen:empty {
  display: none
}
.fav-modal .bm-tools {
  margin-inline-end: -38px!important;
  justify-content: stretch!important
}
.fav-modal .bm-tools>.bm-wseg-host {
  flex: 2 1 0!important;
  margin-inline-end: 0!important
}
.fav-modal .bm-tools>.bm-hbtn {
  flex: 1 1 0!important;
  width: auto!important;
  min-width: 0!important
}
.fav-modal .bm-seg.bm-wseg {
  flex: 1 1 auto;
  width: 100%;
  max-width: none
}
.fav-modal .bm-seg.bm-wseg .bm-tbtn {
  flex: 1 1 0;
  width: auto;
  padding: 0!important;
  height: 30px!important
}
.fav-modal .bm-seg.bm-wseg svg {
  width: 21px;
  height: 21px
}
.fav-modal .bm-tools .bm-prevbtn svg,
.fav-modal .bm-tools .bm-colsbtn svg {
  width: 18px;
  height: 18px
}
.fav-modal .bm-tools .bm-hbtn.bm-gear {
  border: 1px solid var(--th)!important
}
.fav-modal .bm-tbar {
  flex: none;
  display: block;
  padding: 10px 14px;
  border-bottom: 1px solid rgba(255,255,255,.07)
}
.fav-modal .bm-tbar .bm-tools {
  order: 0!important;
  flex: none!important;
  margin: 0!important;
  justify-content: stretch!important
}
.fav-modal .bm-tbar .bm-setp {
  margin-top: 8px
}
.fav-modal.mode-hist .bm-tbar,
.fav-modal.mode-cal .bm-tbar,
.fav-modal.mode-mv .bm-tbar {
  display: none!important
}
.panel .hdr-btns .h-btn {
  width: 30px;
  height: 30px
}
.panel .hdr-btns .h-btn.lang {
  width: 34px
}
.panel .hdr-btns .h-btn svg {
  width: 22px!important;
  height: 22px!important
}
.panel .hdr-btns {
  gap: 3px;
  justify-content: flex-end;
  min-width: 0
}
.panel .hdr-btns .h-btn {
  position: relative
}
.panel .hdr-btns {
  align-items: center
}
.panel .hdr-btns .hsep {
  flex: 0 0 1px;
  width: 1px;
  height: 16px;
  margin: 0 2px;
  background: rgba(255,255,255,.16);
  pointer-events: none
}
.panel .hdr-btns .stats+.hsep {
  margin-inline-start: 0
}
.panel .hdr-btns {
  margin-right: 2px
}
.panel .hdr-btns .stats.off+.hsep {
  display: none
}
.fav-modal .bm-list.bm-c1.bm-gallery-mode .bm-del,
.fav-modal .bm-list.bm-c1.bm-gallery-mode .bm-pin {
  top: 5px;
  width: 32px;
  height: 32px;
  border-radius: 8px
}
.fav-modal .bm-list.bm-c1.bm-gallery-mode .bm-del {
  right: 8px
}
.fav-modal .bm-list.bm-c1.bm-gallery-mode .bm-pin {
  right: 46px
}
.fav-modal .bm-list.bm-c1.bm-gallery-mode .bm-del svg,
.fav-modal .bm-list.bm-c1.bm-gallery-mode .bm-pin svg {
  width: 18px!important;
  height: 18px!important
}
.fav-modal .bm-list.bm-c1.bm-gallery-mode .bm-nwrap {
  padding-inline-end: 86px
}
:host([data-rtl]) .fav-modal .bm-list.bm-c1.bm-gallery-mode .bm-del {
  right: auto;
  left: 8px
}
:host([data-rtl]) .fav-modal .bm-list.bm-c1.bm-gallery-mode .bm-pin {
  right: auto;
  inset-inline-end: auto;
  left: 46px
}
.fav-modal .bm-hdr .bm-tabs {
  display: flex!important;
  width: 100%!important;
  max-width: 100%!important
}
.fav-modal .bm-hdr .bm-tabs .bm-tbtn {
  flex: 1 1 0!important;
  width: 0!important;
  min-width: 0!important;
  padding: 0 4px!important;
  gap: 4px!important;
  overflow: hidden;
  justify-content: center
}
.fav-modal .bm-hdr .bm-tabs .bm-tbtn .bm-tl {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap
}
.fav-modal .bm-hdr .bm-tabs .bm-tbtn svg {
  flex: none
}
.fav-modal .mv-pane>.bm-seg.mv-seg {
  margin-inline: 14px!important;
  width: calc(100% - 28px)!important;
  display: flex!important;
  flex: 0 0 auto!important;
  flex-direction: row!important;
  flex-wrap: nowrap!important;
  max-width: 100%!important;
  min-height: 30px;
  box-sizing: border-box;
  overflow: hidden
}
.fav-modal .mv-pane>.bm-seg.mv-seg .bm-tbtn {
  display: inline-flex!important;
  flex: 1 1 0!important;
  width: 0!important;
  min-width: 0!important;
  padding: 0 2px!important;
  gap: 0!important;
  justify-content: center;
  align-items: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12px!important
}
`;
const CSS_POLISH = `@keyframes bcuIn {
  from {
    opacity: 0;
    translate: 0 8px
  }
  to {
    opacity: 1;
    translate: 0 0
  }
}
@keyframes bcuPop {
  from {
    opacity: 0;
    scale: .96
  }
  to {
    opacity: 1;
    scale: 1
  }
}
@keyframes bcuRing {
  0% {
    box-shadow: 0 0 0 0 var(--th)
  }
  100% {
    box-shadow: 0 0 0 12px transparent
  }
}
:host ::selection {
  background: var(--th);
  color: #fff
}
:host .panel:not(.mini):not(.hidden) {
  animation: bcuIn .28s cubic-bezier(.2,.8,.2,1)
}
:host .dlg-card,
:host .cf-card,
:host .pick-bar {
  animation: bcuPop .2s cubic-bezier(.2,.8,.2,1)
}
:host .bm-overlay .bm-modal {
  opacity: 0;
  scale: .97;
  translate: 0 10px;
  transition: opacity .2s ease,scale .26s cubic-bezier(.2,.8,.2,1),translate .26s cubic-bezier(.2,.8,.2,1)
}
:host .bm-overlay.open .bm-modal {
  opacity: 1;
  scale: 1;
  translate: 0 0
}
:host .bm-overlay.bm-nofade .bm-modal {
  transition: none!important
}
:host .panel .btn span {
  display: flex;
  transition: transform .18s cubic-bezier(.2,.8,.2,1)
}
:host .panel .btn:hover span {
  transform: scale(1.1)
}
:host .panel .btn:active span {
  transform: scale(.94)
}
:host .panel .btn.on {
  box-shadow: inset 0 1px rgba(255,255,255,.12),0 2px 5px rgba(0,0,0,.4)
}
:host .panel .h-btn,
:host .panel .z-btn,
:host .panel .btn {
  -webkit-tap-highlight-color: transparent
}
:host .panel button:focus-visible,
:host .bm-modal button:focus-visible,
:host .dlg-row:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--th)
}
:host :is(.vval,.s-val,.stats,.st-val,.bm-seen,.bm-view) {
  font-variant-numeric: tabular-nums
}
:host .vslider {
  height: 5px;
  transition: filter .15s
}
:host .vslider::-webkit-slider-thumb {
  width: 15px;
  height: 15px;
  transition: transform .15s,box-shadow .15s
}
:host .vslider::-moz-range-thumb {
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: #fff;
  border: 2px solid var(--th);
  cursor: pointer
}
:host .vslider:hover::-webkit-slider-thumb {
  transform: scale(1.15)
}
:host .vslider:active::-webkit-slider-thumb {
  transform: scale(1.25);
  box-shadow: 0 0 0 7px color-mix(in srgb,var(--th) 28%,transparent)
}
:host .bm-sw {
  transition: background .22s ease
}
:host .bm-sw::after {
  transition: left .22s cubic-bezier(.3,1.3,.5,1),transform .15s;
  box-shadow: 0 1px 3px rgba(0,0,0,.45)
}
:host .bm-sw:active::after {
  transform: scaleX(1.12)
}
:host .bm-modal .bm-close {
  transition: background .15s,color .15s
}
:host .bm-modal:not(.fav-modal) .bm-hdr {
  position: sticky;
  top: 0;
  z-index: 3;
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px)
}
:host .ghost:not([style*="display: none"]),
:host .restore:not([style*="display: none"]) {
  animation: bcuPop .25s cubic-bezier(.2,.8,.2,1)
}
:host .bm-empty {
  opacity: .85;
  line-height: 1.5
}
:host .fav-modal .bm-list:not(.bm-gallery-mode) .bm-item {
  transition: background .2s,border-color .2s,transform .2s,box-shadow .2s
}
:host .fav-modal .bm-item:hover {
  transform: translateY(-1px);
  box-shadow: 0 8px 20px -12px rgba(0,0,0,.8)
}
:host .fav-modal .bm-avatar {
  transition: transform .2s,filter .3s,border-color .3s
}
:host .fav-modal .bm-item:hover .bm-avatar {
  transform: scale(1.04)
}
:host * {
  scrollbar-width: thin
}
:host ::-webkit-scrollbar {
  width: 8px;
  height: 8px
}
:host ::-webkit-scrollbar-thumb {
  background: rgba(255,255,255,.16);
  border-radius: 8px;
  border: 2px solid transparent;
  background-clip: content-box
}
:host ::-webkit-scrollbar-thumb:hover {
  background: rgba(255,255,255,.3);
  background-clip: content-box;
  border: 2px solid transparent
}
:host ::-webkit-scrollbar-track {
  background: transparent
}
@media (pointer:coarse) {
  :host .panel .btn {
    min-height: 56px
  }
  :host .bm-modal .bm-close {
    min-width: 40px;
    min-height: 40px
  }
  :host .bm-modal .bm-btn,
  :host .bm-modal .bm-tbtn {
    min-height: 40px
  }
  :host .dlg-row {
    min-height: 48px
  }
}
@media (max-width:520px) {
  :host .bm-modal {
    max-width: 100vw!important;
    width: 100vw!important;
    border-radius: 16px 16px 0 0!important;
    align-self: flex-end
  }
  :host .bm-overlay .bm-modal {
    translate: 0 24px
  }
  :host .bm-overlay.open .bm-modal {
    translate: 0 0
  }
}
@media (prefers-reduced-motion:reduce) {
  :host *,
  :host *::before,
  :host *::after {
    animation-duration: .01ms!important;
    animation-iteration-count: 1!important;
    transition-duration: .01ms!important
  }
}
`;
const STAR_CSS = `.bcu-host {
  position: relative!important
}
.bcu-star {
  box-sizing: border-box;
  padding: 0;
  line-height: 0;
  position: absolute;
  left: 6px;
  bottom: 6px;
  z-index: 6;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  filter: drop-shadow(0 1px 2px rgba(0,0,0,.75));
  cursor: pointer;
  opacity: .95;
  transition: transform .15s,color .15s;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
  touch-action: manipulation
}
a.thm_a>.bcu-star {
  left: auto;
  right: 6px
}
a.lst_link>.bcu-star {
  left: auto;
  right: 6px;
  bottom: auto;
  top: 50%;
  margin-top: -15px
}
a[data-model-list-link]>.bcu-star {
  left: auto;
  right: 6px;
  bottom: auto;
  top: 50%;
  margin-top: -15px
}
.bcu-star svg {
  display: block;
  flex: none;
  margin: 0;
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linejoin: round;
  pointer-events: none
}
.bcu-star:hover {
  transform: scale(1.12)
}
.bcu-star.on {
  color: #F2B93B
}
.bcu-ring {
  display: none;
  position: absolute;
  inset: 0;
  z-index: 5;
  border: 2px solid #ffc83d;
  border-radius: inherit;
  box-sizing: border-box;
  pointer-events: none
}
.bcu-fav>.bcu-ring {
  display: block
}
`;

class Helpers {
    static getModelSecretData() {
        try {
            for (const el of document.querySelectorAll('script[type="application/json"]')) {
                const t = el.textContent;
                if (!/chatHost|chatHeaderOptions|chatLocalData/.test(t)) continue;
                if (this._secretSrc === t) return this._secretData;
                this._secretSrc = t;
                this._secretData = null;
                return (this._secretData = JSON.parse(t));
            }
        } catch (e) { dbg(e, 'Helpers.getModelSecretData'); }
        return null;
    }
    static createElement(tag, className = '') {
        const el = document.createElement(tag);
        if (className) el.className = className;
        return el;
    }
    static createButton(iconHTML, label, className = '') {
        const b = this.createElement('button', `btn ${className}`.trim());
        b.innerHTML = `<span>${iconHTML}</span><sub>${label}</sub>`;
        return b;
    }
    static formatTime(s) {
        const p = n => pad2(n | 0);
        return `${p(s / 60)}:${p(s % 60)}`;
    }
    static getTimestamp() { return fmtStamp(Date.now()).replace(' ', '_').replace(/:/g, '-'); }
    static download(blob, filename) {
        const url = URL.createObjectURL(blob), a = document.createElement('a');
        a.style.display = 'none';
        a.href = url;
        a.download = String(filename).replace(/[\\/:*?"<>|]+/g, '_');
        document.body.appendChild(a);
        a.click();
        setTimeout(() => { a.remove(); URL.revokeObjectURL(url); }, 60000);
    }
    static bindLongPress(el, onClick, onHold, ms = 400) {
        let timer = 0, fired = false, active = false, sx = 0, sy = 0;
        const clear = () => { clearTimeout(timer); timer = 0; };
        const reset = () => { active = false; fired = false; clear(); };
        const safe = (fn, e, what) => { try { fn(e); } catch (err) { console.error('[BCU] ' + what, err); } };
        el.oncontextmenu = e => e.preventDefault();
        el.onpointerdown = e => {
            if (!isPrimaryPress(e)) return;
            active = true;
            fired = false;
            sx = e.clientX; sy = e.clientY;
            clear();
            timer = setTimeout(() => { timer = 0; if (!active) return; fired = true; safe(onHold, e, 'hold'); }, ms);
        };
        el.onpointermove = e => {
            if (active && !fired && Math.hypot(e.clientX - sx, e.clientY - sy) > 14) reset();
        };
        el.onpointerup = e => {
            if (!active) return;
            const wasHold = fired;
            reset();
            if (!wasHold) safe(onClick, e, 'click');
        };
        el.onpointercancel = reset;
        el.onpointerleave = e => { if (e.pointerType === 'mouse') reset(); };
    }
    static snap(v, name, done) {
        try {
            const c = document.createElement('canvas');
            c.width = v.videoWidth || 1280; c.height = v.videoHeight || 720;
            c.getContext('2d').drawImage(v, 0, 0);
            c.toBlob(b => {
                if (!b) return done(false);
                Helpers.download(b, `${name || 'Cam'}_${Helpers.getTimestamp()}.jpg`);
                done(true);
            }, 'image/jpeg', 0.95);
        } catch { done(false); }
    }
    static bindHold(el, { ms = 450, dist = 10, ok, onHold, always = false }) {
        let t = 0, done = false, armed = false, x = 0, y = 0, type = 'mouse';
        const clear = () => { clearTimeout(t); t = 0; };
        el.addEventListener('pointerdown', e => {
            clear();
            done = false;
            armed = false;
            if (e.pointerType === 'mouse' && e.button !== 0) return;
            if (ok && !ok(e)) return;
            armed = true;
            type = e.pointerType;
            x = e.clientX;
            y = e.clientY;
            t = setTimeout(() => { t = 0; done = true; onHold(e); }, ms);
        });
        el.addEventListener('pointermove', e => { if (t && Math.hypot(e.clientX - x, e.clientY - y) > dist) clear(); });
        ['pointerup', 'pointercancel', 'pointerleave'].forEach(n => el.addEventListener(n, clear));
        el.addEventListener('click', e => { if (done) { done = false; e.preventDefault(); e.stopImmediatePropagation(); } }, true);
        el.addEventListener('contextmenu', e => { if (always || (armed && (type !== 'mouse' || done))) e.preventDefault(); });
    }
    static trackDrag(e, { onMove, onEnd, minDist = 2, capEl }) {
        const el = capEl || e.target, sX = e.clientX, sY = e.clientY;
        try { el.setPointerCapture(e.pointerId); } catch (e) { dbg(e, 'Helpers.trackDrag'); }
        let moved = false;
        const move = ev => {
            const dx = ev.clientX - sX, dy = ev.clientY - sY;
            if (!moved && Math.hypot(dx, dy) > minDist) moved = true;
            if (moved) onMove(dx, dy, ev);
        };
        const end = ev => {
            try { el.releasePointerCapture(ev.pointerId); } catch (e) { dbg(e, 'trackDrag.end'); }
            window.removeEventListener('pointermove', move);
            window.removeEventListener('pointerup', end);
            window.removeEventListener('pointercancel', end);
            onEnd?.(moved, ev);
        };
        window.addEventListener('pointermove', move, { passive: false });
        window.addEventListener('pointerup', end);
        window.addEventListener('pointercancel', end);
    }
    static copy(text, ok, err) {
        text = String(text);
        const legacy = () => {
            try {
                const ta = document.createElement('textarea');
                ta.value = text;
                ta.setAttribute('readonly', '');
                ta.style.cssText = 'position:fixed;top:0;left:0;opacity:0;pointer-events:none';
                (document.body || document.documentElement).appendChild(ta);
                ta.select();
                ta.setSelectionRange(0, text.length);
                const done = document.execCommand('copy');
                ta.remove();
                return done;
            } catch { return false; }
        };
        const fail = e => { if (legacy()) ok?.(); else err?.(e); };
        try {
            if (navigator.clipboard?.writeText) navigator.clipboard.writeText(text).then(() => ok?.(), fail);
            else fail();
        } catch (e) { fail(e); }
    }
    static copyCb(text, cb) { this.copy(text, () => cb(true), () => cb(false)); }
    static flashBtn(btn, ok) {
        const orig = btn.innerHTML;
        btn.innerHTML = ok ? icons.ok : '<span style="color:#e74c3c">ERR</span>';
        setTimeout(() => { if (btn.isConnected) btn.innerHTML = orig; }, 1000);
    }
}

const mk = (tag, className) => Helpers.createElement(tag, className);
const textEl = (cls, text, tag = 'div') => { const e = mk(tag, cls); e.textContent = text; return e; };
const htmlEl = (cls, html, tag = 'div') => { const e = mk(tag, cls); e.innerHTML = html; return e; };
const bindGear = (btn, panel) => { btn.onclick = () => { const o = panel.classList.toggle('open'); btn.classList.toggle('on', o); }; };
const mkSetRow = (label, ctl, cls) => { const r = mk('div', cls ? `bm-setrow ${cls}` : 'bm-setrow'); const t = mk('span'); t.textContent = label; r.append(t, ctl); return r; };
const isPrimaryPress = e => e.button === 0 || e.pointerType !== 'mouse';
const halt = e => { e.preventDefault(); e.stopPropagation(); };
const swallow = e => { halt(e); e.stopImmediatePropagation(); };
const bindEscape = close => {
    const onKey = e => { if (e.key === 'Escape') { halt(e); close(); } };
    document.addEventListener('keydown', onKey, true);
    return () => document.removeEventListener('keydown', onKey, true);
};
const onRemoteChange = (key, handler) => {
    if (typeof GM_addValueChangeListener !== 'function') return;
    try { GM_addValueChangeListener(Store.key(key), (n, o, v, remote) => { if (remote) handler(v); }); } catch (e) { dbg(e, 'onRemoteChange'); }
};

const injectShadowCss = (shadow, key, css) => {
    if (shadow[key]) return;
    shadow[key] = true;
    mkStyle(shadow, css);
};

const DLG_CSS = `
.bm-grp-btn {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  text-align: start;
  cursor: pointer;
  font-family: inherit
}
.bm-grp-arr {
  opacity: .6;
  font-size: 12px
}
.dlg-ov {
  pointer-events: auto;
  position: fixed;
  inset: 0;
  z-index: 2147483647;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(0,0,0,.55);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  animation: dlgIn .15s ease
}
@keyframes dlgIn {
  from {
    opacity: 0
  }
  to {
    opacity: 1
  }
}
.dlg-card {
  width: min(340px,100%);
  max-height: 80%;
  display: flex;
  flex-direction: column;
  background: #1c1c20;
  border: 1px solid rgba(255,255,255,.1);
  border-radius: 14px;
  box-shadow: 0 18px 50px rgba(0,0,0,.6);
  overflow: hidden;
  color: #eee;
  font-size: 15px
}
.dlg-list {
  overflow-y: auto;
  padding: 6px
}
.dlg-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  padding: 13px 12px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: #eee;
  font: inherit;
  text-align: start;
  cursor: pointer
}
.dlg-row:active {
  background: rgba(255,255,255,.08)
}
.dlg-row.on {
  background: rgba(255,255,255,.07)
}
.dlg-dot {
  flex: none;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 2px solid rgba(255,255,255,.35)
}
.dlg-row.on .dlg-dot {
  border-color: var(--th,#4aa3ff);
  background: radial-gradient(circle,var(--th,#4aa3ff) 0 45%,transparent 50%)
}
.dlg-txt {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap
}
.dlg-new {
  display: flex;
  gap: 8px;
  padding: 10px;
  border-top: 1px solid rgba(255,255,255,.08)
}
.dlg-in {
  flex: 1;
  min-width: 0;
  height: 40px;
  padding: 0 12px;
  border-radius: 8px;
  background: rgba(0,0,0,.4);
  border: 1px solid rgba(255,255,255,.12);
  color: #f0f0f0;
  -webkit-text-fill-color: #f0f0f0;
  font: inherit;
  outline: none
}
.dlg-in:focus {
  border-color: var(--th,#4aa3ff)
}
.dlg-ok {
  flex: none;
  height: 40px;
  padding: 0 14px;
  border: 0;
  border-radius: 8px;
  background: var(--th,#4aa3ff);
  color: #fff;
  font: inherit;
  font-weight: 600;
  cursor: pointer
}
`;

const openGroupDialog = (ui, { current, groups, onPick }) => {
    const shadow = ui.shadow;
    injectShadowCss(shadow, '_dlgCss', DLG_CSS);
    const ov = mk('div', 'dlg-ov');
    const card = mk('div', 'dlg-card');
    const list = mk('div', 'dlg-list');
    const unbindEsc = bindEscape(() => close());
    const close = () => { ov.remove(); unbindEsc(); };
    const pick = v => { close(); if (v !== current) onPick(v); };

    const row = (label, value) => {
        const r = mk('button', 'dlg-row' + (value === current ? ' on' : ''));
        r.type = 'button';
        const t = mk('span', 'dlg-txt'); t.innerHTML = `${icons.folder}<span>${escHTML(label)}</span>`;
        r.append(t, mk('span', 'dlg-dot'));
        r.onclick = e => { e.stopPropagation(); pick(value); };
        return r;
    };
    list.append(row(L().grp_none, ''), ...groups.map(g => row(g, g)));

    const nw = mk('div', 'dlg-new');
    const inp = mk('input', 'dlg-in');
    inp.type = 'text'; inp.maxLength = 40; inp.placeholder = L().grp_new.replace(/^\+\s*/, '').replace(/…$/, '') + '…';
    inp.autocomplete = 'off'; inp.spellcheck = false;
    const ok = mk('button', 'dlg-ok'); ok.type = 'button'; ok.textContent = '+';
    const submit = () => { const v = inp.value.trim().slice(0, 40); if (v) pick(v); else inp.focus(); };
    ok.onclick = e => { e.stopPropagation(); submit(); };
    inp.onkeydown = e => { e.stopPropagation(); if (e.key === 'Enter') { e.preventDefault(); submit(); } };
    inp.onkeyup = e => e.stopPropagation();
    nw.append(inp, ok);

    card.append(list, nw);
    card.onclick = e => e.stopPropagation();
    ov.onclick = close;
    ov.append(card);
    shadow.appendChild(ov);
};

const cleanAlts = (arr, main) => {
    const seen = new Set([String(main || '').trim().toLowerCase()]), out = [];
    for (const x of arr) {
        const v = String(x || '').trim().slice(0, 60), k = v.toLowerCase();
        if (!v || seen.has(k)) continue;
        seen.add(k); out.push(v);
        if (out.length >= 10) break;
    }
    return out;
};

const SCI_RU = {
    "ii": {
        "interestedInEverybody": "Всеми",
        "interestedInMen": "Мужчинами",
        "interestedInTranssexuals": "Трансами",
        "interestedInWomen": "Женщинами"
    },
    "bt": {
        "bodyTypeAthletic": "Мускулистые",
        "bodyTypeAthleticForFemale": "Спортивные",
        "bodyTypeAverage": "Обычное телосложение",
        "bodyTypeAverageForFemale": "Обычное телосложение",
        "bodyTypeCurvy": "Пухлые",
        "bodyTypeCurvyForFemale": "Фигуристые",
        "bodyTypeLarge": "Крупные",
        "bodyTypeLargeForFemale": "Толстушки",
        "bodyTypeThin": "Худышки",
        "bodyTypeThinForFemale": "Худышки",
        "bodyTypeBBW": "Толстушки",
        "bodyTypeBig": "Крупные",
        "bodyTypeMuscular": "Мускулистые",
        "bodyTypePetite": "Миниатюрные",
        "bodyTypeSkinny": "Худышки",
        "bodyTypeMedium": "Обычные",
        "bodyTypeChunky": "Пухлые"
    },
    "et": {
        "ethnicityAsian": "Азиаты",
        "ethnicityEbony": "Темнокожие",
        "ethnicityFrench": "Французы",
        "ethnicityGerman": "Немцы",
        "ethnicityIndian": "Индианка",
        "ethnicityItalian": "Итальянцы",
        "ethnicityLatino": "Латинская Америка",
        "ethnicityMiddleEastern": "Арабы",
        "ethnicityMultiracial": "Смешанной расы",
        "ethnicityMixed": "Смешанной расы",
        "ethnicityRussian": "Русские",
        "ethnicitySpanish": "Испанцы",
        "ethnicityWhite": "Белые"
    },
    "hc": {
        "hairColorBlonde": "Блонд",
        "hairColorBlack": "Чёрные",
        "hairColorBrown": "Каштановые",
        "hairColorRed": "Рыжие",
        "hairColorColorful": "Цветные",
        "hairColorHairless": "Без волос",
        "hairColorOther": "Другой цвет"
    },
    "ec": {
        "eyeColorBlack": "Черные",
        "eyeColorBlue": "Голубые",
        "eyeColorBrown": "Карие",
        "eyeColorGreen": "Зеленые",
        "eyeColorGrey": "Серые",
        "eyeColorHazel": "Светло-карие"
    },
    "sc": {
        "subcultureNerds": "Ботанки",
        "subculturePunks": "Панки",
        "subcultureDragQueens": "Дрэг-квины",
        "subcultureChubbies": "Пухленькие",
        "subcultureAnime": "Аниме",
        "subcultureClubber": "Клаберы",
        "subcultureCubs": "Молодые медведи",
        "subcultureEboy": "E-boy",
        "subcultureEgirl": "E-girl",
        "subcultureGymBabe": "Фитоняшки",
        "subcultureGymRats": "Качки",
        "subcultureHunks": "Мачо",
        "subcultureJocks": "Атлеты",
        "subcultureKpop": "Кейпоп",
        "subcultureOtters": "Оттеры",
        "subcultureStuds": "Альфы",
        "subcultureBottom": "Пассив",
        "subcultureTop": "Актив",
        "subcultureBdsm": "БДСМ",
        "subcultureBears": "Бородатые здоровяки",
        "subcultureCuckold": "Рогоносцы",
        "subcultureEmo": "Эмо",
        "subcultureGamers": "Геймеры",
        "subcultureGlamour": "Гламур",
        "subcultureGoth": "Готы",
        "subcultureHousewives": "Домохозяйки",
        "subcultureMistresses": "Госпожи",
        "subcultureNone": "Нет данных",
        "subcultureRomantic": "Романтика",
        "subcultureStudent": "Студентки",
        "subcultureSwingers": "Свингеры"
    },
    "sp": {
        "specificQueer": "Квиры",
        "specificCut": "Обрезанные",
        "specificUncut": "Необрезанные",
        "specificBubblebutt": "Круглая попа",
        "specificBoots": "Сапоги",
        "specificJeans": "Джинсы",
        "specificSneakersSocks": "Кроссы и носки",
        "specificSportGear": "Спортивная форма",
        "specificUniform": "Униформа",
        "specificPowerBottom": "Пауэр-боттом",
        "specificPowerTop": "Пауэр-топ",
        "specificSide": "Сайд",
        "specificVers": "Уни",
        "specificCD": "Кроссдресс",
        "specificFtM": "Транс-мужчины",
        "specificHairyMan": "Волосатые",
        "specificMtF": "Транс-женщины",
        "specificShemale": "Shemale",
        "specificTG": "Трансгендеры",
        "specificTS": "Транссексуалы",
        "specificTV": "Трансвеститы",
        "specificBigBalls": "Большие яйца",
        "specificBeardy": "Бородатые",
        "specificMustache": "Усатые",
        "specificTrimmed": "Стриженые",
        "specificNonBinary": "Небинарные",
        "specificInterracial": "Межрасовый секс",
        "specificPregnant": "Беременные",
        "specificShaven": "Бритые",
        "specificSkinny": "Худышки",
        "specificSmallTits": "Маленькая грудь",
        "specificsBigAss": "Большая задница",
        "specificsBigCock": "Большой член",
        "specificsBigTits": "Большие сиськи",
        "specificsHairy": "Волосатая киска",
        "specificsPiercing": "Пирсинг",
        "specificsTattoos": "Тату",
        "specificBBC": "Большой черный член",
        "specificSmallCock": "Маленький член",
        "specificTomboy": "Томбои",
        "specificBigClit": "Большой клитор",
        "specificBigNipples": "Большие соски",
        "specificFemboy": "Фембои",
        "specificSissy": "Сисси",
        "specificHairyArmpits": "Волосатые подмышки",
        "specificPOV": "POV",
        "specificLesbians": "Лесбиянка",
        "specificsNoPiercing": "Без пирсинга",
        "specificsNoTattoos": "Без тату",
        "specificsHairyArmpits": "Волосатые подмышки",
        "hairLengthShort": "Короткие волосы",
        "hairLengthMedium": "Средние волосы",
        "hairLengthLong": "Длинные волосы",
        "emo": "Эмо",
        "goth": "Готы"
    },
    "it": {
        "dancing": "Танцы",
        "art": "Искусство и творчество",
        "singing": "Пение",
        "videomaking": "Видеосъемка",
        "photo": "Фото",
        "makeup": "Макияж",
        "blogging": "Блогерство",
        "handmade": "Ручная работа",
        "acting": "Актерство",
        "writing": "Писательство",
        "vlogging": "Видеоблогерство",
        "playing-musical-instruments": "Музыкальные инструменты",
        "cosplay": "Косплей",
        "music-production": "Написание музыки",
        "football-soccer": "Футбол",
        "cycling": "Велоспорт",
        "cricket": "Крикет",
        "basketball": "Баскетбол",
        "hockey": "Хоккей",
        "tennis": "Теннис",
        "volleyball": "Волейбол",
        "table-tennis": "Настольный теннис",
        "baseball": "Бейсбол",
        "rugby": "Регби",
        "american-football": "Американский футбол",
        "golf": "Гольф",
        "wrestling": "Реслинг",
        "formula-1": "Формула-1",
        "mma": "MMA",
        "swimming": "Плавание",
        "diving": "Ныряние",
        "water-polo": "Водное поло",
        "snowboarding": "Сноуборд",
        "skiing": "Лыжный спорт",
        "ice-skating": "Катание на коньках",
        "boxing": "Бокс",
        "gym": "Тренажерный зал",
        "yoga": "Йога",
        "bowling": "Боулинг",
        "chess": "Шахматы",
        "billiards": "Бильярд",
        "bars": "Бары",
        "cafes-restaurants": "Кафе и рестораны",
        "concerts": "Концерты",
        "parties": "Вечеринки",
        "standup": "Стендап",
        "night-clubs": "Ночные клубы",
        "karaoke": "Караоке",
        "picnics": "Пикники",
        "theater": "Театр",
        "bookcrossing": "Буккроссинг",
        "music-festivals": "Музыкальные фестивали",
        "video-games": "Видеоигры",
        "cooking": "Кулинария",
        "board-games": "Настольные игры",
        "diy": "Сделай сам",
        "gardening": "Садоводство",
        "technology-computers": "Компьютеры и технологии",
        "reading": "Чтение книг",
        "napping": "Дневной сон",
        "watching-tv": "ТВ",
        "anime": "Аниме",
        "hentai": "Хентай",
        "erotica": "Эротика",
        "comedy": "Комедии",
        "action": "Действие",
        "superheroes": "Супергерои",
        "horror": "Ужасы",
        "fantasy": "Фэнтези",
        "reality-tv": "Реалити-шоу",
        "documentary": "Документальные фильмы",
        "drama": "Драмы",
        "sci-fi": "Научная фантастика",
        "romance": "Романтические фильмы",
        "thriller": "Триллеры",
        "mystery": "Детективы",
        "cartoon": "Мультфильмы",
        "adventure": "Приключенческие фильмы",
        "musical": "Мюзиклы",
        "historical": "Исторические фильмы",
        "edm": "EDM",
        "k-pop": "K-pop",
        "r-and-b": "R&B",
        "pop": "Поп",
        "rock": "Рок",
        "techno": "Техно",
        "rap": "Рэп",
        "house": "Хаус",
        "latino": "Latino",
        "classic": "Классическая музыка",
        "reggae": "Регги",
        "country": "Кантри",
        "jazz": "Джаз",
        "hip-hop": "Хип-хоп",
        "punk-rock": "Панк-рок",
        "folk": "Фолк",
        "indie": "Инди",
        "metal": "Метал",
        "electronic": "Электронная музыка",
        "vegan": "Веганство",
        "vegeterian": "Вегетарианский",
        "sweet-tooth": "Сладости",
        "pizza": "Пицца",
        "chinese": "Китайская кухня",
        "sushi": "Суши",
        "thai": "Тайская кухня",
        "tea": "Чай",
        "coffee": "Кофе",
        "energy-drinks": "Энергетические напитки",
        "beer": "Пиво",
        "wine": "Вино",
        "gin": "Джин",
        "whiskey": "Виски",
        "vodka": "Водка",
        "cocktails": "Коктейли",
        "mexican": "Мексиканская кухня",
        "barbeque": "Барбекю",
        "wok": "Вок",
        "healthy": "Здоровая еда",
        "burgers": "Гамбургеры",
        "beach": "Пляж",
        "hiking": "Походы",
        "fishing": "Рыбалка",
        "hitchhiking": "Автостоп",
        "trains": "Поезда",
        "camping": "Кемпинг",
        "family-vacation": "Семейный отдых",
        "road-trips": "Путешествия на машине",
        "mountaineering": "Альпинизм",
        "bdsm": "БДСМ",
        "bondage": "Бондаж",
        "domination": "Доминирование",
        "swinging": "Свингерство",
        "role-playing": "Ролевые игры",
        "group-sex": "Групповой секс",
        "mistress": "Госпожи",
        "porn": "Порно",
        "submission": "Подчинение",
        "foot-fetish": "Фут-фетиш",
        "anal": "Анал",
        "blowjob": "Минет",
        "hardcore": "Жесткий секс",
        "doggy-style": "Раком",
        "deepthroat": "Глубокая глотка",
        "fisting": "Фистинг",
        "latex": "Латекс",
        "squirt": "Сквирт",
        "twerk": "Тверкинг",
        "nylon": "Нейлон",
        "gentle-sex": "Нежный секс",
        "voyeurism": "Вуайеризм",
        "underwear": "Нижнее белье",
        "sex-toys": "Секс-игрушки",
        "dirty-talk": "Грязные разговоры",
        "french-kiss": "Французский поцелуй",
        "cuckold": "Рогоносцы",
        "object-sexuality": "Объектофилия",
        "astrology": "Астрология",
        "drones": "Дроны",
        "investments": "Инвестиции",
        "foreign-languages": "Иностранные языки",
        "astronomy": "Астрономия",
        "psychology": "Психология",
        "mindfulness": "Осознанность",
        "politics": "Политика",
        "poetry": "Поэзия",
        "darts": "Дартс",
        "horse-riding": "Верховая езда",
        "poker": "Покер",
        "mountain-biking": "Горный велоспорт",
        "triathlon": "Триатлон",
        "yacht-racing": "Регаты",
        "karate": "Карате",
        "pilates": "Пилатес",
        "nascar": "NASCAR",
        "surfing": "Серфинг",
        "racing": "Гонки",
        "running": "Бег",
        "volunteering": "Волонтерство",
        "museums": "Музеи",
        "art-galleries": "Художественные галереи",
        "shopping": "Шопинг",
        "comic-books": "Комиксы",
        "puzzles": "Пазлы",
        "vr-ar-games": "Игры VR/AR",
        "meditation": "Медитации",
        "origami": "Оригами",
        "seafood": "Морепродукты",
        "steak": "Стейк"
    },
    "lg": {
        "ar": "Арабский",
        "br": "Португальский (Бразилия)",
        "cs": "Чешский",
        "de": "Немецкий",
        "da": "Датский",
        "en": "Английский",
        "es": "Испанский",
        "fi": "Финский",
        "fr": "Французский",
        "hr": "Хорватский",
        "hu": "Венгерский",
        "id": "Индонезийский",
        "it": "Итальянский",
        "ja": "Японский",
        "ko": "Корейский",
        "ms": "Малайский",
        "nl": "Нидерландский",
        "nn": "Норвежский",
        "no": "Норвежский",
        "pl": "Польский",
        "pt": "Португальский",
        "ro": "Румынский",
        "ru": "Русский",
        "sk": "Словацкий",
        "sq": "Албанский",
        "sr": "Сербский",
        "sv": "Шведский",
        "th": "Тайский",
        "tr": "Турецкий",
        "vi": "Вьетнамский",
        "uk": "Украинский",
        "zh": "Китайский (упрощенный)",
        "zt": "Китайский (традиционный)",
        "hi": "Хинди",
        "bn": "Бенгальский",
        "mr": "Маратхи",
        "te": "Телугу",
        "ta": "Тамильский",
        "gu": "Гуджарати",
        "kn": "Каннада",
        "or": "Одия",
        "ml": "Малайялам",
        "pa": "Панджаби",
        "as": "Ассамский",
        "he": "Иврит",
        "bg": "Болгарский",
        "el": "Греческий"
    }
};

const SCI_EN = {
    ii: { interestedInEverybody: 'Everybody', interestedInMen: 'Men', interestedInTranssexuals: 'Transsexuals', interestedInWomen: 'Women' },
    bt: { bodyTypeThin: 'Thin', bodyTypeAverage: 'Average', bodyTypeAthletic: 'Athletic', bodyTypeCurvy: 'Curvy', bodyTypeLarge: 'Large', bodyTypeThinForFemale: 'Thin', bodyTypeAverageForFemale: 'Average', bodyTypeAthleticForFemale: 'Athletic', bodyTypeCurvyForFemale: 'Curvy', bodyTypeLargeForFemale: 'BBW', bodyTypeBBW: 'BBW', bodyTypeBig: 'Big', bodyTypeMuscular: 'Muscular', bodyTypePetite: 'Petite', bodyTypeSkinny: 'Skinny', bodyTypeMedium: 'Medium', bodyTypeChunky: 'Chunky' },
    et: { ethnicityMiddleEastern: 'Middle Eastern', ethnicityAsian: 'Asian', ethnicityEbony: 'Ebony', ethnicityIndian: 'Indian', ethnicityLatino: 'Latino', ethnicityMixed: 'Mixed', ethnicityWhite: 'White', ethnicityFrench: 'French', ethnicityGerman: 'German', ethnicityItalian: 'Italian', ethnicityMultiracial: 'Multiracial', ethnicityRussian: 'Russian', ethnicitySpanish: 'Spanish' },
    hc: { hairColorBlonde: 'Blonde', hairColorBlack: 'Black', hairColorColorful: 'Colorful', hairColorRed: 'Red', hairColorHairless: 'Hairless', hairColorOther: 'Other', hairColorBrown: 'Brown' },
    ec: { eyeColorGrey: 'Grey', eyeColorGreen: 'Green', eyeColorBlue: 'Blue', eyeColorHazel: 'Hazel', eyeColorBrown: 'Brown', eyeColorBlack: 'Black' },
    sc: { subcultureNerds: 'Nerds', subculturePunks: 'Punks', subcultureDragQueens: 'Drag queens', subcultureChubbies: 'Chubbies', subcultureAnime: 'Anime', subcultureClubber: 'Clubbers', subcultureCubs: 'Cubs', subcultureEboy: 'E-boy', subcultureEgirl: 'E-girl', subcultureGymBabe: 'Gym babes', subcultureGymRats: 'Gym rats', subcultureHunks: 'Hunks', subcultureJocks: 'Jocks', subcultureKpop: 'K-pop', subcultureOtters: 'Otters', subcultureStuds: 'Studs', subcultureBottom: 'Bottom', subcultureTop: 'Top', subcultureBdsm: 'BDSM', subcultureBears: 'Bears', subcultureCuckold: 'Cuckold', subcultureEmo: 'Emo', subcultureGamers: 'Gamers', subcultureGlamour: 'Glamour', subcultureGoth: 'Goth', subcultureHousewives: 'Housewives', subcultureMistresses: 'Mistresses', subcultureNone: 'No data', subcultureRomantic: 'Romantic', subcultureStudent: 'Students', subcultureSwingers: 'Swingers' },
    sp: { specificQueer: 'Queer', specificCut: 'Cut', specificUncut: 'Uncut', specificBubblebutt: 'Bubble butt', specificBoots: 'Boots', specificJeans: 'Jeans', specificSneakersSocks: 'Sneakers & socks', specificSportGear: 'Sport gear', specificUniform: 'Uniform', specificPowerBottom: 'Power bottom', specificPowerTop: 'Power top', specificSide: 'Side', specificVers: 'Vers', specificCD: 'Crossdresser', specificFtM: 'Trans men', specificHairyMan: 'Hairy', specificMtF: 'Trans women', specificShemale: 'Shemale', specificTG: 'Transgender', specificTS: 'Transsexual', specificTV: 'Transvestite', specificBigBalls: 'Big balls', specificBeardy: 'Beard', specificMustache: 'Mustache', specificTrimmed: 'Trimmed', specificNonBinary: 'Non-binary', specificInterracial: 'Interracial', specificPregnant: 'Pregnant', specificShaven: 'Shaved', specificSkinny: 'Skinny', specificSmallTits: 'Small tits', specificsBigAss: 'Big ass', specificsBigCock: 'Big cock', specificsBigTits: 'Big tits', specificsHairy: 'Hairy pussy', specificsPiercing: 'Piercing', specificsTattoos: 'Tattoos', specificBBC: 'BBC', specificSmallCock: 'Small cock', specificTomboy: 'Tomboy', specificBigClit: 'Big clit', specificBigNipples: 'Big nipples', specificFemboy: 'Femboy', specificSissy: 'Sissy', specificHairyArmpits: 'Hairy armpits', specificPOV: 'POV', specificLesbians: 'Lesbian', specificsNoPiercing: 'No piercing', specificsNoTattoos: 'No tattoos', specificsHairyArmpits: 'Hairy armpits', hairLengthShort: 'Short hair', hairLengthMedium: 'Medium hair', hairLengthLong: 'Long hair', emo: 'Emo', goth: 'Goth' },
    it: { dancing: 'Dancing', art: 'Art & creativity', singing: 'Singing', videomaking: 'Video making', photo: 'Photography', makeup: 'Makeup', blogging: 'Blogging', handmade: 'Handmade', acting: 'Acting', writing: 'Writing', vlogging: 'Vlogging', 'playing-musical-instruments': 'Playing instruments', cosplay: 'Cosplay', 'music-production': 'Music production', 'football-soccer': 'Football (soccer)', cycling: 'Cycling', cricket: 'Cricket', basketball: 'Basketball', hockey: 'Hockey', tennis: 'Tennis', volleyball: 'Volleyball', 'table-tennis': 'Table tennis', baseball: 'Baseball', rugby: 'Rugby', 'american-football': 'American football', golf: 'Golf', wrestling: 'Wrestling', 'formula-1': 'Formula 1', mma: 'MMA', swimming: 'Swimming', diving: 'Diving', 'water-polo': 'Water polo', snowboarding: 'Snowboarding', skiing: 'Skiing', 'ice-skating': 'Ice skating', boxing: 'Boxing', gym: 'Gym', yoga: 'Yoga', bowling: 'Bowling', chess: 'Chess', billiards: 'Billiards', bars: 'Bars', 'cafes-restaurants': 'Cafes & restaurants', concerts: 'Concerts', parties: 'Parties', standup: 'Stand-up', 'night-clubs': 'Night clubs', karaoke: 'Karaoke', picnics: 'Picnics', theater: 'Theater', bookcrossing: 'Bookcrossing', 'music-festivals': 'Music festivals', 'video-games': 'Video games', cooking: 'Cooking', 'board-games': 'Board games', diy: 'DIY', gardening: 'Gardening', 'technology-computers': 'Tech & computers', reading: 'Reading', napping: 'Napping', 'watching-tv': 'Watching TV', anime: 'Anime', hentai: 'Hentai', erotica: 'Erotica', comedy: 'Comedy', action: 'Action', superheroes: 'Superheroes', horror: 'Horror', fantasy: 'Fantasy', 'reality-tv': 'Reality TV', documentary: 'Documentary', drama: 'Drama', 'sci-fi': 'Sci-fi', romance: 'Romance', thriller: 'Thriller', mystery: 'Mystery', cartoon: 'Cartoons', adventure: 'Adventure', musical: 'Musicals', historical: 'Historical', edm: 'EDM', 'k-pop': 'K-pop', 'r-and-b': 'R&B', pop: 'Pop', rock: 'Rock', techno: 'Techno', rap: 'Rap', house: 'House', latino: 'Latino', classic: 'Classical music', reggae: 'Reggae', country: 'Country', jazz: 'Jazz', 'hip-hop': 'Hip-hop', 'punk-rock': 'Punk rock', folk: 'Folk', indie: 'Indie', metal: 'Metal', electronic: 'Electronic music', vegan: 'Vegan', vegeterian: 'Vegetarian', 'sweet-tooth': 'Sweet tooth', pizza: 'Pizza', chinese: 'Chinese food', sushi: 'Sushi', thai: 'Thai food', tea: 'Tea', coffee: 'Coffee', 'energy-drinks': 'Energy drinks', beer: 'Beer', wine: 'Wine', gin: 'Gin', whiskey: 'Whiskey', vodka: 'Vodka', cocktails: 'Cocktails', mexican: 'Mexican food', barbeque: 'Barbecue', wok: 'Wok', healthy: 'Healthy food', burgers: 'Burgers', beach: 'Beach', hiking: 'Hiking', fishing: 'Fishing', hitchhiking: 'Hitchhiking', trains: 'Trains', camping: 'Camping', 'family-vacation': 'Family vacation', 'road-trips': 'Road trips', mountaineering: 'Mountaineering', bdsm: 'BDSM', bondage: 'Bondage', domination: 'Domination', swinging: 'Swinging', 'role-playing': 'Role-playing', 'group-sex': 'Group sex', mistress: 'Mistress', porn: 'Porn', submission: 'Submission', 'foot-fetish': 'Foot fetish', anal: 'Anal', blowjob: 'Blowjob', hardcore: 'Hardcore', 'doggy-style': 'Doggy style', deepthroat: 'Deepthroat', fisting: 'Fisting', latex: 'Latex', squirt: 'Squirt', twerk: 'Twerking', nylon: 'Nylon', 'gentle-sex': 'Gentle sex', voyeurism: 'Voyeurism', underwear: 'Underwear', 'sex-toys': 'Sex toys', 'dirty-talk': 'Dirty talk', 'french-kiss': 'French kiss', cuckold: 'Cuckold', 'object-sexuality': 'Objectum sexuality', astrology: 'Astrology', drones: 'Drones', investments: 'Investments', 'foreign-languages': 'Foreign languages', astronomy: 'Astronomy', psychology: 'Psychology', mindfulness: 'Mindfulness', politics: 'Politics', poetry: 'Poetry', darts: 'Darts', 'horse-riding': 'Horse riding', poker: 'Poker', 'mountain-biking': 'Mountain biking', triathlon: 'Triathlon', 'yacht-racing': 'Yacht racing', karate: 'Karate', pilates: 'Pilates', nascar: 'NASCAR', surfing: 'Surfing', racing: 'Racing', running: 'Running', volunteering: 'Volunteering', museums: 'Museums', 'art-galleries': 'Art galleries', shopping: 'Shopping', 'comic-books': 'Comic books', puzzles: 'Puzzles', 'vr-ar-games': 'VR/AR games', meditation: 'Meditation', origami: 'Origami', seafood: 'Seafood', steak: 'Steak' }
};

const SCI_PREFIX = { ii: 'interestedIn', bt: 'bodyType', et: 'ethnicity', hc: 'hairColor', ec: 'eyeColor', sc: 'subculture', sp: 'specifics?|subculture|hairLength', it: '' };

const NX_K = ["snd","snd_bv","snd_boost","snd_voice","snd_reset","snd_comp","snd_hp","snd_lim","snd_hint","hz","info","add_nick","add","nicks","recheck","found","refresh","noprof","loading","reffail","updated","loadfail","notset","was","profile","interests","current","fromsite","added","q_tip","q_hint","r_lg","r_a","r_ii","r_bt","r_et","r_hc","r_ec","r_loc","r_sc","r_sp","r_st","r_ds","g_female","g_male","g_tranny","g_maleFemale","g_males","g_females","g_trannies","g_maleTranny","g_femaleTranny"];
const NX_T = {
    RU: ["Звук","Бустер и голос","Усиление","Чёткость голоса","Сбросить звук","Компрессор","Фильтр гула 90 Гц","Ограничитель","Работает на потоках скрипта и сайта (blob/MSE). Запись идёт без усиления.","Гц","Инфо","Добавить ник…","Добавить","Ники","Проверить снова","Где найдено","Обновить","Данные профиля для этого сайта пока не поддерживаются: доступны ники и поиск по сайтам.","Загружаю профиль…","Не удалось обновить профиль, показаны сохранённые данные.","Обновлено","Не удалось получить профиль. Модель могла сменить ник или скрыть профиль.","не указано","было","Профиль","Интересы","сейчас","с сайта","добавлен","Не проверяется, открыть поиск","«?» — сайт не проверяется скриптом: нажмите, чтобы открыть поиск и посмотреть вручную.","Языки","Возраст","Интересуюсь","Телосложение","Этничность","Цвет волос","Цвет глаз","Место","Субкультура","Особенности","Статус","Обо мне","Девушка","Парень","Транс","Пара","Пара парней","Пара девушек","Пара трансов","Пара: парень и транс","Пара: девушка и транс"],
    EN: ["Sound","Booster & voice","Boost","Voice clarity","Reset sound","Compressor","Rumble filter 90 Hz","Limiter","Works on script and site streams (blob/MSE). Recordings are not boosted.","Hz","Info","Add a nick…","Add","Nicks","Check again","Found on","Refresh","Profile data is not supported for this site yet: nicks and site search are available.","Loading profile…","Could not refresh the profile, showing saved data.","Updated","Could not load the profile. The model may have changed nick or hidden the profile.","not set","was","Profile","Interests","current","from site","added","Not checked, open search","“?” — not checked by the script: tap to open the search and look manually.","Languages","Age","Interested in","Body type","Ethnicity","Hair color","Eye color","Location","Subculture","Specifics","Status","About","Woman","Man","Trans","Couple","Male couple","Female couple","Trans couple","Male and trans couple","Female and trans couple"],
    UK: ["Звук","Бустер і голос","Підсилення","Чіткість голосу","Скинути звук","Компресор","Фільтр гулу 90 Гц","Обмежувач","Працює з потоками скрипта і сайту (blob/MSE). Запис іде без підсилення.","Гц","Інфо","Додати нік…","Додати","Ніки","Перевірити знову","Де знайдено","Оновити","Дані профілю для цього сайту поки не підтримуються: доступні ніки та пошук по сайтах.","Завантажую профіль…","Не вдалося оновити профіль, показано збережені дані.","Оновлено","Не вдалося отримати профіль. Модель могла змінити нік або приховати профіль.","не вказано","було","Профіль","Інтереси","зараз","із сайту","додано","Не перевіряється, відкрити пошук","«?» — сайт не перевіряється скриптом: натисніть, щоб відкрити пошук і подивитися вручну.","Мови","Вік","Цікавлюся","Статура","Етнічність","Колір волосся","Колір очей","Місце","Субкультура","Особливості","Статус","Про себе","Дівчина","Хлопець","Транс","Пара","Пара хлопців","Пара дівчат","Пара трансів","Пара: хлопець і транс","Пара: дівчина і транс"],
    ES: ["Sonido","Potenciador y voz","Amplificación","Claridad de voz","Restablecer sonido","Compresor","Filtro de graves 90 Hz","Limitador","Funciona con las transmisiones del script y del sitio (blob/MSE). Las grabaciones no se amplifican.","Hz","Info","Añadir apodo…","Añadir","Apodos","Comprobar de nuevo","Dónde se encontró","Actualizar","Los datos de perfil aún no son compatibles con este sitio: están disponibles los apodos y la búsqueda en sitios.","Cargando perfil…","No se pudo actualizar el perfil, se muestran los datos guardados.","Actualizado","No se pudo cargar el perfil. La modelo pudo cambiar de apodo u ocultar el perfil.","sin indicar","antes","Perfil","Intereses","actual","del sitio","añadido","No se comprueba, abrir búsqueda","«?» — el script no comprueba este sitio: pulsa para abrir la búsqueda y mirar a mano.","Idiomas","Edad","Interesado en","Complexión","Etnia","Color de pelo","Color de ojos","Ubicación","Subcultura","Particularidades","Estado","Acerca de","Mujer","Hombre","Trans","Pareja","Pareja de hombres","Pareja de mujeres","Pareja trans","Pareja: hombre y trans","Pareja: mujer y trans"],
    DE: ["Ton","Booster & Stimme","Verstärkung","Stimmklarheit","Ton zurücksetzen","Kompressor","Brummfilter 90 Hz","Limiter","Funktioniert mit Streams des Skripts und der Seite (blob/MSE). Aufnahmen werden nicht verstärkt.","Hz","Info","Nick hinzufügen…","Hinzufügen","Nicks","Erneut prüfen","Gefunden auf","Aktualisieren","Profildaten werden für diese Seite noch nicht unterstützt: Nicks und Seitensuche sind verfügbar.","Profil wird geladen…","Profil konnte nicht aktualisiert werden, gespeicherte Daten werden angezeigt.","Aktualisiert","Profil konnte nicht geladen werden. Das Model hat möglicherweise den Nick geändert oder das Profil ausgeblendet.","nicht angegeben","war","Profil","Interessen","aktuell","von der Seite","hinzugefügt","Nicht geprüft, Suche öffnen","„?“ — vom Skript nicht geprüft: tippen, um die Suche zu öffnen und manuell zu schauen.","Sprachen","Alter","Interessiert an","Körperbau","Ethnie","Haarfarbe","Augenfarbe","Ort","Subkultur","Besonderheiten","Status","Über mich","Frau","Mann","Trans","Paar","Männerpaar","Frauenpaar","Trans-Paar","Paar: Mann und Trans","Paar: Frau und Trans"],
    FR: ["Son","Amplificateur et voix","Amplification","Clarté de la voix","Réinitialiser le son","Compresseur","Filtre anti-grondement 90 Hz","Limiteur","Fonctionne avec les flux du script et du site (blob/MSE). Les enregistrements ne sont pas amplifiés.","Hz","Infos","Ajouter un pseudo…","Ajouter","Pseudos","Revérifier","Trouvé sur","Actualiser","Les données de profil ne sont pas encore prises en charge pour ce site : pseudos et recherche sur les sites disponibles.","Chargement du profil…","Impossible d’actualiser le profil, données enregistrées affichées.","Actualisé","Impossible de charger le profil. La modèle a peut-être changé de pseudo ou masqué son profil.","non indiqué","était","Profil","Centres d’intérêt","actuel","du site","ajouté","Non vérifié, ouvrir la recherche","« ? » — non vérifié par le script : touchez pour ouvrir la recherche et regarder à la main.","Langues","Âge","Intéressé par","Morphologie","Origine","Couleur des cheveux","Couleur des yeux","Lieu","Sous-culture","Particularités","Statut","À propos","Femme","Homme","Trans","Couple","Couple d’hommes","Couple de femmes","Couple trans","Couple : homme et trans","Couple : femme et trans"],
    PT: ["Som","Reforço e voz","Amplificação","Clareza da voz","Redefinir som","Compressor","Filtro de ruído grave 90 Hz","Limitador","Funciona com as transmissões do script e do site (blob/MSE). As gravações não são amplificadas.","Hz","Info","Adicionar apelido…","Adicionar","Apelidos","Verificar novamente","Onde foi encontrado","Atualizar","Os dados de perfil ainda não são suportados para este site: apelidos e pesquisa nos sites disponíveis.","A carregar perfil…","Não foi possível atualizar o perfil, mostrando os dados guardados.","Atualizado","Não foi possível carregar o perfil. A modelo pode ter mudado de apelido ou ocultado o perfil.","não indicado","era","Perfil","Interesses","atual","do site","adicionado","Não verificado, abrir pesquisa","“?” — não verificado pelo script: toque para abrir a pesquisa e ver manualmente.","Idiomas","Idade","Interessado em","Tipo físico","Etnia","Cor do cabelo","Cor dos olhos","Local","Subcultura","Particularidades","Estado","Sobre","Mulher","Homem","Trans","Casal","Casal de homens","Casal de mulheres","Casal trans","Casal: homem e trans","Casal: mulher e trans"],
    IT: ["Audio","Booster e voce","Amplificazione","Chiarezza della voce","Reimposta audio","Compressore","Filtro rumble 90 Hz","Limitatore","Funziona con i flussi dello script e del sito (blob/MSE). Le registrazioni non vengono amplificate.","Hz","Info","Aggiungi nick…","Aggiungi","Nick","Ricontrolla","Dove trovato","Aggiorna","I dati del profilo non sono ancora supportati per questo sito: sono disponibili nick e ricerca sui siti.","Caricamento profilo…","Impossibile aggiornare il profilo, mostrati i dati salvati.","Aggiornato","Impossibile caricare il profilo. La modella potrebbe aver cambiato nick o nascosto il profilo.","non indicato","era","Profilo","Interessi","attuale","dal sito","aggiunto","Non verificato, apri la ricerca","«?» — non verificato dallo script: tocca per aprire la ricerca e guardare a mano.","Lingue","Età","Interessato a","Corporatura","Etnia","Colore dei capelli","Colore degli occhi","Luogo","Sottocultura","Particolarità","Stato","Chi sono","Donna","Uomo","Trans","Coppia","Coppia di uomini","Coppia di donne","Coppia trans","Coppia: uomo e trans","Coppia: donna e trans"],
    PL: ["Dźwięk","Wzmacniacz i głos","Wzmocnienie","Wyrazistość głosu","Resetuj dźwięk","Kompresor","Filtr buczenia 90 Hz","Limiter","Działa ze strumieniami skryptu i strony (blob/MSE). Nagrania nie są wzmacniane.","Hz","Info","Dodaj nick…","Dodaj","Nicki","Sprawdź ponownie","Gdzie znaleziono","Odśwież","Dane profilu nie są jeszcze obsługiwane dla tej strony: dostępne są nicki i wyszukiwanie po stronach.","Wczytywanie profilu…","Nie udało się odświeżyć profilu, pokazano zapisane dane.","Zaktualizowano","Nie udało się wczytać profilu. Modelka mogła zmienić nick lub ukryć profil.","nie podano","było","Profil","Zainteresowania","obecny","ze strony","dodano","Niesprawdzane, otwórz wyszukiwanie","„?” — skrypt nie sprawdza tej strony: dotknij, aby otworzyć wyszukiwanie i sprawdzić ręcznie.","Języki","Wiek","Interesuje się","Budowa ciała","Pochodzenie","Kolor włosów","Kolor oczu","Miejsce","Subkultura","Cechy szczególne","Status","O mnie","Kobieta","Mężczyzna","Trans","Para","Para mężczyzn","Para kobiet","Para trans","Para: mężczyzna i trans","Para: kobieta i trans"],
    TR: ["Ses","Güçlendirici ve ses","Yükseltme","Ses netliği","Sesi sıfırla","Kompresör","Uğultu filtresi 90 Hz","Sınırlayıcı","Betik ve site akışlarında çalışır (blob/MSE). Kayıtlar yükseltilmez.","Hz","Bilgi","Takma ad ekle…","Ekle","Takma adlar","Yeniden kontrol et","Bulunduğu yerler","Yenile","Bu site için profil verileri henüz desteklenmiyor: takma adlar ve site araması kullanılabilir.","Profil yükleniyor…","Profil yenilenemedi, kayıtlı veriler gösteriliyor.","Güncellendi","Profil yüklenemedi. Model takma adını değiştirmiş veya profilini gizlemiş olabilir.","belirtilmemiş","önceki","Profil","İlgi alanları","güncel","siteden","eklendi","Kontrol edilmiyor, aramayı aç","“?” — betik bu siteyi kontrol etmiyor: aramayı açıp elle bakmak için dokunun.","Diller","Yaş","İlgilendiği","Vücut tipi","Etnik köken","Saç rengi","Göz rengi","Konum","Alt kültür","Özellikler","Durum","Hakkında","Kadın","Erkek","Trans","Çift","Erkek çift","Kadın çift","Trans çift","Çift: erkek ve trans","Çift: kadın ve trans"],
    NL: ["Geluid","Booster en stem","Versterking","Stemhelderheid","Geluid resetten","Compressor","Bromfilter 90 Hz","Limiter","Werkt met streams van het script en de site (blob/MSE). Opnames worden niet versterkt.","Hz","Info","Nick toevoegen…","Toevoegen","Nicks","Opnieuw controleren","Gevonden op","Vernieuwen","Profielgegevens worden voor deze site nog niet ondersteund: nicks en zoeken op sites zijn beschikbaar.","Profiel laden…","Profiel vernieuwen mislukt, opgeslagen gegevens worden getoond.","Bijgewerkt","Profiel laden mislukt. Het model heeft mogelijk de nick gewijzigd of het profiel verborgen.","niet opgegeven","was","Profiel","Interesses","huidig","van de site","toegevoegd","Niet gecontroleerd, zoekopdracht openen","“?” — niet gecontroleerd door het script: tik om de zoekopdracht te openen en handmatig te kijken.","Talen","Leeftijd","Geïnteresseerd in","Lichaamsbouw","Etniciteit","Haarkleur","Oogkleur","Locatie","Subcultuur","Kenmerken","Status","Over mij","Vrouw","Man","Trans","Koppel","Mannenkoppel","Vrouwenkoppel","Transkoppel","Koppel: man en trans","Koppel: vrouw en trans"],
    CS: ["Zvuk","Zesilovač a hlas","Zesílení","Srozumitelnost hlasu","Obnovit zvuk","Kompresor","Filtr hučení 90 Hz","Limiter","Funguje s proudy skriptu i webu (blob/MSE). Nahrávky se nezesilují.","Hz","Info","Přidat přezdívku…","Přidat","Přezdívky","Zkontrolovat znovu","Kde nalezeno","Obnovit","Data profilu pro tento web zatím nejsou podporována: k dispozici jsou přezdívky a vyhledávání na webech.","Načítání profilu…","Profil se nepodařilo obnovit, zobrazena uložená data.","Aktualizováno","Profil se nepodařilo načíst. Modelka mohla změnit přezdívku nebo skrýt profil.","neuvedeno","bylo","Profil","Zájmy","aktuální","z webu","přidáno","Nekontroluje se, otevřít vyhledávání","„?“ — skript tento web nekontroluje: klepnutím otevřete vyhledávání a podívejte se ručně.","Jazyky","Věk","Zajímá se o","Postava","Etnikum","Barva vlasů","Barva očí","Místo","Subkultura","Zvláštnosti","Stav","O mně","Žena","Muž","Trans","Pár","Pár mužů","Pár žen","Trans pár","Pár: muž a trans","Pár: žena a trans"],
    RO: ["Sunet","Amplificator și voce","Amplificare","Claritatea vocii","Resetează sunetul","Compresor","Filtru de zumzet 90 Hz","Limitator","Funcționează cu fluxurile scriptului și ale site-ului (blob/MSE). Înregistrările nu sunt amplificate.","Hz","Info","Adaugă poreclă…","Adaugă","Porecle","Verifică din nou","Unde a fost găsit","Actualizează","Datele de profil nu sunt încă acceptate pentru acest site: sunt disponibile poreclele și căutarea pe site-uri.","Se încarcă profilul…","Profilul nu a putut fi actualizat, se afișează datele salvate.","Actualizat","Profilul nu a putut fi încărcat. Modelul și-ar fi putut schimba porecla sau ascunde profilul.","nespecificat","era","Profil","Interese","actuală","de pe site","adăugată","Nu se verifică, deschide căutarea","„?” — scriptul nu verifică acest site: atinge pentru a deschide căutarea și a căuta manual.","Limbi","Vârstă","Interesat de","Constituție","Etnie","Culoarea părului","Culoarea ochilor","Locație","Subcultură","Particularități","Stare","Despre","Femeie","Bărbat","Trans","Cuplu","Cuplu de bărbați","Cuplu de femei","Cuplu trans","Cuplu: bărbat și trans","Cuplu: femeie și trans"],
    HU: ["Hang","Erősítő és hang","Erősítés","Hangtisztaság","Hang visszaállítása","Kompresszor","Zúgásszűrő 90 Hz","Limiter","A szkript és az oldal streamjeivel működik (blob/MSE). A felvételek nincsenek erősítve.","Hz","Infó","Név hozzáadása…","Hozzáadás","Nevek","Újraellenőrzés","Hol található","Frissítés","A profiladatok ehhez az oldalhoz még nem támogatottak: a nevek és az oldalankénti keresés elérhető.","Profil betöltése…","A profil frissítése nem sikerült, a mentett adatok láthatók.","Frissítve","A profil betöltése nem sikerült. A modell megváltoztathatta a nevét vagy elrejthette a profilt.","nincs megadva","volt","Profil","Érdeklődés","jelenlegi","az oldalról","hozzáadva","Nem ellenőrzött, keresés megnyitása","„?” — a szkript nem ellenőrzi ezt az oldalt: koppintson a keresés megnyitásához és kézi ellenőrzéshez.","Nyelvek","Kor","Érdeklődik","Testalkat","Etnikum","Hajszín","Szemszín","Hely","Szubkultúra","Jellemzők","Állapot","Rólam","Nő","Férfi","Transz","Pár","Férfipár","Női pár","Transz pár","Pár: férfi és transz","Pár: nő és transz"],
    JA: ["サウンド","ブースターと音声","増幅","声の明瞭さ","サウンドをリセット","コンプレッサー","ランブルフィルター 90 Hz","リミッター","スクリプトとサイトのストリームで動作します(blob/MSE)。録音は増幅されません。","Hz","情報","ニックネームを追加…","追加","ニックネーム","再確認","見つかった場所","更新","このサイトのプロフィールデータにはまだ対応していません。ニックネームとサイト検索は利用できます。","プロフィールを読み込み中…","プロフィールを更新できませんでした。保存済みのデータを表示しています。","更新","プロフィールを取得できませんでした。モデルがニックネームを変更したか、プロフィールを非表示にした可能性があります。","未設定","以前","プロフィール","興味","現在","サイトより","追加","確認なし、検索を開く","「?」— スクリプトでは確認していません。タップして検索を開き、手動で確認してください。","言語","年齢","興味","体型","民族","髪の色","目の色","場所","サブカルチャー","特徴","ステータス","自己紹介","女性","男性","トランス","カップル","男性カップル","女性カップル","トランスカップル","カップル:男性とトランス","カップル:女性とトランス"],
    ZH: ["声音","增强与人声","增益","人声清晰度","重置声音","压缩器","低频嗡嗡滤波 90 Hz","限幅器","适用于脚本和网站的流(blob/MSE)。录制内容不会被增强。","Hz","信息","添加昵称…","添加","昵称","重新检查","发现于","刷新","此网站暂不支持资料数据:可使用昵称和站点搜索。","正在加载资料…","无法刷新资料,显示已保存的数据。","已更新","无法获取资料。模特可能已更改昵称或隐藏了资料。","未填写","之前","资料","兴趣","当前","来自网站","添加于","未检查,打开搜索","“?”— 脚本未检查此网站:点按以打开搜索并手动查看。","语言","年龄","感兴趣","体型","种族","发色","瞳色","地点","亚文化","特点","状态","关于","女生","男生","跨性别","情侣","男男情侣","女女情侣","跨性别情侣","情侣:男生和跨性别","情侣:女生和跨性别"],
    KO: ["사운드","부스터 및 음성","증폭","음성 선명도","사운드 초기화","컴프레서","럼블 필터 90 Hz","리미터","스크립트와 사이트 스트림에서 작동합니다(blob/MSE). 녹화에는 증폭이 적용되지 않습니다.","Hz","정보","닉네임 추가…","추가","닉네임","다시 확인","발견된 곳","새로고침","이 사이트의 프로필 데이터는 아직 지원되지 않습니다. 닉네임과 사이트 검색은 사용할 수 있습니다.","프로필 불러오는 중…","프로필을 새로고침하지 못해 저장된 데이터를 표시합니다.","업데이트","프로필을 불러오지 못했습니다. 모델이 닉네임을 바꾸었거나 프로필을 숨겼을 수 있습니다.","미지정","이전","프로필","관심사","현재","사이트에서","추가됨","확인 안 함, 검색 열기","“?” — 스크립트가 확인하지 않는 사이트입니다. 탭하여 검색을 열고 직접 확인하세요.","언어","나이","관심 대상","체형","인종","머리색","눈 색","위치","서브컬처","특징","상태","소개","여성","남성","트랜스","커플","남성 커플","여성 커플","트랜스 커플","커플: 남성과 트랜스","커플: 여성과 트랜스"],
    HE: ["צליל","מגבר וקול","הגברה","בהירות הקול","איפוס צליל","מדחס","מסנן רעש 90 הרץ","מגביל","פועל עם זרמי הסקריפט והאתר (blob/MSE). הקלטות אינן מוגברות.","הרץ","מידע","הוספת כינוי…","הוספה","כינויים","בדיקה חוזרת","נמצא ב","רענון","נתוני פרופיל עדיין אינם נתמכים באתר זה: זמינים כינויים וחיפוש באתרים.","טוען פרופיל…","לא ניתן היה לרענן את הפרופיל, מוצגים נתונים שמורים.","עודכן","לא ניתן היה לטעון את הפרופיל. ייתכן שהמודל שינתה כינוי או הסתירה את הפרופיל.","לא צוין","היה","פרופיל","תחומי עניין","נוכחי","מהאתר","נוסף","לא נבדק, פתיחת חיפוש","“?” — הסקריפט לא בודק את האתר: הקישו כדי לפתוח חיפוש ולבדוק ידנית.","שפות","גיל","מתעניין ב","מבנה גוף","מוצא","צבע שיער","צבע עיניים","מיקום","תת־תרבות","מאפיינים","סטטוס","אודות","אישה","גבר","טרנס","זוג","זוג גברים","זוג נשים","זוג טרנס","זוג: גבר וטרנס","זוג: אישה וטרנס"],
    AR: ["الصوت","المعزّز والصوت","التضخيم","وضوح الصوت","إعادة ضبط الصوت","الضاغط","مرشح الضجيج 90 هرتز","المحدّد","يعمل مع بثوث السكربت والموقع (blob/MSE). التسجيلات لا يتم تضخيمها.","هرتز","معلومات","إضافة اسم مستعار…","إضافة","الأسماء المستعارة","إعادة الفحص","وُجد في","تحديث","بيانات الملف الشخصي غير مدعومة لهذا الموقع بعد: الأسماء المستعارة والبحث في المواقع متاحان.","جارٍ تحميل الملف الشخصي…","تعذّر تحديث الملف الشخصي، تُعرض البيانات المحفوظة.","تم التحديث","تعذّر تحميل الملف الشخصي. ربما غيّرت العارضة اسمها المستعار أو أخفت ملفها.","غير محدد","كان","الملف الشخصي","الاهتمامات","الحالي","من الموقع","أُضيف","غير مفحوص، افتح البحث","“?” — لا يفحص السكربت هذا الموقع: اضغط لفتح البحث والتحقق يدويًا.","اللغات","العمر","مهتم بـ","بنية الجسم","العرق","لون الشعر","لون العينين","الموقع","الثقافة الفرعية","المميزات","الحالة","نبذة","فتاة","شاب","ترانس","زوجان","زوجان من الرجال","زوجان من النساء","زوجان ترانس","زوجان: رجل وترانس","زوجان: امرأة وترانس"],
    HI: ["ध्वनि","बूस्टर और आवाज़","प्रवर्धन","आवाज़ की स्पष्टता","ध्वनि रीसेट करें","कंप्रेसर","गुंजन फ़िल्टर 90 Hz","लिमिटर","स्क्रिप्ट और साइट की स्ट्रीम पर काम करता है (blob/MSE)। रिकॉर्डिंग में प्रवर्धन नहीं होता।","Hz","जानकारी","उपनाम जोड़ें…","जोड़ें","उपनाम","फिर से जाँचें","कहाँ मिला","ताज़ा करें","इस साइट के लिए प्रोफ़ाइल डेटा अभी समर्थित नहीं है: उपनाम और साइट खोज उपलब्ध हैं।","प्रोफ़ाइल लोड हो रही है…","प्रोफ़ाइल ताज़ा नहीं हो सकी, सहेजा हुआ डेटा दिखाया जा रहा है।","अपडेट किया","प्रोफ़ाइल लोड नहीं हो सकी। हो सकता है मॉडल ने उपनाम बदल लिया हो या प्रोफ़ाइल छिपा ली हो।","निर्दिष्ट नहीं","पहले","प्रोफ़ाइल","रुचियाँ","वर्तमान","साइट से","जोड़ा गया","जाँच नहीं हुई, खोज खोलें","“?” — स्क्रिप्ट इस साइट की जाँच नहीं करती: खोज खोलकर हाथ से देखने के लिए टैप करें।","भाषाएँ","उम्र","रुचि","शारीरिक बनावट","जातीयता","बालों का रंग","आँखों का रंग","स्थान","उपसंस्कृति","विशेषताएँ","स्थिति","परिचय","महिला","पुरुष","ट्रांस","जोड़ा","पुरुष जोड़ा","महिला जोड़ा","ट्रांस जोड़ा","जोड़ा: पुरुष और ट्रांस","जोड़ा: महिला और ट्रांस"],
    ID: ["Suara","Penguat & suara","Penguatan","Kejernihan suara","Atur ulang suara","Kompresor","Filter dengung 90 Hz","Pembatas","Berfungsi pada stream skrip dan situs (blob/MSE). Rekaman tidak diperkuat.","Hz","Info","Tambah nama panggilan…","Tambah","Nama panggilan","Periksa lagi","Ditemukan di","Muat ulang","Data profil belum didukung untuk situs ini: nama panggilan dan pencarian situs tersedia.","Memuat profil…","Gagal memuat ulang profil, menampilkan data tersimpan.","Diperbarui","Gagal memuat profil. Model mungkin mengganti nama panggilan atau menyembunyikan profil.","belum diisi","sebelumnya","Profil","Minat","saat ini","dari situs","ditambahkan","Tidak diperiksa, buka pencarian","“?” — tidak diperiksa oleh skrip: ketuk untuk membuka pencarian dan mencari manual.","Bahasa","Usia","Tertarik pada","Tipe tubuh","Etnis","Warna rambut","Warna mata","Lokasi","Subkultur","Ciri khusus","Status","Tentang","Wanita","Pria","Trans","Pasangan","Pasangan pria","Pasangan wanita","Pasangan trans","Pasangan: pria dan trans","Pasangan: wanita dan trans"],
    VI: ["Âm thanh","Tăng âm & giọng","Khuếch đại","Độ rõ giọng","Đặt lại âm thanh","Bộ nén","Lọc ồn trầm 90 Hz","Bộ giới hạn","Hoạt động với luồng của tập lệnh và trang web (blob/MSE). Bản ghi không được khuếch đại.","Hz","Thông tin","Thêm biệt danh…","Thêm","Biệt danh","Kiểm tra lại","Tìm thấy ở","Làm mới","Dữ liệu hồ sơ chưa được hỗ trợ cho trang này: có biệt danh và tìm kiếm trên các trang.","Đang tải hồ sơ…","Không thể làm mới hồ sơ, đang hiển thị dữ liệu đã lưu.","Đã cập nhật","Không thể tải hồ sơ. Người mẫu có thể đã đổi biệt danh hoặc ẩn hồ sơ.","chưa nêu","trước đây","Hồ sơ","Sở thích","hiện tại","từ trang web","đã thêm","Không kiểm tra, mở tìm kiếm","“?” — tập lệnh không kiểm tra trang này: chạm để mở tìm kiếm và xem thủ công.","Ngôn ngữ","Tuổi","Quan tâm đến","Dáng người","Sắc tộc","Màu tóc","Màu mắt","Vị trí","Tiểu văn hóa","Đặc điểm","Trạng thái","Giới thiệu","Nữ","Nam","Chuyển giới","Cặp đôi","Cặp đôi nam","Cặp đôi nữ","Cặp đôi chuyển giới","Cặp đôi: nam và chuyển giới","Cặp đôi: nữ và chuyển giới"],
    TH: ["เสียง","ตัวขยายและเสียงพูด","ระดับขยาย","ความชัดของเสียงพูด","รีเซ็ตเสียง","คอมเพรสเซอร์","ตัวกรองเสียงอื้อ 90 Hz","ลิมิตเตอร์","ใช้ได้กับสตรีมของสคริปต์และเว็บไซต์ (blob/MSE) การบันทึกจะไม่ถูกขยายเสียง","Hz","ข้อมูล","เพิ่มชื่อเล่น…","เพิ่ม","ชื่อเล่น","ตรวจสอบอีกครั้ง","พบที่","รีเฟรช","เว็บไซต์นี้ยังไม่รองรับข้อมูลโปรไฟล์ แต่ใช้ชื่อเล่นและการค้นหาตามเว็บไซต์ได้","กำลังโหลดโปรไฟล์…","รีเฟรชโปรไฟล์ไม่ได้ กำลังแสดงข้อมูลที่บันทึกไว้","อัปเดตแล้ว","โหลดโปรไฟล์ไม่ได้ โมเดลอาจเปลี่ยนชื่อเล่นหรือซ่อนโปรไฟล์","ไม่ระบุ","เดิม","โปรไฟล์","ความสนใจ","ปัจจุบัน","จากเว็บไซต์","เพิ่มแล้ว","ไม่ได้ตรวจสอบ เปิดการค้นหา","“?” — สคริปต์ไม่ตรวจสอบเว็บไซต์นี้ แตะเพื่อเปิดการค้นหาแล้วดูเอง","ภาษา","อายุ","สนใจ","รูปร่าง","เชื้อชาติ","สีผม","สีตา","สถานที่","วัฒนธรรมย่อย","ลักษณะเฉพาะ","สถานะ","เกี่ยวกับ","ผู้หญิง","ผู้ชาย","ทรานส์","คู่รัก","คู่ชาย","คู่หญิง","คู่ทรานส์","คู่: ชายและทรานส์","คู่: หญิงและทรานส์"]
};
const nxT = k => { const i = NX_K.indexOf(k), d = NX_T[String(AppState.lang).toUpperCase()]; return (d && d[i]) || NX_T.EN[i] || ''; };
const nxLoc = () => String(AppState.lang || 'EN').toLowerCase();

const SCI_SITES = { sc: 'Stripchat', bc: 'BongaCams', cb: 'Chaturbate' };

const INF_ROWS = [
    ['lg', 'Языки', 'Languages', ['lg']],
    ['a', 'Возраст', 'Age', ['a']],
    ['ii', 'Интересуюсь', 'Interested in', ['ii']],
    ['bt', 'Телосложение', 'Body type', ['bt']],
    ['et', 'Этничность', 'Ethnicity', ['et']],
    ['hc', 'Цвет волос', 'Hair color', ['hc']],
    ['ec', 'Цвет глаз', 'Eye color', ['ec']],
    ['loc', 'Место', 'Location', ['co', 'rg', 'ci']],
    ['sc', 'Субкультура', 'Subculture', ['sc']],
    ['sp', 'Особенности', 'Specifics', ['sp']],
    ['st', 'Статус', 'Status', []],
    ['ds', 'Обо мне', 'About', []]
];

const INF_FRESH_MS = 6 * 3600000;
const INF_CHANGE_MS = 30 * 86400000;

const sciPlural = (n, a, b, c) => {
    const m10 = n % 10, m100 = n % 100;
    return m100 >= 11 && m100 <= 14 ? c : m10 === 1 ? a : m10 >= 2 && m10 <= 4 ? b : c;
};

const sciHuman = (code, pre) => {
    const t = String(code).replace(new RegExp(`^(?:${pre})`), '').replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/[-_]+/g, ' ').trim();
    return t ? t.charAt(0).toUpperCase() + t.slice(1) : String(code);
};

const sciLabel = (grp, code, gender) => {
    if (!code) return '';
    const dic = ruT(SCI_RU, SCI_EN)[grp] || {};
    if (grp === 'bt' && gender === 'female' && dic[`${code}ForFemale`]) return dic[`${code}ForFemale`];
    const hit = dic[code] || (grp === 'sp' && ruT(SCI_RU.sc, SCI_EN.sc)[code]);
    if (hit) return hit;
    if (Object.keys(dic).length) { try { ParserDiag.code(grp, code); } catch (e) { dbg(e, 'sciLabel'); } }
    return sciHuman(code, SCI_PREFIX[grp] || '');
};

const sciLang = code => {
    const ru = ruT(SCI_RU.lg, null);
    if (ru && ru[code]) return ru[code];
    const alias = { br: 'pt-BR', zt: 'zh-Hant', zh: 'zh-Hans' };
    try {
        const n = new Intl.DisplayNames([nxLoc()], { type: 'language' }).of(alias[code] || code);
        if (n) return n.charAt(0).toUpperCase() + n.slice(1);
    } catch (e) { dbg(e, 'sciLang'); }
    return String(code).toUpperCase();
};

const sciCountry = c => {
    c = String(c || '').trim();
    if (/^[a-z]{2}$/i.test(c)) {
        try {
            const n = new Intl.DisplayNames([nxLoc()], { type: 'region' }).of(c.toUpperCase());
            if (n) return n;
        } catch (e) { dbg(e, 'sciCountry'); }
    }
    return c;
};

const sciGender = g => (NX_K.includes(`g_${g}`) ? nxT(`g_${g}`) : '');

const sciAge = n => {
    const l = String(AppState.lang).toUpperCase();
    if (l === 'RU') return `${n} ${sciPlural(n, 'год', 'года', 'лет')}`;
    if (l === 'UK') return `${n} ${sciPlural(n, 'рік', 'роки', 'років')}`;
    return l === 'EN' ? `${n} y.o.` : String(n);
};

const sciFmt = (k, v, g) => {
    if (v == null || v === '' || (Array.isArray(v) && !v.length)) return '';
    if (k === 'g') return sciGender(v);
    if (k === 'a') return sciAge(v);
    if (k === 'lg') return v.map(sciLang).join(', ');
    if (k === 'co') return sciCountry(v);
    if (k === 'sp') return v.map(x => sciLabel('sp', x, g)).join(', ');
    if (k === 'rg' || k === 'ci' || k === 'st' || k === 'ds') return String(v);
    return sciLabel(k, v, g);
};

const sciTrim = (v, n) => clip(v, n).replace(/[\ud800-\udbff]$/, '').trim();

const sciArr = (v, n) => (Array.isArray(v) ? v.filter(x => isStr(x) && x.trim()).map(x => x.trim().slice(0, 48)).slice(0, n) : []);

const SCI_KEYS = ['languages', 'bodyType', 'interests', 'interestedIn', 'ethnicity', 'gender', 'birthDate', 'hairColor', 'eyeColor', 'specifics', 'previousUsername'];
const SCI_NEED = ['languages', 'age', 'gender', 'interestedIn', 'bodyType', 'ethnicity'];
const sciScore = u => (isPlain(u) ? SCI_KEYS.reduce((n, k) => n + (k in u ? 1 : 0), 0) : 0);
const sciHasProfile = u => sciScore(u) >= 3;

const sciPick = u => {
    const age = Number.isFinite(+u.age) && +u.age > 0 && u.isAgeHidden !== true ? Math.round(+u.age) : 0;
    const raw = {
        g: clip(u.gender, 20),
        a: age,
        lg: sciArr(u.languages, 12),
        ii: clip(u.interestedIn, 40),
        bt: clip(u.bodyType, 40),
        et: clip(u.ethnicity, 40),
        hc: clip(u.hairColor, 40),
        ec: clip(u.eyeColor, 40),
        sc: clip(u.subculture, 40),
        sp: sciArr(u.specifics, 30),
        it: sciArr(u.interests, 40),
        co: clip(u.country, 60).trim(),
        rg: clip(u.region, 60).trim(),
        ci: clip(u.city, 60).trim(),
        ds: sciTrim(u.description, 400),
        st: sciTrim(u.offlineStatus, 200)
    };
    const inf = {};
    for (const k of Object.keys(raw)) {
        const v = raw[k];
        if (Array.isArray(v) ? v.length : v) inf[k] = v;
    }
    return { inf, pn: clip(u.previousUsername, 60).trim(), nick: clip(u.username, 100).trim() };
};

const sciWalk = (o, out, d = 0, seen = new Set()) => {
    if (!isObj(o) || d > 8 || out.length > 20 || seen.has(o)) return out;
    seen.add(o);
    if (!Array.isArray(o) && isStr(o.username) && sciHasProfile(o)) out.push(o);
    for (const k of Object.keys(o)) sciWalk(o[k], out, d + 1, seen);
    return out;
};

const sciFindUser = (o, want) => {
    const c = sciWalk(o, []);
    const w = String(want || '').toLowerCase();
    const hit = w && c.find(x => x.username.toLowerCase() === w);
    return hit || (c.length === 1 ? c[0] : null);
};

const sciBalanced = (s, i) => {
    if (s[i] !== '{') return '';
    let d = 0, q = false, e = false;
    const end = Math.min(s.length, i + 6e6);
    for (let j = i; j < end; j++) {
        const c = s[j];
        if (q) { if (e) e = false; else if (c === '\\') e = true; else if (c === '"') q = false; continue; }
        if (c === '"') q = true;
        else if (c === '{') d++;
        else if (c === '}' && --d === 0) return s.slice(i, j + 1);
    }
    return '';
};

const sciStates = function* (html) {
    const h = isStr(html) ? html : '';
    const mk = /__PRELOADED_STATE__\s*=\s*/g;
    let m;
    while ((m = mk.exec(h))) {
        const i = mk.lastIndex;
        if (h[i] === '{') {
            const raw = sciBalanced(h, i);
            if (raw) { try { yield [JSON.parse(raw), 'json:state']; } catch (e) { dbg(e, 'sciStates'); } }
        } else {
            const jp = /^JSON\.parse\(\s*("(?:[^"\\]|\\.)*")\s*\)/.exec(h.slice(i, i + 4e6));
            if (jp) { try { yield [JSON.parse(JSON.parse(jp[1])), 'json:parse']; } catch (e) { dbg(e, 'sciStates'); } }
        }
    }
    for (const x of h.matchAll(/<script\b([^>]*)>([\s\S]*?)<\/script>/gi)) {
        const body = x[2].trim();
        if (body.length < 20 || body.length > 4e6 || body.charCodeAt(0) !== 123 || !/json/i.test(x[1])) continue;
        try { yield [JSON.parse(body), 'json:script']; } catch (e) { dbg(e, 'sciStates'); }
    }
};

const sciFromHtml = (html, want) => {
    for (const [st, via] of sciStates(html)) {
        const u = st?.viewCam?.model;
        if (sciHasProfile(u)) return { u, via };
        const f = sciFindUser(st, want);
        if (f) return { u: f, via: `${via}-deep` };
    }
    return null;
};

const sciFromCam = (raw, want) => {
    let j;
    try { j = JSON.parse(raw); } catch { return null; }
    const u = j?.user?.user;
    if (sciHasProfile(u)) return { u, via: 'api:cam' };
    const f = sciFindUser(j, want);
    return f ? { u: f, via: 'api:cam-deep' } : null;
};

const sciSig = (u, raw) => ({ u: String(u).slice(0, 120), len: raw.length, state: raw.includes('__PRELOADED_STATE__'), head: raw.slice(0, 100).replace(/\s+/g, ' ') });

const sciDiagOk = (slug, r) => {
    const miss = SCI_NEED.filter(k => !(k in r.u));
    ParserDiag.recInfo(slug, r.via, false, miss.length ? { miss } : null);
};

const sciFetch = async (b, signal) => {
    const slug = modelSlug(b.url), tried = [];
    for (const u of profileUrls(b)) {
        const raw = await requestOnce(u, undefined, signal, 9000);
        if (!raw || signal?.aborted) continue;
        const r = u === b.url ? (sciFromHtml(raw, slug) || sciFromCam(raw, slug)) : (sciFromCam(raw, slug) || sciFromHtml(raw, slug));
        if (r) { sciDiagOk(slug, r); return r.u; }
        tried.push(sciSig(u, raw));
    }
    if (tried.length && !signal?.aborted) ParserDiag.recInfo(slug, '', true, { tried });
    return null;
};

const infEq = (x, y) => JSON.stringify(x ?? null) === JSON.stringify(y ?? null);

const bcInfoFetch = async (b, signal) => {
    const pu = bcProfileUrl(b);
    if (!pu) return null;
    for (const hd of [{ 'User-Agent': MOBILE_UA }, null]) {
        const h = await requestOnce(pu, hd, signal, 12000);
        if (signal?.aborted) return null;
        if (!h) continue;
        const roots = bcRoots(h), bp = parseBcProfile(h, roots, true);
        if (bp) return { ...bcExtraSrc(h, roots).out, bp };
    }
    return null;
};

const addAlt = (b, nick, fromSite) => {
    const cur = Array.isArray(b.alts) ? b.alts : [];
    const next = cleanAlts([...cur, nick], b.name);
    if (next.length === cur.length) return false;
    const k = String(nick).trim().toLowerCase();
    b.alts = next;
    b.altd = { ...(isPlain(b.altd) ? b.altd : {}), [k]: Date.now() };
    if (fromSite) b.alte = [...new Set([...(Array.isArray(b.alte) ? b.alte : []), k])];
    return true;
};

const applyInfo = (b, pick) => {
    const now = Date.now(), prev = isPlain(b.inf) ? b.inf : null;
    const hist = Array.isArray(b.infh) ? b.infh.filter(h => isPlain(h) && isStr(h.k)) : [];
    if (prev) {
        for (const k of INF_TRACK) {
            if (k === 'a' && !pick.inf.a) continue;
            if (!infEq(prev[k], pick.inf[k])) hist.push({ k, o: prev[k] ?? null, n: pick.inf[k] ?? null, t: now });
        }
    }
    b.inf = { ...pick.inf, t: now };
    if (hist.length) b.infh = hist.slice(-30); else delete b.infh;
    if (pick.pn && pick.pn.toLowerCase() !== String(b.name).toLowerCase()) addAlt(b, pick.pn, true);
};

const INFO_CSS = `.rs-pult .in-head {
  display: flex;
  align-items: center;
  gap: 12px
}
.rs-pult .in-av {
  flex: none;
  width: 56px;
  height: 56px;
  border-radius: 14px;
  object-fit: cover;
  background: var(--pt);
  border: 1px solid var(--pl)
}
.rs-pult .in-hm {
  flex: 1;
  min-width: 0
}
.rs-pult .in-nm {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 17px;
  font-weight: 700
}
.rs-pult .in-sb {
  margin-top: 2px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12px;
  opacity: .65
}
.rs-pult .in-btn {
  flex: none;
  min-height: 44px;
  margin: 0;
  padding: 0 14px;
  border: 1px solid var(--pl);
  border-radius: 12px;
  background: var(--pt);
  color: #fff;
  font: 500 13px/1.2 Onest,system-ui,sans-serif;
  cursor: pointer;
  transition: background .12s,transform .12s
}
.rs-pult .in-btn:active {
  transform: scale(.97)
}
.rs-pult .in-btn[disabled] {
  opacity: .5;
  pointer-events: none
}
.rs-pult .in-btn:focus-visible,
.rs-pult .in-in:focus-visible,
.rs-pult .in-x:focus-visible {
  outline: 2px solid var(--th);
  outline-offset: 2px
}
.rs-pult .in-r {
  display: flex;
  gap: 12px;
  padding: 9px 12px;
  border: 1px solid var(--pl);
  border-radius: 12px;
  background: var(--pt);
  font-size: 14px;
  line-height: 1.35
}
.rs-pult .in-k {
  flex: 0 0 36%;
  max-width: 140px;
  font-size: 13px;
  opacity: .62
}
.rs-pult .in-v {
  flex: 1;
  min-width: 0;
  overflow-wrap: anywhere
}
.rs-pult .in-was {
  display: block;
  margin-top: 3px;
  font-size: 12px;
  color: #f5b942
}
.rs-pult .in-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px
}
.rs-pult .in-chip {
  padding: 6px 10px;
  border: 1px solid var(--pl);
  border-radius: 10px;
  background: var(--pt);
  font-size: 13px
}
.rs-pult .in-tl {
  margin: 2px 0 2px 8px;
  padding-left: 18px;
  border-left: 2px solid var(--pl)
}
.rs-pult .in-n {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 0
}
.rs-pult .in-n:before {
  content: "";
  position: absolute;
  left: -25px;
  top: 50%;
  width: 10px;
  height: 10px;
  margin-top: -5px;
  border-radius: 50%;
  background: var(--pl);
  box-shadow: 0 0 0 3px rgba(0,0,0,.34)
}
.rs-pult .in-n.cur:before {
  background: var(--th)
}
.rs-pult .in-t {
  flex: 1;
  min-width: 0
}
.rs-pult .in-tn {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 14px;
  font-weight: 600
}
.rs-pult .in-td {
  display: block;
  font-size: 12px;
  opacity: .62
}
.rs-pult .in-x {
  flex: none;
  width: 40px;
  height: 40px;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color: #ff9aa6;
  font-size: 22px;
  line-height: 1;
  cursor: pointer
}
.rs-pult .in-x:active {
  background: rgba(255,255,255,.1)
}
.rs-pult .in-add {
  display: flex;
  gap: 6px
}
.rs-pult .in-in {
  flex: 1;
  min-width: 0;
  height: 44px;
  margin: 0;
  padding: 0 12px;
  border: 1px solid var(--pl);
  border-radius: 12px;
  outline: 0;
  background: var(--pt);
  color: #fff;
  font: 500 14px/1.2 Onest,system-ui,sans-serif
}
.rs-pult .in-in:focus {
  border-color: var(--th)
}
.rs-pult .in-tw {
  overflow-x: auto;
  border: 1px solid var(--pl);
  border-radius: 12px;
  background: var(--pt)
}
.rs-pult .in-tb {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px
}
.rs-pult .in-tb th,
.rs-pult .in-tb td {
  padding: 9px 8px;
  border-top: 1px solid var(--pl);
  text-align: center;
  white-space: nowrap
}
.rs-pult .in-tb tr:first-child th {
  border-top: 0
}
.rs-pult .in-tb th {
  max-width: 100px;
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: 600
}
.rs-pult .in-tb th:first-child,
.rs-pult .in-tb td:first-child {
  padding-left: 12px;
  text-align: start;
  font-weight: 400
}
.rs-pult .in-tb a {
  display: block;
  color: #2ecc71;
  font-weight: 700;
  text-decoration: none
}
.rs-pult .in-no {
  opacity: .35
}
.rs-pult .in-tb a.in-q {
  color: inherit;
  font-weight: 600;
  opacity: .75
}
.rs-pult .in-qh {
  padding: 8px 12px 0;
  font-size: 12px;
  opacity: .65;
  line-height: 1.4
}
.rs-pult .in-wt {
  opacity: .6;
  animation: inPulse 1s infinite
}
@keyframes inPulse {
  50% {
    opacity: .15
  }
}
.rs-pult .in-acts {
  display: flex;
  gap: 6px
}
.rs-pult .in-acts .in-btn {
  flex: 1
}`;

const openInfoPult = (ui, b) => {
    const site = urlSite(b.url);
    const bmo = ui.bookmarkManager;
    const sites = SEARCH_ENGINES.filter(e => !e[2]);
    const manual = SEARCH_ENGINES.filter(e => e[2]);
    const res = {};
    const owner = { _opAt: Date.now() };
    let ac = new AbortController();
    let qn = 0;
    let loading = false;
    let failed = false;

    const { overlay, modal, title, closeBtn, body } = buildPultShell(ui.shadow, RS_PULT_CSS + INFO_CSS);
    pultTitle(title, icons.info, nxT('info'));
    const btnBack = htmlEl('bm-close bm-back', icons.back, 'button');
    modal.querySelector('.bm-hdr').insertBefore(btnBack, closeBtn);

    const unbindEsc = bindEscape(() => close());
    const close = () => {
        ac.abort();
        unbindEsc();
        overlay.classList.remove('open');
        setTimeout(() => overlay.remove(), 400);
    };
    const back = () => {
        noFade(overlay, bmo?.overlay);
        close();
        bmo?.open();
    };
    btnBack.onclick = back;
    closeBtn.onclick = close;
    bindOutsideClose(owner, overlay, close);

    const fmtD = ts => (ts ? new Date(ts).toLocaleDateString(nxLoc(), { day: 'numeric', month: 'short', year: 'numeric' }) : '');
    const save = () => { bmDirty = true; saveBookmarks(); };
    const alts = () => (Array.isArray(b.alts) ? b.alts : []);

    const gHead = mk('div', 'rs-g');
    const gProf = mk('div', 'rs-g');
    const gInt = mk('div', 'rs-g');
    const gNick = mk('div', 'rs-g');
    const gFound = mk('div', 'rs-g');
    body.append(gHead, gProf, gInt, gNick, gFound);

    const head = mk('div', 'in-head');
    const av = mk('img', 'in-av');
    av.alt = '';
    av.referrerPolicy = 'no-referrer';
    const hm = mk('div', 'in-hm');
    const nm = mk('div', 'in-nm');
    const sb = mk('div', 'in-sb');
    hm.append(nm, sb);
    const btnRef = mk('button', 'in-btn');
    btnRef.type = 'button';
    head.append(av, hm, btnRef);
    const headNote = mk('div', 'rs-note');
    gHead.append(head, headNote);

    const tl = mk('div', 'in-tl');
    const add = mk('div', 'in-add');
    const inp = mk('input', 'in-in');
    inp.type = 'text'; inp.maxLength = 120; inp.autocomplete = 'off'; inp.spellcheck = false;
    inp.placeholder = nxT('add_nick');
    const btnAdd = mk('button', 'in-btn');
    btnAdd.type = 'button';
    btnAdd.textContent = nxT('add');
    add.append(inp, btnAdd);
    gNick.append(textEl('hd-sec', nxT('nicks')), tl, add);

    const tw = mk('div', 'in-tw');
    const btnAgain = mk('button', 'in-btn');
    btnAgain.type = 'button';
    btnAgain.textContent = nxT('recheck');
    const acts = mk('div', 'in-acts');
    acts.append(btnAgain);
    gFound.append(textEl('hd-sec', nxT('found')), tw, acts);

    const probe = nick => sites.forEach(([, tmpl]) => {
        const url = engineUrl(tmpl, nick);
        if (url in res) return;
        res[url] = 'wait';
        const sig = ac.signal;
        setTimeout(() => {
            if (sig.aborted) return;
            gmReq({ url, timeout: 8000, signal: sig }).then(r => {
                if (sig.aborted || r.fail === 'abort') return;
                res[url] = (!r.fail && !pageHasError(url, { status: r.status, responseText: r.body })) ? 'ok' : 'err';
                drawFound();
                drawNicks();
            });
        }, qn++ * 120);
    });

    const foundOf = nick => {
        let ok = 0, waiting = 0;
        sites.forEach(([, tmpl]) => {
            const v = res[engineUrl(tmpl, nick)];
            if (v === 'ok') ok++; else if (v === 'wait') waiting++;
        });
        return waiting ? '…' : `${ok}/${sites.length}${manual.length ? ` +${manual.length}?` : ''}`;
    };

    const changeOf = keys => {
        const now = Date.now();
        const h = Array.isArray(b.infh) ? b.infh : [];
        for (let i = h.length - 1; i >= 0; i--) if (keys.includes(h[i].k) && now - h[i].t < INF_CHANGE_MS) return h[i];
        return null;
    };

    const drawHead = () => {
        const inf = isPlain(b.inf) ? b.inf : null;
        const supported = site === 'sc' || site === 'bc' || site === 'cb';
        const stamp = site === 'bc' || site === 'cb' ? (isPlain(b.bp) ? b.bp.t : 0) : inf?.t;
        av.style.display = b.avatar ? '' : 'none';
        if (b.avatar && av.src !== b.avatar) av.src = b.avatar;
        nm.textContent = b.name;
        const parts = [SCI_SITES[site] || site];
        const g = inf ? sciGender(inf.g) : '';
        if (g) parts.push(g);
        sb.textContent = parts.join(' · ');
        btnRef.style.display = supported ? '' : 'none';
        btnRef.disabled = loading;
        btnRef.textContent = loading ? '…' : nxT('refresh');
        let note = '';
        if (!supported) note = nxT('noprof');
        else if (loading) note = nxT('loading');
        else if (failed && stamp) note = nxT('reffail') + ' ' + nxT('updated') + ' ' + fmtAgo(stamp);
        else if (failed) note = nxT('loadfail');
        else if (stamp) note = nxT('updated') + ' ' + fmtAgo(stamp);
        headNote.textContent = note;
        headNote.style.display = note ? '' : 'none';
    };

    const bpRow = (l, v, vol) => {
        const r = mk('div', 'in-r');
        const val = mk('div', 'in-v');
        val.append(document.createTextNode(v));
        const ch = vol ? null : changeOf([`bp:${l}`]);
        if (ch) val.append(textEl('in-was', `${nxT('was')}: ${ch.o || nxT('notset')} · ${fmtD(ch.t)}`, 'span'));
        r.append(textEl('in-k', l), val);
        return r;
    };

    const drawBp = () => {
        const bp = isPlain(b.bp) && Array.isArray(b.bp.g) ? b.bp : null;
        gProf.replaceChildren();
        gProf.style.display = bp ? '' : 'none';
        if (!bp) return;
        bp.g.forEach((x, i) => {
            gProf.append(textEl('hd-sec', x.h || (i === 0 ? nxT('profile') : '')));
            x.r.forEach(([l, v, vol]) => gProf.append(bpRow(l, v, vol)));
        });
        gProf.querySelectorAll('.hd-sec:empty').forEach(e => e.remove());
    };

    const drawProf = () => {
        if (site === 'bc' || site === 'cb') return drawBp();
        const inf = isPlain(b.inf) ? b.inf : null;
        gProf.replaceChildren();
        if (!inf) { gProf.style.display = 'none'; return; }
        const rows = [];
        for (const [key, ru, en, keys] of INF_ROWS) {
            const val = key === 'loc' ? [sciCountry(inf.co), inf.rg, inf.ci].filter(Boolean).join(', ') : sciFmt(key, inf[key], inf.g);
            const ch = changeOf(keys);
            if (!val && !ch) continue;
            const r = mk('div', 'in-r');
            const k = textEl('in-k', nxT(`r_${key}`));
            const v = mk('div', 'in-v');
            v.append(document.createTextNode(val || '—'));
            if (ch) {
                const was = sciFmt(ch.k, ch.o, inf.g) || nxT('notset');
                v.append(textEl('in-was', `${nxT('was')}: ${was} · ${fmtD(ch.t)}`, 'span'));
            }
            r.append(k, v);
            rows.push(r);
        }
        gProf.style.display = rows.length ? '' : 'none';
        if (rows.length) gProf.append(textEl('hd-sec', nxT('profile')), ...rows);
    };

    const drawInt = () => {
        const inf = isPlain(b.inf) ? b.inf : null;
        gInt.replaceChildren();
        const bc = site === 'bc';
        const list = bc ? (Array.isArray(b.ptags) ? b.ptags : []) : (inf?.it || []);
        gInt.style.display = list.length ? '' : 'none';
        if (!list.length) return;
        const chips = mk('div', 'in-chips');
        list.forEach(x => chips.append(textEl('in-chip', bc ? String(x) : (ruT(SCI_RU.it[x], null) || sciHuman(x, '')), 'span')));
        gInt.append(textEl('hd-sec', nxT('interests')), chips);
    };

    const drawNicks = () => {
        tl.replaceChildren();
        const fromSite = new Set(Array.isArray(b.alte) ? b.alte : []);
        const rowOf = (nick, cur) => {
            const k = nick.toLowerCase();
            const r = mk('div', 'in-n' + (cur ? ' cur' : ''));
            const t = mk('div', 'in-t');
            const when = cur ? nxT('current')
                : `${fromSite.has(k) ? nxT('fromsite') : nxT('added')} ${fmtD(isPlain(b.altd) ? b.altd[k] : 0)}`;
            t.append(textEl('in-tn', nick, 'span'), textEl('in-td', `${when} · ${foundOf(nick)}`, 'span'));
            r.append(t);
            if (!cur) {
                const x = mk('button', 'in-x');
                x.type = 'button';
                x.textContent = '×';
                x.title = L().bm_del;
                x.onclick = e => {
                    e.stopPropagation();
                    b.alts = alts().filter(v => v !== nick);
                    if (!b.alts.length) { delete b.alts; delete b.altd; delete b.alte; }
                    else {
                        if (isPlain(b.altd)) { const d = { ...b.altd }; delete d[k]; b.altd = d; }
                        if (Array.isArray(b.alte)) b.alte = b.alte.filter(v => v !== k);
                    }
                    save();
                    drawNicks();
                    drawFound();
                };
                r.append(x);
            }
            return r;
        };
        alts().forEach(n => tl.append(rowOf(n, false)));
        tl.append(rowOf(b.name, true));
    };

    const drawFound = () => {
        const nicks = [b.name, ...alts()];
        const cell = (tmpl, n) => {
            const url = engineUrl(tmpl, n), v = res[url];
            if (v === 'ok') return `<td><a href="${escHTML(url)}" target="_blank" rel="noopener">✓</a></td>`;
            if (v === 'wait') return '<td><span class="in-wt">…</span></td>';
            return '<td><span class="in-no">—</span></td>';
        };
        const tb = mk('table', 'in-tb');
        const qcell = (tmpl, n) => `<td><a class="in-q" href="${escHTML(engineUrl(tmpl, n))}" target="_blank" rel="noopener" title="${escHTML(nxT('q_tip'))}">?</a></td>`;
        tb.innerHTML = `<tr><th></th>${nicks.map(n => `<th title="${escHTML(n)}">${escHTML(n)}</th>`).join('')}</tr>`
            + sites.map(([txt, tmpl]) => `<tr><td>${escHTML(txt)}</td>${nicks.map(n => cell(tmpl, n)).join('')}</tr>`).join('')
            + manual.map(([txt, tmpl]) => `<tr><td>${escHTML(txt)}</td>${nicks.map(n => qcell(tmpl, n)).join('')}</tr>`).join('');
        const hint = manual.length ? [textEl('in-qh', nxT('q_hint'))] : [];
        tw.replaceChildren(tb, ...hint);
    };

    const drawAll = () => { drawHead(); drawProf(); drawInt(); drawNicks(); drawFound(); };

    const load = async () => {
        if (!['sc', 'bc', 'cb'].includes(site) || loading) return;
        loading = true;
        failed = false;
        drawHead();
        const sig = ac.signal;
        if (site === 'bc' || site === 'cb') {
            const ex = await (site === 'bc' ? bcInfoFetch : cbInfoFetch)(b, sig);
            if (sig.aborted) return;
            loading = false;
            if (ex) { applyProfile(b, ex); save(); } else failed = true;
            drawAll();
            return;
        }
        const u = await sciFetch(b, sig);
        if (sig.aborted) return;
        loading = false;
        if (u) {
            applyInfo(b, sciPick(u));
            save();
            [b.name, ...alts()].forEach(probe);
        } else failed = true;
        drawAll();
    };

    const submit = () => {
        const next = cleanAlts([...alts(), ...inp.value.split(/[,;\n]+/)], b.name);
        const fresh = next.filter(n => !alts().includes(n));
        if (!fresh.length) { inp.focus(); return; }
        fresh.forEach(n => addAlt(b, n, false));
        inp.value = '';
        save();
        drawNicks();
        fresh.forEach(probe);
        drawFound();
    };
    btnAdd.onclick = e => { e.stopPropagation(); submit(); };
    inp.onkeydown = e => { e.stopPropagation(); if (e.key === 'Enter') { e.preventDefault(); submit(); } };
    inp.onkeyup = e => e.stopPropagation();
    btnRef.onclick = e => { e.stopPropagation(); load(); };
    btnAgain.onclick = e => {
        e.stopPropagation();
        ac.abort();
        ac = new AbortController();
        Object.keys(res).forEach(k => delete res[k]);
        qn = 0;
        [b.name, ...alts()].forEach(probe);
        drawFound();
        drawNicks();
    };

    drawAll();
    [b.name, ...alts()].forEach(probe);
    noFade(bmo?.overlay, overlay);
    bmo?.overlay?.classList.remove('open');
    overlay.classList.add('open');
    if (site === 'sc' && (!isPlain(b.inf) || Date.now() - (b.inf.t || 0) > INF_FRESH_MS)) load();
    else if ((site === 'bc' || site === 'cb') && (!isPlain(b.bp) || Date.now() - (b.bp.t || 0) > INF_FRESH_MS)) load();
};

const FRAME_CSS = `.fr-ov {
  pointer-events: auto;
  position: fixed;
  inset: 0;
  z-index: 2147483647;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(0,0,0,.8);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px)
}
.fr-card {
  max-width: 100%;
  max-height: 100%;
  display: flex;
  align-items: center;
  justify-content: center
}
.fr-img {
  max-width: 100%;
  max-height: 90vh;
  border-radius: 3px;
  box-shadow: 0 18px 50px rgba(0,0,0,.6);
  display: block
}
.fr-spd {
  position: absolute;
  left: 50%;
  bottom: calc(18px + env(safe-area-inset-bottom,0px));
  transform: translateX(-50%);
  min-width: 64px;
  padding: 10px 18px;
  font: bold 15px/1 system-ui,sans-serif;
  color: #eee;
  background: rgba(28,28,32,.92);
  border: 1px solid rgba(255,255,255,.18);
  border-radius: 12px;
  cursor: pointer
}
.fr-msg {
  color: #eee;
  font-size: 15px;
  text-align: center;
  padding: 18px 22px;
  border-radius: 12px;
  background: #1c1c20;
  border: 1px solid rgba(255,255,255,.1)
}`;

const loadFrame = async (url, gmFirst) => {
    if (gmFirst) { const g = await gmImgData(url, true).catch(() => ''); if (g) return g; }
    const ok = await new Promise(res => {
        const t = new Image();
        t.onload = () => res(true);
        t.onerror = () => res(false);
        t.src = url;
    });
    return ok ? url : gmImgData(url, true).catch(() => '');
};

const frameShowAvatar = async (b, fv) => {
    const list = [toHttps(b.avatar), toHttps(histLoad().m[bmKey(b.url)]?.a)].filter(Boolean);
    for (const u of list) {
        const d = await loadFrame(u);
        if (fv.closed) return;
        if (d) {
            fv.img.style.cssText = 'width:min(100%,560px);height:auto;object-fit:contain';
            fv.img.src = d;
            fv.card.replaceChildren(fv.img);
            return;
        }
    }
    fv.msg.textContent = L().fr_none;
    fv.card.replaceChildren(fv.msg);
};

const PREV_SPD = [1000, 2000, 500, 200, 0];
const prevSpd = () => { const v = Store.get('bc_prev_spd', 1000); return PREV_SPD.includes(v) ? v : 1000; };
const prevSpdNext = () => PREV_SPD[(PREV_SPD.indexOf(prevSpd()) + 1) % PREV_SPD.length];
const prevSpdTitle = () => prevSpd() ? L().st_prev_spd : `${L().st_prev_spd}: ${L().st_prev_off}`;
const prevSpdLabel = v => v ? `${(v / 1000).toFixed(1)}s` : '0';
const PREV_SPD_SEG = [200, 500, 1000, 2000];
const buildSpdSeg = onPick => {
    const seg = mk('div', 'bm-spdseg');
    const btns = PREV_SPD_SEG.map(v => {
        const b = mk('button', 'bm-spdopt');
        b.type = 'button';
        b.textContent = prevSpdLabel(v);
        b.onclick = () => { Store.set('bc_prev_spd', v); onPick(); };
        seg.append(b);
        return [v, b];
    });
    return { seg, btns };
};
const paintPrev = (btn, seg, btns, en) => {
    btn.classList.toggle('on', en);
    btn.innerHTML = en ? icons.eye : icons.eyeOff;
    btn.title = L().st_prev;
    seg.style.opacity = en ? '' : '.4';
    seg.style.pointerEvents = en ? '' : 'none';
    seg.title = prevSpdTitle();
    btns.forEach(([v, b]) => { b.classList.toggle('on', prevSpd() === v); b.disabled = !en; });
};

const frameLive = (b, fv, frame, onFirstFail) => {
    const tick = async () => {
        if (fv.closed || fv.busy) return;
        fv.busy = true;
        clearTimeout(fv.timer);
        const url = frameBust(frame);
        const d = await loadFrame(url, !!b._lastData);
        fv.busy = false;
        if (fv.closed) return;
        if (d) {
            fv.img.src = d;
            if (d === url) { b._lastImg = url; b._lastData = ''; } else b._lastData = d;
        } else if (!fv.img.isConnected) {
            return onFirstFail();
        }
        const sp = prevSpd();
        if (sp) fv.timer = setTimeout(tick, sp);
    };
    fv.tick = tick;
    tick();
};

const frameViaProfile = async (b, fv) => {
    const fail = () => { if (!fv.closed) frameShowAvatar(b, fv); };
    const h = await requestProfile(b);
    if (fv.closed) return;
    const info = h ? parseProfile(h, urlSite(b.url), modelSlug(b.url)) : null;
    const fr = info && info.status ? info.frame : '';
    if (!fr) return fail();
    frameLive(b, fv, fr, fail);
};

const openFrameView = (ui, b) => {
    const shadow = ui.shadow;
    injectShadowCss(shadow, '_frCss', FRAME_CSS);
    const ov = mk('div', 'fr-ov');
    const card = mk('div', 'fr-card');
    const msg = textEl('fr-msg', '…');
    const img = mk('img', 'fr-img');
    const fv = { closed: false, timer: 0, img, card, msg };
    const spdBtn = mk('button', 'fr-spd');
    const paintSpd = () => { spdBtn.textContent = prevSpdLabel(prevSpd()); spdBtn.title = prevSpdTitle(); };
    paintSpd();
    spdBtn.onclick = e => {
        e.stopPropagation();
        Store.set('bc_prev_spd', prevSpdNext());
        paintSpd();
        clearTimeout(fv.timer);
        if (prevSpd() && fv.tick) fv.tick();
    };
    img.onload = () => { if (!fv.closed && !img.isConnected) card.replaceChildren(img); };
    const unbindEsc = bindEscape(() => close());
    const close = () => { fv.closed = true; clearTimeout(fv.timer); ov.remove(); unbindEsc(); };
    ov.onclick = close;
    card.append(msg);
    ov.append(card, spdBtn);
    shadow.appendChild(ov);
    if (b.frame && (b.status ?? 0) !== 0) {
        const cached = b._lastData || b._lastImg;
        if (cached) img.src = cached;
        frameLive(b, fv, b.frame, () => frameViaProfile(b, fv));
    } else frameViaProfile(b, fv);
};

const BM_HLS = new Map();
const copyHls = (u, cb) => u ? Helpers.copyCb(u, cb) : cb(false);
const shareHls = (u, title, cb) => (u && navigator.share) ? navigator.share({ title, url: u }).catch(() => {}) : cb(false);

const largestVideo = () => {
    let best = null, max = 0;
    for (const cur of document.querySelectorAll('video')) {
        const area = (cur.videoWidth * cur.videoHeight) || (cur.offsetWidth * cur.offsetHeight) || 0;
        if (area > max) { max = area; best = cur; }
    }
    return best;
};
const selectorVideo = () => currentSite.videoSelectors.map(s => safeQuery(s)).find(el => el?.tagName === 'VIDEO');
const safeClosest = (el, sel) => { try { return el.closest(sel); } catch { return null; } };
const safeQuery = sel => { try { return document.querySelector(sel); } catch { return null; } };
const isQualityShown = q => q.style.display === 'block' || (!q.classList.contains('__hidden') && q.offsetWidth > 0);
const jq = () => window.unsafeWindow?.$ || window.$ || window.jQuery;
const fsActive = () => document.fullscreenElement || document.webkitFullscreenElement;
const wheelVol = e => (e.deltaY < 0 ? 0.05 : -0.05);
const fsRequest = el => (el.requestFullscreen || el.webkitRequestFullscreen)?.call(el);
const fsExit = () => (document.exitFullscreen || document.webkitExitFullscreen)?.call(document);
const containerOf = v => currentSite.containerSelectors.map(s => safeClosest(v, s)).find(Boolean) || v.parentElement;
const isTyping = e => {
    try { const t = e.composedPath()[0]; return !!(t?.matches?.('input, textarea, select') || t?.isContentEditable); } catch { return false; }
};
const volIcon = (vol, muted) => muted ? icons.mute : vol < 0.4 ? icons.volLow : vol < 0.75 ? icons.volMed : icons.vol;
const VOL_STEP = { ArrowUp: 0.1, ArrowDown: -0.1 };
const VJS_UI = '.vjs-control-bar,.vjs-control,.vjs-button,.vjs-menu,.vjs-menu-item,.vjs-menu-content,[role="menu"],[role="menuitem"],[role="button"],[role="slider"],button,a';

class VideoController {
    constructor(uiController) {
        this.ui = uiController;
        this.cachedVideo = null;
        this.lastVolume = 1;
        this.isTheater = false;
        this.theaterWrap = null;
        this._anc = null;
        this.bindEvents();
        this.bindGlobalTap();
        mkStyle(document.head || document.documentElement, 'video.bc-blur-mute{filter:blur(24px)!important;transition:filter .2s}', 'bc-blur-mute-css');
        this._findTimer = setInterval(() => { if (!realHidden()) { this.findVideo(); this.applyBlur(this.cachedVideo); } }, 2000);
        onPageEvent('visibilitychange', () => { if (!realHidden()) this.findVideo(); });
    }

    bindEvents() {
        const actions = {
            KeyM: () => this.toggleMute(),
            KeyF: () => this.toggleFullscreen(),
            KeyP: () => this.cachedVideo && this.togglePiP(),
            KeyT: () => this.toggleTheater(),
            KeyS: () => this.takeScreenshot(),
            KeyH: () => this.ui.toggleHide(!AppState.isHidden)
        };
        const hasMod = e => e.ctrlKey || e.altKey || e.metaKey;
        window.addEventListener('keydown', e => {
            if (isTyping(e) || hasMod(e)) return;
            const act = actions[e.code];
            if (act) { e.preventDefault(); if (!e.repeat) act(); return; }
            const step = VOL_STEP[e.code];
            if (e.shiftKey && step && this.cachedVideo) {
                e.preventDefault();
                this.setVolume(this.cachedVideo.volume + step);
            }
        });
    }

    findVideo() {
        const cv0 = this.cachedVideo;
        if (cv0?.isConnected) {
            if (cv0.videoWidth || cv0.offsetWidth) return;
            const alt = largestVideo();
            if (!alt || alt === cv0 || !(alt.videoWidth || alt.offsetWidth)) return;
        }
        if (this.cachedVideo) {
            this.cachedVideo.classList.remove('bc-blur-mute');
            if (this.isTheater) this.toggleTheater();
            this.cachedVideo = this.theaterWrap = null;
        }
        const v = largestVideo() || selectorVideo() || null;
        if (v !== this.cachedVideo) {
            this.cachedVideo = v;
            if (v) { this.attachInteractions(v); AudioFx.watch(v); }
        }
    }

    attachInteractions(v) {
        if (v._bcBound) return;
        v._bcBound = true;

        this._vcEvts = this._vcEvts || (e => {
            const cv = this.cachedVideo;
            if (e?.type === 'volumechange' && AppState.gmute && cv && (!cv.muted || cv.volume > 0)) {
                hardMute(cv);
            }
            this.syncUI();
        });
        ['volumechange', 'pause', 'play'].forEach(ev => v.addEventListener(ev, this._vcEvts, { passive: true }));
        v.addEventListener('enterpictureinpicture', () => this.pipPoster(v, true));
        v.addEventListener('leavepictureinpicture', () => this.pipPoster(v, false));

        if (AppState.gmute) hardMute(v);

        if (SITE !== 'cb') {
            v.style.setProperty('z-index', '9999', 'important');
            v.parentElement?.style.setProperty('z-index', '9999', 'important');
        }
        const target = containerOf(v);
        if (target && !target._bcTouchBound) {
            target._bcTouchBound = true;
            this.bindWheel(target);
            this.bindDoubleClick(target);
        }
        this.syncUI();
    }

    bindWheel(target) {
        let pending = false;
        target.addEventListener('wheel', e => {
            if (safeClosest(e.target, '.vjs-control-bar, .vjs-menu, [role="slider"], [role="menu"]')) return;
            if (!this.isTheater && !fsActive() && !e.shiftKey) return;
            e.preventDefault();
            if (pending) return;
            pending = true;
            requestAnimationFrame(() => {
                pending = false;
                const cv = this.cachedVideo;
                if (cv) this.setVolume(cv.volume + wheelVol(e));
            });
        }, { passive: false });
    }

    bindDoubleClick(target) {
        let clicks = 0, timer = null, sX = 0, sY = 0;
        target.addEventListener('pointerdown', e => { sX = e.clientX; sY = e.clientY; }, { passive: true, capture: true });
        target.addEventListener('click', e => {
            if (Date.now() - (this._tapAt || 0) < 600) { swallow(e); return; }
            if (safeClosest(e.target, VJS_UI)) return;
            if (Math.abs(e.clientX - sX) > 10 || Math.abs(e.clientY - sY) > 10) return;
            if (++clicks === 1) { timer = setTimeout(() => { clicks = 0; }, 250); return; }
            swallow(e);
            clearTimeout(timer);
            clicks = 0;
            this.toggleFullscreen();
        }, true);
    }

    bindGlobalTap() {
        let dX = 0, dY = 0, dT = 0, lastT = 0, lastX = 0, lastY = 0;
        const inUi = e => { try { return e.composedPath().some(n => n && (n.id === 'bc-unchained-host' || n.id === 'bc-cb-qual')); } catch { return false; } };
        const box = () => { const v = this.cachedVideo; return v && v.isConnected ? (containerOf(v) || v) : null; };
        const smallControl = (el, r) => {
            const c = safeClosest(el, VJS_UI);
            if (!c) return false;
            const cr = c.getBoundingClientRect();
            return cr.width * cr.height < r.width * r.height * 0.5;
        };
        window.addEventListener('pointerdown', e => {
            if (e.pointerType === 'mouse') return;
            dX = e.clientX; dY = e.clientY; dT = Date.now();
        }, { capture: true, passive: true });
        window.addEventListener('pointerup', e => {
            if (e.pointerType === 'mouse' || !e.isPrimary || inUi(e)) return;
            const now = Date.now();
            if (now - dT > 350 || Math.abs(e.clientX - dX) > 10 || Math.abs(e.clientY - dY) > 10) { lastT = 0; return; }
            const b = box();
            if (!b) { lastT = 0; return; }
            const r = b.getBoundingClientRect();
            if (r.width < 1 || e.clientX < r.left || e.clientX > r.right || e.clientY < r.top || e.clientY > r.bottom || smallControl(e.target, r)) { lastT = 0; return; }
            if (lastT && now - lastT < 350 && Math.abs(e.clientX - lastX) < 40 && Math.abs(e.clientY - lastY) < 40) {
                lastT = 0;
                this._tapAt = now;
                this.toggleFullscreen();
                return;
            }
            lastT = now; lastX = e.clientX; lastY = e.clientY;
        }, { capture: true, passive: true });
    }

    setVolume(val) {
        const v = this.cachedVideo;
        if (!v) return;
        val = clamp(val, 0, 1);
        if (val > 0 && AppState.gmute) setGMute(false);
        v.volume = val;
        if (val > 0) {
            v.muted = false;
            this.lastVolume = val;
        }
        this.syncUI();
    }

    clickSiteUnmute() {
        try {
            const sb = document.querySelector('.stream_muted_button');
            if (sb && sb.offsetParent !== null) sb.click();
        } catch (e) { dbg(e, 'VideoController.clickSiteUnmute'); }
    }

    toggleMute() {
        const v = this.cachedVideo;
        if (!v) return;
        if (v.muted || v.volume === 0) this.unmuteVideo(v);
        else this.muteVideo(v);
        this.syncUI();
    }

    unmuteVideo(v) {
        if (AppState.gmute) setGMute(false);
        this.clickSiteUnmute();
        v.muted = false;
        v.volume = this.lastVolume || 1;
    }

    muteVideo(v) {
        v.muted = true;
        this.lastVolume = v.volume || 1;
    }

    pipPoster(v, on) {
        try {
            if (on) {
                if (v.hasAttribute('poster') && !v._bcPoster) return;
                if (!v.videoWidth || !v.videoHeight) return;
                const w = 320, h = Math.max(1, Math.round(w * v.videoHeight / v.videoWidth));
                const c = document.createElement('canvas');
                c.width = w; c.height = h;
                c.getContext('2d').drawImage(v, 0, 0, w, h);
                const url = c.toDataURL('image/jpeg', 0.7);
                if (url && url.length > 100) { v._bcPoster = true; v.setAttribute('poster', url); }
            } else if (v._bcPoster) {
                v._bcPoster = false;
                v.removeAttribute('poster');
            }
        } catch (e) { dbg(e, 'VideoController.pipPoster'); }
    }

    async togglePiP() {
        const v = this.cachedVideo;
        if (!v) return;
        try {
            if (document.pictureInPictureElement) await document.exitPictureInPicture();
            else if (document.pictureInPictureEnabled && v.requestPictureInPicture) {
                v.removeAttribute('disablePictureInPicture');
                this.pipPoster(v, true);
                await v.requestPictureInPicture();
            } else this.ui.flash(this.ui.elements.bPip, 'ERR');
            this.ui?.elements?.bPip?.classList.toggle('on', !!document.pictureInPictureElement);
        } catch { this.ui.flash(this.ui.elements.bPip, 'ERR'); }
    }

    toggleFullscreen() {
        const t = currentSite.containerSelectors.map(s => safeQuery(s)).find(Boolean) || (this.cachedVideo && containerOf(this.cachedVideo)) || this.cachedVideo;
        if (!t) return;
        try { fsActive() ? fsExit() : fsRequest(t); } catch (e) { dbg(e, 'VideoController.toggleFullscreen'); }
    }

    enterTheater(v) {
        this.theaterWrap = containerOf(v);
        this.theaterWrap?.classList.add('bc-thtr-wrap');
        v.classList.add('bc-thtr-video');
        this._anc = [];
        for (let e = v.parentElement; e && !isRootEl(e); e = e.parentElement) {
            e.classList.add('bc-thtr-anc');
            this._anc.push(e);
        }
        window.scrollTo(0, 0);
    }

    leaveTheater(v) {
        this.theaterWrap?.classList.remove('bc-thtr-wrap');
        v?.classList.remove('bc-thtr-video');
        this._anc?.forEach(e => e.classList.remove('bc-thtr-anc'));
        this._anc = null;
        this.theaterWrap = null;
    }

    cbTheaterControls(on) {
        const id = 'bc-cb-controls-theater';
        document.getElementById(id)?.remove();
        if (!on || SITE !== 'cb') return;
        mkStyle(document.head || document.documentElement, '#mobileVideoControls,.MobileVideoControls{position:fixed!important;top:0!important;left:0!important;width:100vw!important;height:100vh!important;height:100dvh!important;z-index:2147483645!important}', id);
    }

    toggleTheater() {
        const v = this.cachedVideo, on = !this.isTheater;
        if (on && !v) return;
        this.isTheater = on;
        this.ui?.elements?.bThtr?.classList.toggle('on', on);
        document.body.classList.toggle('bc-theater-mode', on);
        document.documentElement.classList.toggle('bc-theater-html', on);
        on ? this.enterTheater(v) : this.leaveTheater(v);
        this.cbTheaterControls(on);
        window.dispatchEvent(new Event('resize'));
    }

    takeScreenshot() {
        const v = this.cachedVideo;
        if (!v) return;
        const b = this.ui.elements.bShot;
        if (b) {
            b.classList.remove('snap-go');
            void b.offsetWidth;
            b.classList.add('snap-go');
            clearTimeout(b._snapT);
            b._snapT = setTimeout(() => b.classList.remove('snap-go'), 650);
        }
        Helpers.snap(v, currentSite.getName(), ok => this.ui.flash(b, ok ? 'OK' : 'ERR'));
    }

    applyBlur(v) {
        if (!v || !v.isConnected) return;
        v.classList.toggle('bc-blur-mute', AppState.blurMute && (v.muted || v.volume === 0));
    }

    paintMuteBtn(btn, muted, gMute) {
        const sub = btn.querySelector('sub');
        if (sub) sub.textContent = gMute ? (L().lock || 'LOCK') : L().sound;
        btn.firstChild.innerHTML = gMute ? icons.muteLock : muted ? icons.mute : icons.vol;
        btn.classList.toggle('on', muted);
        const st = btn.style;
        if (gMute) {
            st.setProperty('background', 'color-mix(in srgb,var(--th) 22%,#000)', 'important');
            st.setProperty('border-color', 'var(--th)', 'important');
            st.setProperty('box-shadow', 'inset 0 0 0 1.5px var(--th)', 'important');
        } else {
            st.removeProperty('background');
            st.removeProperty('border-color');
            st.removeProperty('box-shadow');
        }
    }

    syncUI() {
        const v = this.cachedVideo, ui = this.ui, el = ui.elements;
        if (!v) return;
        this.applyBlur(v);
        if (!el.bMute) return;
        const vol = v.muted ? 0 : v.volume;
        const muted = v.muted || vol === 0;
        const gMute = AppState.gmute;
        const key = `${vol}|${muted}|${gMute}`;
        if (ui._volKey === key) return;
        ui._volKey = key;

        this.paintMuteBtn(el.bMute, muted, gMute);
        el.vSlider.value = String(vol);
        el.vSlider.style.setProperty('--p', `${Math.round(vol * 100)}%`);
        el.vIco.innerHTML = volIcon(vol, muted);
        el.vVal.textContent = `${Math.round(vol * 100)}%`;
    }
}

const WEBM_HEAD = 8192;
const vintReader = head => (p, raw) => {
    const f = head[p];
    if (!f) return null;
    const len = Math.clz32(f) - 23;
    if (len > 8 || p + len > head.length) return null;
    const mask = 0xFF >> len;
    let v = raw ? f : (f & mask), ones = (f & mask) === mask;
    for (let i = 1; i < len; i++) { v = v * 256 + head[p + i]; if (head[p + i] !== 0xFF) ones = false; }
    return { v, len, unknown: ones };
};
const webmSegmentStart = rd => {
    const ebml = rd(0, true);
    if (!ebml || ebml.v !== 0x1A45DFA3) return -1;
    const es = rd(ebml.len, false);
    if (!es) return -1;
    const p = ebml.len + es.len + es.v;
    const seg = rd(p, true);
    if (!seg || seg.v !== 0x18538067) return -1;
    const ss = rd(p + seg.len, false);
    return ss?.unknown ? p + seg.len + ss.len : -1;
};
const webmInfoScale = (rd, head, ds, de) => {
    let q = ds, scale = 1e6;
    while (q < de) {
        const cid = rd(q, true);
        const csz = cid && rd(q + cid.len, false);
        if (!csz || cid.v === 0x4489) return null;
        const cs = q + cid.len + csz.len;
        if (cid.v === 0x2AD7B1) { scale = 0; for (let i = 0; i < csz.v; i++) scale = scale * 256 + head[cs + i]; }
        q = cs + csz.v;
    }
    return scale;
};
const webmWithDuration = (blob, head, p, idLen, ds, de, durationMs, scale) => {
    const dur = new Uint8Array(11);
    dur.set([0x44, 0x89, 0x88]);
    new DataView(dur.buffer).setFloat64(3, durationMs * 1e6 / scale);
    const size8 = new Uint8Array(8);
    size8[0] = 0x01;
    new DataView(size8.buffer).setUint32(4, (de - ds) + dur.length);
    return new Blob([
        head.subarray(0, p), head.subarray(p, p + idLen), size8,
        head.subarray(ds, de), dur, head.subarray(de),
        blob.slice(WEBM_HEAD)
    ], { type: blob.type });
};
const webmElement = (rd, p) => {
    const id = rd(p, true);
    const sz = id && rd(p + id.len, false);
    if (!sz || sz.unknown) return null;
    const ds = p + id.len + sz.len;
    return { id, ds, de: ds + sz.v };
};
const fixWebmDuration = async (blob, durationMs) => {
    const head = new Uint8Array(await blob.slice(0, WEBM_HEAD).arrayBuffer());
    const rd = vintReader(head);
    let p = webmSegmentStart(rd);
    while (p >= 0 && p < head.length) {
        const el = webmElement(rd, p);
        if (!el) break;
        if (el.id.v === 0x114D9B74) return blob;
        if (el.id.v === 0x1549A966) {
            const scale = el.de > head.length ? null : webmInfoScale(rd, head, el.ds, el.de);
            return scale === null ? blob : webmWithDuration(blob, head, p, el.id.len, el.ds, el.de, durationMs, scale);
        }
        p = el.de;
    }
    return blob;
};

const u8cat = parts => {
    let n = 0;
    for (const p of parts) n += p.length;
    const o = new Uint8Array(n);
    let q = 0;
    for (const p of parts) { o.set(p, q); q += p.length; }
    return o;
};
const u8ascii = s => Uint8Array.from(s, c => c.charCodeAt(0));
const box = (type, ...parts) => {
const body = u8cat(parts), o = new Uint8Array(8 + body.length);
new DataView(o.buffer).setUint32(0, o.length);
o.set(u8ascii(type), 4);
o.set(body, 8);
return o;
};
const fullBox = (type, ver, flags, ...parts) => box(type, Uint8Array.of(ver, (flags >> 16) & 255, (flags >> 8) & 255, flags & 255), ...parts);

const MediaFix = (() => {
    const rd32 = (b, p) => ((b[p] << 24) | (b[p + 1] << 16) | (b[p + 2] << 8) | b[p + 3]) >>> 0;
    const rd64 = (b, p) => rd32(b, p) * 4294967296 + rd32(b, p + 4);
    const tag = (b, p) => String.fromCharCode(b[p], b[p + 1], b[p + 2], b[p + 3]);
    const w32 = n => { const o = new Uint8Array(4); new DataView(o.buffer).setUint32(0, n >>> 0); return o; };
    const w64 = n => { const o = new Uint8Array(8), dv = new DataView(o.buffer); dv.setUint32(0, Math.floor(n / 4294967296)); dv.setUint32(4, n >>> 0); return o; };
    const words = (list, per) => {
        const o = new Uint8Array(list.length * per * 4), dv = new DataView(o.buffer);
        let q = 0;
        for (const e of list) for (let i = 0; i < per; i++, q += 4) dv.setUint32(q, (per === 1 ? e : e[i]) >>> 0);
        return o;
    };
    const rle = arr => {
        const out = [];
        for (const v of arr) {
            const l = out[out.length - 1];
            if (l && l[0] === v) l[1]++; else out.push([v, 1]);
        }
        return out;
    };
    const kids = (b, s, e) => {
        const out = [];
        let p = s;
        while (p + 8 <= e) {
            let sz = rd32(b, p), hdr = 8;
            if (sz === 1) { sz = rd64(b, p + 8); hdr = 16; } else if (sz === 0) sz = e - p;
            if (sz < hdr || p + sz > e) break;
            out.push({ t: tag(b, p + 4), s: p, e: p + sz, d: p + hdr });
            p += sz;
        }
        return out;
    };
    const kid = (list, t) => list.find(x => x.t === t);
    const setDur = (copy, off, v1, value) => {
        const dv = new DataView(copy.buffer);
        if (v1) { dv.setUint32(off, Math.floor(value / 4294967296)); dv.setUint32(off + 4, value >>> 0); } else dv.setUint32(off, value >>> 0);
    };

    const req = (msg, ...xs) => { if (xs.some(x => !x)) throw new Error(msg); };
    const slice = (moov, c) => moov.slice(c.s, c.e);
    const rest = (moov, list, skip) => list.filter(c => !skip.includes(c.t)).map(c => slice(moov, c));
    const patchDur = (moov, c, off, v1, val) => { const cp = slice(moov, c); setDur(cp, off - c.s, v1, val); return cp; };
    const rleBox = (type, ver, arr) => {
        const r = rle(arr);
        return fullBox(type, ver, 0, words([r.length], 1), words(r.map(([v, c]) => [c, v]), 2));
    };

    const boxHead = (b, p, avail) => {
        let sz = rd32(b, p), hdr = 8;
        if (sz === 1) { sz = rd64(b, p + 8); hdr = 16; } else if (sz === 0) sz = avail;
        return sz < hdr ? null : { sz, hdr };
    };
    const scanTop = async (blob, size) => {
        const top = [];
        for (let p = 0; p + 8 <= size;) {
            const h = new Uint8Array(await blob.slice(p, Math.min(size, p + 16)).arrayBuffer());
            const bh = boxHead(h, 0, size - p);
            if (!bh || p + bh.sz > size) break;
            top.push({ t: tag(h, 4), s: p, e: p + bh.sz, d: p + bh.hdr });
            p += bh.sz;
        }
        return top;
    };
    const readTrex = (moov, mk) => {
        const trex = {};
        const mvex = kid(mk, 'mvex');
        if (!mvex) return trex;
        for (const c of kids(moov, mvex.d, mvex.e).filter(x => x.t === 'trex')) {
            trex[rd32(moov, c.d + 4)] = { dur: rd32(moov, c.d + 12), size: rd32(moov, c.d + 16), flags: rd32(moov, c.d + 20) };
        }
        return trex;
    };
    const parseTrak = (moov, trak, trex) => {
        const tk = kids(moov, trak.d, trak.e);
        const tkhd = kid(tk, 'tkhd'), mdia = kid(tk, 'mdia');
        req('bad trak', tkhd, mdia);
        const tv1 = moov[tkhd.d] === 1, idOff = tkhd.d + 4 + (tv1 ? 16 : 8);
        const id = rd32(moov, idOff);
        const mk2 = kids(moov, mdia.d, mdia.e);
        const mdhd = kid(mk2, 'mdhd'), minf = kid(mk2, 'minf');
        req('bad mdia', mdhd, minf);
        const dv1 = moov[mdhd.d] === 1, tsOff = mdhd.d + 4 + (dv1 ? 16 : 8);
        const mi = kids(moov, minf.d, minf.e), stbl = kid(mi, 'stbl');
        const stsd = stbl && kid(kids(moov, stbl.d, stbl.e), 'stsd');
        req('no stsd', stbl, stsd);
        return {
            id, trak, tk, tkhd, tv1, durOffT: idOff + 8, mdia, mk2, mdhd, dv1, ts: rd32(moov, tsOff), durOffM: tsOff + 4,
            minf, mi, stbl, stsd: moov.slice(stsd.s, stsd.e),
            dur: [], sz: [], sync: [], cto: [], chunks: [], dts: 0, start: 0, negCto: false,
            trex: trex[id] || { dur: 0, size: 0, flags: 0 }
        };
    };
    const parseTracks = (moov, mk, trex) => {
        const order = mk.filter(x => x.t === 'trak').map(trak => parseTrak(moov, trak, trex));
        if (!order.length) throw new Error('no tracks');
        const tracks = {};
        for (const tr of order) tracks[tr.id] = tr;
        return { tracks, order };
    };

    const TFHD_DEFAULTS = [[0x08, 'dur'], [0x10, 'size'], [0x20, 'flags']];
    const readTfhd = (b, tfhd, trex) => {
        const fl = rd32(b, tfhd.d) & 0xffffff;
        const th = { fl, baseOff: null, dur: trex.dur, size: trex.size, flags: trex.flags };
        let o = tfhd.d + 8;
        if (fl & 1) { th.baseOff = rd64(b, o); o += 8; }
        if (fl & 2) o += 4;
        for (const [bit, key] of TFHD_DEFAULTS) if (fl & bit) { th[key] = rd32(b, o); o += 4; }
        return th;
    };
    const fragBase = (th, first, moofStart, lastEnd) => {
        if (th.baseOff !== null) return th.baseOff;
        return ((th.fl & 0x20000) || first) ? moofStart : lastEnd;
    };
    const applyTfdt = (b, tf, tr) => {
        const tfdt = kid(tf, 'tfdt');
        if (!tfdt) return;
        const t = b[tfdt.d] === 1 ? rd64(b, tfdt.d + 4) : rd32(b, tfdt.d + 4);
        if (!tr.dur.length) { tr.start = t; tr.dts = t; return; }
        if (t === tr.dts) return;
        const nl = tr.dur[tr.dur.length - 1] + (t - tr.dts);
        if (nl > 0) tr.dur[tr.dur.length - 1] = nl;
        tr.dts = t;
    };
    const TRUN_FIELDS = [[0x100, 'd'], [0x200, 's'], [0x400, 'f']];
    const readSample = (b, q, rf, rv, smp) => {
        for (const [bit, key] of TRUN_FIELDS) if (rf & bit) { smp[key] = rd32(b, q); q += 4; }
        if (rf & 0x800) { smp.c = rv ? (rd32(b, q) | 0) : rd32(b, q); q += 4; }
        return q;
    };
    const readTrun = (b, run, base, nextPos, th, tr) => {
        const rf = rd32(b, run.d) & 0xffffff, rv = b[run.d], n = rd32(b, run.d + 4);
        let q = run.d + 8, pos = nextPos, firstFlags = null;
        if (rf & 1) { pos = base + (rd32(b, q) | 0); q += 4; }
        if (rf & 4) { firstFlags = rd32(b, q); q += 4; }
        let total = 0;
        for (let i = 0; i < n; i++) {
            const smp = { d: th.dur, s: th.size, f: (i === 0 && firstFlags !== null) ? firstFlags : th.flags, c: 0 };
            q = readSample(b, q, rf, rv, smp);
            if (smp.c < 0) tr.negCto = true;
            tr.dur.push(smp.d); tr.sz.push(smp.s); tr.sync.push(!(smp.f & 0x10000)); tr.cto.push(smp.c);
            tr.dts += smp.d; total += smp.s;
        }
        if (n) tr.chunks.push({ pos, n });
        return pos + total;
    };
    const readTraf = (b, traf, tracks, mo, ti, lastEnd) => {
        const tf = kids(b, traf.d, traf.e);
        const tfhd = kid(tf, 'tfhd');
        req('no tfhd', tfhd);
        const tr = tracks[rd32(b, tfhd.d + 4)];
        req('unknown track', tr);
        const th = readTfhd(b, tfhd, tr.trex);
        const base = fragBase(th, ti === 0, mo.s, lastEnd);
        applyTfdt(b, tf, tr);
        let nextPos = base;
        for (const run of tf.filter(x => x.t === 'trun')) {
            nextPos = readTrun(b, run, base, nextPos, th, tr);
            lastEnd = Math.max(lastEnd, nextPos);
        }
        return lastEnd;
    };
    const readMoof = async (blob, mo, tracks) => {
        const b = new Uint8Array(await blob.slice(mo.s, mo.e).arrayBuffer());
        const mf = kids(b, mo.d - mo.s, b.length);
        let lastEnd = 0;
        mf.filter(x => x.t === 'traf').forEach((traf, ti) => { lastEnd = readTraf(b, traf, tracks, mo, ti, lastEnd); });
    };

    const findMdat = (mdats, pos) => {
        let lo = 0, hi = mdats.length - 1;
        while (lo <= hi) {
            const mid = (lo + hi) >> 1;
            if (pos < mdats[mid].d) hi = mid - 1; else if (pos >= mdats[mid].e) lo = mid + 1; else return mid;
        }
        return -1;
    };
    const checkOverrun = (tr, mdats) => {
        let si = 0;
        for (const ch of tr.chunks) {
            let bytes = 0;
            for (let i = 0; i < ch.n; i++) bytes += tr.sz[si + i];
            si += ch.n;
            if (ch.pos + bytes > mdats[ch.k].e) throw new Error('chunk overruns mdat');
        }
    };
    const mapChunks = (order, mdats) => {
        let payload = 0;
        const mdOff = mdats.map(m => { const o = payload; payload += m.e - m.d; return o; });
        for (const tr of order) for (const ch of tr.chunks) {
            ch.k = findMdat(mdats, ch.pos);
            if (ch.k < 0) throw new Error('chunk outside mdat');
        }
        order.forEach(tr => checkOverrun(tr, mdats));
        return { payload, mdOff };
    };

    const stssBox = sync => {
        const idx = [];
        sync.forEach((s, i) => { if (s) idx.push(i + 1); });
        return fullBox('stss', 0, 0, words([idx.length], 1), words(idx, 1));
    };
    const stscBox = chunks => {
        const sc = [];
        let prev = -1;
        chunks.forEach((ch, i) => { if (ch.n !== prev) { sc.push([i + 1, ch.n, 1]); prev = ch.n; } });
        return fullBox('stsc', 0, 0, words([sc.length], 1), words(sc, 3));
    };
    const stcoBox = (tr, mp, payloadStart) => {
        const offs = tr.chunks.map(ch => payloadStart + mp.mdOff[ch.k] + (ch.pos - mp.mdats[ch.k].d));
        return mp.use64
            ? fullBox('co64', 0, 0, words([offs.length], 1), u8cat(offs.map(w64)))
            : fullBox('stco', 0, 0, words([offs.length], 1), words(offs, 1));
    };
    const buildStbl = (tr, mp, payloadStart) => {
        const parts = [tr.stsd, rleBox('stts', 0, tr.dur)];
        if (tr.cto.some(v => v !== 0)) parts.push(rleBox('ctts', tr.negCto ? 1 : 0, tr.cto));
        if (!tr.sync.every(Boolean)) parts.push(stssBox(tr.sync));
        parts.push(stscBox(tr.chunks), fullBox('stsz', 0, 0, w32(0), w32(tr.sz.length), words(tr.sz, 1)), stcoBox(tr, mp, payloadStart));
        return box('stbl', ...parts);
    };
    const buildTrak = (tr, mp, payloadStart) => {
        const { moov, movTs } = mp;
        const sum = tr.dur.reduce((a, v) => a + v, 0);
        const t0 = movTs ? Math.round(tr.start / tr.ts * movTs) : 0;
        const tdur = movTs ? Math.round(sum / tr.ts * movTs) : 0;
        const minf = box('minf', ...rest(moov, tr.mi, ['stbl']), buildStbl(tr, mp, payloadStart));
        const mdia = box('mdia', patchDur(moov, tr.mdhd, tr.durOffM, tr.dv1, sum), ...rest(moov, tr.mk2, ['mdhd', 'minf']), minf);
        const tkhd = patchDur(moov, tr.tkhd, tr.durOffT, tr.tv1, t0 + tdur);
        const edts = t0 > 0
            ? [box('edts', fullBox('elst', 0, 0, w32(2), w32(t0), w32(0xFFFFFFFF), w32(0x00010000), w32(tdur), w32(0), w32(0x00010000)))]
            : [];
        return { trak: box('trak', tkhd, ...edts, ...rest(moov, tr.tk, ['tkhd', 'mdia', 'edts']), mdia), end: t0 + tdur };
    };
    const buildMoov = (mp, payloadStart) => {
        const { moov, mk, mvhd, mv1 } = mp;
        const built = mp.order.map(tr => buildTrak(tr, mp, payloadStart));
        const movDur = built.reduce((m, x) => Math.max(m, x.end), 0);
        const mvh = patchDur(moov, mvhd, mvhd.d + 4 + (mv1 ? 16 : 8) + 4, mv1, movDur);
        return box('moov', mvh, ...rest(moov, mk, ['mvhd', 'trak', 'mvex']), ...built.map(x => x.trak));
    };

    const mp4 = async blob => {
        const size = blob.size;
        const top = await scanTop(blob, size);
        const moofs = top.filter(x => x.t === 'moof'), mdats = top.filter(x => x.t === 'mdat');
        const moovTop = top.find(x => x.t === 'moov');
        if (!moovTop) throw new Error('no moov');
        if (!moofs.length) return blob;
        const moov = new Uint8Array(await blob.slice(moovTop.s, moovTop.e).arrayBuffer());
        const mk = kids(moov, moovTop.d - moovTop.s, moov.length);
        const mvhd = kid(mk, 'mvhd');
        req('no mvhd', mvhd);
        const mv1 = moov[mvhd.d] === 1, movTs = rd32(moov, mvhd.d + 4 + (mv1 ? 16 : 8));
        const { tracks, order } = parseTracks(moov, mk, readTrex(moov, mk));
        for (const mo of moofs) await readMoof(blob, mo, tracks);
        if (order.some(tr => !tr.dur.length)) throw new Error('empty track');

        const { payload, mdOff } = mapChunks(order, mdats);
        const hdr = payload + 8 < 4294967296 ? 8 : 16;
        const use64 = size + moov.length + 4096 > 4294967295;
        const ftyp = box('ftyp', u8ascii('isom'), w32(512), u8ascii('isom'), u8ascii('iso2'), u8ascii('avc1'), u8ascii('mp41'));
        const mp = { moov, mk, mvhd, mv1, movTs, order, mdats, mdOff, use64 };
        const m1 = buildMoov(mp, 0);
        const payloadStart = ftyp.length + m1.length + hdr;
        const m2 = buildMoov(mp, payloadStart);
        if (m2.length !== m1.length) throw new Error('moov size changed');
        const mdatHead = hdr === 8 ? u8cat([w32(payload + 8), u8ascii('mdat')]) : u8cat([w32(1), u8ascii('mdat'), w64(payload + 16)]);
        return new Blob([ftyp, m2, mdatHead, ...mdats.map(m => blob.slice(m.d, m.e))], { type: 'video/mp4' });
    };

    const EBML = { HEADER: 0x1A45DFA3, SEGMENT: 0x18538067, CUES: 0x1C53BB6B, SEEKHEAD: 0x114D9B74, CLUSTER: 0x1F43B675, TRACKS: 0x1654AE6B, TRACK_ENTRY: 0xAE, TRACK_NUM: 0xD7, TRACK_TYPE: 0x83, TC: 0xE7, SBLOCK: 0xA3, GROUP: 0xA0, BLOCK: 0xA1, REFBLOCK: 0xFB };
    const CLUSTER_END = new Set([EBML.CLUSTER, EBML.CUES, 0x1254C367, 0x1043A770]);
    const vintLen = (b, max) => { let l = 1; while (l <= max && !(b & (0x80 >> (l - 1)))) l++; return l; };

    const ebmlReader = blob => {
        const size = blob.size;
        let ws = 0, wb = new Uint8Array(0);
        const need = async (p, n) => {
            if (p + n > size) return false;
            if (p >= ws && p + n <= ws + wb.length) return true;
            ws = p;
            wb = new Uint8Array(await blob.slice(p, Math.min(size, p + Math.max(n, 4194304))).arrayBuffer());
            return true;
        };
        const needAny = async (p, a, b) => (await need(p, a)) || (await need(p, b));
        const at = p => wb[p - ws];
        const uintAt = (p, n) => { let v = 0; for (let i = 0; i < n; i++) v = v * 256 + at(p + i); return v; };
        const readId = p => {
            const l = vintLen(at(p), 4);
            return l > 4 ? null : { len: l, v: uintAt(p, l) };
        };
        const readSize = p => {
            const b = at(p), l = vintLen(b, 8);
            if (l > 8) return null;
            const m = 0xff >> l;
            let v = b & m, ones = v === m;
            for (let i = 1; i < l; i++) { const x = at(p + i); v = v * 256 + x; if (x !== 255) ones = false; }
            return { len: l, v, unk: ones };
        };
        const readEl = async p => {
            if (!(await needAny(p, 24, 2))) return null;
            const id = readId(p);
            if (!id) return null;
            if (!(await needAny(p, id.len + 8, id.len + 1))) return null;
            const sz = readSize(p + id.len);
            return sz && { id: id.v, hdr: id.len + sz.len, size: sz.v, unk: sz.unk, p };
        };
        const blockHead = async pl => {
            if (!(await need(pl, 4))) return null;
            const b0 = at(pl), l = vintLen(b0, 8);
            if (!(await need(pl, l + 3))) return null;
            let tn = b0 & (0xff >> l);
            for (let i = 1; i < l; i++) tn = tn * 256 + at(pl + i);
            const rel = (at(pl + l) << 8) | at(pl + l + 1);
            return { tn, rel: rel > 32767 ? rel - 65536 : rel, flags: at(pl + l + 2) };
        };
        return { size, readEl, uintAt, blockHead };
    };

    const readTrackEntry = async (rd, q, te) => {
        let num = 0, type = 0, r = q + te.hdr;
        const tend = r + te.size;
        while (r < tend) {
            const ce = await rd.readEl(r);
            if (!ce) break;
            if (ce.id === EBML.TRACK_NUM) num = rd.uintAt(r + ce.hdr, ce.size);
            if (ce.id === EBML.TRACK_TYPE) type = rd.uintAt(r + ce.hdr, ce.size);
            r += ce.hdr + ce.size;
        }
        return { num, type };
    };
    const videoTrackIn = async (rd, el) => {
        const end = el.p + el.hdr + el.size;
        let q = el.p + el.hdr, found = 0;
        while (q < end) {
            const te = await rd.readEl(q);
            if (!te) break;
            if (te.id === EBML.TRACK_ENTRY && !found) {
                const { num, type } = await readTrackEntry(rd, q, te);
                if (type === 1) found = num;
            }
            q += te.hdr + te.size;
        }
        return found;
    };
    const headerStop = (el, videoTrack, p) => {
        if (el.id === EBML.CUES || el.id === EBML.SEEKHEAD) return { r: null };
        if (el.id === EBML.CLUSTER) return { r: videoTrack ? { videoTrack, firstCluster: p } : null };
        if (el.unk) throw new Error('unknown size header element');
        return null;
    };
    const scanHeader = async (rd, start) => {
        let p = start, videoTrack = 0;
        while (p < rd.size) {
            const el = await rd.readEl(p);
            if (!el) throw new Error('bad element');
            const stop = headerStop(el, videoTrack, p);
            if (stop) return stop.r;
            if (el.id === EBML.TRACKS) videoTrack ||= await videoTrackIn(rd, el);
            p += el.hdr + el.size;
        }
        return null;
    };

    const readGroup = async (rd, pl, ch) => {
        let r = pl, h = null, ref = false;
        const gend = pl + ch.size;
        while (r < gend) {
            const ge = await rd.readEl(r);
            if (!ge) break;
            if (ge.id === EBML.BLOCK) h = await rd.blockHead(r + ge.hdr);
            else if (ge.id === EBML.REFBLOCK) ref = true;
            r += ge.hdr + ge.size;
        }
        return { h, ref };
    };
    const keyframeRel = async (rd, ch, pl, videoTrack) => {
        if (ch.id === EBML.SBLOCK) {
            const h = await rd.blockHead(pl);
            return h && h.tn === videoTrack && (h.flags & 0x80) ? h.rel : null;
        }
        if (ch.id === EBML.GROUP) {
            const { h, ref } = await readGroup(rd, pl, ch);
            return h && h.tn === videoTrack && !ref ? h.rel : null;
        }
        return null;
    };
    const scanChild = async (rd, ch, pl, st, cp, addCue) => {
        if (ch.id === EBML.TC) { st.tc = rd.uintAt(pl, ch.size); return; }
        const rel = await keyframeRel(rd, ch, pl, st.videoTrack);
        if (rel !== null) addCue(st.tc + rel, cp);
    };
    const scanCluster = async (rd, cl, cp, videoTrack, addCue) => {
        let q = cp + cl.hdr;
        const st = { tc: 0, videoTrack };
        const cend = cl.unk ? rd.size : cp + cl.hdr + cl.size;
        while (q < cend) {
            const ch = await rd.readEl(q);
            if (!ch) return rd.size;
            if (CLUSTER_END.has(ch.id)) break;
            if (ch.unk) throw new Error('unknown size child');
            const pl = q + ch.hdr;
            await scanChild(rd, ch, pl, st, cp, addCue);
            q = pl + ch.size;
        }
        return q;
    };
    const collectCues = async (rd, firstCluster, videoTrack, sds) => {
        const cues = [];
        let lastT = -1e9;
        const addCue = (t, pos) => { if (!cues.length || t - lastT >= 500) { cues.push({ t: Math.max(0, t), pos: pos - sds }); lastT = t; } };
        let cp = firstCluster;
        while (cp < rd.size) {
            const cl = await rd.readEl(cp);
            if (!cl || cl.id !== EBML.CLUSTER) break;
            cp = await scanCluster(rd, cl, cp, videoTrack, addCue);
        }
        return cues;
    };
    const writeCues = (cues, videoTrack, total) => {
        const out = new Uint8Array(total), dv = new DataView(out.buffer);
        out.set([0x1C, 0x53, 0xBB, 0x6B, 0x01], 0);
        dv.setUint32(8, 19 * cues.length);
        let o = 12;
        for (const c of cues) {
            out.set([0xBB, 0x91, 0xB3, 0x84], o); dv.setUint32(o + 4, c.t);
            out.set([0xB7, 0x89, 0xF7, 0x81, videoTrack & 255, 0xF1, 0x84], o + 8); dv.setUint32(o + 15, c.pos + total);
            o += 19;
        }
        return out;
    };

    const intactEnd = async (rd, p) => {
        let ok = p;
        while (p < rd.size) {
            const cl = await rd.readEl(p);
            if (!cl || cl.id !== EBML.CLUSTER) break;
            if (!cl.unk) { const e = p + cl.hdr + cl.size; if (e > rd.size) break; ok = p = e; continue; }
            let q = p + cl.hdr;
            ok = q;
            while (q < rd.size) {
                const ch = await rd.readEl(q);
                if (!ch || CLUSTER_END.has(ch.id) || ch.unk) break;
                const e = q + ch.hdr + ch.size;
                if (e > rd.size) return ok;
                ok = q = e;
            }
            p = q;
        }
        return ok;
    };
    const webm = async blob => {
        const rd = ebmlReader(blob), size = blob.size;
        const eh = await rd.readEl(0);
        if (!eh || eh.id !== EBML.HEADER) throw new Error('no EBML');
        const sp = eh.hdr + eh.size;
        const seg = await rd.readEl(sp);
        if (!seg || seg.id !== EBML.SEGMENT) throw new Error('no segment');
        if (!seg.unk) return blob;
        const sds = sp + seg.hdr;
        const info = await scanHeader(rd, sds);
        if (!info) return blob;
        const { videoTrack, firstCluster } = info;
        const cues = await collectCues(rd, firstCluster, videoTrack, sds);
        if (!cues.length) return blob;
        const total = 12 + 19 * cues.length;
        if (size - sds + total > 4294967295) return blob;
        const out = writeCues(cues, videoTrack, total);
        const cut = await intactEnd(rd, firstCluster);
        return new Blob([blob.slice(0, firstCluster), out, cut > firstCluster && cut < size ? blob.slice(firstCluster, cut) : blob.slice(firstCluster)], { type: blob.type });
    };
    const clusterEnd = (rd, cl, cp) => cl.unk ? rd.size : cp + cl.hdr + cl.size;
    const blockHeadOf = async (rd, ch, pl) => {
        if (ch.id === EBML.SBLOCK) return rd.blockHead(pl);
        if (ch.id === EBML.GROUP) return (await readGroup(rd, pl, ch)).h;
        return null;
    };
    const clusterMax = async (rd, cl, cp) => {
        let q = cp + cl.hdr, tc = 0, max = null;
        const cend = clusterEnd(rd, cl, cp);
        while (q < cend) {
            const ch = await rd.readEl(q);
            if (!ch) { q = rd.size; break; }
            if (CLUSTER_END.has(ch.id)) break;
            if (ch.unk) throw new Error('unknown size child');
            const pl = q + ch.hdr;
            if (ch.id === EBML.TC) tc = rd.uintAt(pl, ch.size);
            else {
                const h = await blockHeadOf(rd, ch, pl);
                if (h) max = Math.max(max === null ? -1 : max, tc + h.rel);
            }
            q = pl + ch.size;
        }
        return { end: q, max: max === null ? tc : max };
    };
    const readSegment = async rd => {
        const eh = await rd.readEl(0);
        if (!eh || eh.id !== EBML.HEADER) return null;
        const seg = await rd.readEl(eh.hdr + eh.size);
        return seg && seg.id === EBML.SEGMENT ? seg : null;
    };
    const readTimescale = async (rd, info, scale) => {
        let q = info.p + info.hdr;
        const end = q + info.size;
        while (q < end) {
            const ce = await rd.readEl(q);
            if (!ce) break;
            if (ce.id === 0x2AD7B1) scale = rd.uintAt(q + ce.hdr, ce.size);
            q += ce.hdr + ce.size;
        }
        return scale;
    };
    const scanSegmentHead = async (rd, seg) => {
        let p = seg.p + seg.hdr, scale = 1e6;
        while (p < rd.size) {
            const el = await rd.readEl(p);
            if (!el) return null;
            if (el.id === EBML.CLUSTER) break;
            if (el.unk) return null;
            if (el.id === 0x1549A966) scale = await readTimescale(rd, el, scale);
            p += el.hdr + el.size;
        }
        return { p, scale };
    };
    const lastCluster = async (rd, p) => {
        let last = null;
        while (p < rd.size) {
            const cl = await rd.readEl(p);
            if (!cl || cl.id !== EBML.CLUSTER) break;
            last = { cl, p };
            if (cl.unk) { const r = await clusterMax(rd, cl, p); last.max = r.max; p = r.end; } else p += cl.hdr + cl.size;
        }
        return last;
    };
    const webmDuration = async blob => {
        const rd = ebmlReader(blob);
        const seg = await readSegment(rd);
        if (!seg) return null;
        const head = await scanSegmentHead(rd, seg);
        if (!head) return null;
        const last = await lastCluster(rd, head.p);
        if (!last) return null;
        if (last.max === undefined) last.max = (await clusterMax(rd, last.cl, last.p)).max;
        const ms = last.max * head.scale / 1e6;
        return ms > 0 ? ms : null;
    };
    const blankSeekHead = async (blob, p, tot) => {
        if (p + tot > WEBM_HEAD) return blob;
        const head = new Uint8Array(await blob.slice(0, WEBM_HEAD).arrayBuffer());
        const small = tot - 3 < 16384, hl = small ? 3 : 9, len = tot - hl;
        head[p] = 0xEC;
        if (small) { head[p + 1] = 0x40 | (len >> 8); head[p + 2] = len & 255; }
        else { head[p + 1] = 0x01; head.fill(0, p + 2, p + 5); new DataView(head.buffer).setUint32(p + 5, len); }
        head.fill(0, p + hl, p + tot);
        return new Blob([head, blob.slice(WEBM_HEAD)], { type: blob.type });
    };
    const stripSeekHead = async blob => {
        const rd = ebmlReader(blob);
        const seg = await readSegment(rd);
        if (!seg || !seg.unk) return blob;
        let p = seg.p + seg.hdr;
        while (p < Math.min(rd.size, WEBM_HEAD)) {
            const el = await rd.readEl(p);
            if (!el || el.unk || el.id === EBML.CLUSTER) return blob;
            const tot = el.hdr + el.size;
            if (el.id === EBML.SEEKHEAD) return blankSeekHead(blob, p, tot);
            p += tot;
        }
        return blob;
    };
    return { mp4, webm, webmDuration, stripSeekHead };
})();
const HlsKit = (() => {
    const TWO32 = 4294967296, TWO33 = 8589934592;
    const be = (n, len) => { const o = new Uint8Array(len); let v = n; for (let i = len - 1; i >= 0; i--) { o[i] = v % 256; v = Math.floor(v / 256); } return o; };
    const b32 = n => be(n >>> 0, 4);
    const z = n => new Uint8Array(n);
    const MATRIX = u8cat([b32(0x10000), z(8), b32(0), b32(0x10000), z(8), b32(0), b32(0x40000000)].flatMap(x => [x]));
    const eq = (a, b) => !!a && !!b && a.length === b.length && a.every((v, i) => v === b[i]);
    class NeedNewInit extends Error {}

    const unescapeRbsp = (nal, skip = 1) => {
        const rb = []; let zeros = 0;
        for (let i = skip; i < nal.length; i++) {
            const b = nal[i];
            if (zeros >= 2 && b === 3) { zeros = 0; continue; }
            zeros = b === 0 ? zeros + 1 : 0;
            rb.push(b);
        }
        return rb;
    };
    const bitReader = rb => {
        let pos = 0;
        const u = n => { let v = 0; for (let i = 0; i < n; i++, pos++) v = v * 2 + ((rb[pos >> 3] >> (7 - (pos & 7))) & 1); return v; };
        const ue = () => { let l = 0; while (l < 32 && u(1) === 0) l++; return l ? (2 ** l - 1) + u(l) : 0; };
        const se = () => { const k = ue(); return k & 1 ? (k + 1) / 2 : -(k / 2); };
        return { u, ue, se };
    };
    const HIGH_PROFILES = [100, 110, 122, 244, 44, 83, 86, 118, 128, 138, 139, 134, 135];
    const skipScalingList = (r, size) => {
        let last = 8, next = 8;
        for (let j = 0; j < size; j++) { if (next) next = (last + r.se() + 256) % 256; last = next || last; }
    };
    const skipScalingMatrix = (r, chroma) => {
        for (let i = 0; i < (chroma !== 3 ? 8 : 12); i++) if (r.u(1)) skipScalingList(r, i < 6 ? 16 : 64);
    };
    const readHighProfile = (r, prof) => {
        const out = { chroma: 1, bdl: 8, bdc: 8 };
        if (!HIGH_PROFILES.includes(prof)) return out;
        out.chroma = r.ue();
        if (out.chroma === 3) r.u(1);
        out.bdl = 8 + r.ue(); out.bdc = 8 + r.ue(); r.u(1);
        if (r.u(1)) skipScalingMatrix(r, out.chroma);
        return out;
    };
    const skipPocType = r => {
        const poc = r.ue();
        if (poc === 0) r.ue();
        else if (poc === 1) { r.u(1); r.se(); r.se(); const n = r.ue(); for (let i = 0; i < n; i++) r.se(); }
    };
    const readCrop = r => r.u(1) ? { cl: r.ue(), cr: r.ue(), ct: r.ue(), cb: r.ue() } : { cl: 0, cr: 0, ct: 0, cb: 0 };
    const parseSps = nal => {
        const r = bitReader(unescapeRbsp(nal));
        const prof = r.u(8), compat = r.u(8), level = r.u(8);
        r.ue();
        const { chroma, bdl, bdc } = readHighProfile(r, prof);
        r.ue();
        skipPocType(r);
        r.ue(); r.u(1);
        const wm = r.ue(), hm = r.ue(), fmo = r.u(1);
        if (!fmo) r.u(1);
        r.u(1);
        const { cl, cr, ct, cb } = readCrop(r);
        const cux = chroma === 1 || chroma === 2 ? 2 : 1, cuy = (chroma === 1 ? 2 : 1) * (2 - fmo);
        const w = (wm + 1) * 16 - cux * (cl + cr), h = (2 - fmo) * (hm + 1) * 16 - cuy * (ct + cb);
        if (!(w > 0 && h > 0 && w < 16384 && h < 16384)) throw new Error('bad sps');
        return { prof, compat, level, chroma, bdl, bdc, w, h };
    };

    const parseHevcPtl = r => {
        const space = r.u(2), tier = r.u(1), prof = r.u(5);
        const compat = [r.u(8), r.u(8), r.u(8), r.u(8)];
        const cons = [r.u(8), r.u(8), r.u(8), r.u(8), r.u(8), r.u(8)];
        return { space, tier, prof, compat, cons, level: r.u(8) };
    };
    const skipHevcSubLayers = (r, n) => {
        const pp = [], lp = [];
        for (let i = 0; i < n; i++) { pp.push(r.u(1)); lp.push(r.u(1)); }
        if (n > 0) for (let i = n; i < 8; i++) r.u(2);
        for (let i = 0; i < n; i++) { if (pp[i]) for (let k = 0; k < 11; k++) r.u(8); if (lp[i]) r.u(8); }
    };
    const parseHevcSps = nal => {
        const r = bitReader(unescapeRbsp(nal, 2));
        r.u(4);
        const sub = r.u(3), nest = r.u(1);
        const ptl = parseHevcPtl(r);
        skipHevcSubLayers(r, sub);
        r.ue();
        const chroma = r.ue();
        if (chroma === 3) r.u(1);
        const pw = r.ue(), ph = r.ue();
        let cl = 0, cr = 0, ct = 0, cb = 0;
        if (r.u(1)) { cl = r.ue(); cr = r.ue(); ct = r.ue(); cb = r.ue(); }
        const bdl = 8 + r.ue(), bdc = 8 + r.ue();
        const sw = chroma === 1 || chroma === 2 ? 2 : 1, sh = chroma === 1 ? 2 : 1;
        const w = pw - sw * (cl + cr), h = ph - sh * (ct + cb);
        if (!(w > 0 && h > 0 && w < 16384 && h < 16384)) throw new Error('bad hevc sps');
        return { codec: 'hevc', ...ptl, chroma, bdl, bdc, w, h, layers: sub + 1, nest };
    };
    const hevcCodecString = v => {
        const hex = n => n.toString(16);
        const fl = ((v.compat[0] << 24) | (v.compat[1] << 16) | (v.compat[2] << 8) | v.compat[3]) >>> 0;
        let rev = 0;
        for (let i = 0; i < 32; i++) if (fl & (1 << i)) rev |= 1 << (31 - i);
        const c = v.cons.slice();
        while (c.length > 1 && c[c.length - 1] === 0) c.pop();
        return `hvc1.${['', 'A', 'B', 'C'][v.space]}${v.prof}.${hex(rev >>> 0)}.${v.tier ? 'H' : 'L'}${v.level}.${c.map(x => hex(x).toUpperCase()).join('.')}`;
    };

    const AAC_SR = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
    const tsTime = (b, o) => (b[o] & 0x0e) * 536870912 + b[o + 1] * 4194304 + (b[o + 2] & 0xfe) * 16384 + b[o + 3] * 128 + (b[o + 4] >> 1);
    const splitNals = b => {
        const out = [], n = b.length;
        let i = 0, start = -1;
        const push = e => { while (e > start && b[e - 1] === 0) e--; if (e > start) out.push(b.subarray(start, e)); };
        while (i < n - 2) {
            if (b[i + 2] > 1) i += 3;
            else if (b[i] === 0 && b[i + 1] === 0 && b[i + 2] === 1) { if (start >= 0) push(i); start = i + 3; i += 3; }
            else i++;
        }
        if (start >= 0) push(n);
        return out;
    };
    const findTsSync = buf => {
        let p = 0;
        while (p + 376 <= buf.length && !(buf[p] === 0x47 && buf[p + 188] === 0x47)) p++;
        return p;
    };
    const parsePes = d => {
        if (d.length < 9 || d[0] || d[1] || d[2] !== 1) return null;
        const fl = d[7] >> 6, hl = d[8];
        if (!(fl & 2) || d.length < 9 + hl) return null;
        const pts = tsTime(d, 9);
        return { pts, dts: fl === 3 ? tsTime(d, 14) : pts, pay: d.subarray(9 + hl) };
    };
    const collectNals = (nals, res) => {
        const keep = [];
        let key = false;
        for (const nl of nals) {
            const t = nl[0] & 31;
            if (t === 7) { if (!res.sps) res.sps = nl.slice(); }
            else if (t === 8) { if (!res.pps) res.pps = nl.slice(); }
            else if (t >= 1 && t <= 5) { keep.push(nl); if (t === 5) key = true; }
        }
        return { keep, key };
    };
    const collectHevcNals = (nals, res) => {
        const keep = [];
        let key = false;
        for (const nl of nals) {
            if (nl.length < 3 || ((nl[0] & 1) << 5 | nl[1] >> 3) !== 0) continue;
            const t = (nl[0] >> 1) & 63;
            if (t === 32) { if (!res.vps) res.vps = nl.slice(); }
            else if (t === 33) { if (!res.sps) res.sps = nl.slice(); }
            else if (t === 34) { if (!res.pps) res.pps = nl.slice(); }
            else if (t <= 21) { keep.push(nl); if (t >= 16) key = true; }
        }
        return { keep, key };
    };
    const avcSample = keep => {
        let n = 0;
        for (const k of keep) n += 4 + k.length;
        const data = new Uint8Array(n);
        let q = 0;
        for (const k of keep) { new DataView(data.buffer).setUint32(q, k.length); data.set(k, q + 4); q += 4 + k.length; }
        return data;
    };
    const addVideo = (res, pay, pts, dts) => {
        const nals = splitNals(pay);
        const { keep, key } = res.codec === 'hevc' ? collectHevcNals(nals, res) : collectNals(nals, res);
        if (!keep.length) return;
        res.video.push({ pts, dts, key, data: avcSample(keep) });
    };
    const parseAdts = (pay, q) => {
        const prot = pay[q + 1] & 1, prof = (pay[q + 2] >> 6) & 3, sfi = (pay[q + 2] >> 2) & 15;
        const ch = ((pay[q + 2] & 1) << 2) | (pay[q + 3] >> 6), fl = ((pay[q + 3] & 3) << 11) | (pay[q + 4] << 3) | (pay[q + 5] >> 5);
        return { prof, sfi, ch, fl, hl: prot ? 7 : 9 };
    };
    const addAudio = (res, pay, pts) => {
        let q = 0, i = 0;
        while (q + 7 <= pay.length) {
            if (pay[q] !== 0xff || (pay[q + 1] & 0xf0) !== 0xf0) { q++; continue; }
            const f = parseAdts(pay, q);
            if (f.sfi > 12 || f.fl < f.hl || q + f.fl > pay.length) break;
            const sr = AAC_SR[f.sfi];
            if (!res.aac) res.aac = { aot: f.prof + 1, sfi: f.sfi, sr, ch: f.ch || 2 };
            res.audio.push({ pts: pts + i * 1024 * 90000 / sr, data: pay.subarray(q + f.hl, q + f.fl) });
            i++; q += f.fl;
        }
    };
    const flushPes = (st, pid) => {
        const pe = st.pes.get(pid);
        if (!pe || !pe.chunks.length) return;
        const d = u8cat(pe.chunks); pe.chunks = [];
        const pes = parsePes(d);
        if (!pes) return;
        if (pid === st.vPid) addVideo(st.res, pes.pay, pes.pts, pes.dts);
        else if (st.aType === 0x0f) addAudio(st.res, pes.pay, pes.pts);
    };
    const parsePat = (buf, s, end, st) => {
        for (let o = s + 8; o + 4 <= end; o += 4) {
            const prog = (buf[o] << 8) | buf[o + 1];
            if (prog) { st.pmtPid = ((buf[o + 2] & 0x1f) << 8) | buf[o + 3]; break; }
        }
    };
    const parsePmt = (buf, s, end, st) => {
        let o = s + 12 + (((buf[s + 10] & 0x0f) << 8) | buf[s + 11]);
        while (o + 5 <= end) {
            const type = buf[o], sp = ((buf[o + 1] & 0x1f) << 8) | buf[o + 2];
            if (type === 0x1b && st.vPid < 0) { st.vPid = sp; st.res.codec = 'avc'; }
            else if (type === 0x0f && st.aPid < 0) { st.aPid = sp; st.aType = type; }
            else if (type === 0x24 && st.vPid < 0) { st.vPid = sp; st.res.codec = 'hevc'; }
            o += 5 + (((buf[o + 3] & 0x0f) << 8) | buf[o + 4]);
        }
    };
    const parsePsi = (buf, p, q, pid, st) => {
        const s = q + 1 + buf[q], len = ((buf[s + 1] & 0x0f) << 8) | buf[s + 2], end = Math.min(p + 188, s + 3 + len - 4);
        if (pid === 0 && buf[s] === 0) parsePat(buf, s, end, st);
        else if (pid === st.pmtPid && buf[s] === 2) parsePmt(buf, s, end, st);
    };
    const tsPayloadOffset = (buf, p) => {
        const afc = (buf[p + 3] >> 4) & 3;
        let q = p + 4;
        if (afc & 2) q += 1 + buf[p + 4];
        return !(afc & 1) || q >= p + 188 ? -1 : q;
    };
    const addPesChunk = (st, pid, pusi, chunk) => {
        if (pusi) flushPes(st, pid);
        let pe = st.pes.get(pid);
        if (!pe) st.pes.set(pid, pe = { chunks: [] });
        if (pusi || pe.chunks.length) pe.chunks.push(chunk);
    };
    const tsPacket = (buf, p, st) => {
        const q = tsPayloadOffset(buf, p);
        if (q < 0) return;
        const pusi = buf[p + 1] & 0x40, pid = ((buf[p + 1] & 0x1f) << 8) | buf[p + 2];
        if (pid === 0 || pid === st.pmtPid) { if (pusi) parsePsi(buf, p, q, pid, st); return; }
        if (pid === st.vPid || pid === st.aPid) addPesChunk(st, pid, pusi, buf.subarray(q, p + 188));
    };
    const demuxTs = buf => {
        const st = { res: { video: [], audio: [], sps: null, pps: null, vps: null, codec: 'avc', aac: null }, pmtPid: -1, vPid: -1, aPid: -1, aType: 0, pes: new Map() };
        for (let p = findTsSync(buf); p + 188 <= buf.length; p += 188) {
            if (buf[p] !== 0x47) { const nx = buf.indexOf(0x47, p + 1); if (nx < 0) break; p = nx - 188; continue; }
            tsPacket(buf, p, st);
        }
        flushPes(st, st.vPid); flushPes(st, st.aPid);
        return st.res;
    };

    const desc = (tag, ...p) => { const b = u8cat(p); if (b.length > 127) throw new Error('desc too long'); return u8cat([Uint8Array.of(tag, b.length), b]); };
    const esds = a => fullBox('esds', 0, 0, desc(3, be(0, 2), Uint8Array.of(0), desc(4, Uint8Array.of(0x40, 0x15), be(0, 3), be(0, 4), be(0, 4), desc(5, a)), desc(6, Uint8Array.of(2))));
    const avcC = v => {
        const parts = [Uint8Array.of(1, v.prof, v.compat, v.level, 0xff, 0xe1), be(v.sps.length, 2), v.sps, Uint8Array.of(1), be(v.pps.length, 2), v.pps];
        if ([100, 110, 122, 144].includes(v.prof)) parts.push(Uint8Array.of(0xfc | v.chroma, 0xf8 | (v.bdl - 8), 0xf8 | (v.bdc - 8), 0));
        return box('avcC', ...parts);
    };
    const hvcC = v => {
        const arr = (type, n) => [Uint8Array.of(0x80 | type, 0, 1), be(n.length, 2), n];
        return box('hvcC', Uint8Array.of(1, (v.space << 6) | (v.tier << 5) | v.prof), Uint8Array.from(v.compat), Uint8Array.from(v.cons),
            Uint8Array.of(v.level, 0xf0, 0x00, 0xfc, 0xfc | v.chroma, 0xf8 | (v.bdl - 8), 0xf8 | (v.bdc - 8), 0, 0, (v.layers << 3) | (v.nest << 2) | 3, 3),
            ...arr(32, v.vps), ...arr(33, v.sps), ...arr(34, v.pps));
    };
    const trakBox = (id, ts, kind, entry, w, h) => box('trak',
        fullBox('tkhd', 0, 7, b32(0), b32(0), b32(id), b32(0), b32(0), z(8), be(0, 2), be(0, 2), be(kind === 'soun' ? 0x0100 : 0, 2), z(2), MATRIX, b32(w * 65536), b32(h * 65536)),
        box('mdia',
            fullBox('mdhd', 0, 0, b32(0), b32(0), b32(ts), b32(0), be(0x55c4, 2), z(2)),
            fullBox('hdlr', 0, 0, b32(0), u8ascii(kind), z(12), u8ascii(kind === 'vide' ? 'Video\0' : 'Audio\0')),
            box('minf',
                kind === 'vide' ? fullBox('vmhd', 0, 1, z(8)) : fullBox('smhd', 0, 0, z(4)),
                box('dinf', fullBox('dref', 0, 0, b32(1), fullBox('url ', 0, 1))),
                box('stbl', fullBox('stsd', 0, 0, b32(1), entry), fullBox('stts', 0, 0, b32(0)), fullBox('stsc', 0, 0, b32(0)), fullBox('stsz', 0, 0, b32(0), b32(0)), fullBox('stco', 0, 0, b32(0))))));
    const buildInit = (v, a) => {
        const hevc = v.codec === 'hevc';
        const avc1 = box(hevc ? 'hvc1' : 'avc1', z(6), be(1, 2), z(16), be(v.w, 2), be(v.h, 2), b32(0x480000), b32(0x480000), z(4), be(1, 2), z(32), be(0x18, 2), be(0xffff, 2), hevc ? hvcC(v) : avcC(v));
        const traks = [trakBox(1, 90000, 'vide', avc1, v.w, v.h)];
        const trex = [fullBox('trex', 0, 0, b32(1), b32(1), b32(0), b32(0), b32(0))];
        if (a) {
            const ascb = be(((a.aot << 11) | (a.sfi << 7) | (a.ch << 3)), 2);
            const mp4a = box('mp4a', z(6), be(1, 2), z(8), be(a.ch, 2), be(16, 2), z(4), be(a.sr < 65536 ? a.sr * 65536 : 0, 4), esds(ascb));
            traks.push(trakBox(2, a.sr, 'soun', mp4a, 0, 0));
            trex.push(fullBox('trex', 0, 0, b32(2), b32(1), b32(0), b32(0), b32(0)));
        }
        return u8cat([
            box('ftyp', u8ascii('isom'), b32(512), u8ascii('isom'), u8ascii('iso5'), u8ascii('iso6'), u8ascii(v.codec === 'hevc' ? 'hvc1' : 'avc1'), u8ascii('mp41')),
            box('moov', fullBox('mvhd', 0, 0, b32(0), b32(0), b32(1000), b32(0), b32(0x10000), be(0x0100, 2), z(10), MATRIX, z(24), b32(a ? 3 : 2)), ...traks, box('mvex', ...trex))
        ]);
    };
    const buildFragment = (seq, tracks) => {
        const mk = base => {
            let off = base;
            const trafs = tracks.map(t => {
                const run = fullBox('trun', 1, 0xf01, b32(t.samples.length), b32(off), ...t.samples.map(s => u8cat([b32(s.dur), b32(s.data.length), b32(s.flags), b32(s.cts)])));
                off += t.samples.reduce((n, s) => n + s.data.length, 0);
                return box('traf', fullBox('tfhd', 0, 0x20000, b32(t.id)), fullBox('tfdt', 1, 0, be(t.t, 8)), run);
            });
            return box('moof', fullBox('mfhd', 0, 0, b32(seq)), ...trafs);
        };
        const moofLen = mk(0).length, moof = mk(moofLen + 8);
        let n = 0;
        for (const t of tracks) for (const s of t.samples) n += s.data.length;
        const mdat = new Uint8Array(8 + n);
        new DataView(mdat.buffer).setUint32(0, mdat.length);
        mdat.set(u8ascii('mdat'), 4);
        let q = 8;
        for (const t of tracks) for (const s of t.samples) { mdat.set(s.data, q); q += s.data.length; }
        return u8cat([moof, mdat]);
    };
    const KEY_FLAGS = 0x02000000, DELTA_FLAGS = 0x01010000;
    const unwrapper = () => { let off = 0, last = null; return v => { v += off; if (last !== null) { const d = v - last; if (d < -TWO32) { off += TWO33; v += TWO33; } else if (d > TWO32) { off -= TWO33; v -= TWO33; } } last = v; return v; }; };
    const modCts = d => (((d + TWO32) % TWO33) + TWO33) % TWO33 - TWO32;

    const differs = (a, b) => !!a && !!b && !eq(a, b);
    const audioChanged = (a, b) => !!a && !!b && (a.sr !== b.sr || a.ch !== b.ch);
    class Fmp4Mux {
        constructor() {
            this.init = null; this.vi = null; this.ai = null; this.sps = null; this.pps = null; this.vps = null; this.codec = null;
            this.t0 = null; this.shift = 0; this.seq = 0; this.pend = null; this.lastDur = 3000;
            this.aNext = null; this.uwV = unwrapper(); this.uwA = unwrapper();
        }
        _check(seg) {
            if (seg.sps && this.codec && seg.codec !== this.codec) throw new NeedNewInit('codec');
            if (differs(seg.sps, this.sps) || differs(seg.pps, this.pps) || differs(seg.vps, this.vps)) throw new NeedNewInit('params');
            if (audioChanged(seg.aac, this.ai)) throw new NeedNewInit('audio');
        }
        _ensureInit(seg, out) {
            if (this.init) return true;
            const hevc = seg.codec === 'hevc';
            if (!seg.sps || !seg.pps || (hevc && !seg.vps)) return false;
            this.sps = seg.sps; this.pps = seg.pps; this.vps = hevc ? seg.vps : null; this.codec = hevc ? 'hevc' : 'avc';
            this.vi = { ...(hevc ? parseHevcSps(seg.sps) : parseSps(seg.sps)), sps: seg.sps, pps: seg.pps, vps: this.vps };
            this.ai = seg.aac || null;
            this.init = buildInit(this.vi, this.ai);
            out.push(this.init);
            return true;
        }
        _align(vs, disc) {
            if (this.t0 === null) {
                const k = vs.findIndex(s => s.key);
                if (k < 0) return null;
                vs = vs.slice(k);
                this.t0 = vs[0].dts;
            } else if (vs.length) {
                const firstAdj = vs[0].dts - this.t0 + this.shift, exp = this.pend ? this.pend.adj + this.lastDur : firstAdj;
                if (disc || Math.abs(firstAdj - exp) > 180000) this.shift += exp - firstAdj;
            }
            return vs;
        }
        _videoTrack(vs) {
            const q = this.pend ? [this.pend, ...vs] : vs;
            if (!q.length) return null;
            this.pend = q[q.length - 1];
            const samples = [];
            for (let i = 0; i < q.length - 1; i++) {
                let dur = q[i + 1].adj - q[i].adj;
                if (dur <= 0) dur = this.lastDur; else this.lastDur = dur;
                samples.push({ dur, cts: q[i].cts, flags: q[i].key ? KEY_FLAGS : DELTA_FLAGS, data: q[i].data });
            }
            return samples.length ? { id: 1, t: Math.max(0, q[0].adj), samples } : null;
        }
        _audioTrack(au) {
            if (!this.ai || !au.length) return null;
            const sr = this.ai.sr, a = au.map(s => ({ t: (s.pts - this.t0 + this.shift) * sr / 90000, data: s.data })).filter(s => s.t >= -1024);
            if (!a.length) return null;
            const t0 = Math.round(Math.max(0, a[0].t));
            const start = this.aNext === null || Math.abs(t0 - this.aNext) > sr * 0.04 ? t0 : this.aNext;
            this.aNext = start + a.length * 1024;
            return { id: 2, t: start, samples: a.map(s => ({ dur: 1024, cts: 0, flags: KEY_FLAGS, data: s.data })) };
        }
        push(seg, disc) {
            const out = [];
            this._check(seg);
            if (!this._ensureInit(seg, out)) return out;
            let vs = seg.video.map(s => ({ dts: this.uwV(s.dts), cts: modCts(s.pts - s.dts), key: s.key, data: s.data }));
            const au = seg.audio.map(s => ({ pts: this.uwA(s.pts), data: s.data }));
            vs = this._align(vs, disc);
            if (!vs) return out;
            for (const s of vs) s.adj = s.dts - this.t0 + this.shift;
            const tracks = [this._videoTrack(vs), this._audioTrack(au)].filter(Boolean);
            if (tracks.length) out.push(buildFragment(++this.seq, tracks));
            return out;
        }
        flush() {
            if (!this.pend) return [];
            const s = this.pend; this.pend = null;
            return [buildFragment(++this.seq, [{ id: 1, t: Math.max(0, s.adj), samples: [{ dur: this.lastDur, cts: s.cts, flags: s.key ? KEY_FLAGS : DELTA_FLAGS, data: s.data }] }])];
        }
    }

    const attrs = s => { const o = {}, re = /([A-Z0-9-]+)=("[^"]*"|[^,]*)/g; let m; while ((m = re.exec(s))) o[m[1]] = m[2].replace(/^"|"$/g, ''); return o; };
    const TAG_HANDLERS = {
        '#EXT-X-STREAM-INF': (s, val) => { s.sv = attrs(val); },
        '#EXT-X-MEDIA': (s, val) => {
            const a = attrs(val);
            if (a.TYPE === 'AUDIO' && a.URI) s.res.audio.push({ url: s.abs(a.URI), group: a['GROUP-ID'] || '', def: a.DEFAULT === 'YES', auto: a.AUTOSELECT === 'YES' });
        },
        '#EXT-X-TARGETDURATION': (s, val) => { s.res.target = +val || 6; },
        '#EXT-X-MEDIA-SEQUENCE': (s, val) => { s.seq = +val || 0; },
        '#EXTINF': (s, val) => { s.dur = parseFloat(val) || 0; },
        '#EXT-X-DISCONTINUITY': s => { s.disc = true; },
        '#EXT-X-ENDLIST': s => { s.res.end = true; },
        '#EXT-X-BYTERANGE': (s, val) => { const [n, o] = val.trim().split('@'); s.br = { n: +n, o: o === undefined ? null : +o }; },
        '#EXT-X-KEY': (s, val) => { const a = attrs(val); s.key = a.METHOD === 'NONE' ? null : { method: a.METHOD, uri: a.URI ? s.abs(a.URI) : '', iv: a.IV || '' }; },
        '#EXT-X-MAP': (s, val) => {
            const a = attrs(val), uri = s.abs(a.URI);
            let range = null;
            if (a.BYTERANGE) { const [n, o] = a.BYTERANGE.split('@').map(Number); if (!(n > 0)) throw new Error('bad byte-range'); range = { n, o: o || 0 }; }
            s.map = { uri, range, key: `${uri}#${range ? `${range.o}-${range.n}` : ''}` };
        }
    };
    const takeRange = (s, uri) => {
        if (!s.br) return null;
        const { n, o } = s.br;
        if (!(n > 0)) throw new Error('bad byte-range');
        const off = o !== null && !Number.isNaN(o) ? o : (s.ends.get(uri) || 0);
        s.ends.set(uri, off + n); s.br = null;
        return { n, o: off };
    };
    const addUri = (s, l) => {
        if (s.sv) {
            const [w, h] = (s.sv.RESOLUTION || '').split('x').map(Number);
            s.res.variants.push({ url: s.abs(l), bw: +s.sv.BANDWIDTH || 0, w: w || 0, h: h || 0, aud: s.sv.AUDIO || '', codecs: s.sv.CODECS || '' });
            s.res.master = true; s.sv = null;
        } else {
            const uri = s.abs(l);
            s.res.segs.push({ uri, seq: s.seq++, dur: s.dur, disc: s.disc, key: s.key, map: s.map, range: takeRange(s, uri) });
            s.disc = false;
        }
    };
    const parsePlaylist = (text, base) => {
        const lines = text.split(/\r?\n/).map(l => l.trim()).filter(Boolean);
        if (!lines.length || !lines[0].startsWith('#EXTM3U')) throw new Error('not an m3u8');
        const s = { abs: u => new URL(u, base).href, res: { master: false, variants: [], audio: [], segs: [], target: 6, end: false }, seq: 0, dur: 0, disc: false, key: null, map: null, sv: null, br: null, ends: new Map() };
        for (const l of lines) {
            if (l[0] !== '#') { addUri(s, l); continue; }
            const c = l.indexOf(':'), tag = c < 0 ? l : l.slice(0, c), val = c < 0 ? '' : l.slice(c + 1);
            const handler = TAG_HANDLERS[tag];
            if (handler) handler(s, val);
        }
        return s.res;
    };
    const pickVariant = (vs, maxH) => {
        if (!vs.length) throw new Error('empty master playlist');
        const withH = vs.filter(v => v.h);
        if (withH.length) {
            const ok = withH.filter(v => v.h <= maxH);
            return (ok.length ? ok : withH).sort((a, b) => ok.length ? (b.h - a.h || b.bw - a.bw) : (a.h - b.h || a.bw - b.bw))[0];
        }
        const s = [...vs].sort((a, b) => b.bw - a.bw);
        return maxH >= 720 ? s[0] : s[s.length - 1];
    };

    const pickAudio = (pl, v) => {
        if (!pl.audio.length) return null;
        const own = v.aud ? pl.audio.filter(a => a.group === v.aud) : [];
        if (!own.length && (!v.codecs || /mp4a|ac-3|ec-3|opus|flac/i.test(v.codecs))) return null;
        const c = own.length ? own : pl.audio;
        return c.find(a => a.def) || c.find(a => a.auto) || c[0];
    };

    const DIR = 'bcu_rec';
    const defRoot = () => navigator.storage.getDirectory();
    class DiskBuffer {
        constructor(o = {}) {
            this.id = o.id || `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`;
            this.meta = o.meta || {};
            this.chunk = o.chunkBytes || 4 * 1048576;
            this.getRoot = o.getRoot || defRoot;
            this.mem = []; this.memBytes = 0; this.files = []; this.size = 0; this.n = 0; this.dir = null; this.base = null;
            this._q = this._init();
        }
        get onDisk() { return !!this.dir; }
        async _init() {
            try {
                const root = await this.getRoot();
                this.base = await root.getDirectoryHandle(DIR, { create: true });
                this.dir = await this.base.getDirectoryHandle(this.id, { create: true });
                await this._put('meta.json', new Blob([JSON.stringify({ ...this.meta, id: this.id, t: Date.now() })]));
            } catch { this.dir = null; }
        }
        async _put(name, blob) {
            const fh = await this.dir.getFileHandle(name, { create: true });
            const w = await fh.createWritable();
            try { await w.write(blob); } finally { await w.close(); }
            return fh;
        }
        async _flush() {
            if (!this.dir || !this.memBytes) return;
            const parts = this.mem, bytes = this.memBytes;
            this.mem = []; this.memBytes = 0;
            try {
                const fh = await this._put(`${String(++this.n).padStart(6, '0')}.bin`, new Blob(parts));
                this.files.push(fh);
            } catch { this.mem = parts.concat(this.mem); this.memBytes += bytes; this.dir = null; }
        }
        append(data) {
            const len = data.size ?? data.byteLength;
            if (!len) return;
            this.size += len; this.mem.push(data); this.memBytes += len;
            if (this.memBytes >= this.chunk) this._q = this._q.then(() => this._flush());
        }
        async finish() {
            await this._q;
            const f = await Promise.all(this.files.map(h => h.getFile()));
            return new Blob([...f, ...this.mem], { type: this.meta.mime || '' });
        }
        async markDone() { await this._q; if (this.dir) { try { await this._put('.done', new Blob([])); } catch (e) { dbg(e, 'DiskBuffer.markDone'); } } }
        async remove() { await this._q.catch(() => {}); try { await this.base?.removeEntry(this.id, { recursive: true }); } catch (e) { dbg(e, 'DiskBuffer.remove'); } this.dir = null; this.files = []; }
        dispose(delay = 900000) { setTimeout(() => this.remove(), delay); }
        static async list(getRoot = defRoot, minAge = 120000) {
            const out = [];
            try {
                const base = await (await getRoot()).getDirectoryHandle(DIR);
                for await (const [id, h] of base.entries()) {
                    if (h.kind !== 'directory') continue;
                    const e = { id, meta: {}, bins: [], done: false, last: 0, bytes: 0 };
                    for await (const [name, fh] of h.entries()) {
                        const f = await fh.getFile();
                        e.last = Math.max(e.last, f.lastModified);
                        if (name === '.done') e.done = true;
                        else if (name === 'meta.json') { try { e.meta = JSON.parse(await f.text()); } catch (e) { dbg(e, 'DiskBuffer.list'); } }
                        else if (name.endsWith('.bin')) { e.bins.push([name, f]); e.bytes += f.size; }
                    }
                    e.bins.sort((a, b) => a[0] < b[0] ? -1 : 1);
                    e.stale = Date.now() - e.last >= minAge;
                    out.push(e);
                }
            } catch (e) { dbg(e, 'DiskBuffer.list'); }
            return out;
        }
        static async orphans(getRoot, minAge) { return (await DiskBuffer.list(getRoot, minAge)).filter(e => !e.done && e.stale && e.bytes > 0); }
        static blobOf(e) { return new Blob(e.bins.map(b => b[1]), { type: e.meta.mime || '' }); }
        static async cleanup(getRoot = defRoot, doneAge = 1800000) {
            try {
                const base = await (await getRoot()).getDirectoryHandle(DIR);
                for (const e of await DiskBuffer.list(getRoot, 0)) {
                    const old = Date.now() - e.last;
                    if ((e.done && old > doneAge) || (!e.bytes && old > 300000)) { try { await base.removeEntry(e.id, { recursive: true }); } catch (e) { dbg(e, 'DiskBuffer.cleanup'); } }
                }
            } catch (e) { dbg(e, 'DiskBuffer.cleanup'); }
        }
        static async markDoneById(id, getRoot = defRoot) { try { const d = await (await (await getRoot()).getDirectoryHandle(DIR)).getDirectoryHandle(id); const w = await (await d.getFileHandle('.done', { create: true })).createWritable(); await w.close(); } catch (e) { dbg(e, 'DiskBuffer.markDoneById'); } }
        static async removeById(id, getRoot = defRoot) { try { await (await (await getRoot()).getDirectoryHandle(DIR)).removeEntry(id, { recursive: true }); } catch (e) { dbg(e, 'DiskBuffer.removeById'); } }
    }

    const sleepMs = (o, ms) => new Promise(r => { const t = setTimeout(r, ms); o._wake = () => { clearTimeout(t); r(); }; });
    const ivBytes = (iv, seq) => {
        if (!iv) return be(seq, 16);
        const h = iv.replace(/^0x/i, '').padStart(32, '0'), o = new Uint8Array(16);
        for (let i = 0; i < 16; i++) o[i] = parseInt(h.substr(i * 2, 2), 16);
        return o;
    };
    const rangeSlice = (d, r) => d.length === r.n ? d : d.length >= r.o + r.n ? d.subarray(r.o, r.o + r.n) : null;
    const isFmp4Media = d => d.length > 8 && /^(ftyp|styp|moof|sidx)$/.test(String.fromCharCode(d[4], d[5], d[6], d[7]));
    class HlsCapture {
        constructor(o) {
            this.o = { maxHeight: Infinity, partBytes: 150 * 1048576, totalBytes: 0, maxBacklog: 6, retries: 3, ...o };
            this.stats = { segments: 0, missed: 0, gaps: 0, bytes: 0, parts: 0, variant: null };
            this.stopped = false; this.error = null;
            this.sink = null; this.mux = null; this.partNo = 0; this.saved = 0;
            this.initBytes = null; this.initUri = null; this.keys = new Map(); this.pending = [];
        }
        stop() { this.stopped = true; this._wake?.(); }
        start() { this.done = this._run().catch(e => { this.error = e; }).then(() => Promise.all(this.pending)).then(() => ({ error: this.error, stats: this.stats })); return this.done; }
        async _bin(url, tries = this.o.retries, range = null) {
            for (let i = 0; i < tries && !this.stopped; i++) {
                let d = null;
                try { d = await this.o.fetchBin(url, range || undefined); } catch (e) { dbg(e, 'HlsCapture._bin'); }
                if (d && range) d = rangeSlice(d, range);
                if (d && d.length) return d;
                if (i < tries - 1) await sleepMs(this, 400 * 2 ** i);
            }
            return null;
        }
        async _pl(url, tries) {
            const d = await this._bin(url, tries);
            return d ? parsePlaylist(new TextDecoder().decode(d), url) : null;
        }
        async _newSink() { this.partNo++; this.sink = await this.o.makeSink(this.partNo); this.mux = new Fmp4Mux(); if (this.initBytes) this.sink.append(this.initBytes); }
        async _closePart(final) {
            if (!this.sink) return;
            const sink = this.sink, no = this.partNo;
            this.sink = null;
            if (this.mux) for (const c of this.mux.flush()) sink.append(c);
            this.mux = null;
            this.saved += sink.size;
            if (sink.size > (this.initBytes ? this.initBytes.length : 0)) {
                this.stats.parts++;
                this.pending.push(Promise.resolve().then(async () => this.o.onPart(await sink.finish(), no, { sink, final: !!final })).catch(e => { this.error ||= e; }));
            } else { try { await sink.remove?.(); } catch (e) { dbg(e, 'HlsCapture._closePart'); } }
        }
        async _rotate() { await this._closePart(false); if (!this.stopped) await this._newSink(); }
        _progress() { this.stats.part = this.sink ? this.sink.size : 0; this.stats.saved = this.saved; this.stats.bytes = this.saved + this.stats.part; this.o.onProgress?.(this.stats); }
        async _decrypt(data, seg) {
            if (seg.key.method !== 'AES-128') throw new Error(`unsupported encryption ${seg.key.method}`);
            let k = this.keys.get(seg.key.uri);
            if (!k) { k = await this._bin(seg.key.uri); if (!k || k.length !== 16) throw new Error('cannot get key'); this.keys.set(seg.key.uri, k); }
            const ck = await crypto.subtle.importKey('raw', k, 'AES-CBC', false, ['decrypt']);
            return new Uint8Array(await crypto.subtle.decrypt({ name: 'AES-CBC', iv: ivBytes(seg.key.iv, seg.seq) }, ck, data));
        }
        async _appendTs(data, seg) {
            const parsed = demuxTs(data);
            let out;
            try { out = this.mux.push(parsed, seg.disc); }
            catch (e) { if (!(e instanceof NeedNewInit)) throw e; await this._rotate(); out = this.mux.push(parsed, true); }
            for (const c of out) this.sink.append(c);
        }
        async _loadInit(map) {
            const uri = map.key, init = await this._bin(map.uri, undefined, map.range);
            if (!init) throw new Error('cannot get init segment');
            const changed = this.initUri !== null;
            this.initUri = uri; this.initBytes = init;
            if (changed && this.sink.size > 0) await this._rotate(); else if (this.sink.size === 0) this.sink.append(init);
        }
        async _appendFmp4(data, seg) {
            if (seg.map && seg.map.key !== this.initUri) await this._loadInit(seg.map);
            this.sink.append(data);
        }
        async _segment(seg) {
            let data = await this._bin(seg.uri, undefined, seg.range);
            if (!data) { this.stats.missed++; return false; }
            if (seg.key) data = await this._decrypt(data, seg);
            if (data[0] === 0x47) await this._appendTs(data, seg);
            else if (isFmp4Media(data)) await this._appendFmp4(data, seg);
            else throw new Error('unsupported segment format');
            this.stats.segments++;
            return true;
        }
        async _openHinted() {
            const h = this.o.hint;
            if (!h || !Array.isArray(h.variants) || !h.variants.length) return null;
            const v = pickVariant(h.variants, this.o.maxHeight);
            const pl = await this._pl(v.url, 1);
            if (!pl || pl.master || !pl.segs.length) return null;
            this.stats.variant = v; this.stats.variants = h.variants;
            const au = pickAudio({ audio: h.audio || [] }, v);
            this.stats.audioUrl = au ? au.url : '';
            if (au) this.o.onAudio?.(au);
            this._refreshLadder();
            return { url: v.url, pl };
        }
        _refreshLadder() {
            this._pl(this.o.url, 1).then(m => {
                if (!m || !m.master || this.stopped) return;
                this.stats.variants = m.variants;
                this.o.onOpened?.({ variants: m.variants, audio: m.audio });
            }).catch(() => {});
        }
        async _openPlaylist() {
            const o = this.o;
            const quick = await this._openHinted();
            if (quick) return quick;
            let url = o.url, pl = await this._pl(url, o.hint ? 1 : undefined);
            if (!pl) throw new Error('playlist unavailable');
            if (pl.master) {
                const masterAudio = pl.audio;
                const v = pickVariant(pl.variants, o.maxHeight);
                this.stats.variant = v; this.stats.variants = pl.variants; url = v.url;
                const au = pickAudio(pl, v);
                this.stats.audioUrl = au ? au.url : '';
                if (au) this.o.onAudio?.(au);
                pl = await this._pl(url);
                if (!pl) throw new Error('media playlist unavailable');
                this.o.onOpened?.({ variants: this.stats.variants, audio: masterAudio });
            }
            return { url, pl };
        }
        _freshSegs(pl, last) {
            let fresh = pl.segs.filter(s => s.seq > last);
            if (last < 0 && !pl.end && fresh.length > this.o.maxBacklog) fresh = fresh.slice(-this.o.maxBacklog);
            if (last >= 0 && fresh.length && fresh[0].seq > last + 1) {
                this.stats.gaps += fresh[0].seq - last - 1;
                fresh[0] = { ...fresh[0], disc: true };
            }
            return fresh;
        }
        async _waitGate() {
            const o = this.o;
            if (!o.gate || !o.gate()) return false;
            while (o.gate() && !this.stopped) await sleepMs(this, 400);
            return true;
        }
        async _consume(fresh, st) {
            const o = this.o;
            for (const seg of fresh) {
                if (this.stopped) break;
                if (await this._waitGate()) return true;
                if (await this._segment(seg)) st.idleSince = Date.now();
                st.last = seg.seq;
                this._progress();
                if (this.sink.size >= o.partBytes) await this._rotate();
                if (o.totalBytes && this.saved + this.sink.size >= o.totalBytes) this.stopped = true;
            }
            return false;
        }
        async _refreshPl(url, pl) { return (await this._pl(url, 1).catch(() => null)) || pl; }
        _shouldStop(pl, fresh, idleSince) {
            return this.stopped || pl.end || (!fresh.length && Date.now() - idleSince > Math.max(20000, pl.target * 4000));
        }
        async _run() {
            let { url, pl } = await this._openPlaylist();
            await this._newSink();
            const st = { last: -1, idleSince: Date.now(), fails: 0 };
            while (!this.stopped) {
                const fresh = this._freshSegs(pl, st.last);
                const held = await this._consume(fresh, st);
                if (held && !this.stopped) { pl = await this._refreshPl(url, pl); continue; }
                if (this._shouldStop(pl, fresh, st.idleSince)) break;
                await sleepMs(this, Math.max(500, pl.target * 500));
                if (this.stopped) break;
                const next = await this._pl(url, 1).catch(() => null);
                if (next) { pl = next; st.fails = 0; } else if (++st.fails > 12) break;
            }
            await this._closePart(true);
        }
    }
    const boxAt = (b, tag) => {
        const c0 = tag.charCodeAt(0), c1 = tag.charCodeAt(1), c2 = tag.charCodeAt(2), c3 = tag.charCodeAt(3);
        for (let i = 4; i + 4 <= b.length; i++) if (b[i] === c0 && b[i + 1] === c1 && b[i + 2] === c2 && b[i + 3] === c3) return i + 4;
        return -1;
    };
    const aotOf = (b, e) => {
        let i = e + 4;
        const len = () => { let n = 0, c; do { c = b[i++]; n = (n << 7) | (c & 127); } while ((c & 128) && i < b.length); return n; };
        try {
            if (b[i++] !== 3) return 2;
            len();
            const fl = b[i + 2];
            i += 3;
            if (fl & 0x80) i += 2;
            if (fl & 0x40) i += 1 + b[i];
            if (fl & 0x20) i += 2;
            if (b[i++] !== 4) return 2;
            len();
            i += 13;
            if (b[i++] !== 5) return 2;
            len();
            const aot = b[i] >> 3;
            return aot === 31 || !aot ? 2 : aot;
        } catch { return 2; }
    };
    const codecsOf = b => {
        const hex = n => n.toString(16).padStart(2, '0');
        const v = boxAt(b, 'avcC'), h = v < 0 ? boxAt(b, 'hvcC') : -1;
        if (v < 0 && h < 0) return null;
        const out = [v >= 0 ? `avc1.${hex(b[v + 1])}${hex(b[v + 2])}${hex(b[v + 3])}` : hevcCodecString({ space: b[h + 1] >> 6, tier: (b[h + 1] >> 5) & 1, prof: b[h + 1] & 31, compat: [...b.subarray(h + 2, h + 6)], cons: [...b.subarray(h + 6, h + 12)], level: b[h + 12] })];
        if (boxAt(b, 'mp4a') >= 0) {
            const e = boxAt(b, 'esds');
            out.push(`mp4a.40.${e >= 0 ? aotOf(b, e) : 2}`);
        }
        return out.join(',');
    };
    const audioCodecOf = b => {
        if (boxAt(b, 'mp4a') >= 0) { const e = boxAt(b, 'esds'); return `mp4a.40.${e >= 0 ? aotOf(b, e) : 2}`; }
        if (boxAt(b, 'Opus') >= 0) return 'opus';
        if (boxAt(b, 'ac-3') >= 0) return 'ac-3';
        if (boxAt(b, 'ec-3') >= 0) return 'ec-3';
        return null;
    };
    const timescaleOf = b => {
        const i = boxAt(b, 'mdhd');
        if (i < 0) return 0;
        const dv = new DataView(b.buffer, b.byteOffset, b.byteLength);
        return b[i] === 1 ? dv.getUint32(i + 20) : dv.getUint32(i + 12);
    };
    const tfdtSec = (b, ts) => {
        const i = boxAt(b.subarray(0, Math.min(b.length, 1024)), 'tfdt');
        if (i < 0 || !ts) return null;
        const dv = new DataView(b.buffer, b.byteOffset, b.byteLength);
        const t = b[i] === 1 ? dv.getUint32(i + 4) * TWO32 + dv.getUint32(i + 8) : dv.getUint32(i + 4);
        return t / ts;
    };
    const isInit = d => d.length > 8 && d[4] === 0x66 && d[5] === 0x74 && d[6] === 0x79 && d[7] === 0x70;

    const bufferedHas = (b, t) => {
        for (let i = 0; i < b.length; i++) if (t >= b.start(i) - 0.05 && t <= b.end(i)) return true;
        return false;
    };
    class HlsPlayer {
        constructor(o) {
            this.o = { maxHeight: Infinity, maxBacklog: 2, keep: 30, lag: 10, ...o };
            this.v = this.o.video;
            this.ms = null; this.sb = null; this.src = ''; this.q = []; this.codec = ''; this.ts = 90000;
            this.align = false; this.dead = false; this.started = false; this.cap = null; this.timer = 0; this.error = null; this.shift = false; this.full = false;
            this.asb = null; this.aq = []; this.acap = null; this.aCodec = ''; this.wantAudio = false; this.aDead = false; this.t0 = Date.now();
        }
        get stats() { return this.cap ? this.cap.stats : {}; }
        async start() {
            if (typeof MediaSource === 'undefined') throw new Error('MediaSource is not supported');
            const ms = this.ms = new MediaSource();
            this.src = URL.createObjectURL(ms);
            this.v.src = this.src;
            await new Promise((res, rej) => { const tm = setTimeout(() => rej(new Error('sourceopen timeout')), 10000); ms.addEventListener('sourceopen', () => { clearTimeout(tm); res(); }, { once: true }); });
            if (this.dead) return { error: null, stats: {} };
            this._onErr = () => this.fail(new Error('video error'));
            this.v.addEventListener('error', this._onErr);
            this._onPause = () => { if (!this.dead && this.started) { this.o.hold = true; this.shift = true; } };
            this._onPlay = () => { this.o.hold = false; if (this.full) { this.full = false; this._pump(); } };
            this.v.addEventListener('pause', this._onPause);
            this.v.addEventListener('play', this._onPlay);
            this.timer = setInterval(() => this._tick(), 1000);
            this.cap = new HlsCapture({
                url: this.o.url, fetchBin: this.o.fetchBin, maxHeight: this.o.maxHeight, maxBacklog: this.o.maxBacklog, gate: () => this._gate(),
                hint: this.o.hint, onOpened: this.o.onOpened,
                partBytes: Infinity, totalBytes: 0,
                onAudio: au => this._startAudio(au),
                makeSink: () => this._sink(), onPart: async () => {}
            });
            const res = await this.cap.start();
            this.acap?.stop();
            const out = { error: this.error || res.error, stats: res.stats };
            if (!this.dead) this.o.onEnd?.(out);
            return out;
        }
        _sinkTo(fn) { return { size: 0, append(d) { this.size += d.byteLength; fn(d); }, finish: async () => new Blob([]), remove: async () => {} }; }
        _sink() { return this._sinkTo(d => this._enqueue(d)); }
        _enqueue(d) {
            if (this.dead) return;
            this.q.push({ init: isInit(d), data: d });
            this._pump();
        }
        _startAudio(au) {
            if (this.dead || this.acap) return;
            this.wantAudio = true;
            this.acap = new HlsCapture({
                url: au.url, fetchBin: this.o.fetchBin, maxBacklog: this.o.maxBacklog, gate: () => this._aGate(),
                partBytes: Infinity, totalBytes: 0,
                makeSink: () => this._aSink(), onPart: async () => {}
            });
            this.acap.start().then(r => { if (!this.dead && r && r.error) this._dropAudio(r.error); }).catch(e => this._dropAudio(e));
        }
        _aSink() { return this._sinkTo(d => this._aEnqueue(d)); }
        _aEnqueue(d) {
            if (this.dead || this.aDead) return;
            this.aq.push({ init: isInit(d), data: d });
            this._pump();
            this._aPump();
        }
        _aGate() { return this._gateOf(this.aq); }
        _aMime(data) {
            const codec = audioCodecOf(data);
            if (!codec) throw new Error('unsupported audio codec');
            const mime = `audio/mp4; codecs="${codec}"`;
            if (!MediaSource.isTypeSupported(mime)) throw new Error(`unsupported audio ${codec}`);
            return { codec, mime };
        }
        _aCreate(data) {
            const { codec, mime } = this._aMime(data);
            const sb = this.asb = this.ms.addSourceBuffer(mime);
            sb.addEventListener('updateend', () => { this._after(); this._aPump(); });
            sb.addEventListener('error', () => this._dropAudio(new Error('audio append error')));
            this.aCodec = codec;
        }
        _aInit(data) {
            if (!this.asb) { this._aCreate(data); return; }
            const { codec, mime } = this._aMime(data);
            if (codec !== this.aCodec) { this.asb.changeType(mime); this.aCodec = codec; }
        }
        _audioReady() {
            if (!this.wantAudio || this.aDead || this.asb) return true;
            const a = this.aq[0];
            if (!a || !a.init) return false;
            try { this._aCreate(a.data); queueMicrotask(() => this._aPump()); } catch (e) { this._dropAudio(e); }
            return true;
        }
        _aOpen() { return !this.dead && !this.aDead && !!this.ms && this.ms.readyState === 'open'; }
        _aCanPump() { return this._aOpen() && this.aq.length > 0 && !(this.asb && this.asb.updating); }
        _aPrepare(op) {
            if (op.init) { this._aInit(op.data); return true; }
            if (!this.asb) return false;
            if (this.sb) this.asb.timestampOffset = this.sb.timestampOffset;
            return true;
        }
        _aPump() {
            if (!this._aCanPump() || !this.asb) return;
            const op = this.aq[0];
            try {
                const ok = this._aPrepare(op);
                this.aq.shift();
                if (ok) this.asb.appendBuffer(op.data);
            } catch (e) { this._aErr(e, op); }
        }
        _aErr(e, op) {
            if (!e || e.name !== 'QuotaExceededError') { this._dropAudio(e); return; }
            if (this.aq[0] !== op) this.aq.unshift(op);
            if (!this._trim(true) && !(this.v.paused || this.o.hold)) this._dropAudio(e);
        }
        _dropAudio(e) {
            if (this.aDead) return;
            this.aDead = true;
            this.o.onAudioFail?.(e);
            try { this.acap?.stop(); } catch (e) { dbg(e, 'HlsPlayer._dropAudio'); }
            this.aq.length = 0;
            if (this.asb) {
                try { this.asb.abort(); } catch (e) { dbg(e, 'HlsPlayer._dropAudio'); }
                try { this.ms.removeSourceBuffer(this.asb); } catch (e) { dbg(e, 'HlsPlayer._dropAudio'); }
                this.asb = null;
            }
            this._after();
            this._pump();
        }
        _ahead() { const b = this.v.buffered; return b.length ? b.end(b.length - 1) - this.v.currentTime : 0; }
        _gateOf(q) { return this.full || q.length > 6 || (!!this.o.hold && this._ahead() > 90); }
        _gate() { return this._gateOf(this.q); }
        _end() { const b = this.sb && this.sb.buffered; return b && b.length ? b.end(b.length - 1) : 0; }
        _canPump() {
            const ms = this.ms;
            if (this.dead || !ms || ms.readyState !== 'open' || !this.q.length) return false;
            return !(this.sb && this.sb.updating);
        }
        _createBuffer(mime, codec) {
            if (!MediaSource.isTypeSupported(mime)) throw new Error(`unsupported codec ${codec}`);
            const sb = this.sb = this.ms.addSourceBuffer(mime);
            sb.addEventListener('updateend', () => { this._after(); this._pump(); });
            sb.addEventListener('error', () => this.fail(new Error('append error')));
            this.codec = codec;
        }
        _applyInit(data) {
            const codec = codecsOf(data);
            if (!codec) throw new Error('unsupported codec');
            const mime = `video/mp4; codecs="${codec}"`;
            if (!this.sb) this._createBuffer(mime, codec);
            else {
                if (codec !== this.codec) { this.sb.changeType(mime); this.codec = codec; }
                this.align = true;
            }
            this.ts = timescaleOf(data) || 90000;
        }
        _alignOffset(data) {
            this.align = false;
            const t0 = tfdtSec(data, this.ts), end = this._end();
            this.sb.timestampOffset = (t0 !== null && t0 + 0.5 < end) ? end - t0 : 0;
        }
        _onAppendError(e, op) {
            if (!e || e.name !== 'QuotaExceededError') { this.fail(e); return; }
            if (this.q[0] !== op) this.q.unshift(op);
            if (this._trim(true)) return;
            if (this.v.paused || this.o.hold) this.full = true; else this.fail(e);
        }
        _pump() {
            if (!this._canPump()) return;
            const op = this.q[0];
            if (op.init && !this.sb && !this._audioReady()) return;
            try {
                if (op.init) this._applyInit(op.data);
                else if (this.align) this._alignOffset(op.data);
                this.q.shift();
                this.sb.appendBuffer(op.data);
            } catch (e) { this._onAppendError(e, op); }
        }
        _trimAudio(cut) {
            const a = this.asb;
            if (!a || a.updating || !a.buffered.length || cut <= a.buffered.start(0)) return;
            try { a.remove(a.buffered.start(0), cut); } catch (e) { dbg(e, 'HlsPlayer._trimAudio'); }
        }
        _trim(force) {
            const sb = this.sb;
            if (!sb || sb.updating || !sb.buffered.length) return false;
            const s = sb.buffered.start(0), cut = this.v.currentTime - (force ? 2 : this.o.keep);
            if (cut - s <= 1) return false;
            this._trimAudio(cut);
            try { sb.remove(s, cut); return true; } catch { return false; }
        }
        _after() {
            const v = this.v;
            if (!this.started && v.buffered.length && !(this.wantAudio && !this.asb && !this.aDead)) {
                this.started = true; this.tStart = Date.now();
                try { v.currentTime = v.buffered.start(0) + 0.001; } catch (e) { dbg(e, 'HlsPlayer._after'); }
                v.play().catch(() => { if (!v.muted) { v.muted = true; this.o.onMuted?.(); v.play().catch(() => {}); } });
            }
        }
        _seekIntoBuffer(b) {
            const v = this.v, cur = v.currentTime;
            for (let i = 0; i < b.length; i++) if (b.start(i) > cur) { v.currentTime = b.start(i) + 0.001; return; }
            if (cur < b.start(0)) v.currentTime = b.start(0) + 0.001;
        }
        _chaseLive(b) {
            const v = this.v, cur = v.currentTime, end = b.end(b.length - 1);
            if (end - cur > this.o.lag && !this.shift) v.currentTime = end - 1.5;
            else if (v.paused && !v.ended && !this.o.hold) v.play().catch(() => {});
        }
        _audioTick() {
            if (!this.wantAudio || this.aDead) return;
            this._pump();
            this._aPump();
            if (!this.started && Date.now() - this.t0 > 15000) this._dropAudio(new Error('audio timeout'));
        }
        _diag() {
            const r = b => { try { return b && b.length ? `${b.start(0).toFixed(1)}-${b.end(b.length - 1).toFixed(1)}` : '-'; } catch { return '?'; } };
            const v = this.v;
            return `no video v${this.sb ? r(this.sb.buffered) : 'x'} a${this.asb ? r(this.asb.buffered) : 'x'} s${v.readyState} t${v.currentTime.toFixed(1)}${this.aDead ? ' ad' : ''}`;
        }
        _videoWatch() {
            if (!this.started || this.error || this.v.videoWidth || Date.now() - this.tStart < 8000) return;
            this.fail(new Error(this._diag()));
        }
        _syncPlayback() {
            const v = this.v, b = v.buffered;
            if (!this.started || !b.length) return;
            if (bufferedHas(b, v.currentTime)) this._chaseLive(b); else this._seekIntoBuffer(b);
        }
        _tick() {
            if (this.dead) return;
            this._audioTick();
            const v = this.v, sb = this.sb;
            if (!sb) return;
            if (!sb.updating) this._trim(false);
            if (this.full && !v.paused) { this.full = false; this._pump(); }
            this._videoWatch();
            this._syncPlayback();
        }
        fail(e) {
            if (this.dead) return;
            this.error = this.error || e;
            this.o.onError?.(e);
            this.cap?.stop();
            this.acap?.stop();
        }
        stop() {
            if (this.dead) return;
            this.dead = true;
            this.cap?.stop();
            try { this.acap?.stop(); } catch (e) { dbg(e, 'HlsPlayer.stop'); }
            clearInterval(this.timer);
            this.q.length = 0; this.aq.length = 0;
            try { this.v.removeEventListener('error', this._onErr); this.v.removeEventListener('pause', this._onPause); this.v.removeEventListener('play', this._onPlay); } catch (e) { dbg(e, 'HlsPlayer.stop'); }
            try { unloadMedia(this.v); } catch (e) { dbg(e, 'HlsPlayer.stop'); }
            if (this.src) { try { URL.revokeObjectURL(this.src); } catch (e) { dbg(e, 'HlsPlayer.stop'); } }
        }
    }
    return { Fmp4Mux, NeedNewInit, demuxTs, parseSps, parseHevcSps, hevcCodecString, codecsOf, parsePlaylist, pickVariant, DiskBuffer, HlsCapture, HlsPlayer, buildInit };
})();

const REC_MIME_MP4 = ['video/mp4;codecs=avc1,mp4a.40.2', 'video/mp4'];
const REC_MIME_WEBM = ['video/webm;codecs=vp9,opus', 'video/webm;codecs=vp8,opus', 'video/webm'];
const recMimeFor = fmt => {
    const order = fmt === 'webm' ? [...REC_MIME_WEBM, ...REC_MIME_MP4] : [...REC_MIME_MP4, ...REC_MIME_WEBM];
    return order.find(t => MediaRecorder.isTypeSupported(t)) || 'video/webm';
};
const recFmtText = () => REC_FMT_T[AppState.lang] || REC_FMT_T.EN;

class MediaRecorderManager {
    constructor(videoController, uiController) {
        this.vc = videoController;
        this.ui = uiController;

        this.mRec = null;
        this.disk = null;
        this.interval = null;
        this.seconds = 0;
        this.totalSize = 0;
        this.savedBytes = 0;
        this.cfg = recCfg();
        this.partNum = 1;
        this.partStart = 0;
        this.recStartTime = '';
        this.splitRequested = false;
        this.wakeLock = null;
        this.currentStream = null;
        this._onTrackEnded = () => this.stopActive();

        onPageEvent('visibilitychange', () => {
            if (!realHidden() && this.mRec && this.mRec.state === 'recording') this.acquireWakeLock();
        });
        setTimeout(async () => {
            try {
                await HlsKit.DiskBuffer.cleanup();
                const n = (await HlsKit.DiskBuffer.orphans()).length;
                const b = this.ui?.elements?.bRec;
                if (n && b) this.ui.flash(b, `↻ ${n}`);
            } catch (e) { dbg(e, 'MediaRecorderManager'); }
        }, 6000);
        window.addEventListener('beforeunload', e => {
            if (this.mRec && this.mRec.state === 'recording') {
                e.preventDefault();
                e.returnValue = '';
            }
        });
    }

    getStream(v) {
        return v.captureStream?.(30) ?? v.mozCaptureStream?.(30) ?? null;
    }

    acquireWakeLock() {
        if (!('wakeLock' in navigator) || this.wakeLock || this._wlPending) return;
        this._wlPending = true;
        navigator.wakeLock.request('screen').then(wl => {
            this._wlPending = false;
            if (!(this.mRec && this.mRec.state !== 'inactive')) { wl.release().catch(() => {}); return; }
            this.wakeLock = wl;
            wl.addEventListener('release', () => { if (this.wakeLock === wl) this.wakeLock = null; });
        }).catch(() => { this._wlPending = false; });
    }

    stopActive() {
        if (this.mRec && this.mRec.state !== 'inactive') {
            this.splitRequested = false;
            try { this.mRec.stop(); } catch (e) { dbg(e, 'MediaRecorderManager.stopActive'); }
        }
    }

    cleanupSession(btn, sub) {
        clearInterval(this.interval);
        btn.classList.remove('on', 'rec-active');
        if (sub) sub.textContent = L().rec;
        if (this.wakeLock) { this.wakeLock.release().catch(() => {}); this.wakeLock = null; }
        try { stopTracks(this.currentStream); } catch (e) { dbg(e, 'MediaRecorderManager.cleanupSession'); }
    }

    saveBlob(blob, ext, durationMs, name, disk) {
        return saveRecording(blob, ext, durationMs, name, disk);
    }

    async recoverOrphans() {
        let n = 0;
        for (const e of await HlsKit.DiskBuffer.orphans(undefined, 0)) {
            const ext = e.meta.ext || 'webm';
            await this.saveBlob(HlsKit.DiskBuffer.blobOf(e), ext, 0, `${e.meta.name || 'REC'}_recovered_${e.id}.${ext}`, null);
            await HlsKit.DiskBuffer.markDoneById(e.id);
            n++;
            await wait(800);
        }
        return n;
    }

    async fillRecovery(box) {
        try {
            const n = (await HlsKit.DiskBuffer.orphans(undefined, 0)).length;
            if (!n || !box.isConnected) return;
            const b = textEl('bm-tbtn', `${L().rs_recover} (${n})`, 'button');
            b.onclick = async () => { b.disabled = true; await this.recoverOrphans(); b.remove(); };
            box.append(b);
        } catch (e) { dbg(e, 'MediaRecorderManager.fillRecovery'); }
    }

    hlsFetch(url, range) {
        return gmBytes(url, null, false, range);
    }

    startHls(btn, sub) {
        const url = getStreamUrl();
        if (!url) throw new Error('no hls url');
        const cfg = this.cfg = recCfg();
        const base = `${currentSite.getName() || 'REC'}_${this.recStartTime = Helpers.getTimestamp()}`;
        this.partNum = 1; this.seconds = 0; this.savedBytes = 0; this.totalSize = 0; this.currentStream = null;
        let userStop = false;
        const cap = newHlsCapture({
            url, cfg, base, fetchBin: (u, r) => this.hlsFetch(u, r),
            onProgress: st => { this.totalSize = st.part; this.savedBytes = st.saved; },
            save: (blob, name, sink) => this.saveBlob(blob, 'mp4', 0, name, sink)
        });
        const shim = this.mRec = { state: 'recording', stop: () => { userStop = true; shim.state = 'inactive'; cap.stop(); } };
        this.hls = cap;
        this.startTicker(btn, sub);
        this.acquireWakeLock();
        cap.start().then(res => {
            shim.state = 'inactive';
            if (this.hls !== cap || this.mRec !== shim) return;
            const v = this.vc.cachedVideo;
            if (res.error && !userStop && !res.stats.segments && v) {
                this.ui.flash(btn, '! HLS');
                try { this.start(v, btn, sub); return; } catch (e) { dbg(e, 'MediaRecorderManager.startHls'); }
            }
            if (res.error && !userStop) this.ui.flash(btn, `! ${L().err}`);
            this.cleanupSession(btn, sub);
        });
    }

    updateInfo() {
        const el = this._rs?.info;
        if (!el || !el.isConnected) return;
        const l = L(), on = this.mRec && this.mRec.state !== 'inactive';
        el.textContent = on ? `${l.rs_now}: ${l.rs_p} ${fmtBytes(this.totalSize)} · ${l.rs_t} ${fmtBytes(this.savedBytes + this.totalSize)}` : '';
    }

    buildRsShell() {
        const { overlay, modal, title, closeBtn, body } = buildPultShell(this.ui.shadow, RS_PULT_CSS);
        const close = () => { overlay.classList.remove('open'); clearInterval(this._rsTimer); overlay._fromSt = false; overlay._bk?.(); };
        closeBtn.onclick = close;
        overlay.onclick = e => { if (e.target === overlay && Date.now() - (this._rsAt || 0) > 400) close(); };
        this._rs = { overlay, title, body, close, info: null };
    }

    renderRecSettings() {
        const { title, body } = this._rs;
        const l = L(), cfg = this.cfg = recCfg();
        pultTitle(title, icons.rec, l.rs_title);
        const set = patch => { Store.set('bc_rec', { ...recCfg(), ...patch }); this.renderRecSettings(); };
        const chip = chipBtn;
        const size = v => v >= 1024 ? `${v / 1024} ${l.u_gb}` : `${v} ${l.u_mb}`;

        const qs = mk('div', 'rs-qs');
        Object.keys(REC_Q).forEach(q => {
            const mb = (REC_Q[q][0] + REC_Q[q][1]) / 8 * 60 / 1048576;
            const b = mk('button', `bm-btn${cfg.q === q ? ' sel' : ''}`);
            const r1 = mk('b'); r1.textContent = REC_RES[q];
            const r2 = mk('span'); r2.textContent = l['rs_q_' + q];
            const r3 = mk('small'); r3.textContent = `${l.rs_upto} ${Math.round(mb)} ${l.rs_mbmin} · ${(mb * 60 / 1024).toFixed(1)} ${l.rs_gbh}`;
            b.append(r1, r2, r3);
            b.onclick = () => set({ q });
            qs.append(b);
        });
        const fmts = mk('div', 'rs-chips');
        [['mp4', 'MP4'], ['webm', 'WebM']].forEach(([v, n]) => fmts.append(chip(n, cfg.fmt === v, () => set({ fmt: v }))));
        const fmtNote = cfg.fmt === 'mp4' && typeof MediaRecorder !== 'undefined' && !REC_MIME_MP4.some(t => MediaRecorder.isTypeSupported(t))
            ? textEl('rs-note', recFmtText()[1]) : null;
        const srcs = mk('div', 'rs-chips rs-src');
        [['cap', l.rs_src_cap], ['hls', l.rs_src_hls]].forEach(([v, n]) => srcs.append(chip(n, cfg.src === v, () => set({ src: v }))));
        const recov = mk('div', 'rs-chips');
        this.fillRecovery(recov);
        const parts = mk('div', 'rs-chips');
        REC_PART.forEach(v => parts.append(chip(size(v), cfg.part === v, () => set({ part: v }))));
        const totals = mk('div', 'rs-chips');
        REC_TOTAL.forEach(v => totals.append(chip(v ? size(v) : l.rs_off, cfg.total === v, () => set({ total: v }))));
        const info = mk('div', 'rs-info');
        this._rs.info = info;
        const vid = (typeof largestVideo === 'function' && largestVideo()) || null;
        const res = vid && vid.videoHeight ? `${vid.videoWidth}×${vid.videoHeight}` : '—';
        const resEl = textEl('rs-note', `${l.rs_cur} ${res}`);
        const resNote = textEl('rs-note', l.rs_resnote);

        const sum = htmlEl('rs-sum', icons.rec);
        const sumTx = mk('span');
        sumTx.textContent = [REC_RES[cfg.q], cfg.fmt === 'mp4' ? 'MP4' : 'WebM', size(cfg.part), cfg.total ? size(cfg.total) : l.rs_off].join(' · ');
        sum.appendChild(sumTx);
        const grp = (...kids) => { const g = mk('div', 'rs-g'); g.append(...kids); return g; };
        body.replaceChildren(
            sum,
            grp(textEl('hd-sec', l.rs_quality), qs),
            grp(textEl('hd-sec', l.rs_src), srcs, textEl('rs-note', l.rs_src_note), recov),
            grp(textEl('hd-sec', recFmtText()[0]), fmts, ...(fmtNote ? [fmtNote] : []),
                textEl('hd-sec', l.rs_part), parts,
                textEl('hd-sec', l.rs_total), totals),
            info,
            resEl, resNote,
            textEl('rs-note', l.rs_hint),
            textEl('rs-note', l.rs_next)
        );
        this.updateInfo();
    }

    openSettings() {
        if (!this._rs) this.buildRsShell();
        const { overlay } = this._rs;
        this.renderRecSettings();
        this._rsAt = Date.now();
        overlay.classList.add('open');
        clearInterval(this._rsTimer);
        this._rsTimer = setInterval(() => {
            if (!overlay.classList.contains('open')) return clearInterval(this._rsTimer);
            this.updateInfo();
        }, 1000);
    }

    toggle() {
        const v = this.vc.cachedVideo;
        const btn = this.ui.elements.bRec;
        const sub = btn.querySelector('sub');
        if (this.mRec && this.mRec.state !== 'inactive') return this.stop(btn, sub);
        if (recCfg().src === 'hls' && getStreamUrl()) { try { return this.startHls(btn, sub); } catch (e) { dbg(e, 'MediaRecorderManager.toggle'); } }
        if (!v) return this.ui.flash(btn, `! ${L().no_vid}`);
        try { this.start(v, btn, sub); } catch { this.ui.flash(btn, `! ${L().err}`); }
    }

    stop(btn, sub) {
        this.splitRequested = false;
        try { this.mRec.stop(); } catch { stopTracks(this.currentStream); }
        this.cleanupSession(btn, sub);
    }

    start(v, btn, sub) {
        const s = this.getStream(v);
        if (!s) throw new Error('no stream');
        this.currentStream = s;
        if (!s.getAudioTracks().length) this.ui.flash(btn, '! AUD');

        const mt = recMimeFor(recCfg().fmt);
        this.recStartTime = Helpers.getTimestamp();
        this.partNum = 1;
        this.seconds = 0;
        this.savedBytes = 0;
        this.cfg = recCfg();
        this.startNewRecord(s, mt, mt.includes('mp4') ? 'mp4' : 'webm', btn, sub);
        this.startTicker(btn, sub);
    }

    startTicker(btn, sub) {
        btn.classList.add('on', 'rec-active');
        const label = () => `${Helpers.formatTime(this.seconds)} | ${fmtShort(this.savedBytes + this.totalSize)}`;
        if (sub) sub.textContent = label();
        clearInterval(this.interval);
        const t0 = Date.now();
        this.interval = setInterval(() => { this.seconds = Math.floor((Date.now() - t0) / 1000); if (sub) sub.textContent = label(); }, 1000);
    }

    initMediaRecorder(stream, mimeType) {
        this.totalSize = 0;
        this.mRec = null;
        this.mRec = newMediaRecorder(stream, mimeType, this.cfg.q);
        if (!this.mRec) throw new Error('MediaRecorder init failed');
    }

    bindRecorderEvents(stream, mimeType, ext, btn, sub) {
        const stopRec = () => { try { this.mRec.stop(); } catch (e) { dbg(e, 'bindRecorderEvents.stopRec'); } };
        const savePart = (disk, dur) => {
            const suffix = (this.partNum > 1 || this.splitRequested) ? `_PT${this.partNum}` : '';
            const name = `${currentSite.getName() || 'REC'}_${this.recStartTime}${suffix}.${ext}`;
            disk.finish().then(blob => this.saveBlob(blob, ext, dur, name, disk)).catch(() => {});
        };
        const continueSplit = () => {
            this.partNum++;
            this.splitRequested = false;
            const v = this.vc.cachedVideo;
            const nextStream = (!stream.active && v ? this.getStream(v) : null) || stream;
            this.currentStream = nextStream;
            try { this.startNewRecord(nextStream, mimeType, ext, btn, sub); return true; } catch { return false; }
        };

        this.mRec.ondataavailable = e => {
            if (!(e.data?.size > 0)) return;
            this.disk.append(e.data);
            this.totalSize += e.data.size;
            const limTotal = this.cfg.total * 1048576;
            if (limTotal && this.savedBytes + this.totalSize >= limTotal) { this.splitRequested = false; return stopRec(); }
            if (this.totalSize >= this.cfg.part * 1048576 && !this.splitRequested) { this.splitRequested = true; stopRec(); }
        };
        this.mRec.onerror = () => this.stopActive();
        this.mRec.onstop = () => {
            const disk = this.disk;
            this.disk = null;
            const dur = performance.now() - this.partStart;
            if (disk && disk.size > 0) savePart(disk, dur); else disk?.remove();
            this.savedBytes += this.totalSize;
            if (this.splitRequested && continueSplit()) return;
            this.cleanupSession(btn, sub);
        };
    }

    startNewRecord(stream, mimeType, ext, btn, sub) {
        this.initMediaRecorder(stream, mimeType);
        this.disk = new HlsKit.DiskBuffer({ meta: { name: `${currentSite.getName() || 'REC'}_${this.recStartTime}_PT${this.partNum}`, ext, mime: mimeType.split(';')[0] } });
        stream.getTracks().forEach(t => t.addEventListener('ended', this._onTrackEnded));
        this.bindRecorderEvents(stream, mimeType, ext, btn, sub);

        try {
            this.partStart = performance.now();
            this.mRec.start(1000);
            this.acquireWakeLock();
        } catch (err) {
            this.cleanupSession(btn, sub);
            this.ui.flash(btn, `! ${L().err}`);
            throw err;
        }
    }
}

const MV_MAX = 100;
const MV_CSS = `.fav-modal .mv-pane {
  display: none
}
.bm-overlay.pipkeep:not(.open) {
  opacity: .02!important
}
.fav-modal:not(.mode-mv) .mv-pane.keep {
  display: flex!important;
  position: fixed!important;
  left: 0!important;
  top: 0!important;
  width: 100%!important;
  height: auto!important;
  opacity: .02!important;
  pointer-events: none!important;
  z-index: 0!important
}
.bm-overlay.pipkeep:not(.open) .fav-modal:not(.mode-mv) .mv-pane.keep {
  opacity: 1!important
}
.fav-modal.mode-mv .mv-pane {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  gap: 10px;
  padding: 12px 14px
}
.fav-modal.mode-mv .bm-search-wrap,
.fav-modal.mode-mv .bm-tags-wrap,
.fav-modal.mode-mv .bm-list,
.fav-modal.mode-mv .bm-foot,
.fav-modal.mode-mv .bm-tools,
.fav-modal.mode-mv .bm-setp,
.fav-modal.mode-mv .hs-pane,
.fav-modal.mode-mv .cal-pane {
  display: none!important
}
.mv-bar {
  display: flex;
  flex: none;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px
}
.mv-bar .bm-tbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px
}
.mv-bar .bm-tbtn svg {
  display: block;
  flex: none;
  margin: 0
}
.mv-bar {
  flex-wrap: nowrap
}
.mv-bar>* {
  flex: 1 1 0!important;
  min-width: 0!important;
  margin: 0!important
}
.mv-bar>:first-child {
  flex: 2.4 1 0!important
}
.mv-bar>:not(:first-child) {
  padding: 0!important
}
.mv-bar>.bm-hbtn {
  width: auto!important;
  height: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center
}
.mv-bar>.bm-hbtn svg {
  width: 18px!important;
  height: 18px!important
}
.mv-bar>.bm-hbtn.on {
  background: var(--th);
  border-color: transparent
}
.mv-bar>.bm-gear {
  border: 1px solid var(--th)!important
}
.mv-bar>.bm-gear.on {
  border-color: transparent!important
}
.mv-bar>.mv-clr {
  color: #e5707e!important;
  border-color: rgba(229,112,126,.45)!important
}
.mv-stage {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  display: flex
}
.mv-grid {
  flex: 1;
  display: grid;
  gap: 8px;
  align-content: start;
  min-width: 0;
  min-height: 0;
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: rgba(255,255,255,.2) transparent
}
.mv-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  min-width: 0;
  background: #000;
  border-radius: 12px;
  overflow: hidden;
  outline: 2px solid transparent;
  outline-offset: -2px;
  cursor: pointer
}
.mv-tile.act {
  outline-color: var(--th,#3b82f6)
}
.mv-tile video {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  background: #000;
  pointer-events: none
}
.mv-tile video.bc-blur-mute {
  filter: blur(24px);
  transition: filter .2s
}
.mv-stg {
  position: relative;
  flex: none;
  aspect-ratio: 16/9;
  min-width: 0;
  background: #000
}
.mv-nm {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis
}
.mv-vw {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-inline-start: auto
}
.mv-vw svg {
  width: 12px;
  height: 12px
}
.mv-lbl {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: none;
  padding: 5px 10px;
  background: rgba(255,255,255,.08);
  color: #fff;
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  pointer-events: none
}
.mv-tb {
  position: static;
  flex: none;
  display: grid;
  grid-template-columns: repeat(3,minmax(0,1fr));
  gap: 6px;
  padding: 6px 8px;
  background: rgba(255,255,255,.04);
  cursor: default
}
.mv-tb button {
  width: 100%;
  min-width: 0;
  height: 34px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: rgba(0,0,0,.65);
  color: #fff;
  cursor: pointer
}
.mv-tb button:hover {
  background: rgba(0,0,0,.85)
}
.mv-msg {
  white-space: pre-line;
  word-break: break-word;
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 8px;
  font-size: 12px;
  color: #ddd;
  background: rgba(0,0,0,.5);
  pointer-events: none
}
.mv-msg.err {
  pointer-events: auto;
  cursor: pointer
}
.mv-msg.park {
  pointer-events: auto;
  cursor: pointer;
  font-size: clamp(48px,20vw,92px);
  background: rgba(0,0,0,.18);
  text-shadow: 0 2px 8px rgba(0,0,0,.7)
}
.mv-grid.multi .mv-msg.park {
  font-size: clamp(36px,12vw,56px)
}
.mv-grid.multi .mv-ctl {
  display: none!important
}
.mv-tile.bad {
  min-height: 120px
}
.mv-tile.bad .mv-stg {
  aspect-ratio: auto;
  min-height: 120px
}
.mv-tile.bad video {
  display: none
}
.mv-tile.bad .mv-msg {
  position: relative;
  inset: auto;
  padding: 34px 12px 14px;
  font-size: 11px;
  line-height: 1.35
}
.mv-msg.hide {
  display: none
}
.mv-tile:fullscreen {
  aspect-ratio: auto;
  width: 100%;
  height: 100%;
  border-radius: 0;
  outline: 0
}
.mv-tile:-webkit-full-screen {
  aspect-ratio: auto;
  width: 100%;
  height: 100%;
  border-radius: 0;
  outline: 0
}
.mv-tile:fullscreen .mv-stg,
.mv-tile:-webkit-full-screen .mv-stg {
  aspect-ratio: auto;
  flex: 1;
  min-height: 0
}
.mv-ctl {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 22px 8px 6px;
  background: linear-gradient(transparent,rgba(0,0,0,.82));
  display: none;
  flex-direction: column;
  gap: 2px;
  cursor: default;
  user-select: none;
  -webkit-user-select: none
}
.mv-tile.act.ui .mv-ctl {
  display: flex
}
.mv-tile.bad .mv-ctl {
  display: none!important
}
.mv-big {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 2;
  width: 48px;
  height: 48px;
  margin: -24px 0 0 -24px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(0,0,0,.55);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity .2s;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent
}
.mv-tile.ui .mv-big {
  opacity: 1
}
.mv-big svg {
  display: block;
  width: 32px!important;
  height: 32px!important
}
.mv-grid.multi .mv-tile .mv-big {
  display: none
}
.mv-grid.multi .mv-tile:fullscreen .mv-big {
  display: flex
}
.mv-grid.multi .mv-tile:-webkit-full-screen .mv-big {
  display: flex
}
.mv-tile.bad .mv-big,
.mv-msg:not(.hide)~.mv-big {
  display: none!important
}
.mv-tile:fullscreen .mv-big {
  width: 63px;
  height: 63px;
  margin: -31.5px 0 0 -31.5px
}
.mv-tile:fullscreen .mv-big svg {
  width: 42px!important;
  height: 42px!important
}
.mv-tile:-webkit-full-screen .mv-big {
  width: 63px;
  height: 63px;
  margin: -31.5px 0 0 -31.5px
}
.mv-tile:-webkit-full-screen .mv-big svg {
  width: 42px!important;
  height: 42px!important
}
.mv-tile:fullscreen .mv-lbl {
  padding-right: calc(32px + env(safe-area-inset-right,0px))
}
.mv-tile:-webkit-full-screen .mv-lbl {
  padding-right: calc(32px + env(safe-area-inset-right,0px))
}
.mv-tile.nodvr .mv-sk,
.mv-tile.nodvr .mv-tm {
  display: none
}
.mv-sk {
  position: relative;
  height: 24px;
  touch-action: none;
  cursor: pointer;
  display: flex;
  align-items: center
}
.mv-sk-t {
  position: absolute;
  left: 0;
  right: 0;
  height: 4px;
  border-radius: 2px;
  background: rgba(255,255,255,.28)
}
.mv-sk-f {
  position: absolute;
  left: 0;
  height: 4px;
  border-radius: 2px;
  background: var(--th,#3b82f6);
  width: 0
}
.mv-sk-k {
  position: absolute;
  top: 6px;
  width: 12px;
  height: 12px;
  margin-left: -6px;
  border-radius: 50%;
  background: #fff;
  left: 0;
  box-shadow: 0 0 0 2px rgba(0,0,0,.35)
}
.mv-cr {
  display: flex;
  align-items: center;
  gap: 2px;
  min-width: 0
}
.mv-cb {
  flex: none;
  min-width: 34px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 0 6px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: #fff;
  font-size: 11px;
  font-weight: 600;
  line-height: 1;
  cursor: pointer
}
.mv-cb:hover {
  background: rgba(255,255,255,.16)
}
.mv-vol {
  --p:100%;
  flex: 0 1 110px;
  min-width: 28px;
  width: auto;
  height: 36px;
  margin: 0 6px;
  padding: 0;
  background: transparent;
  touch-action: none;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  -webkit-tap-highlight-color: transparent
}
.mv-vol::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: 2px;
  background: linear-gradient(90deg,var(--th,#3b82f6) var(--p),rgba(255,255,255,.3) var(--p))
}
.mv-vol::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 22px;
  height: 22px;
  margin-top: -9px;
  border-radius: 50%;
  background: #fff;
  border: 0;
  box-shadow: 0 0 0 2px rgba(0,0,0,.35)
}
.mv-vol::-moz-range-track {
  height: 4px;
  border-radius: 2px;
  background: rgba(255,255,255,.3)
}
.mv-vol::-moz-range-progress {
  height: 4px;
  border-radius: 2px;
  background: var(--th,#3b82f6)
}
.mv-vol::-moz-range-thumb {
  width: 22px;
  height: 22px;
  border: 0;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 0 2px rgba(0,0,0,.35)
}
.mv-lv {
  flex: none;
  height: 22px;
  padding: 0 9px;
  margin-left: 4px;
  border: 0;
  border-radius: 6px;
  background: rgba(255,255,255,.2);
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  line-height: 22px;
  letter-spacing: .4px;
  cursor: pointer
}
.mv-lv.on {
  background: #e53935
}
.mv-qb {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap
}
.mv-cr>.mv-cb:not(.mv-qb) {
  flex: none
}
.mv-cr>.mv-cb.rec {
  width: 44px;
  min-width: 44px;
  padding: 0;
  border-radius: 8px;
  background: #e53935;
  font-size: 10px
}
.mv-tm {
  flex: none;
  margin-left: 6px;
  color: #ddd;
  font-size: 11px;
  font-variant-numeric: tabular-nums
}
.mv-sp {
  flex: 1
}
.mv-qm {
  position: absolute;
  right: 8px;
  bottom: 50px;
  min-width: 150px;
  max-height: 70%;
  overflow-y: auto;
  padding: 4px;
  border-radius: 10px;
  background: rgba(20,20,24,.97);
  box-shadow: 0 6px 20px rgba(0,0,0,.5);
  display: none;
  flex-direction: column;
  z-index: 3
}
.mv-qm.open {
  display: flex
}
.mv-qi {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 10px;
  border-radius: 7px;
  color: #eee;
  font-size: 13px;
  cursor: pointer;
  white-space: nowrap
}
.mv-qi:hover {
  background: rgba(255,255,255,.1)
}
.mv-qi.sel {
  color: var(--th,#3b82f6);
  font-weight: 700
}
.mv-qs {
  height: 1px;
  margin: 4px 6px;
  background: rgba(255,255,255,.14)
}
.mv-empty {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 16px;
  color: var(--sk-mfg,#aaa);
  font-size: 13px
}
.mv-empty .bm-tbtn {
  display: inline-flex;
  align-items: center;
  gap: 6px
}
.mv-add {
  position: absolute;
  inset: 0;
  display: none;
  flex-direction: column;
  gap: 8px;
  background: transparent;
  z-index: 2;
  overflow: hidden
}
.mv-add.open {
  display: flex
}
.mv-setp {
  display: none;
  flex: none;
  flex-direction: column;
  gap: 2px;
  padding: 4px 10px;
  border-radius: 10px;
  background: rgba(0,0,0,.28)
}
.mv-setp.open {
  display: flex
}
.mv-stage.adding>.mv-grid,
.mv-stage.adding>.mv-empty {
  visibility: hidden
}
.mv-r2 {
  display: flex;
  flex: none;
  gap: 8px;
  align-items: center
}
.mv-inp {
  flex: 1;
  min-width: 0;
  box-sizing: border-box;
  height: 36px;
  padding: 0 12px;
  border-radius: 8px;
  border: 1px solid var(--sk-in,rgba(255,255,255,.18));
  background: var(--sk-fill,rgba(255,255,255,.06));
  color: var(--sk-fg,#fff);
  font: inherit;
  font-size: 14px;
  outline: 0;
  user-select: text;
  -webkit-user-select: text
}
.mv-add .bm-tbtn {
  flex: none;
  white-space: nowrap
}
.mv-hd {
  display: flex;
  flex: none;
  align-items: center;
  gap: 10px;
  font-weight: 600;
  font-size: 14px;
  color: var(--sk-fg,#fff)
}
.mv-sec {
  flex: none;
  font-size: 12px;
  color: var(--sk-mfg,#aaa)
}
.mv-it.cur {
  background: rgba(255,255,255,.1);
  font-weight: 600
}
.mv-add>.mv-inp {
  flex: none
}
.mv-list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 6px;
  color: var(--sk-mfg,#aaa);
  font-size: 13px;
  scrollbar-width: thin;
  scrollbar-color: rgba(255,255,255,.2) transparent
}
.mv-it {
  display: flex;
  flex: none;
  align-items: center;
  gap: 10px;
  padding: 6px 10px;
  border-radius: 10px;
  background: rgba(255,255,255,.09);
  cursor: pointer
}
.mv-it:hover {
  background: rgba(255,255,255,.11)
}
.mv-it.dis {
  opacity: .7;
  pointer-events: none
}
.mv-it img {
  width: 52px;
  height: 34px;
  object-fit: cover;
  border-radius: 6px;
  background: #000;
  flex: none
}
.mv-it b {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
  font-size: 13px;
  color: var(--sk-fg,#fff)
}
.mv-it i {
  font-style: normal;
  color: var(--sk-mfg,#9aa);
  font-size: 11px;
  flex: none
}
.mv-it {
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none
}
.mv-it img {
  pointer-events: none
}
.mv-ck {
  display: none;
  flex: none;
  width: 20px;
  height: 20px;
  box-sizing: border-box;
  border-radius: 50%;
  border: 2px solid rgba(255,255,255,.4);
  align-items: center;
  justify-content: center;
  font-size: 12px;
  line-height: 1;
  color: #fff
}
.mv-list.selmode .mv-ck {
  display: flex
}
.mv-it.sel {
  background: rgba(255,255,255,.16);
  box-shadow: inset 0 0 0 2px var(--th,#3b82f6)
}
.mv-it.sel .mv-ck {
  background: var(--th,#3b82f6);
  border-color: var(--th,#3b82f6)
}
.mv-it.sel .mv-ck::after {
  content: "\\2713"
}
.mv-hd .mv-gob {
  margin-left: auto
}
.mv-tabs {
  display: flex;
  flex: none;
  gap: 4px;
  padding: 4px;
  border-radius: 18px;
  background: rgba(255,255,255,.06)
}
.mv-tab {
  flex: 1;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: 14px;
  background: transparent;
  color: var(--sk-fg,#fff);
  font: inherit;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer
}
.mv-tab.on {
  background: var(--th,#3b82f6);
  color: #fff
}
.mv-off {
  display: none!important
}
.mv-chips {
  flex: none
}
.mv-add .mv-chips .bm-tbtn {
  flex: 1 1 auto;
  white-space: nowrap
}
.mv-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-right: 6px;
  border-radius: 50%;
  vertical-align: middle
}
.mv-fw {
  position: relative;
  flex: none;
  display: block
}
.mv-fw .mv-inp {
  display: block;
  width: 100%;
  padding-left: 44px
}
.mv-fi {
  position: absolute;
  left: 14px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  color: var(--sk-mfg,#aaa);
  pointer-events: none
}
.mv-fi svg {
  width: 20px;
  height: 20px
}
.mv-hd .bm-tbtn.mv-back {
  flex: none;
  width: 44px!important;
  height: 44px!important;
  padding: 0!important;
  border-radius: 50%!important
}
.mv-inf {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px
}
.mv-plus {
  flex: none;
  width: 48px;
  height: 48px;
  padding: 0;
  border: 0;
  border-radius: 16px;
  background: var(--th,#3b82f6);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer
}
.mv-it.dis .mv-plus {
  background: rgba(75,214,138,.16);
  color: #4bd68a
}
.mv-list.selmode .mv-plus {
  display: none
}
.mv-tb button {
  font-size: 10px;
  font-weight: 700
}
.mv-tb button.rec {
  width: 100%;
  padding: 0 4px;
  background: #e53935;
  font-size: 11px;
  font-variant-numeric: tabular-nums
}
.fav-modal.mode-mv .mv-pane {
  gap: 12px;
  padding: 12px
}
.mv-bar {
  gap: 8px;
  padding-bottom: 10px;
  border-bottom: 1px solid rgba(255,255,255,.07)
}
.mv-bar .bm-tbtn,
.mv-bar .bm-hbtn {
  height: 40px;
  margin: 0;
  padding: 0 14px;
  border-radius: 12px;
  font-size: 13px;
  font-weight: 600;
  border: 1px solid rgba(255,255,255,.12);
  background: rgba(255,255,255,.06);
  transition: background .15s,transform .1s,border-color .15s
}
.mv-bar .bm-tbtn.on {
  background: var(--th,#3b82f6);
  border-color: transparent;
  box-shadow: 0 2px 10px rgba(0,0,0,.35)
}
.mv-bar .bm-tbtn:not(.on):hover,
.mv-bar .bm-hbtn:hover {
  background: rgba(255,255,255,.14);
  border-color: rgba(255,255,255,.22)
}
.mv-bar .bm-tbtn:active,
.mv-bar .bm-hbtn:active {
  transform: scale(.96)
}
.mv-grid {
  gap: 12px;
  padding: 2px 2px 16px
}
.mv-tile {
  background: var(--sk-card,#16161a);
  border: 1px solid rgba(255,255,255,.1);
  border-radius: 16px;
  outline: 0;
  box-shadow: 0 4px 16px rgba(0,0,0,.45);
  transition: border-color .2s,box-shadow .2s
}
.mv-tile.act {
  border-color: var(--th,#3b82f6);
  box-shadow: 0 0 0 1px var(--th,#3b82f6),0 6px 22px rgba(0,0,0,.55)
}
.mv-lbl {
  padding: 9px 12px;
  font-size: 13px;
  font-weight: 700;
  background: linear-gradient(180deg,rgba(255,255,255,.1),rgba(255,255,255,.04));
  border-bottom: 1px solid rgba(255,255,255,.06)
}
.mv-nm {
  letter-spacing: .2px
}
.mv-vw {
  padding: 2px 8px;
  border-radius: 10px;
  background: rgba(0,0,0,.35);
  font-size: 11px;
  font-weight: 600
}
.mv-stg {
  border-radius: 0;
  overflow: hidden
}
.mv-tb {
  gap: 8px;
  padding: 8px 10px 10px;
  background: transparent;
  border-top: 1px solid rgba(255,255,255,.06)
}
.mv-tb button {
  height: 38px;
  border-radius: 12px;
  background: rgba(255,255,255,.07);
  border: 1px solid rgba(255,255,255,.1);
  transition: background .15s,transform .1s,border-color .15s
}
.mv-tb button:hover {
  background: rgba(255,255,255,.16);
  border-color: rgba(255,255,255,.22)
}
.mv-tb button:active {
  transform: scale(.93)
}
.mv-tb button svg {
  display: block;
  flex: none
}
.mv-tb button.rec {
  background: #e53935;
  border-color: transparent;
  box-shadow: 0 2px 10px rgba(229,57,53,.45)
}
.mv-tile.act .mv-tb button:nth-child(3) {
  background: var(--th,#3b82f6);
  border-color: transparent
}
.mv-tile:fullscreen .mv-tb,
.mv-tile:-webkit-full-screen .mv-tb {
  display: none
}
.mv-tile.bad .mv-tb {
  opacity: .85
}
.mv-ctl {
  padding: 26px 10px 8px
}
.mv-qm {
  border-radius: 12px;
  border: 1px solid rgba(255,255,255,.1)
}
.mv-empty {
  gap: 14px;
  font-size: 14px
}
.mv-empty .bm-tbtn {
  height: 42px;
  padding: 0 20px;
  border-radius: 14px;
  font-size: 14px
}
.mv-add {
  padding: 2px;
  gap: 10px
}
.mv-hd {
  font-size: 16px;
  font-weight: 700;
  gap: 12px
}
.mv-hd .bm-hbtn,
.mv-hd .bm-tbtn {
  height: 36px;
  padding: 0 14px;
  border-radius: 12px
}
.mv-sec {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .8px;
  text-transform: uppercase;
  padding: 2px 4px 0
}
.mv-inp {
  height: 48px;
  border-radius: 16px;
  padding: 0 14px;
  transition: border-color .15s,box-shadow .15s
}
.mv-inp:focus {
  border-color: var(--th,#3b82f6);
  box-shadow: 0 0 0 3px rgba(255,255,255,.08)
}
.mv-r2 .bm-tbtn {
  height: 48px;
  padding: 0 18px;
  border-radius: 16px
}
.mv-list {
  gap: 8px
}
.mv-it {
  background: rgba(255,255,255,.09);
  gap: 12px;
  padding: 8px;
  border-radius: 20px;
  border: 1px solid rgba(255,255,255,.07);
  transition: background .15s,border-color .15s,transform .1s
}
.mv-it:hover {
  border-color: rgba(255,255,255,.18)
}
.mv-it:active {
  transform: scale(.985)
}
.mv-it img {
  width: 72px;
  height: 56px;
  border-radius: 14px
}
.mv-it b {
  font-size: 16px
}
.mv-it i {
  font-size: 13px;
  font-weight: 600
}
.mv-bar {
  flex-wrap: nowrap;
  gap: 6px;
  padding-bottom: 8px
}
.mv-bar .bm-tbtn,
.mv-bar .bm-hbtn {
  height: 34px;
  padding: 0 10px;
  border-radius: 10px;
  font-size: 12px;
  gap: 5px;
  white-space: nowrap;
  flex: 0 1 auto;
  min-width: 0
}
.mv-bar .bm-tbtn svg,
.mv-bar .bm-hbtn svg {
  width: 14px;
  height: 14px
}
.mv-bar .bm-hbtn {
  min-width: 34px;
  justify-content: center
}
.mv-grid {
  gap: 10px
}
.mv-tile {
  border-radius: 14px
}
.mv-lbl {
  padding: 6px 10px;
  font-size: 12px
}
.mv-vw {
  padding: 1px 7px;
  font-size: 10px
}
.mv-tb {
  gap: 6px;
  padding: 6px 8px 8px
}
.mv-tb button {
  height: 30px;
  border-radius: 9px;
  background: rgba(0,0,0,.45)
}
.mv-tb button svg {
  width: 14px;
  height: 14px
}
.mv-tb button.rec {
  font-size: 10px
}
.mv-grid {
  grid-auto-rows: max-content;
  align-content: start
}
.mv-tile {
  flex: none;
  min-height: min-content
}
.mv-tile.bad {
  min-height: 120px
}
.mv-tile:fullscreen,
.mv-tile:-webkit-full-screen {
  min-height: 0
}
.mv-grid::after {
  content: '';
  display: block;
  height: 1px
}
.mv-tb {
  padding-bottom: 10px
}
.mv-tb button svg,
.mv-cb svg {
  display: block;
  flex: none;
  margin: 0;
  overflow: visible
}
.mv-tile,
.mv-tile *,
.mv-grid,
.mv-bar,
.mv-bar * {
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none
}
.mv-tile *::selection,
.mv-tile::selection,
.mv-bar *::selection {
  background: transparent;
  color: inherit
}
.mv-tile video::selection {
  background: transparent
}
.mv-tile button:focus,
.mv-tile button:focus-visible,
.mv-bar button:focus {
  outline: 0
}
.mv-inp,
.mv-inp * {
  -webkit-user-select: text;
  user-select: text
}
.mv-stage {
  flex: 1 1 0;
  overflow: hidden
}
.mv-grid {
  position: absolute;
  inset: 0;
  overflow-y: scroll;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  touch-action: pan-y;
  overscroll-behavior: contain;
  scrollbar-gutter: stable
}
.mv-list {
  overflow-y: scroll;
  -webkit-overflow-scrolling: touch;
  touch-action: pan-y;
  overscroll-behavior: contain
}
.fav-modal.mode-mv .mv-pane {
  padding: 10px 0 0;
  gap: 10px
}
.fav-modal.mode-mv .mv-bar {
  margin-inline: 0;
  padding-inline: 14px;
  padding-bottom: 10px;
  margin-bottom: 0
}
.mv-grid {
  scrollbar-gutter: auto;
  padding: 0 14px 16px
}
.mv-list {
  padding-inline: 0
}
.mv-add {
  padding: 2px 14px
}
.mv-tb .mv-tbfs {
  display: none
}
.mv-grid.multi .mv-tb {
  grid-template-columns: repeat(4,minmax(0,1fr))
}
.mv-grid.multi .mv-tb .mv-tbfs {
  display: inline-flex
}
.mv-grid.multi .mv-tile.act.ui:fullscreen .mv-ctl,
.mv-grid.multi .mv-tile.act.ui:-webkit-full-screen .mv-ctl {
  display: flex!important
}
.mv-tile:fullscreen,
.mv-tile:-webkit-full-screen {
  box-sizing: border-box;
  border: 0!important;
  border-radius: 0!important;
  box-shadow: none!important;
  outline: 0!important;
  background: #000!important;
  margin: 0!important;
  padding: 0!important;
  display: block!important;
  overflow: hidden!important;
  cursor: default
}
.mv-tile:fullscreen .mv-stg,
.mv-tile:-webkit-full-screen .mv-stg {
  position: absolute!important;
  inset: 0!important;
  width: 100%!important;
  height: 100%!important;
  aspect-ratio: auto!important;
  border-radius: 0!important;
  flex: none!important
}
.mv-tile:fullscreen video,
.mv-tile:-webkit-full-screen video {
  position: absolute;
  inset: 0;
  width: 100%!important;
  height: 100%!important;
  object-fit: contain!important;
  background: #000
}
.mv-tile:fullscreen .mv-lbl,
.mv-tile:-webkit-full-screen .mv-lbl {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 3;
  border: 0;
  border-radius: 0;
  background: none;
  padding: calc(10px + env(safe-area-inset-top,0px)) calc(14px + env(safe-area-inset-right,0px)) 22px calc(14px + env(safe-area-inset-left,0px));
  opacity: 0;
  transition: opacity .2s;
  pointer-events: none
}
.mv-tile.ui:fullscreen .mv-lbl,
.mv-tile.ui:-webkit-full-screen .mv-lbl {
  opacity: 1
}
.mv-tile:fullscreen .mv-ctl,
.mv-tile:-webkit-full-screen .mv-ctl {
  z-index: 3;
  padding: 26px calc(14px + env(safe-area-inset-right,0px)) calc(8px + env(safe-area-inset-bottom,0px)) calc(14px + env(safe-area-inset-left,0px))
}
.mv-tile:fullscreen .mv-qm,
.mv-tile:-webkit-full-screen .mv-qm {
  z-index: 4
}
.mv-tile:fullscreen .mv-cb {
  min-width: 44px;
  height: 42px
}
.mv-tile:fullscreen .mv-cb svg {
  width: 24px!important;
  height: 24px!important
}
.mv-tile:fullscreen .mv-cr>.mv-cb:nth-child(2) svg {
  width: 21px!important;
  height: 21px!important
}
.mv-tile:fullscreen .mv-cr {
  padding-right: 18px;
  box-sizing: border-box
}
.mv-tile:-webkit-full-screen .mv-cb {
  min-width: 44px;
  height: 42px
}
.mv-tile:-webkit-full-screen .mv-cb svg {
  width: 24px!important;
  height: 24px!important
}
.mv-tile:-webkit-full-screen .mv-cr>.mv-cb:nth-child(2) svg {
  width: 21px!important;
  height: 21px!important
}
.mv-tile:-webkit-full-screen .mv-cr {
  padding-right: 18px;
  box-sizing: border-box
}
.mv-tile:fullscreen .mv-cr>.mv-qb {
  flex: none;
  width: 100px;
  min-width: 100px;
  justify-content: flex-end;
  font-size: 14px
}
.mv-tile:-webkit-full-screen .mv-cr>.mv-qb {
  flex: none;
  width: 100px;
  min-width: 100px;
  justify-content: flex-end;
  font-size: 14px
}
`;
const MV_IC = {
    add: '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><path d="M12 5v14M5 12h14"/></svg>',
    close: '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><path d="M6 6l12 12M18 6L6 18"/></svg>'
};
MV_IC.play = '<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>';
MV_IC.pause = '<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M7 5h4v14H7zM13 5h4v14h-4z"/></svg>';
MV_IC.pip = icons.pip.replace('width="20" height="20"', 'width="18" height="18"');
MV_IC.fs = icons.fs.replace('width="20" height="20"', 'width="18" height="18"');
MV_IC.on = icons.vol.replace('width="20" height="20"', 'width="16" height="16"');
MV_IC.off = icons.mute.replace('width="20" height="20"', 'width="16" height="16"');
MV_IC.snap = icons.snap.replace('width="20" height="20"', 'width="16" height="16"');
MV_IC.rec = icons.rec.replace('width="20" height="20"', 'width="16" height="16"');
const mvT = k => L()[k] || '';
const saveRecording = async (blob, ext, durMs, name, disk) => {
    const fix = async (fn, fb) => { try { return await fn(); } catch { return fb; } };
    if (ext === 'webm') {
        blob = await fix(() => MediaFix.stripSeekHead(blob), blob);
        const d = await fix(() => MediaFix.webmDuration(blob), 0);
        blob = await fix(() => fixWebmDuration(blob, d > 0 ? d : durMs), blob);
        blob = await fix(() => MediaFix.webm(blob), blob);
    } else if (ext === 'mp4') blob = await fix(() => MediaFix.mp4(blob), blob);
    Helpers.download(blob, name);
    if (disk) disk.markDone().finally(() => disk.dispose());
};
const newMediaRecorder = (stream, mimeType, q) => {
    const [vBps, aBps] = REC_Q[q] || REC_Q.hi;
    for (const opts of [{ mimeType, videoBitsPerSecond: vBps, audioBitsPerSecond: aBps }, { mimeType }, undefined]) {
        try { return new MediaRecorder(stream, opts); } catch (e) { dbg(e, 'newMediaRecorder'); }
    }
    return null;
};
const newHlsCapture = ({ url, fetchBin, cfg, base, save, onProgress }) => new HlsKit.HlsCapture({
    url, fetchBin, onProgress, maxHeight: REC_H[cfg.q] ?? Infinity,
    partBytes: cfg.part * 1048576, totalBytes: cfg.total * 1048576,
    makeSink: no => new HlsKit.DiskBuffer({ meta: { name: base, ext: 'mp4', mime: 'video/mp4', part: no, hls: 1 } }),
    onPart: (blob, no, info) => save(blob, `${base}${no > 1 || !info.final ? `_PT${no}` : ''}.mp4`, info.sink)
});
const MV_DVR_OPTS = [0, 60, 120, 300];
const mvKeep = () => Math.max(2, mvDvr());
const mvDvr = () => { const v = Store.get('bc_mv_dvr', 120); return MV_DVR_OPTS.includes(v) ? v : 120; };
const mvAbr = () => storeOn('bc_mv_abr');
const mvBg = () => storeOn('bc_mv_bg');
const mvAbrLast = () => { const o = Store.get('bc_mv_abr_h', null); return isObj(o) && o.h > 0 && Date.now() - o.t < 1800000 ? o.h : 0; };
const ABR_UP_MS = 90000;
const ABR_BACK0 = 120000;
const ABR_LOW = 0.3;
const MvNet = {
    c: navigator.connection || navigator.mozConnection || navigator.webkitConnection || null,
    cap() {
        const t = this.c && this.c.effectiveType;
        if (t === 'slow-2g' || t === '2g') return 1;
        if (t === '3g') return 480;
        return 0;
    },
    watch(fn) { if (this.c && this.c.addEventListener) this.c.addEventListener('change', fn); }
};
const mvGet = (url, referer, range) => gmBytes(url, referer, true, range);
const XHR_HDR = { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json, text/plain, */*' };
const bcChatUrl = (o, slug) => `${o}/get-member-chat-data?username=${encodeURIComponent(slug)}&withMiniProfile=1&liveTab=female`;
const bcAmfUrl = o => `${o}/tools/amf.php?m=1&t=${Date.now()}`;
const bcAmfBody = (slug, t, field = '_csrf_token') => `method=getRoomData&args%5B%5D=${encodeURIComponent(slug)}&args%5B%5D=&args%5B%5D=${t ? `&${encodeURIComponent(field)}=${encodeURIComponent(t)}` : ''}`;
const mvUrl = u => (isStr(u) ? u.replace(/\\\//g, '/').replace(/^\/\//, 'https://') : '');
const mvFindM3u8 = (x, d = 0) => {
    if (d > 6 || x == null) return '';
    if (isStr(x)) { const u = mvUrl(x); return /^https?:\/\/[^\s"']+\.m3u8(\?[^\s"']*)?$/i.test(u) ? u : ''; }
    if (typeof x !== 'object') return '';
    for (const k of Object.keys(x)) { const r = mvFindM3u8(x[k], d + 1); if (r) return r; }
    return '';
};
const mvFindKey = (x, key, d = 0) => {
    if (d > 6 || !x || typeof x !== 'object') return '';
    if (isStr(x[key]) && x[key]) return x[key];
    for (const k of Object.keys(x)) { const r = mvFindKey(x[k], key, d + 1); if (r) return r; }
    return '';
};
const mvJson = async (url, ref) => {
    try { return JSON.parse((await requestOnce(url, { ...XHR_HDR, Referer: ref })) || ''); } catch { return null; }
};
const mvShort = v => String(v).replace(/[A-Za-z0-9_\-+/=]{20,}/g, '<t>').slice(0, 56);
const mvScan = (x, path = '', out = [], d = 0) => {
    if (d > 6 || x == null || out.length >= 8) return out;
    if (isStr(x)) { if (x && /video|stream|hls|server|host|edge/i.test(path)) out.push(`${path}=${mvShort(x)}`); return out; }
    if (typeof x !== 'object') return out;
    for (const k of Object.keys(x)) mvScan(x[k], path ? `${path}.${k}` : k, out, d + 1);
    return out;
};
const BC_TOKEN_RES = [/data-csrf_value=["']([^"']+)["']/i, /name=["']_csrf_token["'][^>]*?value=["']([^"']+)["']/i, /value=["']([^"']+)["'][^>]*?name=["']_csrf_token["']/i, /csrf[\w-]*["'\s:=]+["']?([a-f0-9]{24,128})(?![\w-])/i];
const bcCsrfField = html => (String(html).match(/data-csrf_field=["']([^"']+)["']/i) || [])[1] || '_csrf_token';
const bcTokenBag = () => {
    const list = [];
    return { list, add: (t, from, field) => { if (t && !list.some(x => x.t === t)) list.push({ t, from, field: field || '_csrf_token' }); } };
};
const bcHtmlTokens = (html, bag, own) => {
    for (const re of BC_TOKEN_RES) { const m = html.match(re); if (m) { bag.add(m[1], 'html', bcCsrfField(html)); break; } }
    if (own) bag.add((document.cookie.match(/csrf[\w-]*=([^;]+)/i) || [])[1], 'cookie');
};
const bcHtmlDebug = (html, tokens) => {
    const cs = html.search(/csrf/i);
    return `html:${html.length}b tokens:${tokens.map(x => x.from).join('+') || 'no'} csrf@${cs}${cs >= 0 ? `[${mvShort(html.slice(Math.max(0, cs - 12), cs + 48).replace(/\s+/g, ' '))}]` : ''}`;
};
const BC_VS_KEYS = ['videoServerUrl', 'videoServer', 'hlsServerUrl', 'streamServerUrl'];
const bcFindVs = x => {
    for (const k of BC_VS_KEYS) {
        const v = mvFindKey(x?.localData, k) || mvFindKey(x, k);
        if (isStr(v) && /^(https?:)?\/\//.test(mvUrl(v))) return mvUrl(v);
    }
    return '';
};
const bcPick = (st, x) => {
    if (!x) return;
    st.vs = st.vs || bcFindVs(x);
    st.direct = st.direct || mvFindM3u8(x);
    st.guess = st.guess || bcEdgeGuess(mvFindKey(x, 'frame'));
};
const bcFound = st => !!(st.vs || st.direct);
const bcScanHtml = (html, st) => {
    const hv = html.match(new RegExp(`(?:${BC_VS_KEYS.join('|')})["']?\\s*[:=]\\s*["']([^"']+)["']`, 'i'));
    if (hv) st.vs = mvUrl(hv[1]);
    st.guess = st.guess || bcEdgeGuess(html);
    const hm = html.match(/https?:\\?\/\\?\/[^\s"'<>\\]+?\.m3u8[^\s"'<>\\]*/i);
    if (hm) st.direct = mvUrl(hm[0]);
};
const bcAmfTry = async (o, slug, k, st) => {
    const raw = await requestPostOnce(bcAmfUrl(o), bcAmfBody(slug, k.t, k.field), { Referer: `${o}/${encodeURIComponent(slug)}`, Origin: o });
    const j = JSON.parse(raw || '');
    st.dbg.push(`amf(${k.from}):${String(raw).length}b ${j?.status || ''}`);
    bcPick(st, j);
    const un = j?.performerData?.username;
    if (isStr(un) && un) st.name = un;
    if (bcFound(st)) return true;
    const sc = mvScan(j);
    if (sc.length) st.dbg.push(`amf-scan:${sc.join(' ')}`);
    return false;
};
const bcAmf = async (o, slug, tokens, st) => {
    const tries = tokens.length ? tokens.slice(0, 2) : [{ t: '', from: 'none' }];
    for (const k of tries) {
        let found = false;
        try { found = await bcAmfTry(o, slug, k, st); } catch { st.dbg.push(`amf(${k.from}):fail`); }
        if (found) break;
    }
};
const bcChat = async (o, slug, st) => {
    const j = await mvJson(bcChatUrl(o, slug), `${o}/${encodeURIComponent(slug)}`);
    st.dbg.push(j ? 'chat:ok' : 'chat:fail');
    bcPick(st, j);
    if (!j || bcFound(st)) return;
    const sc = mvScan(j);
    st.dbg.push(`chat-scan:${sc.join(' ') || 'nothing'}`);
    st.dbg.push(`chat-keys:${Object.keys(j.result || {}).join(',')}`.slice(0, 260));
};
const bcServerTag = st => st.vs ? originOf(st.vs) || 'bad' : (st.direct ? 'direct' : 'none');
const BC_PLAYLIST_PATHS = ['/hls/stream_{n}/playlist.m3u8'];
const bcEdgeGuess = txt => { const m = String(txt || '').replace(/\\\//g, '/').match(/\/\/mobile-edge(\d+)\.(bcvcdn\.com)/i); return m ? `https://live-edge${m[1]}.${m[2]}` : ''; };
const bcPlaylistCandidates = st => {
    if (st.direct) return [st.direct];
    const bases = [...new Set([st.vs, st.guess].filter(Boolean).map(b => b.replace(/\/$/, '')))];
    const names = [...new Set([st.name, String(st.name).toLowerCase()])];
    return bases.flatMap(b => BC_PLAYLIST_PATHS.flatMap(t => names.map(n => b + t.replace('{n}', n))));
};
const bcProbePlaylist = async (st, ref) => {
    const list = bcPlaylistCandidates(st);
    if (st.direct || !list.length) return list[0] || '';
    for (const u of list) {
        let txt = '';
        try { txt = await requestOnce(u, { Referer: ref }); } catch (e) { dbg(e, 'bcProbePlaylist'); }
        if (isStr(txt) && txt.startsWith('#EXTM3U')) { st.dbg.push(`playlist:ok ${u === list[0] ? 'primary' : 'alt'}`); return u; }
    }
    st.dbg.push('playlist:unverified');
    return st.vs ? list[0] : '';
};
const cbDossier = html => {
    try {
        const m = String(html).match(/initialRoomDossier\s*=\s*("(?:[^"\\]|\\.)*")/);
        if (!m) return null;
        let o = JSON.parse(m[1]);
        if (isStr(o)) o = JSON.parse(o);
        return o && typeof o === 'object' ? o : null;
    } catch { return null; }
};
const cbPageHls = html => {
    const o = cbDossier(html);
    const u = o ? (mvUrl(o.hls_source) || mvFindM3u8(o)) : '';
    if (u) return u;
    const r = String(html).match(/(?:"|\\u0022)hls_source(?:"|\\u0022)\s*:\s*(?:"|\\u0022)(https?:(?:(?!\\u0022|")(?:[^\\]|\\u[0-9a-f]{4}))+)/i);
    if (!r) return '';
    try { return mvUrl(JSON.parse(`"${r[1]}"`)); } catch { return ''; }
};
const mvVault = (() => {
    const DB = 'bcu_warm', ST = 'hls', TTL = 2 * 3600000, MAX = 80;
    const mem = new Map();
    let dbp = null, loaded = null;
    const open = () => dbp || (dbp = new Promise(res => {
        try {
            const r = indexedDB.open(DB, 2);
            r.onupgradeneeded = () => { if (!r.result.objectStoreNames.contains(ST)) r.result.createObjectStore(ST); };
            r.onsuccess = () => res(r.result);
            r.onerror = r.onblocked = () => res(null);
        } catch { res(null); }
    }));
    const store = async mode => { const db = await open(); try { return db ? db.transaction(ST, mode).objectStore(ST) : null; } catch { return null; } };
    const preload = () => loaded || (loaded = (async () => {
        const s = await store('readonly');
        if (!s) return;
        await new Promise(res => {
            const c = s.openCursor();
            c.onsuccess = () => { const cur = c.result; if (!cur) return res(); mem.set(cur.key, cur.value); cur.continue(); };
            c.onerror = () => res();
        });
    })());
    const fresh = r => !!r && Date.now() - r.at < TTL;
    const trim = () => {
        if (mem.size <= MAX) return;
        const old = [...mem.entries()].sort((a, b) => a[1].at - b[1].at).slice(0, mem.size - MAX);
        for (const [k] of old) { mem.delete(k); store('readwrite').then(s => s && s.delete(k)).catch(() => {}); }
    };
    return {
        ready: () => Promise.race([preload(), wait(300)]),
        peek: k => { const r = mem.get(k); return fresh(r) ? r : null; },
        keep: (k, patch) => {
            const r = { ...(mem.get(k) || {}), ...patch, at: Date.now() };
            mem.set(k, r);
            trim();
            store('readwrite').then(s => s && s.put(r, k)).catch(() => {});
        },
        forget: k => { mem.delete(k); store('readwrite').then(s => s && s.delete(k)).catch(() => {}); }
    };
})();
const mvVaultKey = e => {
    try {
        if (e.t === 'url') { const u = new URL(e.url); return `u:${u.origin}${u.pathname}`; }
        const site = urlSite(e.url), slug = modelSlug(e.url);
        return site && slug ? `${site}/${String(slug).toLowerCase()}` : '';
    } catch { return ''; }
};
const mvIsSelf = e => {
    try {
        return e.t !== 'url' && sameBm(e, currentSite.getName()) && !!getStreamUrl();
    } catch { return false; }
};

const MV_RES = {
    cb: async (e, slug) => {
        const j = await mvJson(`https://chaturbate.com/api/chatvideocontext/${encodeURIComponent(slug)}/`, `https://chaturbate.com/${encodeURIComponent(slug)}/`);
        let url = mvUrl(j?.hls_source) || mvFindM3u8(j);
        if (!url) url = cbPageHls((await requestHtml(e.url)) || '');
        return { url, ref: 'https://chaturbate.com/' };
    },
    sc: async (e, slug) => {
        const o = originOf(e.url) || 'https://stripchat.com', ref = `${o}/`;
        const j = await mvJson(`${o}/api/front/v2/models/username/${encodeURIComponent(slug)}/cam`, `${o}/${encodeURIComponent(slug)}`);
        const direct = mvFindM3u8(j);
        if (direct) return { url: direct, ref };
        const ids = [j?.cam?.streamName, j?.user?.user?.id, j?.user?.id].filter(v => v != null && v !== '').map(String);
        for (const id of [...new Set(ids)]) for (const host of ['doppiocdn.com', 'doppiocdn.net', 'doppiocdn.org', 'doppiocdn.media']) {
            const cand = `https://edge-hls.${host}/hls/${id}/master/${id}_auto.m3u8`;
            const txt = await requestOnce(cand, { Referer: ref });
            if (txt && txt.startsWith('#EXTM3U')) return { url: cand, ref };
        }
        return { url: '', ref };
    },
    bc: async (e, slug) => {
        const o = originOf(e.url), own = o === location.origin, bag = bcTokenBag();
        const st = { name: slug, vs: '', direct: '', dbg: [] };
        if (own) bag.add(bcCsrfToken(), 'page');
        const html = (await requestHtml(e.url)) || '';
        bcHtmlTokens(html, bag, own);
        st.dbg.push(bcHtmlDebug(html, bag.list));
        bcScanHtml(html, st);
        if (!bcFound(st)) await bcAmf(o, slug, bag.list, st);
        if (!bcFound(st)) await bcChat(o, slug, st);
        st.dbg.push(`server:${bcServerTag(st)}`);
        const url = await bcProbePlaylist(st, `${o}/`);
        return { url, ref: `${o}/`, dbg: st.dbg.join('\n') };
    }
};
const mvResolve = async e => {
    if (e.t === 'url') return { url: e.url, ref: `${originOf(e.url)}/` };
    const site = urlSite(e.url), slug = modelSlug(e.url);
    if (sameBm(e, currentSite.getName())) {
        const cur = getStreamUrl();
        if (cur) return { url: cur, ref: `${location.origin}/` };
    }
    const fn = MV_RES[site];
    return fn && slug ? fn(e, slug) : { url: '' };
};

const MV_FLT = [
    ['bc_mv_hide_off', true, 'bm_online', 'bm_online_t', ''],
    ['bc_mv_hide_priv', true, 'bm_priv', 'st_private', 'st_private'],
    ['bc_mv_hide_group', false, 'st_group', 'st_group', 'st_group'],
    ['bc_mv_hide_vip', false, 'st_vip', 'st_vip', 'st_vip'],
    ['bc_mv_hide_away', false, 'st_away', 'st_away', 'st_away']
];
const mvFltOn = (key, def) => Store.get(key, def) === true;
const mvErr = x => (x ? String(x.message || x).slice(0, 80) : 'ended');

class MvTile {
    constructor(mv, e) {
        this.mv = mv;
        this.e = e;
        this.gen = 0;
        this.tries = 0;
        this.act = false;
        this.player = null;
        this.rt = 0;
        this.q = 0;
        this.vh = [];
        this.um = false;
        this.drag = false;
        this.uat = 0;
        this.ti = 0;
        this.abrH = 0;
        this.abrAt = 0;
        this.abrTimer = 0;
        this.abrBack = ABR_BACK0;
        this.abrBlock = {};
        this.stalls = [];
        this.lastStall = 0;
        this.waitAt = 0;
        this.okSince = 0;
        this.lowN = 0;
        this.savedAt = 0;
        const el = this.el = mk('div', 'mv-tile');
        const v = this.video = document.createElement('video');
        v.muted = true;
        v.volume = clamp(Number(Store.get('bc_mv_vol', 1)) || 0, 0, 1);
        v.playsInline = true;
        v.setAttribute('playsinline', '');
        AudioFx.watch(v);
        this.lbl = textEl('mv-lbl', e.name || '', 'span');
        this.msg = mk('div', 'mv-msg');
        const tb = mk('div', 'mv-tb');
        this.sndBtn = mk('button');
        this.sndBtn.innerHTML = MV_IC.off;
        this.sndBtn.onclick = ev => { ev.stopPropagation(); this.mv.select(this); };
        const rm = mk('button');
        rm.innerHTML = MV_IC.close;
        Helpers.bindLongPress(rm, () => this.idle(), () => this.mv.remove(this), 700);
        rm.onclick = ev => ev.stopPropagation();
        this.shotBtn = mk('button');
        this.shotBtn.innerHTML = MV_IC.snap;
        this.shotBtn.title = L().snap;
        this.shotBtn.onclick = ev => { ev.stopPropagation(); this.shot(); };
        this.recBtn = mk('button');
        this.recBtn.innerHTML = MV_IC.rec;
        this.recBtn.title = L().rec_tip || L().rec;
        this.recBtn.style.touchAction = 'none';
        this.recBtn.onclick = ev => ev.stopPropagation();
        Helpers.bindLongPress(this.recBtn, () => this.toggleRec(), () => this.mv.ui.recorder?.openSettings());
        this.recS = null;
        this.tbFs = htmlEl('mv-tbfs', MV_IC.fs, 'button');
        this.tbFs.title = mvT('mv_fs');
        this.tbFs.onclick = ev => { ev.stopPropagation(); if (!this.act) this.mv.select(this); this.showUi(); this.toggleFs(); };
        tb.append(this.shotBtn, this.recBtn, rm, this.tbFs);
        const stg = this.stg = mk('div', 'mv-stg');
        stg.append(v, this.msg);
        this.tbEl = tb;
        el.append(this.lbl, stg, tb);
        this.buildCtl();
        ['fullscreenchange', 'webkitfullscreenchange'].forEach(t => el.addEventListener(t, () => this.placeBtns()));
        this.setPoster();
        el.onclick = () => { if (this.mv.grid.classList.contains('multi') && !this.isFs()) { if (!this.act) { this.mv.select(this); return; } if (this.player) this.togglePlay(); return; } if (!this.act) { this.mv.select(this); } else this.toggleUi(); };
        this.msg.onclick = ev => { if (this.msg.classList.contains('err') || this.msg.classList.contains('park')) { ev.stopPropagation(); this.tries = 0; if (!this.act) this.mv.select(this); this.start(); } };
        v.onplaying = () => { this.msg.classList.add('hide'); this.tries = 0; this.sw = 0; this.played = true; this.setWant(!this.userPause); this.syncPlay(); };
        v.onpause = () => { this.syncPlay(); if (mvBg() && !this.userPause && !this.parked && this.player) setTimeout(() => this.bgResume(), 200); };
        v.addEventListener('enterpictureinpicture', () => { this.pip = true; this.pipKeep(true); });
        v.addEventListener('leavepictureinpicture', () => {
            this.pip = false;
            this.pipKeep(false);
            if (!this.mv.isOpen() && this.player && !mvBg()) this.idle();
        });
        v.onplay = () => { this.syncPlay(); if (mvDvr() === 0 && this.player && this.player.shift) this.goLive(); };
        v.onresize = () => this.paintLbl();
        v.addEventListener('waiting', () => {
            const now = Date.now();
            if (!this.abrReady(now)) return;
            if (!this.waitAt) this.waitAt = now;
            this.abrStall(false);
        });
        v.addEventListener('playing', () => { this.waitAt = 0; });
        this.paintLbl();
        this.lblTimer = setInterval(() => { if (this.el.getClientRects().length) this.paintLbl(); }, 4000);
    }

    viewersNow() {
        try {
            const k = bmKey(this.e.url || '');
            const b = k && bmByKey(k);
            return b ? bmViews(b) : 0;
        } catch { return 0; }
    }

    paintLbl() {
        const h = this.video.videoHeight, vw = this.viewersNow();
        const key = `${h || 0}|${vw}`;
        if (this._lk === key) return;
        this._lk = key;
        this.lbl.innerHTML = `<span class="mv-nm">${escHTML(this.e.name || '')}</span>${vw ? `<span class="mv-vw">${icons.eye}${fmtCount(vw)}</span>` : ''}`;
    }

    flashBtn(btn, txt, back) {
        btn.classList.remove('rec');
        btn.textContent = txt;
        setTimeout(back, 1200);
    }

    paintShot() { this.shotBtn.innerHTML = MV_IC.snap; }

    paintRec(sec) {
        const b = this.recBtn;
        if (!this.recS) { b.classList.remove('rec'); b.innerHTML = MV_IC.rec; return; }
        b.classList.add('rec');
        b.textContent = Helpers.formatTime(sec || 0);
    }

    shot() {
        const v = this.video, back = () => this.paintShot();
        if (!v.videoWidth) return this.flashBtn(this.shotBtn, 'ERR', back);
        Helpers.snap(v, this.e.name, ok => this.flashBtn(this.shotBtn, ok ? 'OK' : 'ERR', back));
    }

    toggleRec() {
        if (this.recS) { try { this.recS.stop(); } catch (e) { dbg(e, 'MvTile.toggleRec'); } return; }
        const cfg = recCfg(), base = `${this.e.name || 'REC'}_${Helpers.getTimestamp()}`;
        const t0 = Date.now();
        let timer = 0;
        const begin = ctl => {
            this.recS = ctl;
            this.paintRec(0);
            timer = setInterval(() => this.paintRec(Math.floor((Date.now() - t0) / 1000)), 1000);
        };
        const end = () => { clearInterval(timer); this.recS = null; this.paintRec(0); };
        const fail = t => this.flashBtn(this.recBtn, t || 'ERR', () => this.paintRec(0));
        const capture = () => {
            const v = this.video;
            const st = v.captureStream ? v.captureStream(30) : (v.mozCaptureStream ? v.mozCaptureStream(30) : null);
            if (!st) return fail();
            const mt = recMimeFor(cfg.fmt), ext = mt.includes('mp4') ? 'mp4' : 'webm';
            const mr = newMediaRecorder(st, mt, cfg.q);
            if (!mr) return fail();
            const disk = new HlsKit.DiskBuffer({ meta: { name: base, ext, mime: mt.split(';')[0] } });
            const t1 = performance.now();
            let bytes = 0;
            mr.ondataavailable = e => {
                if (!(e.data && e.data.size > 0)) return;
                disk.append(e.data);
                bytes += e.data.size;
                if (cfg.total && bytes >= cfg.total * 1048576) { try { mr.stop(); } catch (e) { dbg(e, 'toggleRec.capture'); } }
            };
            mr.onerror = () => { try { mr.stop(); } catch (e) { dbg(e, 'toggleRec.capture'); } };
            mr.onstop = () => {
                try { stopTracks(st); } catch (e) { dbg(e, 'toggleRec.capture'); }
                if (disk.size > 0) disk.finish().then(b => saveRecording(b, ext, performance.now() - t1, `${base}.${ext}`, disk)).catch(() => {});
                else disk.remove();
                end();
            };
            try { mr.start(1000); } catch { end(); return fail(); }
            begin({ stop: () => mr.stop() });
        };
        const si = this.srcInfo;
        if (cfg.src === 'hls' && si && si.url) {
            let user = false;
            const cap = newHlsCapture({
                url: si.url, cfg, base, fetchBin: (u, r) => mvGet(u, si.ref, r),
                save: (blob, name, sink) => saveRecording(blob, 'mp4', 0, name, sink)
            });
            begin({ stop: () => { user = true; cap.stop(); } });
            cap.start().then(res => {
                end();
                if (res.error && !user && !res.stats.segments) return capture();
                if (res.error && !user) fail();
            });
            return;
        }
        capture();
    }

    say(text, err) {
        this.msg.textContent = text;
        this.msg.classList.toggle('err', !!err);
        this.el.classList.toggle('bad', !!err);
        this.msg.classList.remove('hide');
    }

    setActive(on) {
        this.act = on;
        this.el.classList.toggle('act', on);
        this.applyAudio();
        if (on) { clearInterval(this.ti); this.ti = setInterval(() => this.tickUi(), 250); } else this.endUi();
    }

    applyAudio() {
        const on = this.act && !this.um && this.video.volume > 0;
        this.video.muted = !this.act || this.um;
        this.video.classList.toggle('bc-blur-mute', AppState.blurMute === true && this.video.muted);
        this.sndBtn.innerHTML = this.act && !this.um ? MV_IC.on : MV_IC.off;
        if (this.muteBtn) this.muteBtn.innerHTML = on ? MV_IC.on : MV_IC.off;
        if (this.volEl) this.volEl.style.setProperty('--p', (this.video.volume * 100) + '%');
    }

    endUi() {
        clearInterval(this.ti);
        this.ti = 0;
        this.el.classList.remove('ui');
        this.menu?.classList.remove('open');
        this.drag = false;
    }

    showUi() {
        this.el.classList.add('ui');
        this.uat = Date.now();
    }

    toggleUi() {
        if (this.el.classList.contains('ui')) { this.el.classList.remove('ui'); this.menu.classList.remove('open'); } else this.showUi();
    }

    placeBtns() {
        const row = this.cr, tb = this.tbEl;
        if (!row || !tb) return;
        const fs = this.isFs();
        this.shotBtn.classList.toggle('mv-cb', fs);
        this.recBtn.classList.toggle('mv-cb', fs);
        if (fs) { row.insertBefore(this.shotBtn, this.qBtn); row.insertBefore(this.recBtn, this.qBtn); }
        else { tb.insertBefore(this.shotBtn, tb.firstChild); tb.insertBefore(this.recBtn, this.shotBtn.nextSibling); }
    }

    syncPlay() {
        if (this.playBtn) this.playBtn.innerHTML = this.video.paused ? MV_IC.play : MV_IC.pause;
        if (this.bigBtn) this.bigBtn.innerHTML = this.video.paused ? MV_IC.play : MV_IC.pause;
    }

    range() {
        const b = this.video.buffered;
        return b.length ? [b.start(0), b.end(b.length - 1)] : null;
    }

    seekTo(t) {
        const v = this.video, r = this.range(), p = this.player;
        if (!r) return;
        const to = Math.min(Math.max(t, r[0] + 0.05), r[1] - 0.3);
        if (p) p.shift = r[1] - to > 3;
        try { v.currentTime = to; } catch (e) { dbg(e, 'MvTile.seekTo'); }
    }

    goLive() {
        const v = this.video, r = this.range(), p = this.player;
        if (p) { p.shift = false; p.o.hold = false; }
        if (r) { try { v.currentTime = r[1] - 1.5; } catch (e) { dbg(e, 'MvTile.goLive'); } }
        v.play().catch(() => {});
    }

    togglePlay() {
        const v = this.video, p = this.player;
        if (v.paused) { this.userPause = false; this.setWant(true); if (p) p.o.hold = false; if (mvDvr() === 0) this.goLive(); else v.play().catch(() => {}); } else { this.userPause = true; this.setWant(false); if (p) { p.o.hold = true; p.shift = true; } v.pause(); }
    }

    bgResume() {
        const v = this.video, p = this.player;
        if (!mvBg() || this.userPause || this.parked || !p || p.dead || !v.isConnected || v.ended || !v.paused) return;
        if (Store.get('bc_sleep_lock', 0)) return;
        p.o.hold = false;
        if (mvDvr() === 0) this.goLive(); else v.play().catch(() => {});
    }

    isFs() {
        const el = this.el;
        try { return el.matches(':fullscreen'); } catch { try { return el.matches(':-webkit-full-screen'); } catch { return false; } }
    }

    toggleFs() {
        if (this.isFs()) fsExit(); else fsRequest(this.el);
    }

    togglePip() {
        const v = this.video;
        if (document.pictureInPictureElement) { document.exitPictureInPicture().catch(() => {}); return; }
        if (document.pictureInPictureEnabled && v.requestPictureInPicture) v.requestPictureInPicture().catch(() => {});
    }

    buildCtl() {
        const v = this.video;
        const ctl = this.ctl = mk('div', 'mv-ctl');
        const stop = ev => { ev.stopPropagation(); this.uat = Date.now(); };
        ctl.onclick = stop;
        ctl.onpointerdown = ev => { this.uat = Date.now(); ev.stopPropagation(); };
        const sk = this.sk = mk('div', 'mv-sk');
        this.skFill = mk('div', 'mv-sk-f');
        this.skKnob = mk('div', 'mv-sk-k');
        sk.append(mk('div', 'mv-sk-t'), this.skFill, this.skKnob);
        const frac = ev => { const r = sk.getBoundingClientRect(); return clamp((ev.clientX - r.left) / (r.width || 1), 0, 1); };
        const paint = f => { this.skFill.style.width = `${f * 100}%`; this.skKnob.style.left = `${f * 100}%`; };
        sk.onpointerdown = ev => {
            ev.stopPropagation();
            this.drag = true;
            try { sk.setPointerCapture(ev.pointerId); } catch (e) { dbg(e, 'MvTile.buildCtl'); }
            paint(frac(ev));
        };
        sk.onpointermove = ev => { if (this.drag) paint(frac(ev)); };
        const fin = ev => {
            if (!this.drag) return;
            this.drag = false;
            const r = this.range();
            if (r) this.seekTo(r[0] + frac(ev) * (r[1] - r[0]));
        };
        sk.onpointerup = fin;
        sk.onpointercancel = () => { this.drag = false; };
        this.paintSk = paint;
        const row = mk('div', 'mv-cr');
        const btn = (cls, title, fn) => { const b = mk('button', cls); b.title = title; b.onclick = ev => { ev.stopPropagation(); this.uat = Date.now(); fn(ev); }; return b; };
        this.playBtn = btn('mv-cb', mvT('mv_play'), () => this.togglePlay());
        this.muteBtn = btn('mv-cb', mvT('mv_mute'), () => { this.um = !this.um; if (!this.um && v.volume === 0) { v.volume = 0.5; this.volEl.value = '0.5'; } this.applyAudio(); });
        const vol = this.volEl = mk('input', 'mv-vol');
        vol.type = 'range'; vol.min = '0'; vol.max = '1'; vol.step = '0.05'; vol.value = String(v.volume);
        vol.oninput = () => { v.volume = +vol.value; if (v.volume > 0) this.um = false; Store.set('bc_mv_vol', v.volume); this.applyAudio(); this.uat = Date.now(); };
        vol.onclick = ev => ev.stopPropagation();
        this.lvBtn = btn('mv-lv', mvT('mv_live'), () => this.goLive());
        this.lvBtn.textContent = mvT('mv_live');
        this.tm = mk('span', 'mv-tm');
        const sp = mk('div', 'mv-sp');
        this.qBtn = btn('mv-cb mv-qb', mvT('mv_qual'), () => this.toggleMenu());
        this.pipBtn = btn('mv-cb', mvT('mv_pip'), () => this.togglePip());
        this.pipBtn.innerHTML = MV_IC.pip;
        this.fsBtn = btn('mv-cb', mvT('mv_fs'), () => this.toggleFs());
        this.fsBtn.innerHTML = MV_IC.fs;
        if (!(document.pictureInPictureEnabled && v.requestPictureInPicture)) this.pipBtn.style.display = 'none';
        row.append(this.playBtn, this.muteBtn, vol, this.lvBtn, this.tm, sp, this.qBtn, this.pipBtn, this.fsBtn);
        this.cr = row;
        ctl.append(sk, row);
        const menu = this.menu = mk('div', 'mv-qm');
        menu.onclick = stop;
        menu.onpointerdown = ev => ev.stopPropagation();
        const big = this.bigBtn = mk('button', 'mv-big');
        big.title = mvT('mv_play');
        big.onpointerdown = ev => ev.stopPropagation();
        big.onclick = ev => {
            ev.stopPropagation();
            if (!this.act) { this.mv.select(this); return; }
            if (this.player) this.togglePlay();
            this.showUi();
        };
        this.stg.append(big, ctl, menu);
        this.syncPlay();
        this.applyAudio();
        this.applyDvr();
    }

    applyDvr() {
        this.el.classList.toggle('nodvr', mvDvr() === 0);
    }

    toggleMenu() {
        const m = this.menu;
        if (m.classList.contains('open')) { m.classList.remove('open'); return; }
        const st = this.player && this.player.stats;
        if (st && st.variants && st.variants.length) this.vh = st.variants;
        const hs = [...new Set(this.vh.map(x => x.h).filter(Boolean))].sort((a, b) => b - a);
        const rows = [];
        const item = (label, sel, fn) => { const r = mk('div', `mv-qi${sel ? ' sel' : ''}`); r.textContent = label; r.onclick = ev => { ev.stopPropagation(); fn(); }; rows.push(r); };
        item(mvT('mv_auto'), !this.q, () => { this.q = 0; m.classList.remove('open'); this.start(); });
        hs.forEach(h => item(`${h}p`, this.q === h, () => { this.q = h; m.classList.remove('open'); this.start(); }));
        m.replaceChildren(...rows);
        m.classList.add('open');
    }

    paintProgress(r) {
        if (!r || this.drag) return;
        const span = Math.max(r[1] - r[0], 0.001);
        this.paintSk(clamp((this.video.currentTime - r[0]) / span, 0, 1));
    }

    qualityLabel() {
        if (this.q) return `${this.q}p`;
        const h = this.video.videoHeight;
        return h ? `${mvT('mv_auto')} ${h}p` : mvT('mv_auto');
    }

    shouldHideUi() {
        return !this.video.paused && !this.drag && !this.menu.classList.contains('open') && Date.now() - this.uat > 3500;
    }

    tickUi() {
        const v = this.video, el = this.el;
        if (!el.classList.contains('ui')) return;
        const r = this.range();
        this.paintProgress(r);
        this.qBtn.textContent = this.qualityLabel();
        if (this.sw && Date.now() - this.sw < 15000) { if (this.shouldHideUi()) el.classList.remove('ui'); return; }
        const behind = r ? Math.max(0, r[1] - v.currentTime) : 0;
        const live = this.isLv = this.isLv ? behind < 9 : behind < 4;
        this.lvBtn.classList.toggle('on', live && !v.paused);
        this.tm.textContent = live ? '' : `\u2212${fmtDur(behind)}`;
        if (this.shouldHideUi()) el.classList.remove('ui');
    }

    setPoster() {
        const u = this.e.url;
        const b = this.mv.ui.bookmarkManager?.prevEn === false ? null : (AppState.bookmarks || []).find(x => isObj(x) && x.frame && bmKey(x.url) === bmKey(u));
        this.posterBase = b ? b.frame : '';
        if (b) { this.video.poster = b.frame; this.video.setAttribute('referrerpolicy', 'no-referrer'); } else { this.video.removeAttribute('poster'); if (this.mv.ui.bookmarkManager?.prevEn !== false) this.avatarPoster(); }
    }

    async avatarPoster() {
        const u = this.e.url, k = (this.apTok = (this.apTok || 0) + 1);
        const bm = (AppState.bookmarks || []).find(x => isObj(x) && bmKey(x.url) === bmKey(u));
        let h = null; try { h = histLoad().m[bmKey(u)]; } catch (e) { dbg(e, 'MvTile.avatarPoster'); }
        const list = [bm && bm.avatar, h && h.a].map(toHttps).filter(Boolean);
        for (const a of list) {
            const d = await loadFrame(a);
            if (k !== this.apTok || this.posterBase || this.mv.ui.bookmarkManager?.prevEn === false) return;
            if (d) { this.video.poster = d; return; }
        }
        if (k === this.apTok && !this.posterBase) this.video.poster = initialsAva((bm && (bm.name || bm.title)) || (this.e && this.e.name) || u);
    }

    stopPoster() {
        clearTimeout(this.pt);
        this.pt = 0;
        this.pBusy = false;
    }

    canRefreshPoster() { return this.mv.isOpen() && !realHidden() && !this.pBusy; }

    async refreshPoster() {
        this.pBusy = true;
        const d = await loadFrame(frameBust(this.posterBase));
        this.pBusy = false;
        if (this.parked && d) this.video.poster = d;
    }

    prevOk() { return !!prevSpd() && this.mv.ui.bookmarkManager?.prevEn !== false; }

    kickPoster() {
        this.stopPoster();
        if (!this.parked || !this.posterBase || !this.prevOk()) return;
        const run = async () => {
            this.pt = 0;
            if (!this.parked || !this.posterBase || !this.prevOk()) return;
            if (this.canRefreshPoster()) await this.refreshPoster();
            if (this.parked && this.prevOk() && !this.pt) this.pt = setTimeout(run, prevSpd());
        };
        run();
    }

    inPip() { return !!this.pip; }

    pipKeep(on) {
        this.mv.pane?.classList.toggle('keep', on);
        this.mv.ui.bookmarkManager?.overlay?.classList.toggle('pipkeep', on);
    }

    setWant(on) {
        if (this.want === on) return;
        this.want = on;
        this.mv.saveOn();
    }

    idle() {
        this.setWant(false);
        this.setPoster();
        this.stop();
        this.endUi();
        this.parked = true;
        this.say('\u25B6');
        this.msg.classList.add('park');
        this.el.classList.remove('bad');
        this.kickPoster();
    }

    shotUrl(src) {
        if (this.e.t === 'url') return '';
        const site = urlSite(this.e.url);
        if (site === 'cb') { const slug = modelSlug(this.e.url); return slug ? cbRiUrl(slug) : ''; }
        const m = site === 'bc' && src && String(src.url || '').match(/^https:\/\/live-edge([\w-]*)\.bcvcdn\.com\/hls\/(stream_[^/]+)\//i);
        return m ? `https://mobile-edge${m[1]}.bcvcdn.com/${m[2]}.jpg` : '';
    }

    async liveShot(id, src) {
        if (this.mv.ui.bookmarkManager?.prevEn === false) return;
        const u = this.shotUrl(src);
        if (!u) return;
        const d = await loadFrame(frameBust(u));
        if (d && id === this.gen && !this.played) this.video.poster = d;
    }

    async resolveSource() {
        try { return { src: await mvResolve(this.e), err: '' }; }
        catch (x) { return { src: null, err: String((x && x.message) || x).slice(0, 60) }; }
    }

    async start() {
        this.stop();
        const id = ++this.gen;
        this.parked = false;
        this.userPause = false;
        this.sw = Date.now();
        this.lazy = false;
        this.stopPoster();
        this.msg.classList.remove('park');
        if (this.act && !this.ti) { this.ti = setInterval(() => this.tickUi(), 250); }
        this.say(L().mv_load);
        const vk = this.vkey = mvVaultKey(this.e);
        const tryWarm = !!vk && !this.skipWarm && !mvIsSelf(this.e);
        this.skipWarm = false;
        if (vk && tryWarm) await mvVault.ready();
        if (id !== this.gen) return;
        const warm = tryWarm ? mvVault.peek(vk) : null;
        this.usedWarm = !!(warm && warm.src && warm.src.url);
        this.liveShot(id, this.usedWarm ? warm.src : null);
        const { src, err } = this.usedWarm ? { src: warm.src, err: '' } : await this.resolveSource();
        if (id !== this.gen) return;
        this.srcInfo = src;
        if (!this.usedWarm && src && this.e.t !== 'url' && urlSite(this.e.url) === 'bc') this.liveShot(id, src);
        this.dbg = [src && src.dbg, err].filter(Boolean).join(' ');
        if (!src || !src.url) { this.why = 'E1'; return this.ended(id); }
        const player = this.player = new HlsKit.HlsPlayer({
            video: this.video, url: src.url, maxHeight: this.maxH(), keep: mvKeep(),
            hint: warm && warm.variants ? { variants: warm.variants, audio: warm.audio } : null,
            onOpened: rec => { if (vk && src.url) mvVault.keep(vk, { src: { url: src.url, ref: src.ref }, variants: rec.variants, audio: rec.audio }); },
            fetchBin: (u, r) => mvGet(u, src.ref, r),
            onMuted: () => { this.um = true; this.applyAudio(); },
            onEnd: out => { this.why = `E2 ${mvErr(out && out.error)}`; this.ended(id); }
        });
        player.start().catch(x => { this.why = `E2 ${mvErr(x)}`; this.ended(id); });
        this.abrStart();
        this.wdStart();
    }

    wdStart() {
        clearInterval(this.wd);
        const v = this.video;
        let lt = -1, at = Date.now();
        this.played = false;
        this.wdT0 = Date.now();
        this.wd = setInterval(() => {
            const p = this.player, now = Date.now();
            if (!p || p.dead) { lt = v.currentTime; at = now; return; }
            if (!this.played) {
                if (now - this.wdT0 > 25000) { this.why = 'WD0'; this.start(); }
                return;
            }
            if (v.paused && mvBg()) this.bgResume();
            if (v.paused || p.shift || v.currentTime !== lt) { lt = v.currentTime; at = now; return; }
            if (now - at > 10000) { this.why = 'WD'; this.tries = 0; this.start(); }
        }, 2000);
    }

    abrOn() { return !this.q && mvAbr(); }

    maxH() {
        if (this.q) return this.q;
        if (!mvAbr()) return Infinity;
        const a = [this.abrH || mvAbrLast(), MvNet.cap()].filter(x => x > 0);
        return a.length ? Math.min(...a) : Infinity;
    }

    ladder() {
        const st = this.player && this.player.stats;
        return st && st.variants ? [...new Set(st.variants.map(x => x.h).filter(Boolean))].sort((a, b) => b - a) : [];
    }

    curH() {
        const st = this.player && this.player.stats;
        return st && st.variant ? st.variant.h || 0 : 0;
    }

    abrReady(now) {
        const p = this.player, v = this.video;
        return !!p && !p.dead && p.started && this.abrOn() && !v.paused && !v.seeking && !p.shift && !realHidden() && now - this.abrAt >= 8000 && now - p.tStart >= 6000;
    }

    abrStart() {
        this.abrStop();
        this.abrAt = Date.now();
        this.okSince = 0;
        this.lowN = 0;
        this.waitAt = 0;
        this.stalls = [];
        this.abrTimer = setInterval(() => this.abrTick(), 1000);
    }

    abrStop() {
        clearInterval(this.abrTimer);
        this.abrTimer = 0;
    }

    abrTick() {
        const now = Date.now();
        if (!this.abrReady(now)) { this.okSince = 0; this.lowN = 0; return; }
        if (now - this.abrAt > 300000 && this.abrBack > ABR_BACK0) this.abrBack = ABR_BACK0;
        const v = this.video, ahead = this.player._ahead();
        if (v.readyState < 3 && this.waitAt && now - this.waitAt > 6000) { this.waitAt = now; this.abrStall(true); return; }
        this.lowN = ahead < ABR_LOW ? this.lowN + 1 : 0;
        if (this.lowN >= 3) { this.lowN = 0; if (this.abrStall(false)) return; }
        if (v.readyState < 3 || ahead < ABR_LOW) { this.okSince = 0; return; }
        if (!this.okSince) this.okSince = now;
        if (now - this.okSince >= ABR_UP_MS) this.abrUp(now);
    }

    abrStall(hard) {
        const now = Date.now();
        this.okSince = 0;
        if (!hard && now - this.lastStall < 3000) return false;
        this.lastStall = now;
        this.stalls = this.stalls.filter(t => now - t < 30000);
        this.stalls.push(now);
        if (hard) this.stalls.push(now);
        return this.stalls.length >= 2 ? this.abrDown() : false;
    }

    abrDown() {
        const cur = this.curH(), lower = this.ladder().filter(h => h < cur)[0];
        if (!cur || !lower) return false;
        this.abrBlock[cur] = Date.now() + this.abrBack;
        this.abrBack = Math.min(this.abrBack * 2, 600000);
        this.abrSwitch(lower);
        return true;
    }

    abrUp(now) {
        const cur = this.curH(), up = this.ladder().filter(h => h > cur).pop(), cap = MvNet.cap();
        if (cur && now - this.savedAt > 60000) { this.savedAt = now; Store.set('bc_mv_abr_h', { h: cur, t: now }); }
        if (!cur || !up || (cap && up > cap) || (this.abrBlock[up] || 0) > now) return;
        this.abrSwitch(up);
    }

    abrNet() {
        if (!this.abrOn() || !this.player) return;
        const cap = MvNet.cap(), cur = this.curH(), hs = this.ladder();
        if (!cap || !cur || cur <= cap || hs.length < 2) return;
        const to = hs.filter(h => h <= cap)[0] || hs[hs.length - 1];
        if (to < cur) this.abrSwitch(to);
    }

    abrSwitch(h) {
        this.abrH = h;
        this.stalls = [];
        this.okSince = 0;
        Store.set('bc_mv_abr_h', { h, t: Date.now() });
        this.start();
    }

    abrOff() {
        if (!this.abrH) return;
        this.abrH = 0;
        if (!this.q && this.player) this.start();
    }

    ended(id) {
        if (id !== this.gen) return;
        if (this.usedWarm && !this.played) {
            this.usedWarm = false;
            this.skipWarm = true;
            mvVault.forget(this.vkey);
            return this.start();
        }
        if (++this.tries <= 3 || this.inPip() || (mvBg() && this.want)) {
            this.say(L().mv_load);
            this.rt = setTimeout(() => { if (id === this.gen) this.start(); }, this.tries === 1 ? 300 : Math.min(5000 * (this.tries - 1), 15000));
        } else this.say(`${L().mv_off} · ${L().mv_retry}\n${this.why || 'E0'}${this.dbg ? `\n${this.dbg}` : ''}`, true);
    }

    stop() {
        this.gen++;
        clearTimeout(this.rt);
        clearInterval(this.wd);
        this.abrStop();
        if (this.player) { this.player.stop(); this.player = null; }
    }

    destroy() {
        this.lazy = false;
        this.setWant(false);
        if (this.inPip()) { this.pip = false; this.pipKeep(false); }
        try { if (this.recS) this.recS.stop(); } catch (e) { dbg(e, 'MvTile.destroy'); }
        this.stop();
        this.stopPoster();
        this.endUi();
        clearInterval(this.lblTimer);
        this.el.remove();
    }
}

const bmEntry = b => ({ t: 'bm', name: String(b.name), url: b.url });

class MultiView {
    constructor(ui) {
        this.ui = ui;
        this.tiles = [];
        this.act = null;
        this.pane = null;
        this.loaded = false;
        this.dismissed = new Set();
        onPageEvent('visibilitychange', () => { if (!realHidden()) this.tiles.forEach(t => t.bgResume()); });
        setInterval(() => { if (this.pane && this.autoOn() && this.isOpen() && !realHidden()) this.autoFill(); }, 30000);
    }

    isOpen() {
        const bm = this.ui.bookmarkManager;
        return !!bm && bm.isOpen() && bm.mode === 'mv';
    }

    open() { this.ui.bookmarkManager?.open('mv'); }

    show() {
        if (!this.pane) this.build();
        this.labels();
        if (!this.loaded) {
            const wasOn = Store.get('bc_mv_on', []);
            this.loaded = true;
            this.restoring = true;
            const saved = Store.get('bc_mv', []);
            (Array.isArray(saved) ? saved : []).filter(e => hasUrl(e) && (e.t === 'bm' || e.t === 'url')).slice(0, MV_MAX).forEach(e => this.add({ t: e.t, name: String(e.name || ''), url: e.url }, true));
            this.restoring = false;
            const was = new Set(Array.isArray(wasOn) ? wasOn : []);
            this.tiles.forEach(t => {
                const on = mvBg() && was.has(this.key(t.e)) && !Store.get('bc_sleep_lock', 0);
                t.idle();
                if (on && t !== this.act) t.lazy = true;
            });
        }
        if (this.autoOn()) {
            this.autoFill();
            this.afterStatuses(() => { if (this.isOpen() && this.autoOn()) this.autoFill(); });
        }
        this.layout();
        requestAnimationFrame(() => { this.layout(); this.lazyCheck(); });
        setTimeout(() => this.lazyCheck(), 350);
    }

    fltBtns(cnt) {
        const l = L();
        return MV_FLT.map(([key, def, label, tip], i) => bmTbtn(
            escHTML(l[label]) + (cnt && cnt[i] ? ` · ${cnt[i]}` : ''), mvFltOn(key, def),
            () => {
                Store.set(key, !mvFltOn(key, def));
                this.paintFlt();
                this.layout();
                if (this.addOpen()) this.fillAdd();
                if (this.autoOn()) this.autoFill();
                this.afterStatuses(() => { this.layout(); if (this.autoOn()) this.autoFill(); });
            },
            l[tip]
        ));
    }

    paintFlt() {
        if (!this.fltSeg) return;
        this.fltSeg.replaceChildren(...this.fltBtns());
    }

    lazyCheck() {
        if (!this.isOpen() || realHidden()) return;
        const H = window.innerHeight;
        for (const t of this.tiles) {
            if (!t.lazy) continue;
            const r = t.el.getBoundingClientRect();
            if (!r.height) continue;
            const vis = Math.min(r.bottom, H) - Math.max(r.top, 0);
            if (vis >= Math.min(r.height, H) * 0.4) { t.lazy = false; t.start(); }
        }
    }

    dropBmKey(k) {
        const same = e => e && e.t === 'bm' && isStr(e.url) && bmKey(e.url) === k;
        if (!this.loaded) {
            const saved = Store.get('bc_mv', []);
            if (Array.isArray(saved) && saved.some(same)) Store.set('bc_mv', saved.filter(e => !same(e)));
            return;
        }
        this.tiles.filter(t => same(t.e)).forEach(t => this.remove(t));
    }

    autoOn() { return storeOn('bc_mv_auto'); }

    addOpen() { return this.addPanel.classList.contains('open'); }

    afterStatuses(fn) { this.ui.bookmarkManager?.checkStatuses().then(fn).catch(() => {}); }

    mvPass(b) {
        const st = b.status ?? 0;
        if (st === 0 && mvFltOn(MV_FLT[0][0], MV_FLT[0][1])) return false;
        const lk = bmStatusKey(b);
        return !MV_FLT.some(([key, def,,, k]) => k && k === lk && mvFltOn(key, def));
    }

    autoFill() {
        if (!this.pane || !this.autoOn()) return;
        this.layout();
        this.restoring = true;
        const fresh = [];
        try {
            for (const b of (AppState.bookmarks || [])) {
                if (this.tiles.length >= MV_MAX) break;
                if (!hasUrl(b) || !isSafeUrl(b.url) || !((b.status ?? 0) > 0) || !this.mvPass(b)) continue;
                const e = bmEntry(b);
                if (this.dismissed.has(this.key(e))) continue;
                const t = this.add(e, true);
                if (t) fresh.push(t);
            }
        } finally { this.restoring = false; }
        fresh.forEach(t => t.idle());
        if (fresh.length) this.save();
    }

    setAuto(on) {
        Store.set('bc_mv_auto', !!on);
        if (on) {
            this.dismissed.clear();
            this.autoFill();
            this.afterStatuses(() => { if (this.pane && this.autoOn()) this.autoFill(); });
        }
        if (this.pane) this.labels();
    }

    suspend() {
        if (mvBg()) return;
        this.tiles.forEach(t => { if (!t.inPip()) t.idle(); });
    }

    build() {
        mkStyle(this.ui.shadow, MV_CSS);
        const onScroll = rafOnce(() => this.lazyCheck());
        this.ui.shadow.addEventListener('scroll', onScroll, true);
        window.addEventListener('scroll', onScroll, { passive: true, capture: true });
        const pane = this.pane = mk('div', 'mv-pane');
        const bar = mk('div', 'mv-bar');
        this.btnAdd = mk('button', 'bm-tbtn on');
        this.btnAdd.onclick = () => this.toggleAdd();
        this.btnCols = mk('button', 'bm-hbtn bm-colsbtn');
        this.btnCols.onclick = () => { colsToggle(); this.layout(); };
        this.btnDvr = mk('div', 'bm-spdseg');
        this.dvrBtns = MV_DVR_OPTS.map(v => {
            const b = mk('button', 'bm-spdopt');
            b.type = 'button';
            b.onclick = () => this.setDvr(v);
            this.btnDvr.append(b);
            return [v, b];
        });
        this.btnPrev = mk('button', 'bm-hbtn bm-prevbtn');
        this.btnPrev.onclick = () => {
            const bm = this.ui.bookmarkManager;
            if (!bm) return;
            bm.prevEn = !bm.prevEn;
            Store.set('bc_previews', bm.prevEn);
            bm.updatePrevUI?.();
            bm.stopPreviews?.();
            this.syncPrev();
        };
        const spd = buildSpdSeg(() => {
            this.labels();
            this.tiles.forEach(t => t.kickPoster());
            const bm = this.ui.bookmarkManager;
            bm?.updatePrevUI?.();
            bm?.stopPreviews?.();
        });
        this.btnSpd = spd.seg;
        this.spdBtns = spd.btns;
        this.btnClr = htmlEl('bm-tbtn mv-clr', MV_IC.close, 'button');
        this.btnClr.onclick = () => askConfirm(this.ui, mvT('mv_clear_q'), () => this.clear());
        this.swAbr = mk('button', 'bm-sw');
        this.swAbr.onclick = () => this.toggleAbr();
        this.swBlur = mk('button', 'bm-sw');
        this.swBlur.onclick = () => { this.ui.setBlurMute(!AppState.blurMute); this.labels(); };
        this.swAuto = mk('button', 'bm-sw');
        this.swAuto.onclick = () => this.setAuto(!this.autoOn());
        this.swBg = mk('button', 'bm-sw');
        this.swBg.onclick = () => { Store.set('bc_mv_bg', !mvBg()); this.labels(); };
        this.btnGear = htmlEl('bm-hbtn bm-gear', icons.gear, 'button');
        this.setPanel = mk('div', 'mv-setp');
        const setRow = (k, ctl, cls) => { const r = mkSetRow('', ctl, cls); this[k] = r.firstChild; return r; };
        this.setPanel.append(setRow('lblAuto', this.swAuto), setRow('lblBg', this.swBg), setRow('lblDvr', this.btnDvr, 'bm-setrow-spd'), setRow('lblAbr', this.swAbr), setRow('lblBlur', this.swBlur), setRow('lblSpd', this.btnSpd, 'bm-setrow-spd'));
        bindGear(this.btnGear, this.setPanel);
        bar.append(this.btnAdd, this.btnPrev, this.btnCols, this.btnGear);
        MvNet.watch(() => this.tiles.forEach(t => t.abrNet()));
        const stage = this.stage = mk('div', 'mv-stage');
        this.grid = mk('div', 'mv-grid');
        this.empty = mk('div', 'mv-empty');
        this.emptyTxt = mk('div');
        this.emptyBtn = mk('button', 'bm-tbtn on');
        this.emptyBtn.onclick = () => this.toggleAdd(true);
        this.empty.append(this.emptyTxt, this.emptyBtn);
        this.addPanel = mk('div', 'mv-add');
        stage.append(this.grid, this.empty, this.addPanel);
        this.fltSeg = mk('div', 'bm-seg mv-seg');
        pane.append(bar, this.fltSeg, this.setPanel, stage);
        this.ui.bookmarkManager.modal.appendChild(pane);
    }

    labels() {
        this.paintFlt();
        const l = L();
        this.btnAdd.innerHTML = `${MV_IC.add} ${escHTML(l.mv_add)}`;
        this.emptyBtn.innerHTML = `${MV_IC.add} ${escHTML(l.mv_add)}`;
        this.emptyTxt.textContent = l.mv_hint;
        this.btnCols.title = l.st_cols || l.mv_cols;
        this.dvrBtns.forEach(([v, b]) => { b.textContent = this.dvrLabel(v); b.classList.toggle('on', mvDvr() === v); });
        this.swAbr.classList.toggle('on', mvAbr());
        this.swBlur.classList.toggle('on', AppState.blurMute === true);
        this.lblBlur.textContent = l.blur_mute;
        this.swAuto.classList.toggle('on', this.autoOn());
        this.swBg.classList.toggle('on', mvBg());
        this.swBg.title = mvT('mv_bg_t');
        this.lblAuto.textContent = mvT('mv_auto_all');
        this.lblBg.textContent = mvT('mv_bg');
        this.swAbr.title = mvT('mv_abr_t');
        this.lblDvr.textContent = mvT('mv_dvr');
        this.lblAbr.textContent = mvT('mv_abr_row');
        this.lblSpd.textContent = l.st_prev_spd;
        this.btnGear.title = l.settings;
        this.btnClr.title = l.mv_close;
        const pe = this.ui.bookmarkManager?.prevEn !== false;
        paintPrev(this.btnPrev, this.btnSpd, this.spdBtns, pe);
    }

    syncPrev() {
        if (!this.pane) return;
        this.labels();
        this.tiles.forEach(t => { t.setPoster(); t.kickPoster(); });
    }

    toggleAbr() {
        const on = !mvAbr();
        Store.set('bc_mv_abr', on);
        if (!on) this.tiles.forEach(t => t.abrOff());
        this.labels();
    }

    dvrLabel(v) {
        return v ? `${v / 60} ${mvT('mv_min_u')}` : mvT('mv_dvr_off');
    }

    setDvr(nx) {
        Store.set('bc_mv_dvr', nx);
        this.tiles.forEach(t => {
            t.applyDvr();
            if (t.player) t.player.o.keep = mvKeep();
            if (!nx && t.act) t.goLive();
        });
        this.labels();
    }

    tilePass(t) {
        if (t.e.t !== 'bm') return true;
        const b = (AppState.bookmarks || []).find(x => isObj(x) && x.url === t.e.url);
        return !b || this.mvPass(b);
    }

    layout() {
        if (!this.pane) return;
        let shown = 0;
        this.tiles.forEach(t => {
            const ok = this.tilePass(t);
            if (ok) shown++;
            else if (t.el.style.display !== 'none') { try { t.idle(); } catch (e) { dbg(e, 'MultiView.layout'); } }
            t.el.style.display = ok ? '' : 'none';
        });
        const n = shown;
        const c = colsGet();
        this.btnCols.innerHTML = COLS_ICONS[colsGet() - 1];
        this.grid.style.gridTemplateColumns = `repeat(${c},minmax(0,1fr))`;
        this.grid.classList.toggle('multi', c > 1);
        this.empty.style.display = n ? 'none' : 'flex';
        this.grid.style.display = n ? 'grid' : 'none';
        this.btnClr.style.display = n ? '' : 'none';
    }

    key(e) { return `${e.t}|${e.url}`; }

    add(e, quiet) {
        if (this.tiles.length >= MV_MAX) {
            this.btnAdd.textContent = L().mv_full;
            setTimeout(() => this.labels(), 2000);
            return null;
        }
        if (this.tiles.some(t => this.key(t.e) === this.key(e))) return null;
        const nm = String(e.name).toLowerCase();
        if (this.tiles.some(t => t.e.t !== e.t && String(t.e.name).toLowerCase() === nm)) return null;
        if (!this.restoring) this.dismissed.delete(this.key(e));
        const t = new MvTile(this, e);
        this.tiles.push(t);
        this.grid.append(t.el);
        this.layout();
        if (this.isOpen() && !this.restoring) { if (!this.act) this.select(t); t.start(); }
        if (!quiet) this.save();
        if (this.addOpen()) this.fillAdd();
        return t;
    }

    remove(t) {
        this.dismissed.add(this.key(t.e));
        t.destroy();
        this.tiles = this.tiles.filter(x => x !== t);
        if (this.act === t) this.act = null;
        this.layout();
        this.save();
        if (this.addOpen()) this.fillAdd();
    }

    clear() {
        this.tiles.forEach(t => this.dismissed.add(this.key(t.e)));
        this.tiles.forEach(t => t.destroy());
        this.tiles = [];
        this.act = null;
        this.layout();
        this.save();
    }

    select(t) {
        const prev = this.act;
        this.act = prev === t ? null : t;
        if (prev) prev.setActive(false);
        if (this.act) this.act.setActive(true);
    }

    save() {
        Store.set('bc_mv', this.tiles.map(t => ({ t: t.e.t, name: t.e.name, url: t.e.url })));
    }

    saveOn() {
        if (!this.loaded || this.restoring || Store.get('bc_sleep_lock', 0)) return;
        Store.set('bc_mv_on', this.tiles.filter(t => t.want).map(t => this.key(t.e)));
    }

    bootResume() {
        const on = Store.get('bc_mv_on', []);
        if (!mvBg() || !Array.isArray(on) || !on.length || Store.get('bc_sleep_lock', 0) || this.loaded) return;
        try { this.show(); } catch (e) { dbg(e, 'MultiView.bootResume'); }
    }

    toggleAdd(force) {
        const on = force === undefined ? !this.addOpen() : force;
        this.addPanel.classList.toggle('open', on);
        this.stage.classList.toggle('adding', on);
        if (this.fltSeg) {
            if (on) this.fltSeg.style.setProperty('display', 'none', 'important');
            else this.fltSeg.style.removeProperty('display');
        }
        if (on) this.fillAdd();
    }

    fillAdd() {
        const l = L(), p = this.addPanel, keep = p.querySelector('.mv-f')?.value || '';
        const finish = t => { if (t) this.toggleAdd(false); };
        const sel = new Map();
        const back = htmlEl('bm-tbtn mv-back', pSvg('<line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/>', 22), 'button');
        back.setAttribute('aria-label', l.mv_back);
        const hd = mk('div', 'mv-hd');
        const gob = mk('button', 'bm-tbtn on mv-gob');
        gob.style.display = 'none';
        hd.append(back, textEl('', l.mv_add, 'span'), gob);
        const icoPlus = pSvg('<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>', 24);
        const icoOk = pSvg('<polyline points="20 6 9 17 4 12"/>', 24);
        const flt = mk('input', 'mv-inp mv-f');
        flt.placeholder = l.mv_search;
        flt.value = keep;
        const bms = (AppState.bookmarks || []).filter(isObj);
        const cnt = MV_FLT.map(([,,,, k], i) => bms.filter(b => (i === 0 ? (b.status ?? 0) > 0 : k === bmStatusKey(b))).length);
        const chips = mk('div', 'st-chips hs-seg mv-chips');
        chips.append(...this.fltBtns(cnt));
        const fw = mk('div', 'mv-fw');
        const fi = htmlEl('mv-fi', icons.search, 'span');
        fw.append(fi, flt);
        const list = mk('div', 'mv-list');
        const paintSel = () => {
            list.classList.toggle('selmode', sel.size > 0);
            gob.style.display = sel.size ? '' : 'none';
            gob.textContent = `${l.mv_go} (${sel.size})`;
        };
        back.onclick = () => {
            if (!sel.size) return this.toggleAdd(false);
            sel.clear();
            list.querySelectorAll('.mv-it.sel').forEach(x => x.classList.remove('sel'));
            paintSel();
        };
        const toggleRow = (it, b) => {
            if (sel.has(b.url)) sel.delete(b.url);
            else {
                if (this.tiles.length + sel.size >= MV_MAX) {
                    gob.textContent = l.mv_full;
                    setTimeout(paintSel, 1500);
                    return;
                }
                sel.set(b.url, b);
            }
            it.classList.toggle('sel', sel.has(b.url));
            paintSel();
        };
        gob.onclick = () => {
            const arr = [...sel.values()];
            this.toggleAdd(false);
            for (const b of arr) if (!this.add(bmEntry(b), true)) break;
            this.save();
        };
        const draw = () => {
            const q = flt.value.trim().toLowerCase();
            const have = new Set(this.tiles.map(t => this.key(t.e)));
            const rows = [];
            const cur = getStreamUrl();
            const curName = String(currentSite.getName() || '');
            if (cur && !q && !(curName && this.tiles.some(t => String(t.e.name).toLowerCase() === curName.toLowerCase()))) {
                const it = mk('div', 'mv-it cur');
                it.append(textEl('', `${l.mv_cur}: ${curName}`, 'b'));
                it.onclick = () => finish(this.add(curName && isSafeUrl(location.origin + location.pathname)
                    ? { t: 'bm', name: curName, url: location.origin + location.pathname }
                    : { t: 'url', name: curName || 'LIVE', url: cur }));
                rows.push(it);
            }
            const items = (AppState.bookmarks || []).filter(b => isObj(b) && this.mvPass(b) && (!q || String(b.name).toLowerCase().includes(q))).sort((a, b) => bmViews(b) - bmViews(a));
            items.forEach(b => {
                const it = mk('div', `mv-it${have.has(this.key({ t: 'bm', url: b.url })) ? ' dis' : ''}${sel.has(b.url) ? ' sel' : ''}`);
                const im = document.createElement('img');
                im.referrerPolicy = 'no-referrer';
                im._bRef = b;
                im.onerror = () => { if (!tryGm(im, b.frame, () => frameFail(im))) frameFail(im); };
                if (b.frame) im.src = b.frame;
                else setTimeout(() => frameFail(im), 0);
                const inf = mk('div', 'mv-inf');
                const sub = mk('i');
                const bst = b.status ?? 0, blk = bmStatusKey(b);
                const dot = mk('span', 'mv-dot');
                dot.style.background = blk === 'st_private' ? '#b58cff' : blk === 'st_away' ? '#f2b93b' : blk ? '#ff9ad5' : bst > 0 ? '#4bd68a' : '#8a7a82';
                const stTxt = blk ? l[blk] : bst > 0 ? l.bm_online : '';
                sub.append(dot, [stTxt, `${urlSite(b.url).toUpperCase()}${bmViews(b) ? ` · ${fmtCount(bmViews(b))}` : ''}`].filter(Boolean).join(' · '));
                inf.append(textEl('', b.name, 'b'), sub);
                const plus = htmlEl('mv-plus', it.classList.contains('dis') ? icoOk : icoPlus, 'button');
                plus.setAttribute('aria-label', l.mv_add);
                plus.onpointerdown = ev => ev.stopPropagation();
                plus.onclick = ev => {
                    ev.stopPropagation();
                    finish(this.add(bmEntry(b)));
                };
                it.append(mk('span', 'mv-ck'), im, inf, plus);
                let timer = 0, sx = 0, sy = 0;
                const stop = () => { clearTimeout(timer); timer = 0; };
                it.onpointerdown = ev => {
                    if (ev.pointerType === 'mouse' && ev.button !== 0) return;
                    it._lp = false;
                    sx = ev.clientX; sy = ev.clientY;
                    stop();
                    timer = setTimeout(() => {
                        timer = 0;
                        it._lp = true;
                        try { if (navigator.vibrate) navigator.vibrate(15); } catch (e) { dbg(e, 'fillAdd.draw'); }
                        toggleRow(it, b);
                    }, 450);
                };
                it.onpointermove = ev => { if (timer && Math.hypot(ev.clientX - sx, ev.clientY - sy) > 10) stop(); };
                it.onpointerup = stop;
                it.onpointercancel = stop;
                it.onpointerleave = stop;
                it.oncontextmenu = ev => ev.preventDefault();
                it.onclick = () => {
                    if (it._lp) { it._lp = false; return; }
                    if (sel.size) return toggleRow(it, b);
                    finish(this.add(bmEntry(b)));
                };
                rows.push(it);
            });
            list.replaceChildren(...(rows.length ? rows : [textEl('', l.mv_none)]));
            paintSel();
        };
        flt.oninput = draw;
        draw();
        const inp = mk('input', 'mv-inp');
        inp.placeholder = l.mv_url;
        const go = textEl('bm-tbtn on', l.mv_go, 'button');
        const submit = () => {
            const u = inp.value.trim();
            if (!/^https?:\/\/\S+$/i.test(u)) return;
            let host = '';
            try { host = new URL(u).hostname; } catch (e) { dbg(e, 'fillAdd.submit'); }
            finish(this.add({ t: 'url', name: host, url: u }));
        };
        go.onclick = submit;
        inp.onkeydown = ev => { if (ev.key === 'Enter') submit(); };
        const r2 = mk('div', 'mv-r2');
        r2.append(inp, go);
        const tabs = mk('div', 'mv-tabs');
        const tabBm = textEl('mv-tab on', l.mv_bm_h, 'button');
        const tabLink = textEl('mv-tab', l.mv_tab_link || l.mv_link_h, 'button');
        const setTab = link => {
            tabBm.classList.toggle('on', !link);
            tabLink.classList.toggle('on', link);
            fw.classList.toggle('mv-off', link);
            chips.classList.toggle('mv-off', link);
            list.classList.toggle('mv-off', link);
            r2.classList.toggle('mv-off', !link);
            if (link) inp.focus();
        };
        tabBm.onclick = () => setTab(false);
        tabLink.onclick = () => setTab(true);
        tabs.append(tabBm, tabLink);
        p.replaceChildren(hd, tabs, fw, chips, list, r2);
        setTab(false);
    }
}

const STATUS_RULES = [
    [/"is(?:Full)?PrivateChat"\s*:\s*true/i, 3, 'st_private'],
    [/"isGroupChat"\s*:\s*true/i, 3, 'st_group'],
    [/"isVipShow"\s*:\s*true/i, 3, 'st_vip'],
    [/"isAway"\s*:\s*true/i, 2, 'st_away'],
    [/"showType"\s*:\s*"(?:private|full_?private|voyeur)"/i, 3, 'st_private'],
    [/"showType"\s*:\s*"group"/i, 3, 'st_group'],
    [/"showType"\s*:\s*"vip"/i, 3, 'st_vip'],
    [/__page_profile|"isOnline"\s*:\s*false|"online"\s*:\s*false|"isOffline"\s*:\s*true|"room_status"\s*:\s*["']offline["']|"isChatShowPaused"\s*:\s*true/i, 0],
    [/"isOnline"\s*:\s*true|"online"\s*:\s*true|"showType"\s*:\s*"public"|"isOffline"\s*:\s*false|"room_status"\s*:\s*["'](?:public|free|chat|group|private)["']/i, 1]
];
const STATUS_RULES_SITE = {
    sc: [
        [/"status"\s*:\s*"(?:private|p2p|p2pVoice|virtualPrivate|spy)"/i, 3, 'st_private'],
        [/"isInPrivate"\s*:\s*true|"isInP2p"\s*:\s*true/i, 3, 'st_private'],
        [/"status"\s*:\s*"(?:group|groupShow|ticketShow)"|"isInGroupShow"\s*:\s*true/i, 3, 'st_group'],
        [/"status"\s*:\s*"public"|"isLive"\s*:\s*true|"isBroadcasting"\s*:\s*true/i, 1],
        [/"status"\s*:\s*"(?:off|offline|idle)"|"isLive"\s*:\s*false|"isOnline"\s*:\s*false/i, 0]
    ],
    cb: [
        [/(?:"|\\u0022)room_status(?:"|\\u0022)\s*:\s*(?:"|\\u0022)(?:private|hidden|password protected)/i, 3, 'st_private'],
        [/(?:"|\\u0022)room_status(?:"|\\u0022)\s*:\s*(?:"|\\u0022)group/i, 3, 'st_group'],
        [/(?:"|\\u0022)room_status(?:"|\\u0022)\s*:\s*(?:"|\\u0022)away/i, 2, 'st_away'],
        [/(?:"|\\u0022)room_status(?:"|\\u0022)\s*:\s*(?:"|\\u0022)public/i, 1],
        [/(?:"|\\u0022)room_status(?:"|\\u0022)\s*:\s*(?:"|\\u0022)offline/i, 0],
        [/(?:"|\\u0022)hls_source(?:"|\\u0022)\s*:\s*(?:"|\\u0022)https?:/i, 1]
    ]
};
const SC_JSON_STATUS = [
    [/^(?:private|p2p|p2pVoice|virtualPrivate|spy)$/i, 3, 'st_private'],
    [/^(?:group|groupShow|ticketShow)$/i, 3, 'st_group']
];
const scJsonUser = h => {
    try {
        const u = JSON.parse(h)?.user?.user;
        return u && isStr(u.status) ? u : null;
    } catch { return null; }
};
const scJsonStatus = h => {
    if (!h || h.charCodeAt(0) !== 123) return null;
    const u = scJsonUser(h);
    if (!u) return null;
    const hit = SC_JSON_STATUS.find(([re]) => re.test(u.status));
    if (hit) return [hit[1], hit[2]];
    return /^public$/i.test(u.status) && u.isLive !== false ? [1, ''] : [0, ''];
};
const parseStatus = (h, site) => {
    if (site === 'sc') { const j = scJsonStatus(h); if (j) return j; }
    const rule = h && (STATUS_RULES_SITE[site] || STATUS_RULES).find(([re]) => re.test(h));
    if (!rule) return [0, '', false];
    const [, st, lk] = rule;
    return [st, lk || '', true];
};

const decodeJson = s => { try { return JSON.parse(`"${s}"`); } catch { return s; } };
const jsonField = (h, re) => { const m = h.match(re); return m?.[1] ? decodeJson(m[1]) : ''; };
const TOPIC_RE = /"currentTopic"\s*:\s*"((?:[^"\\]|\\.)*)"/i;
const FRAME_RE = /"frame"\s*:\s*"((?:[^"\\]|\\.)*)"/i;
const htmlDec = x => { try { return new DOMParser().parseFromString(x, 'text/html').documentElement.textContent || ''; } catch { return x; } };
const metaContent = (h, prop) => {
    const m = h.match(new RegExp(`(?:property|name)=["']${prop}["'][^>]*?content=["']([^"']*)["']`, 'i')) || h.match(new RegExp(`content=["']([^"']*)["'][^>]*?(?:property|name)=["']${prop}["']`, 'i'));
    return m ? htmlDec(m[1]) : '';
};
const intField = (h, re) => { const m = h.match(re); return m ? +m[1] : null; };
const siteProfile = {
    cb: (h, status, slug) => {
        if (!status) return { topic: '', frame: '' };
        const d = cbDossier(h), dv = d && [d.num_users, d.num_viewers, d.viewer_count, d.viewers_count].find(x => Number.isFinite(+x) && x !== null && x !== '');
        const v = dv != null ? +dv : intField(h, /(?:"|\\u0022)(?:num_users|num_viewers|viewer_count|viewers_count)(?:"|\\u0022)\s*:\s*(\d+)/i);
        const dt = d && [d.room_title, d.room_subject, d.subject].find(x => isStr(x) && x.trim());
        return {
            topic: String(dt || jsonField(h, /"(?:room_title|room_subject|subject)"\s*:\s*"((?:[^"\\]|\\.)*)"/i) || metaContent(h, 'og:description')).trim(),
            frame: slug ? cbFrameUrl(slug) : '',
            ...(v != null ? { viewers: v } : {})
        };
    },
    sc: (h, status) => {
        if (!status) return { topic: '', frame: '' };
        const v = intField(h, /"(?:viewersCount|usersCount|viewers)"\s*:\s*(\d+)/i);
        const fr = (jsonField(h, /"(?:previewUrlThumbBig|previewUrlThumbSmall|previewUrl)"\s*:\s*"((?:[^"\\]|\\.)*)"/i) || metaContent(h, 'og:image')).replace(/^\/\//, 'https://');
        return {
            topic: (jsonField(h, /"(?:topic|goalMessage)"\s*:\s*"((?:[^"\\]|\\.)*)"/i) || metaContent(h, 'og:description')).trim(),
            frame: fr.startsWith('https://') ? fr : '',
            ...(v != null ? { viewers: v } : {})
        };
    }
};
const stripTags = s => String(s).replace(/<[^>]+>/g, ' ').replace(/&nbsp;/g, ' ').replace(/&amp;/g, '&').replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/\s+/g, ' ').trim();
const ptIcoUrl = p => (isStr(p) && /^[a-z0-9_\/-]{1,60}$/i.test(p) ? `https://i.bgicdn.com/images/tags/${p}/18/2x/img.png` : '');
const ptIcoMap = () => {
    const m = new Map();
    for (const b of AppState.bookmarks) {
        if (!isObj(b) || !Array.isArray(b.ptags) || !Array.isArray(b.ptic)) continue;
        b.ptags.forEach((x, i) => { const u = ptIcoUrl(b.ptic[i]), k = String(x).toLowerCase(); if (u && !m.has(k)) m.set(k, u); });
    }
    return m;
};
const ptImg = (u, cls) => (u ? `<img class="${cls}" data-pti="1" src="${escHTML(u)}" alt="" decoding="sync" referrerpolicy="no-referrer">` : '');
const pilgSection = (h, cls) => {
    const i = h.indexOf(cls);
    if (i < 0) return '';
    const j = h.indexOf('pilg_section', i + cls.length);
    return h.slice(i, j < 0 ? i + 12000 : j);
};
const PILG_ICO = /images\/tags\/([a-z0-9_\/-]{1,60}?)\/\d+\/(?:\d+x\/)?img\./i;
const parsePilgTags = ts => {
    const run = re => collectProfileTags([...ts.matchAll(re)].map(m => ({ name: m[1], icon: m[1].match(PILG_ICO)?.[1] || '' })));
    let r = run(/<a\b[^>]*\/tags\/[^"'\/?#>\s]+[^>]*>([\s\S]*?)<\/a>/gi);
    if (!r.tags.length) r = run(/class=["']stl_item["'][^>]*>([\s\S]*?)<\/div>/gi);
    return r.tags.length ? { ptags: r.tags, ptic: r.icos.some(Boolean) ? r.icos : [] } : null;
};
const parsePilgRow = row => {
    const rng = [];
    for (const x of row.matchAll(/pilgs_badge[^>]*>([^<]*)</g)) {
        const m = x[1].match(/(\d{1,2}):(\d{2})\s*[-\u2013\u2014]\s*(\d{1,2}):(\d{2})/);
        if (m && !(+m[1] === +m[3] && m[2] === m[4])) rng.push(`${m[1].padStart(2, '0')}:${m[2]}-${m[3].padStart(2, '0')}:${m[4]}`);
    }
    return rng.join(',');
};
const parsePilgSchedule = ss => {
    let hi = -1;
    const days = ss.split('pilg_shelf').slice(1).map((row, i) => {
        if (hi < 0 && row.indexOf('__highlight') >= 0) hi = i;
        return parsePilgRow(row);
    });
    if (days.length !== 7) return null;
    const sh = hi < 0 ? 0 : (((new Date().getDay() + 6) % 7) - hi + 7) % 7;
    return days.map((_, i) => days[(i - sh + 7) % 7]);
};
const bcProfileUrl = b => {
    const slug = modelSlug(b.url);
    if (urlSite(b.url) !== 'bc' || !slug) return '';
    const o = originOf(b.url);
    return o ? `${o}/profile/${encodeURIComponent(slug)}/` : '';
};
const tagText = item => {
    if (isStr(item)) return item;
    return item && typeof item === 'object' ? (item.name || item.title || item.tag || item.value || '') : '';
};
const collectProfileTags = raw => {
    const tags = [], icos = [];
    for (const item of raw) {
        const tag = stripTags(String(tagText(item))).trim();
        if (!tag || tag.length > 40 || tags.includes(tag) || tags.length >= 30) continue;
        tags.push(tag);
        icos.push(item && typeof item === 'object' && isStr(item.icon) ? item.icon : '');
    }
    return { tags, icos };
};
function* jsonArrays(h, keys) {
    for (const m of h.matchAll(new RegExp('"(?:' + keys + ')"\\s*:\\s*(\\[(?:[^\\[\\]]|\\[[^\\]]*\\])*\\])', 'ig'))) {
        try { yield JSON.parse(m[1]); } catch (e) { dbg(e, 'jsonArrays'); }
    }
}
const parseBcJsonTags = h => {
    for (const raw of jsonArrays(h, 'modelTags|profileTags|tags')) {
        if (!Array.isArray(raw)) continue;
        const { tags, icos } = collectProfileTags(raw);
        if (tags.length) return { ptags: tags, ...(icos.some(Boolean) ? { ptic: icos } : {}) };
    }
    return null;
};
const isWeekSchedule = a => Array.isArray(a) && a.length === 7 && a.every(x => isStr(x) || x == null);
const scheduleDays = a => a.map(x => {
    const hits = String(x || '').match(/\d{1,2}:\d{2}\s*[-–—]\s*\d{1,2}:\d{2}/g) || [];
    return hits.map(v => v.replace(/\s+/g, '').replace(/[–—]/g, '-')).join(',');
});
const parseBcJsonSchedule = h => {
    for (const a of jsonArrays(h, 'modelSchedule|schedule')) {
        if (!isWeekSchedule(a)) continue;
        const days = scheduleDays(a);
        if (days.some(Boolean)) return days;
    }
    return null;
};
const pilgPart = (h, name) => (h.indexOf('pilg_section') >= 0 ? pilgSection(h, name) : '');
const bcViewers = h => {
    const m = h && h.match(/class=["'][^"']*\boc_count\b[^"']*["'][^>]*>\s*(\d[\d\s,.\u00a0]*)\s*</i);
    const n = m ? parseInt(m[1].replace(/\D/g, ''), 10) : NaN;
    return Number.isFinite(n) && n < 10000000 ? n : null;
};
const splitBcd = (h, site) => {
    if (site !== 'bc' || !h) return [h, null];
    const i = h.lastIndexOf(BCD_MARK);
    if (i < 0) return [h, null];
    let bcd = null;
    try { bcd = JSON.parse(h.slice(i + BCD_MARK.length)); } catch (e) { dbg(e, 'splitBcd'); }
    return [h.slice(0, i), bcd];
};
const BC_FACT_SCOPES = ['chatShowStatusOptions', 'performerData', 'chatHost'];
const BC_SCHEMA = {
    facts: {
        off: { keys: ['isOffline'], scopes: BC_FACT_SCOPES },
        on: { keys: ['isOnline', 'online'], scopes: ['performerData', 'chatHost'] },
        priv: { keys: ['isPrivateChat', 'isFullPrivateChat', 'isVoyeurChat', 'isInPrivate'], scopes: BC_FACT_SCOPES },
        grp: { keys: ['isGroupChat'], scopes: BC_FACT_SCOPES },
        vip: { keys: ['isVipShow'], scopes: BC_FACT_SCOPES },
        away: { keys: ['isAway'], scopes: ['performerData', 'chatHost', 'chatShowStatusOptions'] }
    },
    showType: { keys: ['showType', 'room_status', 'roomStatus'], scopes: ['chatHost', 'performerData'] },
    topic: { keys: ['currentTopic', 'chatTopic', 'roomTopic'], scopes: ['chatTopicOptions'] },
    frame: { keys: ['frame', 'frameUrl', 'previewFrame', 'previewUrl', 'livePreviewUrl'], scopes: ['initialVideoPoster', 'chatHost', 'performerData'] },
    viewers: { keys: ['viewers', 'viewersCount', 'viewerCount', 'usersCount'], scopes: ['chatHost', 'performerData'] },
    tags: { keys: ['modelTags', 'profileTags'], scopes: ['performerData', 'chatHost'] },
    schedule: { keys: ['modelSchedule', 'schedule'], scopes: ['performerData', 'chatHost'] }
};
const PD_WARN_RATIO = 0.3, PD_WARN_MIN = 5, PD_INFO_MIN = 3, PD_WINDOW = 600000, PD_STALE_MS = 1800000;

const pdT = (k, v) => {
    const d = PD_T[String(AppState.lang).toUpperCase()] || PD_T.EN;
    return String(d[k] ?? PD_T.EN[k]).replace(/\{(\w+)\}/g, (m, x) => (x === 'copy' ? (d.copy ?? PD_T.EN.copy) : (v && v[x] != null ? v[x] : m)));
};

const bcRoots = h => {
    const roots = [];
    if (!isStr(h) || !h) return roots;
    const c0 = h.charCodeAt(0);
    if (c0 === 123 || c0 === 91) { try { const j = JSON.parse(h); if (isObj(j)) roots.push(j); } catch (e) { dbg(e, 'bcRoots'); } }
    if (h.indexOf('<script') >= 0) {
        for (const m of h.matchAll(/<script\b([^>]*)>([\s\S]*?)<\/script>/gi)) {
            if (roots.length >= 12) break;
            const body = m[2].trim(), c = body.charCodeAt(0);
            if (body.length < 2 || body.length > 3e6 || (c !== 123 && c !== 91) || !/json|data-type/i.test(m[1])) continue;
            try { const j = JSON.parse(body); if (isObj(j)) roots.push(j); } catch (e) { dbg(e, 'bcRoots'); }
        }
    }
    return roots;
};
const jsonFind = (roots, names, pred, limit = 30000) => {
    const set = new Set(names.map(n => String(n).toLowerCase()));
    const q = roots.filter(isObj);
    let n = 0;
    for (let i = 0; i < q.length && n < limit; i++) {
        const o = q[i];
        const ent = Array.isArray(o) ? o.map((v, k) => [String(k), v]) : Object.entries(o);
        for (const [k, v] of ent) {
            n++;
            if (set.has(k.toLowerCase()) && (!pred || pred(v))) return { value: v, key: k };
            if (isObj(v)) q.push(v);
        }
    }
    return null;
};
const bcFind = (roots, spec, pred, scopedOnly) => {
    if (!roots.length) return null;
    for (const sc of spec.scopes || []) {
        const s = jsonFind(roots, [sc], v => isObj(v) && !Array.isArray(v));
        const r = s && jsonFind([s.value], spec.keys, pred);
        if (r) return { value: r.value, via: `json:${sc}` };
    }
    if (scopedOnly) return null;
    const r = jsonFind(roots, spec.keys, pred);
    return r ? { value: r.value, via: 'json' } : null;
};
const bcFact = (roots, spec) => {
    let seen = null;
    for (const k of spec.keys) {
        const r = bcFind(roots, { keys: [k], scopes: spec.scopes }, v => typeof v === 'boolean');
        if (!r) continue;
        if (r.value) return { v: true, via: r.via };
        seen = seen || { v: false, via: r.via };
    }
    return seen;
};

const BC_ROOM = new Map();
const bcListHint = slug => {
    const e = slug && BC_ROOM.get(String(slug).toLowerCase());
    if (!e || Date.now() - e.t > 180000) return null;
    const r = String(e.room).toLowerCase();
    if (/private|voyeur/.test(r)) return { st: 3, lk: 'st_private' };
    if (/group/.test(r)) return { st: 3, lk: 'st_group' };
    if (/vip/.test(r)) return { st: 3, lk: 'st_vip' };
    if (/away/.test(r)) return { st: 2, lk: 'st_away' };
    if (/^(?:public|free|chat)$/.test(r)) return { st: 1, lk: '' };
    return null;
};
const bcJsonStatus = roots => {
    if (!roots.length) return null;
    const F = {};
    for (const k of Object.keys(BC_SCHEMA.facts)) F[k] = bcFact(roots, BC_SCHEMA.facts[k]);
    const sh = bcFind(roots, BC_SCHEMA.showType, isStr);
    const T = sh ? sh.value.trim().toLowerCase() : '';
    const hit = f => F[f]?.v === true;
    const first = F.off || F.priv || F.grp || F.vip || F.away || F.on || sh;
    if (!first) return null;
    const via = first.via;
    if (hit('off')) return [0, '', via];
    if (hit('priv') || /^(?:private|full_?private|voyeur|p2p)/.test(T)) return [3, 'st_private', via];
    if (hit('grp') || /^group/.test(T)) return [3, 'st_group', via];
    if (hit('vip') || T === 'vip') return [3, 'st_vip', via];
    if (hit('away')) return [2, 'st_away', via];
    if (F.off?.v === false || hit('on') || /^(?:public|free|chat)$/.test(T)) return [1, '', via];
    if (F.on?.v === false || /^(?:offline|off)$/.test(T)) return [0, '', via];
    return null;
};
const resolveStatusEx = (h, site, bcd, roots, slug) => {
    if (site === 'bc') {
        if (bcd && typeof bcd.o === 'boolean') {
            let r = bcdStatus(bcd, h);
            if (bcd.o && h) { const a = parseStatus(h, site); if (a[0] === 2) r = a; }
            return { st: r[0], lk: r[1] || '', src: 'api:chat' };
        }
        const j = bcJsonStatus(roots);
        if (j) return { st: j[0], lk: j[1], src: j[2] };
        if (bcd && bcd.n === false) return { st: 0, lk: '', src: 'api:amf' };
        if (bcd && bcd.a === true) return { st: 2, lk: 'st_away', src: 'api:amf' };
        if (bcd && bcd.n === true) return { st: 1, lk: '', src: 'api:amf' };
        const ls = bcListHint(slug);
        if (ls) return { ...ls, src: 'listing' };
    }
    const r = parseStatus(h, site);
    if (r[2] === false) return { st: 0, lk: '', src: '', unk: true };
    return { st: r[0], lk: r[1] || '', src: 'regex' };
};

const bcTopic = (h, bcd, roots) => {
    if (isStr(bcd?.t) && bcd.t.trim()) return [bcd.t.trim(), 'api:chat'];
    const j = bcFind(roots, BC_SCHEMA.topic, v => isStr(v) && !!v.trim());
    if (j) return [j.value.trim(), j.via];
    const r = jsonField(h, TOPIC_RE).trim();
    return r ? [r, 'regex'] : ['', ''];
};
const bcFrameNorm = s => mvUrl(String(s)).replace(/^http:\/\//i, 'https://');
const bcFrame = (h, bcd, roots) => {
    const j = bcFind(roots, BC_SCHEMA.frame, v => isStr(v) && HTTP_RE.test(bcFrameNorm(v)));
    if (j) return [bcFrameNorm(j.value), j.via];
    const rx = bcFrameNorm(jsonField(h, FRAME_RE) || jsonField(h, FRAME_ALT_RE));
    if (HTTP_RE.test(rx)) return [rx, 'regex'];
    return isStr(bcd?.f) && HTTP_RE.test(bcd.f) ? [bcFrameNorm(bcd.f), 'api:amf'] : ['', ''];
};
const bcViewersEx = (h, roots) => {
    const n = bcViewers(h);
    if (n != null) return [n, 'html:oc_count'];
    const j = bcFind(roots, BC_SCHEMA.viewers, v => (typeof v === 'number' || (isStr(v) && v.trim() !== '')) && Number.isFinite(+v) && +v >= 0 && +v < 1e7, true);
    return j ? [Math.round(+j.value), j.via] : [null, ''];
};
const bpCp = n => { try { return String.fromCodePoint(n); } catch { return ''; } };
const bpTxt = s => stripTags(String(s).replace(/<[^>]*$/, ' ').replace(/<(script|style|svg|button)\b[\s\S]*?<\/\1>/gi, ' ').replace(/<br\s*\/?>/gi, ' '))
    .replace(/&lt;/g, '<').replace(/&gt;/g, '>')
    .replace(/&#x([0-9a-f]+);/gi, (m, n) => bpCp(parseInt(n, 16))).replace(/&#(\d+);/g, (m, n) => bpCp(+n))
    .replace(/[\u00a0\u200b]/g, ' ').replace(/\s+/g, ' ').trim();
const bpInner = (h, re) => { const m = h.match(re); return m ? m[1] : ''; };
const bpCells = chunk => {
    const rows = [];
    let cur = null;
    for (const m of chunk.matchAll(/<div\b[^>]*class=["'][^"']*\bpilgt_cell\b([^"']*)["'][^>]*>([\s\S]*?)<\/div>/gi)) {
        const caps = [...m[2].matchAll(/class=["'][^"']*\b(?:pilg_f_caption|pilgt_caption)\b[^"']*["'][^>]*>([\s\S]*?)<\/span>/gi)].map(x => bpTxt(x[1]).replace(/^[,;\s]+|[,;\s]+$/g, '')).filter(Boolean);
        const t = caps.length ? [...new Set(caps)].join(', ') : bpTxt(m[2]);
        if (/__accent/.test(m[1])) { cur = [t.replace(/\s*[::]\s*$/, ''), '']; rows.push(cur); }
        else if (cur && t) {
            cur[1] = cur[1] ? `${cur[1]}, ${t}` : t;
            if (/__(?:online|offline|broadcast)/.test(m[1])) cur[2] = 1;
        }
    }
    return rows.filter(r => r[0] && r[1]);
};
const bpShelves = chunk => {
    const rows = [];
    for (const part of chunk.split(/class=["']pilg_shelf\b/).slice(1)) {
        const t = bpTxt(bpInner(part, /pilgs_text[^>]*>([\s\S]*?)<\/div>/));
        const b = [...part.slice(0, 3000).matchAll(/pilgs_badge\b[^>]*>([\s\S]*?)<\/(?:span|div)>/g)].map(x => bpTxt(x[1])).filter(Boolean);
        if (t && b.length) rows.push([t, b.join(', ')]);
    }
    return rows;
};
const bpAlts = chunk => [...chunk.matchAll(/<img\b[^>]*\balt=["']([^"']+)["']/gi)].map(x => bpTxt(x[1])).filter(Boolean);
const bpTally = list => {
    const m = new Map();
    list.forEach(x => m.set(x, (m.get(x) || 0) + 1));
    return [...m].map(([k, n]) => (n > 1 ? `${k} ×${n}` : k)).join(', ');
};
const bpVol = rows => rows.map(([l, v]) => [l, v, 1]);
const bpSection = (title, mod, body, cnt) => {
    if (/__header|__tags/.test(mod)) return null;
    if (body.indexOf('pilgt_cell') >= 0) { const r = bpCells(body); return r.length ? { h: title, r } : null; }
    if (body.indexOf('pilgs_text') >= 0) { const r = bpShelves(body); return r.length ? { h: title, r: bpVol(r) } : null; }
    if (/psgi_img/.test(body)) { const v = bpTally(bpAlts(body)); return v ? { h: '', r: [[title, v, 1]] } : null; }
    if (/bc_media_thumb/.test(body)) {
        const v = body.split(/class=["'][^"']*\bbc_media_thumb\b/).slice(1).map(p => {
            const t = bpTxt(bpInner(p, /mt_item __title[^>]*>([\s\S]*?)<\/span>/));
            const n = bpTxt(bpInner(p, /mt_counter __bg[^>]*>([\s\S]*?)<\/span>/)).match(/\d+/);
            return t ? (n ? `${t} (${n[0]})` : t) : '';
        }).filter(Boolean).join(', ');
        return v ? { h: '', r: [[title, v, 1]] } : null;
    }
    if (/\bpilg_list\b/.test(body) && /\bpl_caption\b/.test(body)) {
        const r = [];
        for (const part of body.split(/class=["']pilg_list\b/).slice(1)) {
            const cap = bpTxt(bpInner(part, /\bpl_caption\b[^>]*>([\s\S]*?)<\/p>/));
            const seen = new Set(), vals = [];
            for (const it of part.split(/class=["']pl_item\b/).slice(1)) {
                const al = [...new Set(bpAlts(it))].join(', '), cp = bpTxt(bpInner(it, /\bpli_caption\b[^>]*>([\s\S]*?)<\/p>/));
                const key = al || cp;
                if (!key || seen.has(key)) continue;
                seen.add(key);
                vals.push(al && cp ? `${al} — ${cp}` : al || cp);
            }
            if (vals.length) r.push([cap || title, vals.join('; ')]);
        }
        return r.length ? { h: title, r: bpVol(r) } : null;
    }
    const an = bpTxt(bpInner(body, /\bqc_announce\b[^>]*>([\s\S]*?)<\/p>/));
    const t = an || bpTxt(body.slice(0, 2500));
    const v = t || cnt;
    return v && title ? { h: '', r: [[title, String(v).slice(0, 400), 1]] } : null;
};
const bpFromPilg = h => {
    const g = [];
    const top = h.indexOf('pilg_detail_wrp');
    if (top >= 0) {
        const ends = ['pilg_d_fan_boost', 'pilg_dm_wrp', 'pilg_section'].map(k => h.indexOf(k, top + 10)).filter(i => i > 0);
        const r = bpCells(h.slice(top, Math.min(...ends, top + 20000)));
        if (r.length) g.push({ h: '', r });
    }
    const dm = [];
    for (const p of h.split(/class=["'][^"']*\bpilg_dm_wrp\b/).slice(1)) {
        const head = p.slice(0, 6000);
        const t = bpTxt(bpInner(head, /pilg_dm_title[^>]*>([\s\S]*?)<\/div>/));
        const v = bpTxt(bpInner(head, /pilg_dm_invisible_wrp[^>]*>\s*<div[^>]*>([\s\S]*?)<\/div>/) || bpInner(head, /pilg_dm_text[^>]*>([\s\S]*?)<\/div>/));
        if (t && v) dm.push([t.replace(/\s*[:\uff1a]\s*$/, ''), v]);
    }
    if (dm.length) g.push({ h: '', r: dm });
    const starts = [...h.matchAll(/class=["']pilg_section\b([^"']*)["']/g)];
    starts.forEach((m, i) => {
        let body = h.slice(m.index, Math.min(starts[i + 1]?.index ?? Infinity, m.index + 60000));
        const cut = body.search(/class=["']pilg_top_btn|<\/jsx>/);
        if (cut > 0) body = body.slice(0, cut);
        const tm = body.match(/class=["'][^"']*\bpilg_title\b[^"']*["'][^>]*>([\s\S]*?)<\/(?:h\d|div|p|span)>/);
        let title = tm ? bpTxt(tm[1]) : '';
        const cm = title.match(/\s*\((\d+)\)\s*$/);
        if (cm) title = title.slice(0, cm.index).trim();
        try {
            const s = bpSection(title, m[1], tm ? body.slice(tm.index + tm[0].length) : body, cm?.[1]);
            if (s) g.push(s);
        } catch (e) { dbg(e, 'bpSection'); }
    });
    const main = g.filter(x => !x.h), rest = g.filter(x => x.h);
    const all = main.length ? [{ h: '', r: main.flatMap(x => x.r) }, ...rest] : rest;
    return all.length ? { g: all } : null;
};
const BP_JSON = [
    [/^age$/i, 'Возраст', 'Age'],
    [/^height$/i, 'Рост', 'Height'],
    [/^weight$/i, 'Вес', 'Weight'],
    [/^(?:hair|hair_?colou?r)$/i, 'Волосы', 'Hair'],
    [/^(?:eyes?|eye_?colou?r)$/i, 'Глаза', 'Eyes'],
    [/^ethnic\w*$/i, 'Этническое происхождение', 'Ethnicity'],
    [/^(?:spoken_?)?languages?$/i, 'Языки', 'Languages'],
    [/^(?:interested_?in|orientation|sexual_?orientation)$/i, 'Интересуют', 'Interested in'],
    [/^(?:body_?type|build)$/i, 'Телосложение', 'Body type'],
    [/^(?:bust|breasts?|breast_?size)$/i, 'Размер груди', 'Breast size'],
    [/^(?:pubic_?hair|shaving)$/i, 'Интимная стрижка', 'Pubic hair'],
    [/^(?:country|location|city|birthplace)$/i, 'Место', 'Location'],
    [/^(?:about|about_?me|description|bio|biography)$/i, 'Обо мне', 'About']
];
const bpScalar = v => {
    if (isStr(v)) return bpTxt(v);
    if (typeof v === 'number' && Number.isFinite(v)) return String(v);
    if (Array.isArray(v) && v.length && v.length < 20) return v.map(x => (isStr(x) ? bpTxt(x) : isPlain(x) ? bpTxt(x.name || x.title || x.label || x.value || '') : '')).filter(Boolean).join(', ');
    return '';
};
const bpFromJson = roots => {
    const q = roots.filter(isObj);
    let best = null, n = 0;
    for (let i = 0; i < q.length && n < 30000; i++) {
        const o = q[i];
        const ent = Array.isArray(o) ? o.map((v, k) => [String(k), v]) : Object.entries(o);
        const rows = [], used = new Set();
        for (const [k, v] of ent) {
            n++;
            if (isObj(v)) q.push(v);
            const d = BP_JSON.find(x => x[0].test(k));
            const t = d && !used.has(d) ? bpScalar(v) : '';
            if (t && t.length <= BP_VAL) { used.add(d); rows.push([ruT(d[1], d[2]), t]); }
        }
        if (rows.length >= 2 && (!best || rows.length > best.length)) best = rows;
    }
    return best ? { g: [{ h: '', r: best }] } : null;
};
const bpFromTables = h => {
    const rows = [], seen = new Set();
    const push = (a, b) => {
        const l = bpTxt(a).replace(/\s*[::]\s*$/, ''), v = bpTxt(b);
        if (!l || !v || l.length > 40 || v.length > 300 || /^[\d\s.,]+$/.test(l) || seen.has(l)) return;
        seen.add(l);
        rows.push([l, v]);
    };
    for (const m of h.matchAll(/<dt\b[^>]*>([\s\S]{1,200}?)<\/dt>\s*<dd\b[^>]*>([\s\S]{1,600}?)<\/dd>/gi)) push(m[1], m[2]);
    for (const m of h.matchAll(/<tr\b[^>]*>\s*<t[hd]\b[^>]*>([\s\S]{1,200}?)<\/t[hd]>\s*<t[hd]\b[^>]*>([\s\S]{1,600}?)<\/t[hd]>\s*<\/tr>/gi)) push(m[1], m[2]);
    return rows.length >= 3 ? { g: [{ h: '', r: rows.slice(0, 30) }] } : null;
};
const parseBcProfile = (h, roots, full) => {
    if (!isStr(h) || h.length < 200) return null;
    let r = h.indexOf('pilg_') >= 0 ? bpFromPilg(h) : null, s = 'html:pilg';
    if (!r && full) { r = bpFromJson(roots || bcRoots(h)); s = 'json'; }
    if (!r && full) { r = bpFromTables(h); s = 'html:table'; }
    return r ? bpClean({ ...r, s }) : null;
};
const CB_VOL = /follower|last[-_ ]?broadcast|time[-_ ]?since|videos?|photos?|online|viewers|подписчик/i;
const CB_DROP = /(?:^|_)(?:id|uid|uuid|url|image|img|thumb\w*|token|csrf|hash|edge|auth|username|slug|password|html)(?:$|_)|url$/i;
const CB_JSON = [
    [/^real_?name$/i, 'Настоящее имя', 'Real name'],
    [/^(?:display_)?age$/i, 'Возраст', 'Age'],
    [/^birth(?:day|date)$/i, 'Дата рождения', 'Birthday'],
    [/^(?:gender|sex|i_?am)$/i, 'Пол', 'I am'],
    [/^interested_?in$/i, 'Интересуют', 'Interested in'],
    [/^location$/i, 'Место', 'Location'],
    [/^languages?$/i, 'Языки', 'Languages'],
    [/^body_?type$/i, 'Телосложение', 'Body type'],
    [/^smoke_?drink$/i, 'Курение / алкоголь', 'Smoke / Drink'],
    [/^body_?decorations?$/i, 'Украшения тела', 'Body decorations'],
    [/^(?:about_?me|bio|description)$/i, 'Обо мне', 'About me'],
    [/^(?:follower_?count|followers_?count|num_?followers|followers)$/i, 'Подписчики', 'Followers'],
    [/^(?:last_?broadcast|time_since_last_broadcast)$/i, 'Последняя трансляция', 'Last broadcast'],
    [/^wish_?list$/i, 'Список желаний', 'Wish list'],
    [/^social_?medias?$/i, 'Соцсети', 'Social media']
];
const cbHuman = k => { const t = String(k).replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/[_-]+/g, ' ').trim().toLowerCase(); return t.charAt(0).toUpperCase() + t.slice(1); };
const cbScalar = v => {
    if (isStr(v)) return bpTxt(v);
    if (typeof v === 'number' && Number.isFinite(v)) return String(v);
    if (Array.isArray(v) && v.length && v.length <= 30) return [...new Set(v.map(x => (isStr(x) || typeof x === 'number' ? bpTxt(String(x)) : isPlain(x) ? bpTxt(String(x.title || x.name || x.label || x.platform || x.value || '')) : '')).filter(Boolean))].join(', ');
    return '';
};
const cbBioFromJson = j => {
    if (!isObj(j)) return null;
    const rows = [], seen = new Set();
    const q = [[j, 0]];
    while (q.length && rows.length < BP_MAX_R) {
        const [o, d] = q.shift();
        for (const [k, v] of Object.entries(o)) {
            const def = CB_JSON.find(x => x[0].test(k));
            if (!def && CB_DROP.test(k)) continue;
            const t = cbScalar(v);
            if (t && t.length <= BP_VAL && t !== 'false' && t !== 'null') {
                const l = def ? ruT(def[1], def[2]) : cbHuman(k);
                if (seen.has(l)) continue;
                seen.add(l);
                rows.push(CB_VOL.test(k) ? [l, t, 1] : [l, t]);
            } else if (isPlain(v) && d < 2) q.push([v, d + 1]);
        }
    }
    return rows.length >= 2 ? bpClean({ g: [{ h: '', r: rows }], s: 'json:bio' }) : null;
};
const cbBioFromHtml = h => {
    if (!isStr(h) || h.length < 100) return null;
    const at = h.search(/BioTabContent|room-bio-tab-contents|bio-tab-real-name|data-testid=["']bio-/);
    const part = at < 0 ? h : h.slice(Math.max(0, at - 200));
    const rows = [];
    for (const m of part.matchAll(/<tr\b[^>]*>([\s\S]*?)<\/tr>/gi)) {
        const tds = [...m[1].matchAll(/<td\b([^>]*)>([\s\S]*?)<\/td>/gi)];
        if (tds.length < 2) continue;
        const l = bpTxt(tds[0][2]).replace(/\s*[::]\s*$/, '');
        const raw = tds[1][2].replace(/<(\w+)\b[^>]*display:\s*none[^>]*>[\s\S]*?<\/\1>/gi, ' ');
        const soc = [...raw.matchAll(/(?:SocialMediaThumbnail__title|data-testid=["']social-media-title["'])[^>]*>([\s\S]*?)<\/(?:span|div)>/g)].map(x => bpTxt(x[1])).filter(Boolean);
        const sets = [...raw.matchAll(/<a\b(?=[^>]*PhotosetThumbnail)[^>]*\btitle=["']([^"']*)["']/gi)].map(x => bpTxt(x[1])).filter(Boolean);
        const v = soc.length ? [...new Set(soc)].join(', ') : sets.length ? [...new Set(sets)].join(', ') : bpTxt(raw);
        if (l && l.length <= 40 && v) rows.push(CB_VOL.test(`${tds[0][1]} ${tds[0][2]} ${tds[1][1]} ${l}`) ? [l, v, 1] : [l, v]);
    }
    return rows.length ? bpClean({ g: [{ h: '', r: rows }], s: 'html:cb' }) : null;
};
const cbRoomH = () => ruT('Комната', 'Room');
const cbRoomGroup = d => {
    if (!isPlain(d)) return null;
    const rows = [], tk = ruT('ток.', 'tk'), mn = ruT('мин', 'min');
    const add = (l, v) => { if (v !== '' && v != null && v !== 'null' && v !== 'undefined') rows.push([l, String(v), 1]); };
    const G = { f: ['Девушка', 'Woman'], m: ['Парень', 'Man'], s: ['Транс', 'Trans'], t: ['Транс', 'Trans'], c: ['Пара', 'Couple'] };
    const g = G[String(d.broadcaster_gender || d.gender || '').trim().toLowerCase().charAt(0)];
    if (g) add(ruT('Пол', 'Gender'), ruT(g[0], g[1]));
    let sc = d.satisfaction_score;
    if (isStr(sc)) { try { sc = JSON.parse(sc); } catch { sc = null; } }
    if (isPlain(sc) && d.hide_satisfaction_score !== true && Number.isFinite(+sc.percent)) add(ruT('Рейтинг', 'Rating'), `${sc.percent}% · +${sc.up_votes ?? 0} / −${sc.down_votes ?? 0}`);
    const price = (k, mk) => { const v = +d[k]; return Number.isFinite(v) && v > 0 ? `${v} ${tk}/${mn}${+d[mk] > 0 ? `, ${ruT('от', 'min')} ${d[mk]} ${mn}` : ''}` : ''; };
    if (d.allow_private_shows !== false) add(ruT('Приват', 'Private'), price('private_show_price', 'private_min_minutes'));
    add(ruT('Подглядывание', 'Spy'), price('spy_private_show_price'));
    add(ruT('Премиум-приват', 'Premium private'), price('premium_private_price', 'premium_private_min_minutes'));
    add(ruT('Face2Face', 'Face2Face'), price('f2f_private_show_price', 'f2f_private_min_minutes'));
    if (d.performer_has_fanclub === true) add(ruT('Фан-клуб', 'Fan club'), ruT('есть', 'yes'));
    if (isStr(d.chat_rules) && d.chat_rules.trim()) add(ruT('Правила чата', 'Chat rules'), bpTxt(d.chat_rules).slice(0, 500));
    return rows.length ? { h: cbRoomH(), r: rows } : null;
};
const cbJoin = (prev, bio, room) => {
    const pg = isPlain(prev) && Array.isArray(prev.g) ? prev.g : [];
    const g = [...(bio?.g || pg.filter(x => x.h !== cbRoomH())), ...[room || pg.find(x => x.h === cbRoomH())].filter(Boolean)];
    return g.length ? bpClean({ g, s: bio?.s || prev?.s || 'cb' }) : null;
};
const cbInfoFetch = async (b, signal) => {
    const slug = modelSlug(b.url);
    if (!slug) return null;
    const e = encodeURIComponent(slug), o = originOf(b.url) || 'https://chaturbate.com';
    let bio = null;
    const t = await requestOnce(`${o}/api/biocontext/${e}/`, { Referer: `${o}/${e}/` }, signal, 12000);
    if (signal?.aborted) return null;
    if (t) { try { bio = cbBioFromJson(JSON.parse(t)); } catch (err) { dbg(err, 'cbInfoFetch'); } }
    const h = await requestOnce(b.url, null, signal, 12000);
    if (signal?.aborted) return null;
    let room = null;
    if (h) {
        room = cbRoomGroup(cbDossier(h));
        bio = bio || cbBioFromHtml(h);
    }
    const bp = cbJoin(b.bp, bio, room);
    return bp && (bio || room) ? { bp } : null;
};
const bcExtraSrc = (h, roots) => {
    const out = {}, src = { tags: '', sch: '', bp: '' };
    if (!h) return { out, src };
    const ts = pilgPart(h, 'pilg_section __tags');
    let tg = ts ? parsePilgTags(ts) : null;
    if (tg) src.tags = 'html:pilg';
    if (!tg) {
        const j = bcFind(roots, BC_SCHEMA.tags, a => Array.isArray(a) && collectProfileTags(a).tags.length > 0);
        if (j) {
            const { tags, icos } = collectProfileTags(j.value);
            tg = { ptags: tags, ...(icos.some(Boolean) ? { ptic: icos } : {}) };
            src.tags = j.via;
        }
    }
    if (!tg) { tg = parseBcJsonTags(h); if (tg) src.tags = 'regex'; }
    if (tg) Object.assign(out, tg);
    const ss = pilgPart(h, 'pilg_section __schedule');
    let sch = ss ? parsePilgSchedule(ss) : null;
    if (sch) src.sch = 'html:pilg';
    if (!sch) {
        const j = bcFind(roots, BC_SCHEMA.schedule, a => isWeekSchedule(a) && scheduleDays(a).some(Boolean));
        if (j) { sch = scheduleDays(j.value); src.sch = j.via; }
    }
    if (!sch) { sch = parseBcJsonSchedule(h); if (sch) src.sch = 'regex'; }
    if (sch) out.sch = sch;
    const bp = parseBcProfile(h, roots, false);
    if (bp) { out.bp = bp; src.bp = bp.s; }
    return { out, src };
};
const parseBcExtra = h => bcExtraSrc(h, bcRoots(h)).out;

const copyErrorLog = () => {
    const txt = JSON.stringify({
        version: (typeof GM_info !== 'undefined' && GM_info.script?.version) || '4.9.0',
        host: location.hostname, ua: navigator.userAgent, at: new Date().toISOString(), count: DBG_LOG.length, errors: DBG_LOG
    }, null, 1);
    const say = msg => {
        try {
            const d = document.createElement('div');
            d.textContent = msg;
            d.style.cssText = 'all:initial;position:fixed;left:50%;bottom:24px;transform:translateX(-50%);z-index:2147483647;background:#1f2a22;color:#d6f5e0;font:13px/1.4 system-ui,sans-serif;padding:10px 16px;border-radius:12px';
            (document.body || document.documentElement).appendChild(d);
            setTimeout(() => d.remove(), 3000);
        } catch (e) { dbg(e, 'copyErrorLog'); }
    };
    Helpers.copy(txt, () => say(`Лог скопирован / Log copied (${DBG_LOG.length})`), () => say('Не удалось скопировать / Copy failed'));
};
const pdToast = msg => {
    try {
        const d = document.createElement('div');
        d.textContent = msg;
        d.style.cssText = 'all:initial;position:fixed;left:50%;bottom:24px;transform:translateX(-50%);max-width:min(560px,92vw);z-index:2147483647;background:#3a2410;color:#ffd9a8;border:1px solid #c77d2a;border-radius:10px;padding:10px 14px;font:13px/1.35 system-ui,sans-serif;cursor:pointer;box-shadow:0 6px 24px rgba(0,0,0,.5)';
        d.onclick = () => { d.remove(); ParserReport.open(); };
        (document.body || document.documentElement).appendChild(d);
        setTimeout(() => d.remove(), 15000);
    } catch (e) { dbg(e, 'pdToast'); }
};
const ParserDiag = {
    recs: new Map(), codes: new Map(), warned: false, iwarned: false, flag: null,
    rec(site, slug, src, unk, status, sig) {
        try {
            if (!slug) return;
            const k = `${site}|${String(slug).toLowerCase()}`;
            this.recs.delete(k);
            this.recs.set(k, { t: Date.now(), site, slug, st: status, unk: !!unk, src: { ...src }, ...(sig ? { sig } : {}) });
            if (this.recs.size > 300) this.recs.delete(this.recs.keys().next().value);
            this.check(site);
        } catch (e) { dbg(e, 'rec'); }
    },
    recInfo(slug, via, unk, sig) {
        try {
            if (!slug) return;
            const k = `sc:info|${String(slug).toLowerCase()}`;
            this.recs.delete(k);
            this.recs.set(k, { t: Date.now(), site: 'sc:info', kind: 'info', slug, st: 0, unk: !!unk, src: { status: via || '' }, ...(sig ? { sig } : {}) });
            if (this.recs.size > 300) this.recs.delete(this.recs.keys().next().value);
            this.check('sc:info');
        } catch (e) { dbg(e, 'recInfo'); }
    },
    code(grp, code) {
        const k = `${grp}:${String(code).slice(0, 60)}`;
        this.codes.delete(k);
        this.codes.set(k, Date.now());
        if (this.codes.size > 100) this.codes.delete(this.codes.keys().next().value);
    },
    stats(site) {
        const now = Date.now();
        let n = 0, u = 0, on = 0;
        const gap = { topic: 0, frame: 0, viewers: 0 };
        for (const r of this.recs.values()) {
            if ((site ? r.site !== site : r.kind) || now - r.t > PD_WINDOW) continue;
            n++;
            if (r.unk) u++;
            else if (r.st > 0) { on++; for (const f in gap) if (!r.src[f]) gap[f]++; }
        }
        return { n, u, on, gap, ratio: n ? u / n : 0 };
    },
    check(site) {
        const info = site === 'sc:info';
        const s = this.stats(site);
        const bad = s.n >= (info ? PD_INFO_MIN : PD_WARN_MIN) && s.ratio >= PD_WARN_RATIO;
        if (!info) this.flag = bad ? { site, ...s } : null;
        const w = info ? 'iwarned' : 'warned';
        if (bad && !this[w]) {
            this[w] = true;
            pdToast(pdT(info ? 'itoast' : 'toast', { site: site.toUpperCase(), u: s.u, n: s.n }));
        } else if (!bad && s.ratio < PD_WARN_RATIO / 3) this[w] = false;
    },
    clear() { this.recs.clear(); this.codes.clear(); this.flag = null; this.warned = false; this.iwarned = false; }
};
const PD_FIELDS = ['status', 'topic', 'frame', 'viewers', 'tags', 'sch'];
const PD_ST = ['offline', 'online', 'away', 'show'];
const prEl = (tag, cls, text) => { const e = mk(tag, cls); if (text != null) e.textContent = text; return e; };
const prSvg = d => `<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${d}</svg>`;
const prSrcCls = s => (s ? s.split(':')[0] : '');
const PR_CSS = RS_PULT_CSS + `
.bm-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,.6);
  display: flex;
  align-items: center;
  justify-content: center;
  font: 13px/1.4 Onest,system-ui,sans-serif
}
.bm-modal.pr-modal {
  width: min(760px,96vw);
  background: var(--pb)
}
.rs-pult .pr-ok,
.rs-pult .pr-warn {
  align-items: flex-start;
  line-height: 1.4;
  font-weight: 400
}
.rs-pult .pr-ok svg,
.rs-pult .pr-warn svg {
  margin-top: 1px!important;
  filter: none!important
}
.rs-pult .pr-ok {
  background: #12301f;
  border-color: #2b7a4b;
  color: #b6f0cf
}
.rs-pult .pr-ok svg {
  color: #b6f0cf!important
}
.rs-pult .pr-warn {
  background: #3a2410;
  border-color: #c77d2a;
  color: #ffd9a8
}
.rs-pult .pr-warn svg {
  color: #ffd9a8!important
}
.pr-btns {
  display: flex;
  gap: 6px
}
.pr-btns button {
  flex: 1 1 0;
  min-width: 0;
  min-height: 44px;
  margin: 0;
  padding: 0 12px;
  border-radius: 12px;
  border: 1px solid var(--pl);
  background: var(--pt);
  color: #fff;
  font: 500 14px/1.2 Onest,system-ui,sans-serif;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  cursor: pointer;
  transition: background .12s,transform .12s
}
.pr-btns button:hover {
  background: color-mix(in srgb,var(--pt) 85%,#fff)
}
.pr-btns button:active {
  transform: scale(.97)
}
.pr-btns button:focus-visible,
.bm-close:focus-visible {
  outline: 2px solid var(--th);
  outline-offset: 2px
}
.pr-btns svg {
  display: block;
  flex: none
}
.rs-pult .hd-sec.pr-hs {
  display: flex!important;
  justify-content: space-between
}
.pr-sum {
  display: grid;
  grid-template-columns: repeat(2,minmax(0,1fr));
  gap: 6px
}
.pr-t {
  min-width: 0;
  padding: 8px 12px;
  border-radius: 12px;
  border: 1px solid var(--pl);
  background: var(--pt)
}
.pr-t b {
  display: block;
  font-size: 12px;
  font-weight: 500;
  opacity: .6
}
.pr-t div {
  display: flex;
  flex-wrap: wrap;
  gap: 2px 8px;
  margin-top: 2px;
  font-size: 13px
}
.pr-wrap {
  overflow-x: auto;
  border-radius: 12px;
  border: 1px solid var(--pl);
  background: var(--pt)
}
.pr-wrap table {
  border-collapse: collapse;
  width: max-content;
  min-width: 100%;
  font-size: 12px
}
.pr-wrap th,
.pr-wrap td {
  text-align: left;
  padding: 8px 10px;
  border-bottom: 1px solid rgba(255,255,255,.07);
  white-space: nowrap
}
.pr-wrap th {
  font-weight: 500;
  color: #9a9aa6;
  border-bottom-color: var(--pl)
}
.pr-wrap tr:last-child td {
  border-bottom: 0
}
.pr-wrap th:first-child,
.pr-wrap td:first-child {
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--pt)
}
.pr-leg {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
  padding: 4px 4px 0;
  font-size: 11px
}
.api {
  color: #6fd39a
}
.json {
  color: #7db7ff
}
.html {
  color: #b0b0ba
}
.regex {
  color: #ffb454
}
.listing {
  color: #c79bff
}
.miss {
  color: #ffb454
}
.dim {
  color: #8a8a96
}
.bad {
  color: #ff6b6b;
  font-weight: 600
}`;

const ParserReport = {
    host: null,
    close() { this._offKey?.(); this._offKey = null; this.host?.remove(); this.host = null; },
    exportText() {
        return JSON.stringify({
            version: (typeof GM_info !== 'undefined' && GM_info.script?.version) || '4.9.0', at: new Date().toISOString(), warn: { ratio: PD_WARN_RATIO, min: PD_WARN_MIN },
            stats: ParserDiag.stats(), info: ParserDiag.stats('sc:info'), codes: [...ParserDiag.codes.keys()], records: [...ParserDiag.recs.values()]
        }, null, 1);
    },
    copy() { Helpers.copy(this.exportText()); },
    open() {
        this.close();
        const host = document.createElement('div');
        host.style.cssText = 'all:initial;position:fixed;inset:0;z-index:2147483647';
        host.style.setProperty('--th', AppState.theme);
        const sh = host.attachShadow({ mode: 'open' });
        const st = prEl('style');
        st.textContent = PR_CSS;
        const ov = prEl('div', 'bm-overlay');
        ov.onclick = e => { if (e.target === ov) this.close(); };
        const modal = prEl('div', 'bm-modal rs-pult pr-modal');
        const hd = prEl('div', 'bm-hdr');
        const title = prEl('span', 'bm-title');
        title.innerHTML = `<span class="rs-ic">${icons.list}</span>`;
        title.append(prEl('span', '', pdT('title')));
        const bX = prEl('button', 'bm-close');
        bX.type = 'button';
        bX.innerHTML = ICON_CLOSE;
        bX.onclick = () => this.close();
        hd.append(title, bX);
        const bd = prEl('div', 'bm-list');

        const recs = [...ParserDiag.recs.values()].sort((a, b) => (b.unk - a.unk) || (b.t - a.t));
        const base = recs.filter(r => !r.kind), ib = this._infoBanner(recs);
        bd.append(this._banner(base), ...(ib ? [ib] : []), this._buttons(), this._summary(base), this._models(recs));

        modal.append(bd, hd);
        ov.append(modal);
        sh.append(st, ov);
        (document.body || document.documentElement).appendChild(host);
        this.host = host;
        this._offKey = bindEscape(() => this.close());
    },
    _banner(recs) {
        const stats = ParserDiag.stats();
        const banner = (cls, icon, text) => { const b = prEl('div', `rs-sum ${cls}`); b.innerHTML = icon; b.append(prEl('span', '', text)); return b; };
        const IC_OK = prSvg('<circle cx="12" cy="12" r="9"/><path d="M8.5 12.5l2.5 2.5 4.5-5"/>');
        const IC_WARN = prSvg('<path d="M12 4l9 16H3z"/><path d="M12 10v4M12 17.5v.01"/>');
        if (!recs.length) return banner('pr-ok', IC_OK, pdT('nodata'));
        if (ParserDiag.flag) return banner('pr-warn', IC_WARN, pdT('warn', { u: stats.u, n: stats.n, p: Math.round(PD_WARN_RATIO * 100) }));
        const few = stats.n < PD_WARN_MIN ? ' ' + pdT('few', { n: stats.n, m: PD_WARN_MIN }) : '';
        const gp = Object.keys(stats.gap).filter(f => stats.on && stats.gap[f] / stats.on >= PD_WARN_RATIO).map(f => `${f} ${stats.gap[f]}/${stats.on}`);
        if (gp.length) return banner('pr-warn', IC_WARN, pdT('gaps', { list: gp.join(', ') }) + few);
        return banner('pr-ok', IC_OK, pdT('ok', { u: stats.u, n: stats.n }) + few);
    },
    _infoBanner(recs) {
        const s = ParserDiag.stats('sc:info');
        const codes = [...ParserDiag.codes.keys()];
        if (!s.n && !codes.length) return null;
        const info = recs.filter(r => r.kind && !r.unk);
        const drift = info.filter(r => r.sig?.miss?.length >= 3).length;
        const alt = info.filter(r => /deep|parse|script/.test(r.src.status)).length;
        const bad = s.u > 0 || codes.length > 0 || drift > 0 || alt > 0;
        const IC_OK = prSvg('<circle cx="12" cy="12" r="9"/><path d="M8.5 12.5l2.5 2.5 4.5-5"/>');
        const IC_WARN = prSvg('<path d="M12 4l9 16H3z"/><path d="M12 10v4M12 17.5v.01"/>');
        const b = prEl('div', `rs-sum ${bad ? 'pr-warn' : 'pr-ok'}`);
        b.innerHTML = bad ? IC_WARN : IC_OK;
        b.append(prEl('span', '', pdT(bad ? 'iwarn' : 'iok', { k: s.n - s.u, u: s.u, n: s.n, a: alt, d: drift, c: codes.length, list: codes.length ? ` (${codes.slice(0, 8).join(', ')})` : '' })));
        return b;
    },
    _buttons() {
        const btns = prEl('div', 'pr-btns');
        const bCopy = prEl('button');
        bCopy.type = 'button';
        bCopy.innerHTML = prSvg('<rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V6a2 2 0 0 1 2-2h9"/>');
        const bCopyT = prEl('span', '', pdT('copy'));
        bCopy.append(bCopyT);
        bCopy.onclick = async () => { await this.copy(); bCopyT.textContent = pdT('copied'); setTimeout(() => { bCopyT.textContent = pdT('copy'); }, 1500); };
        const bClr = prEl('button');
        bClr.type = 'button';
        bClr.innerHTML = prSvg('<path d="M4 7h16M10 11v6M14 11v6M6 7l1 12a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l1-12M9 7V4h6v3"/>');
        bClr.append(prEl('span', '', pdT('reset')));
        bClr.onclick = () => { ParserDiag.clear(); this.open(); };
        btns.append(bCopy, bClr);
        return btns;
    },
    _srcTile(f, recs) {
        const cnt = new Map();
        for (const r of recs) {
            const s = f === 'status' && r.unk ? '?' : (r.src[f] || '');
            if (!s && (r.st === 0 && f !== 'status')) continue;
            cnt.set(s || '—', (cnt.get(s || '—') || 0) + 1);
        }
        const line = prEl('div');
        [...cnt.entries()].sort((a, b) => b[1] - a[1]).forEach(([s, n]) => {
            line.append(prEl('span', s === '?' ? 'bad' : (s === '—' ? 'dim' : prSrcCls(s)), `${s} ${n}`));
        });
        if (!cnt.size) line.append(prEl('span', 'dim', '—'));
        const tile = prEl('div', 'pr-t');
        tile.append(prEl('b', '', f), line);
        return tile;
    },
    _summary(recs) {
        const gSum = prEl('div', 'rs-g');
        gSum.append(prEl('div', 'hd-sec', pdT('sumT')));
        const sum = prEl('div', 'pr-sum');
        for (const f of PD_FIELDS) sum.append(this._srcTile(f, recs));
        gSum.append(sum);
        return gSum;
    },
    _modelRow(r) {
        const tr = prEl('tr');
        tr.append(prEl('td', '', r.slug), prEl('td', 'dim', r.site));
        PD_FIELDS.forEach(f => {
            const s = r.src[f] || '';
            let td;
            if (r.kind && f !== 'status') td = prEl('td', 'dim', '—');
            else if (f === 'status') td = r.unk ? prEl('td', 'bad', `? ${pdT('unknown')}`) : prEl('td', prSrcCls(s), r.kind ? (s || '—') : `${PD_ST[r.st] || r.st} · ${s || '—'}`);
            else if (s) td = prEl('td', prSrcCls(s), s);
            else td = prEl('td', r.st > 0 && !r.unk && (f === 'topic' || f === 'frame' || f === 'viewers') ? 'miss' : 'dim', '—');
            tr.append(td);
        });
        const age = Math.round((Date.now() - r.t) / 1000);
        tr.append(prEl('td', 'dim', age < 90 ? `${age}s` : `${Math.round(age / 60)}m`));
        if (r.sig) tr.title = JSON.stringify(r.sig);
        return tr;
    },
    _models(recs) {
        const gMod = prEl('div', 'rs-g');
        const hs = prEl('div', 'hd-sec pr-hs');
        hs.append(prEl('span', '', pdT('models')), prEl('span', '', String(recs.length)));
        gMod.append(hs);
        const wr = prEl('div', 'pr-wrap'), tb = prEl('table'), thr = prEl('tr');
        ['model', 'site', ...PD_FIELDS, pdT('age')].forEach(c => thr.append(prEl('th', '', c)));
        const thead = prEl('thead'); thead.append(thr); tb.append(thead);
        const tbody = prEl('tbody');
        for (const r of recs.slice(0, 150)) tbody.append(this._modelRow(r));
        tb.append(tbody); wr.append(tb); gMod.append(wr);
        const leg = prEl('div', 'pr-leg');
        [['api', 'API'], ['json', 'JSON'], ['html', 'HTML'], ['regex', 'regex'], ['listing', 'listing']].forEach(([c, n]) => leg.append(prEl('span', c, `● ${n}`)));
        gMod.append(leg, prEl('div', 'rs-note', pdT('dash')));
        return gMod;
    }
};
const HTTP_RE = /^https?:\/\//i;
const FRAME_ALT_RE = /"(?:frameUrl|previewFrame|previewUrl|livePreviewUrl)"\s*:\s*"((?:[^"\\]|\\.)*)"/i;
const bcdExtras = bcd => ({
    ...(Array.isArray(bcd?.ptags) && bcd.ptags.length ? { ptags: bcd.ptags.slice(0, 30) } : {}),
    ...(Array.isArray(bcd?.sch) && bcd.sch.length === 7 ? { sch: bcd.sch } : {})
});
const parseProfile = (h, site = 'bc', slug = '') => {
    let bcd;
    [h, bcd] = splitBcd(h, site);
    const roots = site === 'bc' ? bcRoots(h) : [];
    const R = resolveStatusEx(h, site, bcd, roots, slug);
    const status = R.st, lk = R.lk;
    const src = { status: R.src };
    const done = info => {
        Object.defineProperty(info, 'unk', { value: !!R.unk, enumerable: false, writable: true, configurable: true });
        const sig = R.unk ? { len: isStr(h) ? h.length : 0, head: String(h || '').slice(0, 100).replace(/\s+/g, ' '), roots: roots.map(r => Object.keys(r).slice(0, 8).join(',')) } : (site === 'bc' && info.status > 0 && info.viewers == null && bcd?.nums ? { keys: bcd.k, nums: bcd.nums } : null);
        ParserDiag.rec(site, slug, src, R.unk, info.status, sig);
        return info;
    };
    if (siteProfile[site]) {
        const info = { status, lk, ...siteProfile[site](h, status, slug) };
        src.topic = info.topic ? 'regex' : '';
        src.frame = info.frame ? 'regex' : '';
        src.viewers = info.viewers != null ? 'regex' : '';
        return done(info);
    }
    const [topic, tSrc] = status ? bcTopic(h, bcd, roots) : ['', ''];
    const [frame, fSrc] = bcFrame(h, bcd, roots);
    const [vn, vSrc] = bcViewersEx(h, roots);
    const ex = site === 'bc' ? bcExtraSrc(h, roots) : { out: {}, src: { tags: '', sch: '' } };
    const extra = site === 'bc' ? { ...ex.out, ...bcdExtras(bcd) } : {};
    src.topic = tSrc; src.frame = fSrc; src.viewers = vn != null ? vSrc : '';
    src.tags = extra.ptags ? (bcd?.ptags?.length ? 'api:amf' : ex.src.tags) : '';
    src.sch = extra.sch ? (bcd?.sch?.length === 7 ? 'api:amf' : ex.src.sch) : '';
    return done({
        status, lk,
        topic,
        frame,
        ...(vn != null ? { viewers: vn } : {}),
        ...extra
    });
};

const withBackoff = async (fn, { tries = 2, base = 500, signal } = {}) => {
    for (let i = 0; ; i++) {
        const res = await fn();
        if (res !== null || i >= tries - 1 || signal?.aborted) return res;
        await wait(base * 2 ** i * (0.75 + Math.random() * 0.5));
    }
};
const MOBILE_UA = 'Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36';
const badStatus = c => c >= 500 || c === 403 || c === 429;
const requestOnce = (url, extra, signal, timeout) => {
    let headers = {};
    if (/\/api\//.test(url)) {
        headers = { ...XHR_HDR };
        const o = originOf(url);
        if (o) headers.Referer = `${o}/`;
    }
    if (extra) headers = { ...headers, ...extra };
    return gmReq({ url, headers, signal, ...(timeout && { timeout }) }).then(r => (r.fail || badStatus(r.status) ? null : r.body));
};
const requestPostOnce = (url, data, extra, signal, timeout) => {
    const headers = { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json, text/plain, */*', 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', ...(extra || {}) };
    return gmReq({ method: 'POST', url, data, headers, signal, credentials: 'include', ...(timeout && { timeout }) }).then(r => (r.fail || badStatus(r.status) ? null : r.body));
};
const requestHtml = async (url, signal, timeout) => (await withBackoff(() => requestOnce(url, undefined, signal, timeout), { signal })) ?? '';
const STATUS_KEY = /room_status|"status"\s*:|"isLive"|"isOnline"/i;
const profileUrls = b => {
    const slug = modelSlug(b.url), site = urlSite(b.url);
    if (!slug) return [b.url];
    try {
        if (site === 'cb') return [`https://chaturbate.com/api/chatvideocontext/${encodeURIComponent(slug)}/`, b.url];
        if (site === 'sc') {
            const o = new URL(b.url).origin, e = encodeURIComponent(slug);
            return [...new Set([`${o}/api/front/v2/models/username/${e}/cam`, `https://stripchat.com/api/front/v2/models/username/${e}/cam`])].concat(b.url);
        }
    } catch (e) { dbg(e, 'profileUrls'); }
    return [b.url];
};
const requestProfileHtml = async (b, signal) => {
    for (const u of profileUrls(b)) {
        const h = await requestHtml(u, signal, 4000);
        if (h && (u === b.url || STATUS_KEY.test(h))) return h;
    }
    return '';
};

const BCD_MARK = '\n/*BCD*/';
const bcChatInfo = j => {
    const r = j?.status === 'success' ? j.result : null, s = r?.chatShowStatusOptions;
    if (!isObj(s) || s.isAvailable === false || typeof s.isOffline !== 'boolean') return null;
    return {
        o: s.isOffline,
        p: !!(s.isPrivateChat || s.isFullPrivateChat || s.isVoyeurChat),
        g: !!s.isGroupChat,
        v: !!s.isVipShow,
        t: isStr(r.chatTopicOptions?.currentTopic) ? r.chatTopicOptions.currentTopic : ''
    };
};
const bcChatData = async (b, signal) => {
    const o = originOf(b.url), slug = modelSlug(b.url);
    if (!o || !slug) return null;
    const hdr = { ...XHR_HDR, Referer: `${o}/${encodeURIComponent(slug)}` };
    try {
        return bcChatInfo(JSON.parse((await requestOnce(bcChatUrl(o, slug), hdr, signal, 4000)) || ''));
    } catch { return null; }
};
const bcCsrfToken = () => {
    return document.documentElement.getAttribute('data-csrf_value')
        || document.querySelector('input[name="_csrf_token"]')?.value
        || document.querySelector('meta[name="csrf-token"]')?.content
        || ((document.documentElement.innerHTML || '').match(/(?:_csrf_token|csrfToken)["'\s:=]+([a-f0-9]{24,64})/i) || [])[1];
};
const bcRoomInfo = p => {
    const tags = Array.isArray(p.modelTags) ? p.modelTags.map(x => isStr(x) ? x : (x?.name || x?.title || x?.tag || '')).filter(isStr) : [];
    const schedule = isWeekSchedule(p.schedule) ? scheduleDays(p.schedule) : null;
    return {
        ...(typeof p.isAway === 'boolean' ? { a: p.isAway } : {}),
        ...(typeof p.isOnline === 'boolean' ? { n: p.isOnline } : {}),
        ...(tags.length ? { ptags: tags.slice(0, 30) } : {}),
        ...(isStr(p.frame) ? { f: p.frame } : {}),
        ...(schedule && schedule.some(Boolean) ? { sch: schedule } : {}),
        k: Object.keys(p).slice(0, 80),
        nums: Object.fromEntries(Object.entries(p).filter(([, v]) => typeof v === 'number').slice(0, 40))
    };
};
const parseRoomData = raw => {
    const j = JSON.parse(raw || ''), p = j?.status === 'success' ? j.performerData : null;
    return isObj(p) ? bcRoomInfo(p) : null;
};
const bcRoomData = async (b, signal) => {
    const o = originOf(b.url), slug = modelSlug(b.url);
    if (!o || !slug || location.origin !== o || !document.documentElement) return null;
    const token = bcCsrfToken();
    if (!token) return null;
    try {
        const raw = await requestPostOnce(bcAmfUrl(o), bcAmfBody(slug, token), { Referer: `${o}/` }, signal, 4000);
        return parseRoomData(raw);
    } catch { return null; }
};
const htmlSaysAway = html => /"isAway"\s*:\s*true/i.test(html) || /(?:"|\\u0022)room_status(?:"|\\u0022)\s*:\s*(?:"|\\u0022)away/i.test(html);
const bcdStatus = (d, html = '') => {
    if (d.o) return [0, ''];
    if (d.p) return [3, 'st_private'];
    if (d.g) return [3, 'st_group'];
    if (d.v) return [3, 'st_vip'];
    if (d.a === true || htmlSaysAway(html)) return [2, 'st_away'];
    return [1, ''];
};
const requestProfile = async (b, signal) => {
    if (urlSite(b.url) !== 'bc') return requestProfileHtml(b, signal);
    const [h, d, room] = await Promise.all([requestProfileHtml(b, signal), bcChatData(b, signal), bcRoomData(b, signal)]);
    const payload = { ...(d || {}), ...(room || {}) };
    return (d || room) ? (h || '') + BCD_MARK + JSON.stringify(payload) : h;
};

const BC_LIST_TTL = 20000, BC_LIST_PAGE = 500, BC_LIST_MAX = 4000;
const BC_EXTRA_TABS = ['male', 'couples', 'trans'];
const bcListCache = new Map();
const bcListCandidates = j => {
    const c = Array.isArray(j?.models) ? [...j.models] : [];
    const nav = j?.nav;
    if (!nav || typeof nav !== 'object') return c;
    if (nav.current && typeof nav.current === 'object') c.push(nav.current);
    for (const key of ['next', 'prev']) if (Array.isArray(nav[key])) c.push(...nav[key]);
    return c;
};
const bcListEat = (map, txt) => {
    try {
        const j = JSON.parse(txt);
        for (const m of bcListCandidates(j)) {
            if (m && isStr(m.username) && m.viewers != null && Number.isFinite(+m.viewers)) {
                map.set(m.username.toLowerCase(), +m.viewers);
                if (isStr(m.room) && m.room) BC_ROOM.set(m.username.toLowerCase(), { room: m.room, t: Date.now() });
            }
        }
        return +(j?.total_count ?? j?.nav?.total_count) || 0;
    } catch { return 0; }
};
const bcListTab = async (origin, tab, signal) => {
    const ck = `${origin}|${tab}`, hit = bcListCache.get(ck);
    if (hit && Date.now() - hit.t < BC_LIST_TTL) return hit.map;
    const base = `${origin}/tools/listing_v3.php?livetab=${encodeURIComponent(tab)}&is_mobile=true&limit=${BC_LIST_PAGE}`;
    const hdr = { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json, text/plain, */*', 'Referer': `${origin}/` };
    const map = new Map();
    const eat = txt => bcListEat(map, txt);
    const total = eat((await requestOnce(base, hdr, signal, 12000)) || '');
    const offs = [];
    for (let o = BC_LIST_PAGE; o < Math.min(total, BC_LIST_MAX); o += BC_LIST_PAGE) offs.push(o);
    const rest = await Promise.all(offs.map(o => requestOnce(`${base}&offset=${o}`, hdr, signal, 12000)));
    rest.forEach(t => t && eat(t));
    if (map.size) bcListCache.set(ck, { t: Date.now(), map });
    return map;
};
const bpRowMap = bp => {
    const m = new Map();
    for (const x of bp?.g || []) for (const [l, v, vol] of x.r) if (!vol && !m.has(l)) m.set(l, v);
    return m;
};
const bpTrack = (b, next, now) => {
    if (!isPlain(b.bp)) return;
    const o = bpRowMap(b.bp), hist = [];
    for (const [l, v] of bpRowMap(next)) if (o.has(l) && o.get(l) !== v) hist.push({ k: `bp:${l}`, o: o.get(l), n: v, t: now });
    if (!hist.length) return;
    const cur = Array.isArray(b.infh) ? b.infh.filter(h => isPlain(h) && isStr(h.k)) : [];
    b.infh = [...cur, ...hist].slice(-30);
};
const applyProfile = (b, { frame, ...info }) => {
    const same = (x, y) => (isObj(x) || isObj(y)) ? JSON.stringify(x) === JSON.stringify(y) : x === y;
    if (info.bp) {
        const now = Date.now(), pv = isPlain(b.bp) ? b.bp : null;
        if (pv && infEq({ ...pv, t: 0 }, { ...info.bp, t: 0 })) { pv.t = now; delete info.bp; }
        else { bpTrack(b, info.bp, now); info.bp = { ...info.bp, t: now }; }
    }
    const changed = Object.keys(info).some(k => !same(b[k], info[k])) || !!(frame && b.frame !== frame);
    if (info.viewers != null) b.vts = Date.now();
    Object.assign(b, info);
    if (frame) b.frame = frame;
    return changed;
};
const AVATAR_RES = [
    /(?:property=["']og:image["']|name=["']og:image["'])[^>]*?content=["']([^"']+)["']/i,
    /content=["']([^"']+)["'][^>]*?property=["']og:image["']/i,
    /"(?:profileImage|avatarUrl)"\s*:\s*"([^"]+)"/i
];
const extractAvatar = html => {
    for (const re of AVATAR_RES) {
        const m = html.match(re);
        if (m?.[1]) return toHttps(m[1].replace(/\\/g, ''));
    }
    return '';
};
const ogImage = () => toHttps(document.querySelector('meta[property="og:image"]')?.content);
const bcAvatar = name => {
    const sd = Helpers.getModelSecretData();
    const own = (sd?.chatHost?.username || '').toLowerCase() === name.toLowerCase();
    return toHttps(own ? sd?.chatHeaderOptions?.profileImage : '')
        || toHttps(document.querySelector('.chat-info-avatar img, .chat-header-avatar img, .profile-photo img')?.src);
};
const scAvatar = () => toHttps(document.querySelector('img.image-background--round[src*="avatars"]')?.src) || ogImage();
const cbAvatar = name => name ? `https://thumb.live.mmcdn.com/ri/${encodeURIComponent(name.toLowerCase())}.jpg` : ogImage();
const AVATAR_BY_SITE = new Map([['bc', bcAvatar], ['cb', cbAvatar]]);
const pageAvatar = name => (AVATAR_BY_SITE.get(SITE) || scAvatar)(name);

const GM_IMG = new Map();
const CB_HQ_FRAME = true;
const cbRiUrl = slug => `https://thumb.live.mmcdn.com/ri/${encodeURIComponent(String(slug).toLowerCase())}.jpg`;
const cbFrameUrl = slug => CB_HQ_FRAME ? `https://jpeg.live.mmcdn.com/stream?room=${encodeURIComponent(String(slug).toLowerCase())}` : cbRiUrl(slug);
const frameBust = u => {
    try {
        const o = new URL(u);
        o.searchParams.set(/^jpeg\.live\./.test(o.hostname) ? 'f' : 't', /^jpeg\.live\./.test(o.hostname) ? String(Math.random()) : String(Date.now()));
        return o.href;
    } catch { return `${String(u).split('?')[0]}?t=${Date.now()}`; }
};
const cbHqToRi = u => { const m = /^https?:\/\/jpeg\.live\.mmcdn\.com\/stream\?(?:[^#]*&)?room=([^&#]+)/i.exec(String(u)); return m ? cbRiUrl(decodeURIComponent(m[1])) : ''; };
const IMG_DEBUG = Store.get('cbhp_debug', false) === true;
const imgReferer = url => {
    const h = parseUrl(url)?.hostname;
    if (!h) return '';
    if (/bongocams/.test(h)) return 'https://bongocams.com/';
    if (/bongacams|bgicdn|bcvcdn|webcamsluts/.test(h)) return 'https://bongacams.com/';
    if (/stripchat|doppiocdn/.test(h)) return 'https://stripchat.com/';
    if (/chaturbate|mmcdn/.test(h)) return 'https://chaturbate.com/';
    return '';
};
const blobToData = blob => new Promise(resolve => {
    const fr = new FileReader();
    fr.onload = () => resolve(isStr(fr.result) ? fr.result : '');
    fr.onerror = () => resolve('');
    fr.readAsDataURL(blob);
});
const gmImgFetch = async url => {
    const ref = imgReferer(url);
    const hdr = { Accept: 'image/avif,image/webp,image/apng,image/*,*/*;q=0.8' };
    const tries = ref && ref !== `${location.origin}/` ? [{ ...hdr, Referer: ref }, hdr] : [hdr];
    for (const headers of tries) {
        const r = await gmReq({ url, type: 'blob', timeout: 12000, headers });
        if (r.status >= 200 && r.status < 300 && r.body && r.body.size) {
            const d = await blobToData(r.body);
            if (d) return d;
        }
        if (IMG_DEBUG) console.warn('[cams-unchained] img fail', r.status, r.fail, !!headers.Referer, url);
        if (r.status === 404 || r.status === 410) break;
    }
    return '';
};
const gmImgDataRaw = (url, fresh) => {
    if (!url || /^data:/.test(url) || typeof GM_xmlhttpRequest !== 'function') return Promise.resolve('');
    if (!fresh && GM_IMG.has(url)) return GM_IMG.get(url);
    const p = gmImgFetch(url);
    if (fresh) return p;
    GM_IMG.set(url, p);
    if (GM_IMG.size > 400) GM_IMG.delete(GM_IMG.keys().next().value);
    p.then(d => { if (!d && GM_IMG.get(url) === p) GM_IMG.delete(url); });
    return p;
};
const gmImgData = (url, fresh) => gmImgDataRaw(url, fresh).then(d => {
    if (d) return d;
    const ri = cbHqToRi(url);
    return ri ? gmImgDataRaw(frameBust(ri), true) : d;
});
const tryGm = (img, u, fail) => {
    if (!u || /^data:/.test(u) || img._gm === u || typeof GM_xmlhttpRequest !== 'function') return false;
    img._gm = u;
    gmImgData(u).then(d => { if (d && img.isConnected !== false && (!img.classList.contains('bm-prev') || img.hasAttribute('data-frame'))) img.src = d; else if (fail) fail(); });
    return true;
};

const frameFail = img => {
    const b = img._bRef;
    const giveUp = () => {
        img._gen = (img._gen || 0) + 1;
        img._isUpdating = false;
        img.removeAttribute('data-frame');
        img.style.display = 'none';
    };
    if (!b || !img.isConnected) return giveUp();
    if (img._rec && Date.now() - img._rec < 60000) return giveUp();
    img._rec = Date.now();
    requestProfile(b).then(h => {
        const info = h ? parseProfile(h, urlSite(b.url), modelSlug(b.url)) : null;
        const fr = info && info.status ? info.frame : '';
        if (!fr || !img.isConnected) return giveUp();
        b.frame = fr;
        const next = frameBust(fr);
        const tmp = new Image();
        tmp.onload = () => {
            if (!img.isConnected) return;
            img.setAttribute('data-frame', fr);
            img._u = next; b._lastImg = next; img._gm = null;
            img.src = next; img.style.display = 'block';
        };
        tmp.onerror = () => gmImgData(next, true).then(d => {
            if (d && img.isConnected) { img.setAttribute('data-frame', fr); img._u = next; b._lastImg = next; img.src = d; img.style.display = 'block'; }
            else giveUp();
        });
        tmp.src = next;
    }).catch(giveUp);
};

const paintViewers = (stats, txt) => {
    const key = txt + '|' + AppState.lang;
    if (stats._last === key) return;
    const n = parseCount(txt);
    const shown = Number.isFinite(n) ? fmtCount(n) : txt;
    const sp = stats.querySelector('span');
    if (sp && stats.querySelector('svg')) sp.textContent = shown;
    else stats.innerHTML = `${icons.eye}<span>${escHTML(shown)}</span>`;
    stats.title = Number.isFinite(n) ? n.toLocaleString() : '';
    stats._last = key;
};
const needsExtra = (x, now) => hasUrl(x) && isSafeUrl(x.url) && urlSite(x.url) === 'bc' && (!x._pf || (!x.sch && now - x._pf > 600000));
const frameThrottled = (img, b) => {
    if (!b || b.status !== 2) return false;
    img._skip = (img._skip || 0) + 1;
    return img._skip % 5 !== 0;
};
const paintStatusDot = (stat, st) => {
    const col = STATUS_COLORS[st] || '#888';
    stat.style.background = col;
    stat.style.boxShadow = st ? `0 0 8px ${col}` : '0 0 4px rgba(0,0,0,0.5)';
};
const paintViewsBadge = (viewEl, b) => {
    const vw = bmViews(b), vwTxt = vw ? fmtCount(vw) : '';
    if (viewEl._t === vwTxt) return;
    viewEl._t = vwTxt;
    viewEl.innerHTML = vwTxt ? `${icons.eye}${vwTxt}` : '';
};
const refreshFrame = img => {
    const url = img?.getAttribute('data-frame');
    if (!url || img._isUpdating) return;
    const b = img._bRef;
    if (frameThrottled(img, b)) return;
    img._isUpdating = true;
    const gen = img._gen = (img._gen || 0) + 1;
    const next = frameBust(url);
    const alive = () => img.isConnected && img._gen === gen && img.getAttribute('data-frame') === url;
    const done = () => { if (img._gen === gen) img._isUpdating = false; };
    const show = src => {
        img._u = next;
        if (b) b._lastImg = next;
        img._k = (b && b._lastData ? 'd' : '') + next;
        img.src = src;
        img.style.display = 'block';
    };
    const viaGm = () => gmImgData(next, true).then(d => {
        if (d && alive()) {
            if (b) b._lastData = d;
            if (d !== img._lastD) { img._lastD = d; show(d); }
        } else if (alive() && !(img.complete && img.naturalWidth > 0)) frameFail(img);
        done();
    });
    if (img._gmOnly) return viaGm();
    const tmp = new Image();
    const guard = setTimeout(() => { tmp.onload = tmp.onerror = null; done(); }, 20000);
    tmp.onload = () => {
        clearTimeout(guard);
        if (alive()) { if (b) b._lastData = ''; img._lastD = ''; show(next); }
        done();
    };
    tmp.onerror = () => { clearTimeout(guard); img._gmOnly = true; viaGm(); };
    tmp.src = next;
};

const initialsAva = name => {
    const s = String(name || '?'), t = (s.replace(/[^\p{L}\p{N}]/gu, '').slice(0, 2) || '?').toUpperCase();
    let h = 0; for (const ch of s) h = (h * 31 + ch.charCodeAt(0)) >>> 0;
    const svg = `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'><rect width='64' height='64' fill='hsl(${h % 360},50%,42%)'/><text x='32' y='41' font-size='26' font-weight='700' text-anchor='middle' fill='#fff' font-family='sans-serif'>${t}</text></svg>`;
    return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
};
const bindAvatar = (img, name, getList, onFail) => {
    let i = 0, list = null;
    const next = () => {
        list ||= [...new Set(getList().filter(Boolean))];
        while (i < list.length) {
            const u = list[i++];
            if (u) { img._cur = u; img.src = u; return; }
        }
        img.onerror = null;
        img._fb = 1;
        img.src = initialsAva(name);
        onFail?.();
    };
    img._fb = 0;
    img.onerror = () => { if (img._fb === 0 && tryGm(img, img._cur, next)) return; next(); };
    next();
};

const STATUS_COLORS = { 0: '#e74c3c', 1: '#2ecc71', 2: '#f39c12', 3: '#9b59b6' };
const bmLive = b => +((b.status ?? 0) > 0);
const bmGroups = () => [...new Set(AppState.bookmarks.map(b => b.g).filter(Boolean))].sort((a, b) => a.localeCompare(b));
const bmLabel = (b, l) => b.name + (b.lk && l[b.lk] ? ` (${l[b.lk]})` : '');
const bmSeen = (b, st) => st === 0 && b.seen ? fmtAgo(b.seen) : '';
const bmViews = b => (b.status > 0 && b.vts && Date.now() - b.vts < 7200000) ? (b.viewers || 0) : 0;
const WMARKS = [[0, 'eyeNo', 'w_new'], [1, 'eyeOk', 'w_seen']];
const bmWatch = (b, hist) => (((hist || histLoad()).m[bmKey(b.url)]?.s || 0) >= 30 ? 1 : 0);
const BM_CMP = {
    date: (x, y) => x.i - y.i,
    name: (x, y) => x.b.name.localeCompare(y.b.name),
    online: (x, y) => (bmLive(y.b) - bmLive(x.b)) || (x.i - y.i),
    last: (x, y) => (bmLive(y.b) - bmLive(x.b)) || ((y.b.seen || 0) - (x.b.seen || 0)) || (x.i - y.i),
    viewers: (x, y) => (bmLive(y.b) - bmLive(x.b)) || (bmViews(y.b) - bmViews(x.b)) || (x.i - y.i)
};

const ST_FILTERS = [
    ['hidePrivate', 'bc_bm_hide_priv', 'st_private', 'bm_priv'],
    ['hideGroup', 'bc_bm_hide_group', 'st_group', 'st_group'],
    ['hideVip', 'bc_bm_hide_vip', 'st_vip', 'st_vip'],
    ['hideAway', 'bc_bm_hide_away', 'st_away', 'st_away']
];
const bmStatusKey = b => {
    const st = b.status ?? 0;
    return b.lk || (st === 3 ? 'st_private' : st === 2 ? 'st_away' : '');
};

const curBms = () => {
    const nm = currentSite.getName();
    return nm ? AppState.bookmarks.filter(b => sameBm(b, nm)) : [];
};

const bmSortType = () => Store.get('bc_bm_sort_type', 'date');
const bmSortRev = () => Store.get('bc_bm_sort_rev', false);

class BookmarkManager {
    constructor(uiController) {
        this.ui = uiController;
        this.overlay = null;
        this.list = null;
        if (!Store.get('bc_mig_stf1', false)) {
            if (Store.get('bc_bm_hide_priv', false)) { Store.set('bc_bm_hide_group', true); Store.set('bc_bm_hide_vip', true); }
            Store.set('bc_mig_stf1', true);
        }
        ST_FILTERS.forEach(([prop, key]) => { this[prop] = Store.get(key, false); });
        this.hideOffline = Store.get('bc_bm_hide_off', false);
        { const wf = Store.get('bc_bm_wf', -1); this.wFilter = [0, 1].includes(wf) ? wf : null; }
        this._els = new WeakMap();
        this._lang = AppState.lang;
        this.build();
        this._xTimer = setInterval(() => { this.bgExtra(); }, 8000);
        this._statusTimer = setInterval(() => {
            if (!realHidden() && this.isOpen()) this.checkStatuses();
        }, 20000);
        onPageEvent('visibilitychange', () => {
            if (!realHidden() && this.isOpen()) this.checkStatuses();
        });
    }

    applyRemote(json) {
        try {
            const arr = JSON.parse(json);
            if (!Array.isArray(arr)) return;
            const editing = this.ui.shadow.activeElement?.matches?.('textarea');
            if (!mergeRemoteBookmarks(arr, editing ? ['note'] : [])) return;
            if (this.isOpen() && !editing) this.render();
        } catch (e) { dbg(e, 'BookmarkManager.applyRemote'); }
    }

    async checkStatuses(only) {
        if (!AppState.bookmarks?.length) return;
        if (!this._abortCtrl || this._abortCtrl.signal.aborted) this._abortCtrl = new AbortController();
        const { signal } = this._abortCtrl;
        const runs = this._runs || (this._runs = new Set());
        const busy = this._busy || (this._busy = new Set());
        const sortType = bmSortType();
        const now = Date.now();
        const queue = AppState.bookmarks.filter(b => isSafeUrl(b.url) && !busy.has(b) && (!b._chk || now - b._chk > 15000) && (!only || only.has(urlSite(b.url))))
            .sort((x, y) => (y.status > 0) - (x.status > 0) || (x._chk || 0) - (y._chk || 0));
        if (queue.length) {
            let changedAny = false;
            const mark = () => { changedAny = true; };
            this._vp = this.prefetchViewers(queue, signal);
            const worker = async () => {
                while (queue.length && !signal.aborted) {
                    while (bcPtrDown && !signal.aborted) await wait(150);
                    if (signal.aborted) break;
                    const b = queue.shift();
                    if (!b) break;
                    busy.add(b);
                    try { await this.refreshOne(b, signal, sortType, mark); } catch (e) { dbg(e, 'checkStatuses.worker'); }
                    busy.delete(b);
                }
            };
            const run = Promise.all(Array.from({ length: Math.min(4, queue.length) }, worker)).then(() => { if (changedAny) saveBookmarks(); });
            runs.add(run);
            run.finally(() => runs.delete(run));
        }
        await Promise.all([...runs]);
    }

    async loadExtra(b, signal) {
        const pu = bcProfileUrl(b);
        if (!pu) return {};
        for (const hd of [{ 'User-Agent': MOBILE_UA }, null]) {
            const h = await requestOnce(pu, hd, signal);
            if (signal?.aborted) break;
            const ex = h ? parseBcExtra(h) : {};
            if (ex.sch || ex.ptags) return ex;
        }
        return {};
    }

    async bgExtra(force) {
        if (this._xBusy || (!force && (realHidden() || bcPtrDown))) return 'busy';
        const now = Date.now();
        const b = AppState.bookmarks.find(x => needsExtra(x, now));
        if (!b) return 'none';
        this._xBusy = true;
        b._pf = now;
        let changed = false;
        try {
            const ex = await this.loadExtra(b);
            if (applyProfile(b, ex)) {
                changed = true;
                saveBookmarks();
                if (this.isOpen() && this.mode === 'fav') this.render();
            }
        } catch (e) { dbg(e, 'BookmarkManager.bgExtra'); }
        this._xBusy = false;
        return changed;
    }

    async prefetchViewers(queue, signal) {
        this._vmap = this._vmap || new Map();
        this._vx = this._vx || new Map();
        const origins = new Set(queue.filter(b => urlSite(b.url) === 'bc').map(b => originOf(b.url)).filter(Boolean));
        origins.forEach(o => this._vx.delete(o));
        await Promise.all([...origins].map(async o => {
            try { this._vmap.set(o, new Map(await bcListTab(o, 'female', signal))); } catch (e) { dbg(e, 'BookmarkManager.prefetchViewers'); }
        }));
    }

    async viewersFor(b, signal) {
        await this._vp;
        const o = originOf(b.url), slug = String(modelSlug(b.url) || '').toLowerCase();
        const maps = this._vmap;
        if (!maps || !o || !slug) return null;
        const own = () => maps.get(o)?.get(slug);
        if (own() != null) return own();
        if (!this._vx.has(o)) {
            this._vx.set(o, Promise.all(BC_EXTRA_TABS.map(t => bcListTab(o, t, signal).catch(() => new Map()))).then(ms => {
                const base = maps.get(o) || new Map();
                ms.forEach(m => m.forEach((v, k) => { if (!base.has(k)) base.set(k, v); }));
                maps.set(o, base);
            }).catch(() => {}));
        }
        await this._vx.get(o);
        return own() ?? null;
    }

    async refreshOne(b, signal, sortType, mark) {
        const h = await requestProfile(b, signal);
        if (signal.aborted || !h) return;
        const prevChk = b._chk, prevMob = b._mob;
        b._chk = Date.now();
        const info = await this.enrichInfo(b, h, signal);
        if (signal.aborted) { b._chk = prevChk; b._mob = prevMob; return; }
        if (!info.unk && Number.isFinite(+info.status) && PRES_SITES.has(urlSite(b.url)) && presOn()) presNote(bmKey(b.url), +info.status > 0);
        if (info.status > 0 && !info.unk) this.touchSeen(b, mark);
        if (!applyProfile(b, info)) return;
        mark();
        this.onStatusChange(b, sortType);
    }

    async enrichInfo(b, h, signal) {
        const info = parseProfile(h, urlSite(b.url), modelSlug(b.url));
        if (urlSite(b.url) === 'sc' && isStr(h) && h.includes('"bodyType"') && (!isPlain(b.inf) || Date.now() - (b.inf.t || 0) > INF_FRESH_MS)) {
            const slug = modelSlug(b.url), r = sciFromCam(h, slug);
            if (r) { sciDiagOk(slug, r); applyInfo(b, sciPick(r.u)); this._touched(b); }
            else ParserDiag.recInfo(slug, '', true, { tried: [sciSig(b.url, h)] });
        }
        if (info.unk) {
            const keep = Number.isFinite(+b.status) && (!b._kt || Date.now() - b._kt < PD_STALE_MS);
            if (keep) { info.status = +b.status; delete info.lk; }
            delete info.topic; delete info.frame;
        } else b._kt = Date.now();
        if (urlSite(b.url) === 'bc' && info.status > 0) {
            const vn = await this.viewersFor(b, signal);
            if (vn != null) info.viewers = vn;
        }
        if (this.needsExtra(b, info)) {
            b._mob = Date.now();
            Object.assign(info, await this.loadExtra(b, signal));
        }
        if (info.status === 0 && b._live && Date.now() - b._live < 90000) info.status = 1;
        return info;
    }

    needsExtra(b, info) {
        return urlSite(b.url) === 'bc' && !info.sch && (!b._mob || Date.now() - b._mob > 600000);
    }

    touchSeen(b, mark) {
        b.seen = Date.now();
        if (b._sv && b.seen - b._sv <= 300000) return;
        b._sv = b.seen;
        mark();
    }

    isOpen() { return !!this.overlay?.classList.contains('open'); }

    onStatusChange(b, sortType) {
        if (!this.isOpen()) return;
        b._updateItemDOM?.();
        if (RESORT_SORTS.has(sortType) || this.hideOffline || ST_FILTERS.some(([prop]) => this[prop])) {
            clearTimeout(this._resortTimer);
            const tick = () => {
                if (!this.isOpen() || this.mode !== 'fav') return;
                if (this.ui.shadow.activeElement?.matches?.('textarea,select')) { this._resortTimer = setTimeout(tick, 1000); return; }
                this.render();
            };
            this._resortTimer = setTimeout(tick, 600);
        }
    }

    build() {
        this.buildOverlay();
        const modal = mk('div', 'bm-modal fav-modal');
        this.modal = modal;
        this.mode = 'fav';
        const hdr = mk('div', 'bm-hdr');
        this.buildTabs();
        const closeBtn = htmlEl('bm-close', '×', 'button');
        closeBtn.onclick = () => this.hide();
        const { btnTogglePrev, btnSpeed } = this.buildPreviewControls();
        const btnFavNav = this.buildFavNavSwitch();
        const hideSw = this.buildHideSwitches(modal);
        const sortWrap = this.buildSortControls();
        const btnCols = this.buildColsButton();
        const btnAv = this.buildAvatarRefresh();
        const setPanel = this.buildSettingsPanel({ btnFavNav, btnSpeed, btnAv, ...hideSw });
        const btnGear = htmlEl('bm-hbtn bm-gear', icons.gear, 'button');
        bindGear(btnGear, setPanel);
        const tools = mk('div', 'bm-tools');
        this.wsegHost = mk('div', 'bm-wseg-host');
        tools.append(this.wsegHost, btnTogglePrev, btnCols, btnGear);
        hdr.append(this.title, closeBtn);
        const tbar = mk('div', 'bm-tbar');
        tbar.append(tools);

        this.buildSearch(sortWrap);
        this.tagsWrap = mk('div', 'bm-tags-wrap');
        this.list = mk('div', 'bm-list');
        this.applyCols?.();
        this.bindHostEvents();
        const foot = mk('div', 'bm-foot');
        this.btnAdd = mk('button', 'bm-add');
        this.btnAdd.onclick = () => this.add();

        foot.append(this.btnAdd);
        modal.append(hdr, tbar, this.searchWrap, this.tagsWrap, this.list, setPanel, foot);
        this.overlay.appendChild(modal);
        this.ui.shadow.appendChild(this.overlay);
    }

    buildOverlay() {
        this.overlay = mk('div', 'bm-overlay fav-ov');
        bindOutsideClose(this, this.overlay, () => this.hide());
        this.overlay.addEventListener('click', e => {
            if (e.timeStamp - (this._opPerf || 0) < 400) { halt(e); }
        }, true);
        this.overlay.addEventListener('touchmove', e => {
            if (this.onSlider(e) || this.hasScrollableAncestor(e)) return;
            if (e.cancelable) e.preventDefault();
        }, { passive: false });

    }

    isScrollable(n) {
        const cs = getComputedStyle(n);
        return (/(auto|scroll)/.test(cs.overflowY) && n.scrollHeight > n.clientHeight + 1)
            || (/(auto|scroll)/.test(cs.overflowX) && n.scrollWidth > n.clientWidth + 1);
    }

    onSlider(e) {
        return e.composedPath().some(n => n instanceof Element && n.matches('input[type=range],.mv-sk'));
    }

    hasScrollableAncestor(e) {
        for (const n of e.composedPath()) {
            if (n === this.overlay) return false;
            if (n instanceof Element && this.isScrollable(n)) return true;
        }
        return false;
    }

    buildTabs() {
        this.title = mk('span', 'bm-title');
        this.tabFav = mk('button', 'bm-tbtn on');
        this.tabHist = mk('button', 'bm-tbtn');
        this.tabFav.onclick = () => this.setMode('fav');
        this.tabHist.onclick = () => this.setMode('hist');
        const tabs = mk('div', 'bm-seg bm-tabs');
        this.tabCal = mk('button', 'bm-tbtn');
        this.tabCal.onclick = () => this.setMode('cal');
        this.tabMv = mk('button', 'bm-tbtn');
        this.tabMv.onclick = () => this.setMode('mv');
        tabs.append(this.tabFav, this.tabMv, this.tabHist, this.tabCal);
        this.title.append(tabs);

    }

    buildPreviewControls() {
        const btnTogglePrev = mk('button', 'bm-hbtn bm-prevbtn');
        const { seg: btnSpeed, btns: spdBtns } = buildSpdSeg(() => {
            this.updatePrevUI();
            this.stopPreviews();
            this.render();
        });
        if (!Store.get('bc_mig_prev1', false)) { Store.set('bc_previews', true); Store.set('bc_mig_prev1', true); }
        this.prevEn = storeOn('bc_previews');
        this.updatePrevUI = () => {
            paintPrev(btnTogglePrev, btnSpeed, spdBtns, !!this.prevEn);
        };
        this.updatePrevUI();
        btnTogglePrev.onclick = () => {
            this.prevEn = !this.prevEn;
            Store.set('bc_previews', this.prevEn);
            this.updatePrevUI();
            this.ui.multiView?.syncPrev();
            this.render();
        };
        return { btnTogglePrev, btnSpeed };
    }

    buildFavNavSwitch() {
        const btnFavNav = mk('button', 'bm-sw');
        this.updateFavNavUI = () => {
            btnFavNav.classList.toggle('on', FavNav.on);
        };
        this.updateFavNavUI();
        btnFavNav.onclick = () => { FavNav.set(!FavNav.on); this.updateFavNavUI(); };
        return btnFavNav;
    }

    buildHideSwitches(modal) {
        const on = storeOn;
        const defs = [['btnHideTopic', 'topic'], ['btnHideGrp', 'grp'], ['btnHidePT', 'ptags'], ['btnHideNote', 'note']]
            .map(([n, k]) => ({ n, k, key: `bc_bm_hide_${k}`, btn: mk('button', 'bm-sw') }));
        const applyHide = () => defs.forEach(d => { const v = on(d.key); d.btn.classList.toggle('on', v); modal.classList.toggle(`bm-no-${d.k}`, v); });
        applyHide();
        defs.forEach(d => { d.btn.onclick = () => { Store.set(d.key, !on(d.key)); applyHide(); if (d.k === 'ptags') this.render(); }; });
        return Object.fromEntries(defs.map(d => [d.n, d.btn]));
    }

    buildSortControls() {
        const sortWrap = mk('div', 'bm-sort-wrap');
        this.btnSortType = mk('button', 'bm-hbtn');
        this.btnSortDir = mk('button', 'bm-hbtn');

        this.updateSortUI = () => {
            const type = bmSortType();
            const isRev = bmSortRev();
            const l = L();
            const map = { date: [icons.clock, l.sort_date], name: [icons.name, l.sort_name], online: [icons.dot, l.sort_live], last: [icons.eye, l.sort_last], viewers: [icons.eye, l.sort_viewers] };
            const [icon, label] = map[type] || map.date;
            this.btnSortType.innerHTML = icon + label;
            this.btnSortDir.innerHTML = isRev ? icons.arrowUp : icons.arrowDown;
        };

        this.btnSortType.onclick = () => {
            const types = ['date', 'name', 'online', 'last', 'viewers'];
            const cur = bmSortType();
            Store.set('bc_bm_sort_type', types[(types.indexOf(cur) + 1) % types.length]);
            this.updateSortUI();
            this.render();
        };

        this.btnSortDir.onclick = () => {
            Store.set('bc_bm_sort_rev', !bmSortRev());
            this.updateSortUI();
            this.render();
        };

        this.updateSortUI();
        sortWrap.append(this.btnSortType, this.btnSortDir);
        return sortWrap;
    }

    buildColsButton() {
        const btnCols = mk('button', 'bm-hbtn bm-colsbtn');
        this.applyCols = () => {
            const n = colsGet();
            this.list.style.setProperty('grid-template-columns', `repeat(${n},minmax(0,1fr))`, 'important');
            this.list.classList.toggle('bm-c1', n === 1);
            btnCols.innerHTML = COLS_ICONS[n - 1];
            btnCols.title = L().st_cols;
        };
        btnCols.onclick = () => {
            colsToggle();
            this.applyCols();
        };
        return btnCols;
    }

    buildAvatarRefresh() {
        const btnAv = textEl('bm-hbtn bm-spd bm-avbtn', '↻', 'button');
        let avBusy = false;
        btnAv.onclick = async () => {
            if (avBusy) return;
            avBusy = true;
            btnAv.disabled = true;
            const queue = AppState.bookmarks.slice(), total = queue.length;
            let done = 0;
            const upd = () => { btnAv.textContent = `${done}/${total}`; };
            upd();
            const worker = async () => {
                while (queue.length) {
                    const b = queue.shift();
                    try { await this.fetchAvatar(b); } catch (e) { dbg(e, 'buildAvatarRefresh.worker'); }
                    done++;
                    upd();
                }
            };
            await Promise.all([worker(), worker(), worker()]);
            btnAv.textContent = '✓';
            setTimeout(() => { btnAv.textContent = '↻'; btnAv.disabled = false; avBusy = false; }, 1500);
        };
        return btnAv;
    }

    buildSettingsPanel({ btnFavNav, btnHideTopic, btnHideGrp, btnHidePT, btnHideNote, btnSpeed, btnAv }) {
        const row = mkSetRow;
        const setPanel = mk('div', 'bm-setp');
        setPanel.append(
            row(L().st_favnav, btnFavNav),
            row(L().st_hide_topic, btnHideTopic),
            row(L().st_hide_grp, btnHideGrp),
            row(L().st_ptags, btnHidePT),
            row(L().st_hide_note, btnHideNote),
            row(L().st_prev_spd, btnSpeed, 'bm-setrow-spd'),
            row(L().st_av_refresh, btnAv, 'bm-setrow-av')
        );
        return setPanel;
    }

    buildSearch(sortWrap) {
        this.query = '';
        this.searchWrap = mk('div', 'bm-search-wrap');
        const sBox = mk('div', 'bm-search-box');
        const sIco = htmlEl('bm-search-ico', icons.search, 'span');
        this.searchIn = mk('input', 'bm-search-in');
        this.searchIn.type = 'text';
        this.searchIn.enterKeyHint = 'search';
        this.searchIn.autocomplete = 'off';
        this.searchIn.spellcheck = false;
        this.searchIn.setAttribute('autocapitalize', 'off');
        this.searchClr = textEl('bm-search-clr', '×', 'button');
        let sTimer = 0;
        const onQuery = () => {
            const v = this.searchIn.value;
            this.query = v.trim().toLowerCase();
            this.searchClr.style.display = v ? 'flex' : 'none';
            clearTimeout(sTimer);
            sTimer = setTimeout(() => this.render(), 120);
        };
        this.searchIn.oninput = onQuery;
        this.searchIn.onkeydown = e => {
            e.stopPropagation();
            if (e.key === 'Escape' && this.searchIn.value) { e.preventDefault(); this.searchIn.value = ''; onQuery(); }
        };
        this.searchClr.onclick = () => { this.searchIn.value = ''; onQuery(); this.searchIn.focus(); };
        sBox.append(sIco, this.searchIn, this.searchClr);
        this.searchWrap.append(sBox, sortWrap);

    }

    bindHostEvents() {
        window.addEventListener('bc_topic', e => {
            try {
                const t = String(JSON.parse(e.detail).t || '').trim();
                if (!t) return;
                curBms().filter(b => b.status > 0).forEach(b => { if (applyProfile(b, { topic: t })) b._updateItemDOM?.(); });
            } catch (e) { dbg(e, 'BookmarkManager.bindHostEvents'); }
        });
        window.addEventListener('bc_viewers', e => {
            try {
                const d = JSON.parse(e.detail);
                if (!(d.n >= 0)) return;
                curBms().forEach(b => { if (applyProfile(b, { viewers: d.n })) b._updateItemDOM?.(); });
            } catch (e) { dbg(e, 'BookmarkManager.bindHostEvents'); }
        });
        window.addEventListener('bc_hls', () => {
            if (this.isOpen()) this.list.querySelectorAll('.bm-item').forEach(i => i._syncHls?.());
        });
        this._observer = new IntersectionObserver(entries => entries.forEach(e => e.target._inView = e.isIntersecting), { root: this.list, threshold: 0.05 });

    }

    hide() {
        this.overlay.classList.remove('open');
        this.ui.multiView?.suspend();
        clearTimeout(this._resortTimer);
        this.stopPreviews?.();
        try { this._abortCtrl?.abort(); } catch (e) { dbg(e, 'BookmarkManager.hide'); }
    }

    enterMode(mode, fromOpen) {
        if (mode === 'fav') {
            if (!fromOpen) { this.render(); this.checkStatuses(); }
            return;
        }
        this.stopPreviews?.();
        const manager = { hist: 'historyManager', cal: 'calendarManager', mv: 'multiView' }[mode];
        this.ui[manager]?.show();
    }

    setMode(m, fromOpen) {
        const mode = this.mode = ['hist', 'cal', 'mv'].includes(m) ? m : 'fav';
        for (const k of ['hist', 'cal', 'mv']) this.modal.classList.toggle(`mode-${k}`, mode === k);
        const tabs = { fav: this.tabFav, hist: this.tabHist, cal: this.tabCal, mv: this.tabMv };
        for (const k of Object.keys(tabs)) tabs[k].classList.toggle('on', mode === k);
        if (mode !== 'mv') this.ui.multiView?.suspend();
        this.enterMode(mode, fromOpen);
    }

    updateLabels() {
        const tab = (el, ic, tx) => { el.innerHTML = `${ic}<span class="bm-tl">${escHTML(tx)}</span>`; };
        tab(this.tabFav, icons.star, L().bm_title);
        tab(this.tabHist, icons.clock, L().hist_title);
        tab(this.tabCal, icons.cal, L().cal_title);
        tab(this.tabMv, icons.grid, mvT('mv_title'));
        this.tabMv.title = mvT('mv_title');
        if (this.ui.calendarManager?.isOpen()) this.ui.calendarManager.render();
        this.searchIn.placeholder = L().bm_find;
        this.updateSortUI?.(); this.updatePrevUI?.(); this.applyCols?.();
    }

    open(mode) {
        syncBookmarks();
        this.updateLabels();
        this.query = '';
        this.searchIn.value = '';
        this.searchClr.style.display = 'none';
        this.setMode(mode, true);
        if (this.mode === 'fav') {
            this.render();
            this.list.scrollTop = 0;
            this.checkStatuses();
        }
        this._opAt = Date.now();
        this._opPerf = performance.now();
        this.overlay.classList.add('open');
    }

    statusSeg(l) {
        const toggle = (prop, key) => () => { this[prop] = !this[prop]; Store.set(key, this[prop]); this.render(); };
        const seg = mk('div', 'bm-seg');
        seg.append(
            bmTbtn(l.bm_online, this.hideOffline, toggle('hideOffline', 'bc_bm_hide_off'), l.bm_online_t),
            ...ST_FILTERS.map(([prop, key, lk, label]) => bmTbtn(l[label], this[prop], toggle(prop, key), l[lk]))
        );
        return seg;
    }

    watchSeg(l) {
        const setW = v => () => { this.wFilter = this.wFilter === v ? null : v; Store.set('bc_bm_wf', this.wFilter == null ? -1 : this.wFilter); this.render(); };
        const wseg = mk('div', 'bm-seg bm-wseg');
        wseg.append(
            ...WMARKS.map(([v, ic, key]) => bmTbtn(icons[ic], this.wFilter === v, setW(v), l[key]))
        );
        return wseg;
    }

    appendGroupChips(chips, l) {
        const groups = bmGroups();
        if (this.currentGroup && !groups.includes(this.currentGroup)) this.currentGroup = null;
        if (!groups.length) return;
        const setGrp = g => () => { this.currentGroup = this.currentGroup === g ? null : g; this.render(); };
        chips.append(
            bmTbtn(`${icons.folder}${escHTML(l.grp_all)}`, !this.currentGroup, () => { this.currentGroup = null; this.render(); }),
            ...groups.map(g => bmTbtn(`${icons.folder}${escHTML(g)}`, this.currentGroup === g, setGrp(g)))
        );
    }

    topPtags(hidePT, ptc) {
        if (hidePT) return [];
        return [...ptc.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, 30).map(e => e[0]);
    }

    appendTagChips(chips, l) {
        const allTags = new Set(AppState.bookmarks.flatMap(b => noteHashTags(b).map(t => t.toLowerCase())));
        const ptc = new Map();
        AppState.bookmarks.forEach(b => (b.ptags || []).forEach(t => countInto(ptc, t)));
        const hidePT = storeOn('bc_bm_hide_ptags');
        const ptop = this.topPtags(hidePT, ptc);
        if (this.currentTagFilter && !allTags.has(this.currentTagFilter) && (hidePT || !ptc.has(this.currentTagFilter))) this.currentTagFilter = null;
        if (!allTags.size && !ptop.length) { this.currentTagFilter = null; return; }
        const setTag = tag => () => { this.currentTagFilter = this.currentTagFilter === tag ? null : tag; this.render(); };
        const pim = ptIcoMap();
        const allBtn = bmTbtn(l.bm_all, !this.currentTagFilter, () => { this.currentTagFilter = null; this.render(); });
        allBtn.classList.add('bm-tall');
        chips.append(
            allBtn,
            ...[...allTags].sort().map(tag => bmTbtn(tag, this.currentTagFilter === tag, setTag(tag))),
            ...(allTags.size && ptop.length ? [mk('span', 'bm-chips-sep')] : []),
            ...ptop.map(tag => bmTbtn(`${ptImg(pim.get(tag), 'bm-ptic') || icons.tag}${escHTML(tag)}`, this.currentTagFilter === tag, setTag(tag)))
        );
    }

    renderTags() {
        if (!this.tagsWrap) return;
        const l = L();
        const chips = mk('div', 'bm-chips');
        this.tagsWrap.replaceChildren(this.statusSeg(l));
        this.wsegHost && this.wsegHost.replaceChildren(this.watchSeg(l));
        this.tagsWrap.style.display = 'flex';
        this.appendGroupChips(chips, l);
        this.appendTagChips(chips, l);
        if (chips.children.length) {
            this.tagsWrap.append(chips);
            const sx = this._chipsX || 0;
            chips.scrollLeft = sx;
            requestAnimationFrame(() => { if (chips.isConnected && chips.scrollLeft !== sx) chips.scrollLeft = sx; });
            chips.addEventListener('scroll', () => { this._chipsX = chips.scrollLeft; }, { passive: true });
        }
    }

    itemTopRow(item, b) {
        const top = mk('div', 'bm-item-top');
        const link = mk('a', 'bm-link');
        link.href = isSafeUrl(b.url) ? b.url : '#';

        const { img, applyAva } = this.itemAvatar(b);
        const stat = mk('span', 'bm-stat');
        const nameWrap = mk('div', 'bm-nwrap');
        const nameEl = textEl('bm-name', b.name, 'span');
        const seenEl = mk('span', 'bm-seen');
        const viewEl = mk('span', 'bm-view');
        nameWrap.append(stat, nameEl, viewEl, seenEl);
        link.append(img, nameWrap);
        this.bindItemFrameHold(item, b);
        img.draggable = false;
        img.style.webkitTouchCallout = 'none';
        this.bindNameCopyHold(nameWrap, nameEl, b);

        const { pinBtn, delBtn } = this.itemPinDel(b);
        top.append(link, pinBtn, delBtn);
        return { top, link, img, stat, nameEl, seenEl, viewEl, applyAva, pinBtn, delBtn };
    }

    itemStatusFn(item, b, p) {
        const { link, img, stat, nameEl, seenEl, viewEl, applyAva, pinBtn, updateTopicDOM, applyPT, applyPreview } = p;
        const applyW = () => { item.dataset.w = String(bmWatch(b, this._hist || (this._hist = histLoad()))); };
        return () => {
            const l = L(), st = b.status ?? 0;
            link.href = isSafeUrl(b.url) ? linkFor(b.url) : '#';
            applyAva();
            img.title = l.fr_hold;
            item.classList.toggle('pinned', !!b.pin);
            pinBtn.classList.toggle('on', !!b.pin);
            pinBtn.title = b.pin ? l.bm_unpin : l.bm_pin;
            item.dataset.st = String(st);
            paintStatusDot(stat, st);
            nameEl.textContent = bmLabel(b, l);
            seenEl.textContent = bmSeen(b, st);
            seenEl.title = l.seen_tip;
            paintViewsBadge(viewEl, b);
            viewEl.title = l.viewers_tip;
            updateTopicDOM(b.topic || '');
            applyW();
            applyPT();
            applyPreview(st);
        };
    }

    renderItem(b) {
        const cached = this._els.get(b);
        if (cached) {
            cached.fillGrp();
            cached.apply();
            cached.item._syncNote?.();
            cached.item._syncHls?.();
            b._updateItemDOM = cached.apply;
            return cached.item;
        }
        const item = mk('div', 'bm-item');
        const { top, link, img, stat, nameEl, seenEl, viewEl, applyAva, pinBtn, delBtn } = this.itemTopRow(item, b);

        const { actionsRow, syncHls } = this.itemActions(b);
        item._syncHls = syncHls;
        item._syncHls();

        const { noteInput, autoResize, syncNote } = this.itemNote(b);
        item._syncNote = syncNote;
        const { topicEl, updateTopicDOM } = this.itemTopic(b);
        const { grpSel, fillGrp } = this.itemGroup(b);
        const { ptagsEl, applyPT } = this.itemPtags(b);
        const { previewImg, applyPreview } = this.itemPreview(b);

        item.onclick = e => { if (isSafeUrl(b.url) && !e.target.closest('button, textarea, select, a.tp-btn')) location.href = linkFor(b.url); };
        item.append(top, actionsRow, ptagsEl, topicEl, grpSel, noteInput, previewImg);
        this.itemMore(item, b, actionsRow, pinBtn, delBtn);

        const applyStatus = this.itemStatusFn(item, b, { link, img, stat, nameEl, seenEl, viewEl, applyAva, pinBtn, updateTopicDOM, applyPT, applyPreview });
        applyStatus();
        b._updateItemDOM = applyStatus;
        this._observer?.observe(item);
        this._els.set(b, { item, fillGrp, apply: applyStatus, resize: () => requestAnimationFrame(autoResize) });
        return item;
    }

    itemAvatar(b) {
        const img = mk('img', 'bm-avatar');
        const applyAva = () => {
            const k = b.avatar || '';
            if (img._k === k) return;
            img._k = k;
            bindAvatar(img, b.name, () => [
                toHttps(b.avatar),
                toHttps((this._hist || (this._hist = histLoad())).m[bmKey(b.url)]?.a)
            ], () => { if (!b._avFix) { b._avFix = 1; this.fetchAvatar(b); } });
        };
        applyAva();
        return { img, applyAva };
    }

    bindItemFrameHold(item, b) {
        Helpers.bindHold(item, {
            ok: e => !e.target.closest('button, textarea, select, input, a.tp-btn, .bm-nwrap'),
            onHold: () => openFrameView(this.ui, b)
        });
    }

    bindNameCopyHold(nameWrap, nameEl, b) {
        nameWrap.style.userSelect = nameWrap.style.webkitUserSelect = nameWrap.style.webkitTouchCallout = 'none';
        const flash = ok => {
            nameEl.textContent = ok ? '✓' : 'ERR';
            setTimeout(() => { nameEl.textContent = b.name; }, 800);
        };
        Helpers.bindHold(nameWrap, { always: true, onHold: () => Helpers.copyCb(b.name, flash) });
    }

    itemPinDel(b) {
        const delBtn = htmlEl('bm-del', icons.del, 'button');
        delBtn.title = L().bm_del;
        delBtn.onclick = e => {
            halt(e);
            dropBookmark(b);
            this.render();
        };
        const pinBtn = htmlEl('bm-pin', icons.star, 'button');
        pinBtn.onclick = e => {
            halt(e);
            if (b.pin) delete b.pin; else b.pin = true;
            saveBookmarks();
            this.render();
        };
        return { pinBtn, delBtn };
    }

    itemActions(b) {
        const makeAct = (html, fn, onHold) => {
            const btn = htmlEl('bm-act-btn', html, 'button');
            if (onHold) Helpers.bindLongPress(btn, () => fn(btn), onHold);
            else btn.onclick = e => { halt(e); fn(btn); };
            return btn;
        };
        const onPage = () => sameBm(b, currentSite.getName());
        const canHls = () => onPage() || !!MV_RES[urlSite(b.url)];
        const getHls = () => {
            const hit = BM_HLS.get(b.url);
            if (hit && Date.now() - hit.at < 45000) return hit.p;
            const p = Promise.resolve().then(async () => {
                if (onPage()) { const cur = getStreamUrl(); if (cur) return cur; }
                const r = await mvResolve(bmEntry(b));
                return (r && r.url) || '';
            }).catch(() => '');
            BM_HLS.set(b.url, { at: Date.now(), p });
            if (BM_HLS.size > 40) { const now = Date.now(); for (const [k, v] of BM_HLS) if (now - v.at > 45000) BM_HLS.delete(k); }
            p.then(u => { if (!u && BM_HLS.get(b.url)?.p === p) BM_HLS.delete(b.url); });
            return p;
        };
        const busy = (btn, on) => { btn.style.opacity = on ? '.55' : ''; };
        const actionsRow = mk('div', 'bm-actions-row bm-acts');
        actionsRow.append(
            makeAct(`${icons.search} <span>${L().search}</span>`, () => { noFade(this.overlay, this.ui.searchManager?.overlay); this.overlay.classList.remove('open'); this.ui.searchManager?.open(b.name, true, b.alts); }),
            makeAct(`${icons.hls} <span>HLS</span>`,
                async btn => { busy(btn, true); const u = await getHls(); busy(btn, false); copyHls(u, ok => Helpers.flashBtn(btn, ok)); },
                async () => { const btn = actionsRow.children[1]; busy(btn, true); const u = await getHls(); busy(btn, false); shareHls(u, `${b.name} Stream`, () => Helpers.flashBtn(btn, false)); }
            ),
            makeAct(`${icons.copy} <span>${L().copy}</span>`, btn => {
                Helpers.copyCb(linkFor(b.url), ok => Helpers.flashBtn(btn, ok));
            })
        );
        const hlsBtn = actionsRow.children[1];
        hlsBtn.addEventListener('pointerdown', () => { if (canHls()) getHls(); });
        const syncHls = () => {
            hlsBtn.style.opacity = canHls() ? '' : '.4';
            hlsBtn.title = 'HLS';
        };
        return { actionsRow, syncHls };
    }

    itemMore(item, b, actionsRow, pinBtn, delBtn) {
        const more = mk('button', 'bm-act-btn bm-more');
        more.type = 'button';
        more.innerHTML = icons.dots;
        const pop = mk('div', 'bm-mpop');
        let off = null;
        const close = () => {
            pop.classList.remove('open');
            more.classList.remove('on');
            if (off) { off(); off = null; }
            if (this._mpopClose === close) this._mpopClose = null;
        };
        const defs = [
            ['bm-mrow-info', () => '', () => nxT('info'), () => openInfoPult(this.ui, b)],
            ['bm-mrow-link', () => icons.copy, () => { const t = String(L().copy); return t.charAt(0).toLocaleUpperCase() + t.slice(1).toLocaleLowerCase(); }, () => Helpers.copy(linkFor(b.url), () => Helpers.flashBtn(more, true), () => Helpers.flashBtn(more, false))],
            ['bm-mrow-pin', () => icons.star, () => (b.pin ? L().bm_unpin : String(L().bm_pin).split(' ')[0]), () => pinBtn.click()],
            ['bm-mrow-del', () => icons.del, () => L().bm_del, () => delBtn.click()]
        ];
        const rows = defs.map(([cls, ic, label, fn]) => {
            const r = mk('button', 'bm-mrow ' + cls);
            r.type = 'button';
            r.onclick = e => { halt(e); close(); fn(); };
            pop.append(r);
            return () => { r.innerHTML = ic() + '<span></span>'; r.lastChild.textContent = label(); };
        });
        more.onclick = e => {
            halt(e);
            if (pop.classList.contains('open')) return close();
            this._mpopClose?.();
            rows.forEach(f => f());
            pop.classList.add('open');
            more.classList.add('on');
            const onDown = ev => { const path = ev.composedPath(); if (!path.includes(pop) && !path.includes(more)) close(); };
            document.addEventListener('pointerdown', onDown, true);
            off = () => document.removeEventListener('pointerdown', onDown, true);
            this._mpopClose = close;
        };
        actionsRow.append(more);
        item.append(pop);
    }

    itemNote(b) {
        const noteInput = mk('textarea', 'bm-note');
        noteInput.rows = '1';
        noteInput.style.cssText = 'resize:none;overflow:hidden;min-height:36px;white-space:pre-wrap;word-break:break-word;';
        noteInput.placeholder = L().note_ph;
        noteInput.value = b.note || '';
        const autoResize = () => { noteInput.style.height = 'auto'; noteInput.style.height = `${Math.max(36, noteInput.scrollHeight)}px`; };
        const syncNote = () => {
            const v = b.note || '';
            if (document.activeElement !== noteInput && noteInput.value !== v) { noteInput.value = v; requestAnimationFrame(autoResize); }
        };
        requestAnimationFrame(autoResize);

        let noteT = 0;
        noteInput.oninput = e => {
            requestAnimationFrame(autoResize);
            b.note = e.target.value.trim();
            clearTimeout(noteT);
            bmDirty = true; noteT = setTimeout(saveBookmarks, 400);
        };
        noteInput.onblur = () => { if (noteT) { clearTimeout(noteT); noteT = 0; saveBookmarks(); } };
        noteInput.onchange = () => { clearTimeout(noteT); noteT = 0; saveBookmarks(); this.ui.flash(this.btnAdd, 'OK'); this.render(); };
        noteInput.onkeydown = e => e.stopPropagation();
        return { noteInput, autoResize, syncNote };
    }

    itemGroup(b) {
        const grpSel = mk('button', 'bm-grp bm-grp-btn');
        grpSel.type = 'button';
        const groupList = () => {
            const gs = bmGroups();
            if (b.g && !gs.includes(b.g)) gs.push(b.g);
            return gs;
        };
        const fillGrp = () => {
            const label = b.g || L().grp_none;
            if (grpSel._lbl === label) return;
            grpSel._lbl = label;
            grpSel.innerHTML = `<span>${icons.folder}${escHTML(label)}</span><span class="bm-grp-arr">▾</span>`;
        };
        fillGrp();
        grpSel.onclick = e => {
            e.stopPropagation();
            openGroupDialog(this.ui, {
                current: b.g || '',
                groups: groupList(),
                onPick: v => { b.g = v; saveBookmarks(); this.render(); }
            });
        };
        grpSel.onkeydown = e => e.stopPropagation();
        return { grpSel, fillGrp };
    }

    itemTopic(b) {
        const topicEl = mk('div', 'bm-topic');
        const updateTopicDOM = txt => {
            const key = `${txt}|${!!b._topicExp}|${AppState.lang}`;
            if (topicEl._k === key) return;
            topicEl._k = key;
            if (!txt) { topicEl.style.display = 'none'; return; }
            topicEl.style.display = 'block';
            if (txt.length > 55) {
                const txtMore = L().tp_more;
                const txtLess = L().tp_less;
                topicEl.innerHTML = `<span>${escHTML(b._topicExp ? txt : txt.slice(0, 55) + '...')} </span><a class="tp-btn" style="color:var(--th);cursor:pointer;font-weight:bold;text-decoration:underline;">${escHTML(b._topicExp ? txtLess : txtMore)}</a>`;
                topicEl.querySelector('.tp-btn')?.addEventListener('click', ev => {
                    ev.preventDefault();
                    b._topicExp = !b._topicExp;
                    updateTopicDOM(txt);
                });
            } else topicEl.textContent = txt;
        };
        return { topicEl, updateTopicDOM };
    }

    itemPtags(b) {
        const ptagsEl = mk('div', 'bm-ptags');
        const applyPT = () => {
            const tg = Array.isArray(b.ptags) ? b.ptags : [], cf = this.currentTagFilter || '';
            const key = `${tg.join('|')}#${(Array.isArray(b.ptic) ? b.ptic : []).join('|')}#1`;
            const syncOn = () => ptagsEl.querySelectorAll('.bm-ptag[data-t]').forEach(c => c.classList.toggle('on', c.dataset.t === cf));
            if (ptagsEl._k === key) { syncOn(); return; }
            ptagsEl._k = key;
            ptagsEl.replaceChildren();
            ptagsEl.style.display = tg.length ? 'flex' : 'none';
            const max = 3;
            tg.slice(0, max).forEach((t, ix) => ptagsEl.append(this.ptagChip(b, t, ix, cf)));
            if (tg.length > max) ptagsEl.append(textEl('bm-ptag more', `+${tg.length - max}`, 'span'));
        };
        return { ptagsEl, applyPT };
    }

    ptagChip(b, t, ix, cf) {
        const c = mk('button', `bm-ptag${cf === t.toLowerCase() ? ' on' : ''}`);
        c.type = 'button';
        c.dataset.t = t.toLowerCase();
        const u = ptIcoUrl(Array.isArray(b.ptic) ? b.ptic[ix] : '');
        if (u) {
            const im = mk('img', 'bm-ptic');
            im.alt = '';
            im.decoding = 'sync';
            im.referrerPolicy = 'no-referrer';
            im.dataset.pti = '1';
            im.src = u;
            c.append(im);
        }
        c.append(document.createTextNode(t));
        c.onclick = e => {
            halt(e);
            this.currentTagFilter = this.currentTagFilter === t.toLowerCase() ? null : t.toLowerCase();
            this.render();
        };
        return c;
    }

    itemPreview(b) {
        const previewImg = mk('img', 'bm-prev');
        previewImg.onload = () => previewImg.classList.remove('bm-bad');
        previewImg.onerror = () => {
            previewImg.classList.add('bm-bad');
            if (!previewImg.hasAttribute('data-frame')) { previewImg.style.display = 'none'; return; }
            if (/^data:/.test(previewImg.src) || !tryGm(previewImg, previewImg._u, () => frameFail(previewImg))) frameFail(previewImg);
        };
        const showImg = on => {
            const d = on ? 'block' : 'none';
            if (previewImg.style.display !== d) previewImg.style.display = d;
        };
        const applyPreview = st => {
            if (!(this.prevEn && b.frame && st !== 0)) {
                previewImg.removeAttribute('data-frame');
                previewImg._gen = (previewImg._gen || 0) + 1;
                previewImg._isUpdating = false;
                return showImg(false);
            }
            previewImg.setAttribute('data-frame', b.frame);
            showImg(true);
            previewImg._bRef = b;
            b._lastImg ||= frameBust(b.frame);
            const key = (b._lastData ? 'd' : '') + b._lastImg;
            if (previewImg._k !== key) {
                previewImg._k = key;
                previewImg._u = b._lastImg;
                previewImg.src = b._lastData || b._lastImg;
            }
        };
        return { previewImg, applyPreview };
    }

    scanCbBio(b) {
        const el = document.querySelector('[data-testid="room-bio-tab-contents"], [data-testid="bio-tab-content-desktop"], .BioTabContentDesktop');
        const bio = el ? cbBioFromHtml(el.outerHTML) : null;
        const bp = bio && cbJoin(b.bp, bio, null);
        if (!bp) return false;
        if (applyProfile(b, { bp })) {
            saveBookmarks();
            if (this.isOpen()) this.render();
        }
        return true;
    }

    scanPage() {
        if (SITE === 'cb') { const k = pageKey(), cb = k && bmByKey(k); return cb ? this.scanCbBio(cb) : false; }
        if (!IS_BC) return false;
        const key = pageKey();
        if (!key) return false;
        const b = bmByKey(key);
        if (!b) return false;
        const info = parseBcExtra(document.documentElement.outerHTML);
        if (!info.ptags && !info.sch) return false;
        if (applyProfile(b, info)) {
            saveBookmarks();
            if (this.isOpen()) this.render();
        }
        return true;
    }

    _touched(b) {
        clearTimeout(this._vwSave);
        bmDirty = true; this._vwSave = setTimeout(saveBookmarks, 3000);
        if (this.isOpen()) this.onStatusChange(b, bmSortType());
    }

    markLive(key, known) {
        const b = known || bmByKey(key);
        if (!b) return;
        b._live = Date.now();
        b.seen = b._live;
        if ((b.status ?? 0) !== 0) return;
        b.status = 1;
        this._touched(b);
    }

    setViewers(key, n, known) {
        const b = known || bmByKey(key);
        if (!b || !(n >= 0)) return;
        const changed = b.viewers !== n;
        b.viewers = n;
        b.vts = Date.now();
        if (!changed) return;
        this._touched(b);
    }

    addEntry({ name, url, avatar, viewers }) {
        syncBookmarks();
        if (!isSafeUrl(url) || isFavKey(bmKey(url))) return false;
        const bm = { name: clip(name, 100) || modelSlug(url), url: normUrl(url), avatar: toHttps(avatar), note: '' };
        if (viewers > 0) { bm.viewers = viewers; bm.vts = Date.now(); }
        AppState.bookmarks.push(bm);
        saveBookmarks();
        if (this.isOpen()) this.render();
        this.checkStatuses();
        if (!bm.avatar) this.fetchAvatar(bm);
        return true;
    }

    removeKey(key, after) {
        syncBookmarks();
        const ex = bmByKey(key);
        if (!ex) return false;
        const run = () => {
            dropBookmark(ex);
            if (this.isOpen()) this.render();
            if (after) after();
        };
        if (ex.note || ex.g) askConfirm(this.ui, L().star_del_q, run); else run();
        return true;
    }

    updateAddBtn() {
        const name = currentSite.getName(), l = L();
        const show = !!name && !isFavName(name);
        this.btnAdd.disabled = !show;
        if (show) this.btnAdd.textContent = `${l.bm_add} (${name})`;
        const foot = this.btnAdd.parentNode;
        if (foot) foot.style.display = show ? '' : 'none';
    }

    passesFilters(b) {
        const st = b.status ?? 0, lk = bmStatusKey(b);
        return !(this.hideOffline && st === 0) && !ST_FILTERS.some(([prop,, key]) => this[prop] && key === lk) && this.matchesText(b)
            && (this.wFilter == null || bmWatch(b, this._hist) === this.wFilter);
    }

    matchesText(b) {
        const tag = this.currentTagFilter, q = this.query, g = this.currentGroup;
        const note = b.note || '', grp = b.g || '';
        return bmTagMatch(b, tag)
            && (!g || grp === g)
            && (!q || `${b.name} ${(b.alts || []).join(' ')} ${note} ${grp} ${(b.ptags || []).join(' ')}`.toLowerCase().includes(q));
    }

    syncList(els) {
        const keep = new Set(els);
        Array.from(this.list.children).forEach(c => { if (!keep.has(c)) c.remove(); });
        els.forEach((el, i) => {
            const cur = this.list.children[i];
            if (cur !== el) this.list.insertBefore(el, cur || null);
        });
    }

    sortedFiltered() {
        const dir = bmSortRev() ? -1 : 1;
        const cmp = BM_CMP[bmSortType()] || BM_CMP.date;
        return AppState.bookmarks.map((b, i) => ({ b, i }))
            .sort((x, y) => (!!y.b.pin - !!x.b.pin) || dir * cmp(x, y))
            .filter(({ b }) => this.passesFilters(b));
    }

    render() {
        this.ui.thumbStars?.schedule();
        this._hist = histLoad();
        if (this._lang !== AppState.lang) { this._els = new WeakMap(); this._lang = AppState.lang; }
        const keepScroll = this.list.scrollTop;
        this.searchWrap.style.display = AppState.bookmarks.length ? 'flex' : 'none';
        AppState.bookmarks.forEach(b => { if (b?._updateItemDOM) b._updateItemDOM = null; });
        this.updateAddBtn();

        if (!AppState.bookmarks.length) {
            this.stopPreviews();
            this.list.innerHTML = `<div class="bm-empty">${L().bm_empty}</div>`;
            if (this.tagsWrap) { this.tagsWrap.replaceChildren(); this.tagsWrap.style.display = 'none'; }
            this.wsegHost?.replaceChildren();
            return;
        }

        this.renderTags();
        this.list.classList.add('bm-gallery-mode');
        this.applyCols?.();

        const sortedList = this.sortedFiltered();

        if (!sortedList.length) {
            this.list.innerHTML = `<div class="bm-empty">${L().bm_nomatch}</div>`;
            return;
        }

        this.syncList(sortedList.map(({ b }) => this.renderItem(b)));
        this.list.scrollTop = keepScroll;

        if (!this._previewTimer && this.prevEn && prevSpd()) {
            this._previewTimer = setInterval(() => this.tickPreviews(), prevSpd());
        }
    }

    stopPreviews() {
        clearInterval(this._previewTimer);
        this._previewTimer = null;
    }

    tickPreviews() {
        if (!this.isOpen() || !this.prevEn) return this.stopPreviews();
        if (realHidden()) return;
        this.list.querySelectorAll('.bm-item').forEach(item => {
            if (item._inView) refreshFrame(item.querySelector('img[data-frame]'));
        });
    }

    add() {
        syncBookmarks();
        const name = currentSite.getName();
        if (!name) return;
        if (isFavName(name)) return this.ui.flash(this.btnAdd, L().bm_dup);

        const bm = { name, url: normUrl(location.href), avatar: pageAvatar(name), note: '' };
        if (!validBm(bm)) return this.ui.flash(this.btnAdd, 'ERR');
        const vn = parseCount(readViewers());
        if (vn > 0) { bm.viewers = vn; bm.vts = Date.now(); }
        AppState.bookmarks.push(bm);
        saveBookmarks();
        this.render();
        this.checkStatuses();
        if (!bm.avatar) this.fetchAvatar(bm);
    }

    async fetchAvatar(bm) {
        const avatar = extractAvatar((await withBackoff(() => requestOnce(bm.url))) || '');
        if (!avatar) return;
        if (avatar === bm.avatar) return;
        bm.avatar = avatar;
        saveBookmarks();
        if (this.isOpen()) this.render();
    }
}

const pageHasError = (url, res) => {
    const raw = res.responseText || '', txt = raw.toLowerCase();
    const title = txt.match(/<title[^>]*>(.*?)<\/title>/i)?.[1] || '';
    return [
        () => res.status === 404 || res.status >= 500,
        () => ['not found', '404', 'error'].some(t => title.includes(t)),
        () => /no\s*(?:videos?|results?)\s*found|does\s*not\s*exist|\b0\s*results?\b/i.test(txt),
        () => url.includes('camshowrecordings') && !raw.includes('class="h1modelpage"'),
        () => url.includes('camtapes.com') && !/href="(?:https:\/\/camtapes\.com)?\/p\//.test(raw),
        () => url.includes('pornolab.net') && (/не найден[оа]/.test(txt) || !txt.includes('tlink'))
    ].some(check => check());
};

const engineUrl = (tmpl, v) => tmpl.replace('%q', encodeURIComponent(String(v).replace(/[_\-.]+/g, ' ').trim())).replace('%s', encodeURIComponent(v));

const SEARCH_ENGINES = [
        ['PornoLab', 'https://pornolab.net/forum/tracker.php?nm=%s'],
        ['BestCam.tv', 'https://bestcam.tv/model/%s'],
        ['CamShowRecordings', 'https://www.camshowrecordings.com/?s=%s'],
        ['LiveCamsRip', 'https://www.livecamsrip.com/%s/profile'],
        ['CamDownload', 'https://camdownload.net/performer/%s'],
        ['CamShow.cc', 'https://camshow.cc/streamer/%s'],
        ['CamTapes', 'https://camtapes.com/search?q=%q'],
        ['SimpCity', 'https://simpcity.cr/search/?q=%s&o=date', true],
        ['CamsClips', 'https://www.camsclips.com/search/%s/', true]
];

const SRCH_CSS = `.rs-pult .sr-box {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 48px;
  padding: 0 4px 0 12px;
  border-radius: 12px;
  border: 1px solid var(--pl);
  background: var(--pt)
}
.rs-pult .sr-box:focus-within {
  border-color: var(--th)
}
.rs-pult .sr-box>svg {
  flex: none;
  width: 20px;
  height: 20px;
  margin: 0!important;
  color: var(--th);
  filter: brightness(1.4)
}
.rs-pult .sr-in {
  flex: 1;
  min-width: 0;
  height: 100%;
  margin: 0!important;
  padding: 0!important;
  border: 0!important;
  outline: 0!important;
  box-shadow: none!important;
  background: transparent!important;
  color: #fff!important;
  font: 500 16px/1.2 Onest,system-ui,sans-serif!important
}
.rs-pult .sr-x {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color: #fff;
  opacity: .6;
  cursor: pointer
}
.rs-pult .sr-x:active {
  transform: scale(.92)
}
.rs-pult .sr-x svg {
  display: block;
  width: 18px;
  height: 18px;
  margin: 0!important
}
.rs-pult .sr-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 48px;
  margin: 0;
  padding: 0 12px;
  border: 1px solid var(--pl);
  border-radius: 12px;
  background: var(--pt);
  color: #fff;
  font: 500 14px/1.2 Onest,system-ui,sans-serif;
  text-align: start;
  cursor: pointer;
  transition: background .12s,transform .12s
}
.rs-pult .sr-row:hover {
  background: color-mix(in srgb,var(--pt) 85%,#fff)
}
.rs-pult .sr-row:active {
  transform: scale(.98)
}
.rs-pult .sr-row:focus-visible,
.rs-pult .sr-x:focus-visible {
  outline: 2px solid var(--th);
  outline-offset: 2px
}
.rs-pult .sr-dot {
  flex: none;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: rgba(255,255,255,.25)
}
.rs-pult .sr-nm {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap
}
.rs-pult .sr-ar {
  flex: none;
  display: flex;
  opacity: .5
}
.rs-pult .sr-ar svg {
  display: block;
  width: 18px;
  height: 18px;
  margin: 0!important
}
.rs-pult .sr-row[data-st=check] {
  opacity: .8
}
.rs-pult .sr-row[data-st=check] .sr-dot {
  background: #f39c12
}
.rs-pult .sr-row[data-st=ok] .sr-dot {
  background: #2ecc71
}
.rs-pult .sr-row[data-st=skip] .sr-dot {
  background: #888
}
.rs-pult .sr-row[data-st=err] .sr-dot {
  background: #e74c3c
}
.rs-pult .sr-row[data-st=err] .sr-nm {
  opacity: .55
}
.rs-pult .sr-row[data-st=err] .sr-ar {
  opacity: .3
}`;
const LP_CSS = `.rs-pult .sr-in::placeholder {
  color: rgba(255,255,255,.5)
}
.rs-pult .hd-sec.lp-hs {
  display: flex!important;
  justify-content: space-between
}
.rs-pult .lp-grid {
  display: grid;
  grid-template-columns: repeat(2,minmax(0,1fr));
  gap: 6px
}
.rs-pult .lp-btn {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  min-height: 46px;
  margin: 0;
  padding: 0 8px;
  border: 1px solid var(--pl);
  border-radius: 12px;
  background: var(--pt);
  color: #fff;
  font: 500 13px/1.2 Onest,system-ui,sans-serif;
  text-align: start;
  cursor: pointer;
  transition: background .12s,transform .12s
}
.rs-pult .lp-btn:hover {
  background: color-mix(in srgb,var(--pt) 85%,#fff)
}
.rs-pult .lp-btn:active {
  transform: scale(.97)
}
.rs-pult .lp-btn:focus-visible {
  outline: 2px solid var(--th);
  outline-offset: 2px
}
.rs-pult .lp-code {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 24px;
  border-radius: 6px;
  background: rgba(255,255,255,.1);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .4px;
  font-variant-numeric: tabular-nums
}
.rs-pult .lp-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap
}
.rs-pult .lp-auto {
  width: 100%;
  min-height: 48px;
  padding: 0 10px;
  gap: 10px;
  font-size: 14px
}
.rs-pult .lp-auto .lp-code {
  width: 44px;
  height: 26px
}
.rs-pult .lp-ck {
  flex: none;
  display: flex
}
.rs-pult .lp-ck svg {
  display: block;
  width: 18px;
  height: 18px;
  margin: 0!important
}
.rs-pult .lp-btn.sel {
  background: var(--th);
  border-color: transparent;
  color: var(--pon)
}
.rs-pult .lp-btn.sel .lp-code {
  background: rgba(0,0,0,.25)
}
.rs-pult .lp-empty {
  padding: 8px 4px;
  font-size: 13px;
  opacity: .7
}
.rs-pult .lp-btn[hidden],
.rs-pult .lp-empty[hidden],
.rs-pult .rs-g[hidden] {
  display: none!important
}
:host([data-rtl]) .rs-pult .lp-btn {
  text-align: right
}`;
const LP_CHECK = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg>';
const SRCH_ARROW = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 17L17 7M8 7h9v9"/></svg>';
const SRCH_CLEAR = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><path d="M6 6l12 12M18 6L6 18"/></svg>';
const sentenceCase = t => { t = String(t || ''); return t.charAt(0) + t.slice(1).toLowerCase(); };

const srBox = () => {
    const box = htmlEl('sr-box', icons.search);
    const input = mk('input', 'sr-in');
    input.type = 'text';
    const clear = mk('button', 'sr-x');
    clear.type = 'button';
    clear.innerHTML = SRCH_CLEAR;
    box.append(input, clear);
    return { box, input, clear };
};

class SearchManager {
    constructor(uiController) {
        this.ui = uiController;

        this.overlay = null;
        this.input = null;
        this.btns = [];
        this._live = new Set();
        this._sched = [];
        this._gen = 0;
        this.build();
    }

    build() {
        const { overlay, modal, title, closeBtn, body } = buildPultShell(this.ui.shadow, RS_PULT_CSS + SRCH_CSS);
        this.overlay = overlay;
        this.modal = modal;
        this.title = title;
        bindOutsideClose(this, overlay, () => overlay.classList.remove('open'));
        closeBtn.onclick = () => overlay.classList.remove('open');

        this.btnBack = htmlEl('bm-close bm-back', icons.back, 'button');
        this.btnBack.style.display = 'none';
        this.btnBack.onclick = () => { noFade(this.overlay, this.ui.bookmarkManager?.overlay); this.overlay.classList.remove('open'); if (this.ui.bookmarkManager) this.ui.bookmarkManager.open(); };
        modal.querySelector('.bm-hdr').insertBefore(this.btnBack, closeBtn);

        const { box, input, clear } = srBox();
        this.input = input;
        input.onkeydown = e => e.stopPropagation();
        clear.onclick = () => { input.value = ''; input.focus(); input.oninput?.(); };

        const list = mk('div', 'rs-g');
        this.buildSearchButtons(list);
        this.bindSearchInput();

        const grp = mk('div', 'rs-g');
        this.altHint = textEl('sr-alts', '');
        this.altHint.style.cssText = 'display:none;padding:2px 12px 0;font-size:13px;opacity:.65;overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
        grp.append(box, this.altHint);
        body.replaceChildren(grp, list);
    }

    buildSearchButtons(list) {
        list.append(textEl('hd-sec', L().sr_sites || ruT('Сайты', 'Sites')), ...SEARCH_ENGINES.map(([txt, tmpl, skipCheck]) => {
            const urlFn = v => engineUrl(tmpl, v);
            const btn = mk('button', 'sr-row');
            btn.type = 'button';
            btn.innerHTML = `<span class="sr-dot"></span><span class="sr-nm">${escHTML(txt)}</span><span class="sr-ar">${SRCH_ARROW}</span>`;
            btn.dataset.st = 'idle';
            const item = { btn, urlFn, skipCheck: !!skipCheck, hit: '' };
            btn.onclick = () => {
                const v = this.input.value.trim();
                if (v) window.open(urlFn(item.hit || v), '_blank', 'noopener');
                else this.ui.flash(btn, 'ERR');
            };
            this.btns.push(item);
            return btn;
        }));
    }

    bindSearchInput() {
        let debounceTimer;
        this.input.oninput = () => {
            clearTimeout(debounceTimer);
            debounceTimer = setTimeout(() => {
                this._resetRequests();
                const v = this.input.value.trim();
                this._showAlts(v);
                if (v) this._queue(v, 0, false); else this.btns.forEach(item => { item.btn.dataset.st = 'idle'; });
            }, 600);
        };
    }

    _names(v) {
        const alts = this._alts || [];
        if (alts.length && this._altBase && v.toLowerCase() === this._altBase.toLowerCase()) return [v, ...alts];
        return [v];
    }

    _showAlts(v) {
        const names = v ? this._names(v).slice(1) : [];
        if (!this.altHint) return;
        this.altHint.textContent = names.length ? '+ ' + names.join(', ') : '';
        this.altHint.style.display = names.length ? 'block' : 'none';
    }

    _queue(v, base, idle) {
        const names = this._names(v);
        this.btns.forEach((item, i) => {
            item.hit = '';
            if (item.skipCheck) { item.btn.dataset.st = 'skip'; return; }
            if (idle) item.btn.dataset.st = 'idle';
            this._sched.push(setTimeout(() => this.checkMulti(item, names), base + i * 150));
        });
    }

    checkMulti(item, names) {
        if (!item.btn.isConnected) return;
        item.btn.dataset.st = 'check';
        let left = names.length;
        names.forEach((n, j) => {
            this._sched.push(setTimeout(() => this.checkPage(item.urlFn(n), item.btn, st => {
                left--;
                if (st === 'ok' && !item.hit) { item.hit = n; item.btn.dataset.st = 'ok'; }
                if (!left && !item.hit) item.btn.dataset.st = 'err';
            }), j * 150));
        });
    }

    _resetRequests() {
        this._gen++;
        if (this._sched) { this._sched.forEach(clearTimeout); this._sched = []; }
        this._live.forEach(r => { try { r.abort(); } catch (e) { dbg(e, 'SearchManager._resetRequests'); } });
        this._live.clear();
    }

    checkPage(url, btn, done) {
        if (!btn.isConnected) return;
        if (!done) btn.dataset.st = 'check';
        const gen = this._gen;
        const stale = () => gen !== this._gen || !btn.isConnected;
        const markErr = () => { if (stale()) return; if (done) done('err'); else btn.dataset.st = 'err'; };
        const req = new AbortController();
        this._live.add(req);
        gmReq({ url, timeout: 8000, signal: req.signal }).then(res => {
            this._live.delete(req);
            if (res.fail === 'abort') return;
            if (res.fail) return markErr();
            if (stale()) return;
            if (pageHasError(url, { status: res.status, responseText: res.body })) return markErr();
            if (done) done('ok'); else btn.dataset.st = 'ok';
        });
    }

    open(customName, fromFavs = false, alts = null) {
        if (this._resetRequests) this._resetRequests();
        if (this.btnBack) this.btnBack.style.display = fromFavs ? 'flex' : 'none';
        pultTitle(this.title, icons.search, sentenceCase(L().search));
        const name = (isStr(customName)) ? customName : (currentSite.getName() || '');
        this.input.value = name;
        this._altBase = name;
        this._alts = Array.isArray(alts) ? cleanAlts(alts, name) : [];
        this._showAlts(name);
        this._opAt = Date.now();
        this.overlay.classList.add('open');
        if (name) this._queue(name, 200, true);
    }
}

class PickManager {
    constructor(uiController, onDone) {
        this.ui = uiController;
        this.onDone = onDone;
        this.active = false;
        this.cur = null;
        this._sx = 0;
        this._sy = 0;
        this.box = mk('div', 'pick-box');
        this.bar = mk('div', 'pick-bar');
        this.info = mk('div', 'pick-info');
        const btns = mk('div', 'pick-btns');
        this.btnUp = mk('button', 'bm-btn');
        this.btnOk = mk('button', 'bm-btn');
        this.btnNo = mk('button', 'bm-btn');
        btns.append(this.btnUp, this.btnOk, this.btnNo);
        this.bar.append(this.info, btns);
        this.ui.shadow.append(this.box, this.bar);

        this.btnUp.onclick = () => {
            const p = this.cur?.parentElement;
            if (p && !isRootEl(p)) this.setTarget(p);
        };
        this.btnOk.onclick = () => this.confirm();
        this.btnNo.onclick = () => this.stop();

        this._events = ['pointerdown', 'pointerup', 'mousedown', 'mouseup', 'click', 'touchend'];
        this._handler = e => {
            if (e.composedPath().includes(this.ui.host)) return;
            e.preventDefault();
            e.stopPropagation();
            e.stopImmediatePropagation();
            if (e.type === 'pointerdown') {
                this._sx = e.clientX;
                this._sy = e.clientY;
            } else if (e.type === 'pointerup' && Math.hypot(e.clientX - this._sx, e.clientY - this._sy) < 10) {
                this.pickAt(e.clientX, e.clientY);
            }
        };
        this._sync = () => this.placeBox();
    }

    start() {
        if (this.active) return;
        this.active = true;
        this.cur = null;
        this.box.style.display = 'none';
        this.btnUp.textContent = `↑ ${L().pk_up}`;
        this.btnOk.textContent = L().pk_ok;
        this.btnNo.textContent = L().pk_cancel;
        this.info.textContent = L().pk_hint;
        this.bar.classList.add('open');
        this._events.forEach(ev => window.addEventListener(ev, this._handler, { capture: true, passive: false }));
        window.addEventListener('scroll', this._sync, { capture: true, passive: true });
        window.addEventListener('resize', this._sync, { passive: true });
    }

    stop() {
        if (!this.active) return;
        this.active = false;
        this.cur = null;
        this._events.forEach(ev => window.removeEventListener(ev, this._handler, true));
        window.removeEventListener('scroll', this._sync, true);
        window.removeEventListener('resize', this._sync);
        this.box.style.display = 'none';
        this.bar.classList.remove('open');
        this.onDone?.();
    }

    pickAt(x, y) {
        const el = document.elementFromPoint(x, y);
        if (!el || isRootEl(el) || el === this.ui.host) return;
        this.setTarget(el);
    }

    placeBox() {
        if (!this.cur || !this.cur.isConnected) return;
        const r = this.cur.getBoundingClientRect();
        const s = this.box.style;
        s.display = 'block';
        s.left = `${r.left}px`;
        s.top = `${r.top}px`;
        s.width = `${r.width}px`;
        s.height = `${r.height}px`;
    }

    setTarget(el) {
        this.cur = el;
        this.placeBox();
        const sel = buildSelector(el);
        let n = 0;
        try { n = document.querySelectorAll(sel).length; } catch (e) { dbg(e, 'PickManager.setTarget'); }
        this.info.textContent = `${sel} (${n} ${L().pk_found})`;
    }

    confirm() {
        if (!this.cur) return;
        const sel = buildSelector(this.cur);
        try { document.querySelector(sel); } catch { return; }
        addCustom(sel);
        applyHideCSS();
        this.stop();
    }
}

const HIDE_SITES = [['bc', 'BongaCams'], ['sc', 'Stripchat'], ['cb', 'Chaturbate']];
const hideCfgGet = site => {
    if (site === SITE) return Store.get('bc_hide_cfg', {});
    try {
        const p = JSON.parse(GM_getValue(`${site}:bc_hide_cfg`));
        return isPlain(p) ? p : {};
    } catch { return {}; }
};
const hideCfgSet = (site, cfg) => {
    if (site === SITE) return Store.set('bc_hide_cfg', cfg);
    try { GM_setValue(`${site}:bc_hide_cfg`, JSON.stringify(cfg)); return true; } catch { return false; }
};
const firstMatch = c => {
    for (const x of c.sel) { try { const e = document.querySelector(x); if (e) return e; } catch (e) { dbg(e, 'firstMatch'); } }
    return null;
};

const HIDE_GRP_CHAT = /^(chat_|tips$|tipking$|chathdr$|sc_(topic|king|goal)$|cb_tips$)/;
const HIDE_GRP_WIN = /^(popups|joinpop|age18|modelchat|sc_cookie|sc_promo|cb_alerts)$/;
const HIDE_PULT_CSS = `.rs-pult .hp-sites {
  margin-bottom: 8px
}
.rs-pult .hp-top {
  display: flex;
  gap: 6px
}
.rs-pult .hp-top .rs-sum {
  flex: 1;
  min-width: 0
}
.rs-pult .hp-dt {
  flex: none;
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  margin: 0;
  padding: 0 12px;
  border-radius: 14px;
  border: 1px solid var(--pl);
  background: var(--pt);
  color: #fff;
  font: 500 13px/1 Onest,system-ui,sans-serif;
  cursor: pointer;
  transition: transform .12s
}
.rs-pult .hp-dt svg {
  display: block;
  flex: none
}
.rs-pult .hp-dt.on {
  background: var(--th);
  border-color: transparent;
  color: var(--pon)
}
.rs-pult .hp-dt:active,
.rs-pult .hp-btn:active,
.rs-pult .hp-pr .bm-tbtn:active {
  transform: scale(.97)
}
.rs-pult .hp-pr {
  display: grid;
  grid-template-columns: repeat(2,minmax(0,1fr))
}
.rs-pult .hp-hs {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 2px 4px 0;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .4px;
  text-transform: uppercase;
  color: #fff;
  opacity: .62
}
.rs-pult .hp-list {
  display: flex;
  flex-direction: column
}
.rs-pult .hp-row {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 48px;
  padding: 10px 4px;
  border-bottom: 1px solid var(--pl);
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  touch-action: manipulation
}
.rs-pult .hp-row:last-child {
  border-bottom: 0
}
.rs-pult .hp-tx {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px
}
.rs-pult .hp-tx b {
  font-size: 14px;
  font-weight: 500;
  line-height: 1.25;
  color: #fff
}
.rs-pult .hp-tx small {
  font-size: 12px;
  line-height: 1.3;
  color: #fff;
  opacity: .62
}
.rs-pult .hp-cmp .hp-tx small {
  display: none
}
.rs-pult .hp-nf {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 7px;
  border-radius: 999px;
  background: rgba(240,170,60,.16);
  color: #f0aa3c;
  font-size: 11px;
  font-weight: 400;
  vertical-align: 1px
}
.rs-pult .hp-sw {
  flex: none;
  position: relative;
  width: 46px;
  height: 26px;
  border-radius: 11px;
  background: rgba(255,255,255,.18);
  transition: background .22s ease
}
.rs-pult .hp-sw::after {
  content: "";
  position: absolute;
  top: 3px;
  left: 3px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0,0,0,.45);
  transition: left .22s cubic-bezier(.3,1.3,.5,1),transform .15s
}
.rs-pult .hp-row:active .hp-sw::after {
  transform: scaleX(1.12)
}
.rs-pult .hp-row.on .hp-sw {
  background: var(--th)
}
.rs-pult .hp-row.on .hp-sw::after {
  left: 23px
}
.rs-pult .hp-empty {
  padding: 8px 0;
  text-align: center;
  font-size: 13px;
  color: #fff;
  opacity: .55
}
.rs-pult .hp-rule {
  cursor: default
}
.rs-pult .hp-sel {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font: 12px/1.3 ui-monospace,monospace;
  color: #fff
}
.rs-pult .hp-del {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 10px;
  background: var(--pb);
  color: #fff;
  cursor: pointer
}
.rs-pult .hp-del svg {
  width: 18px;
  height: 18px
}
.rs-pult .hp-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  min-height: 46px;
  margin: 0;
  padding: 0 12px;
  border: 1px solid var(--pl);
  border-radius: 12px;
  background: var(--pt);
  color: #fff;
  font: 500 14px/1.2 Onest,system-ui,sans-serif;
  cursor: pointer;
  transition: transform .12s
}
.rs-pult .hp-btn svg {
  display: block;
  flex: none;
  width: 20px;
  height: 20px
}
.rs-pult .hp-btn.pm {
  background: var(--th);
  border-color: transparent;
  color: var(--pon)
}
.rs-pult .hp-dt:focus-visible,
.rs-pult .hp-btn:focus-visible,
.rs-pult .hp-del:focus-visible {
  outline: 2px solid var(--th);
  outline-offset: 2px
}`;

const resetHideCats = vs => {
    const cfg = hideCfgGet(vs);
    HIDE_CATS.forEach(c => { if (c.site === vs) delete cfg[c.id]; });
    hideCfgSet(vs, cfg);
};

class HideManager {
    constructor(uiController) {
        this.ui = uiController;
        this.build();
        this.picker = new PickManager(uiController, () => this.open());
    }

    build() {
        const { overlay, modal, title, closeBtn, body } = buildPultShell(this.ui.shadow, RS_PULT_CSS + HIDE_PULT_CSS);
        bindOutsideClose(this, overlay, () => this.close());
        closeBtn.onclick = () => this.close();
        this.overlay = overlay;
        this.title = title;
        this.list = body;
    }

    updateLabels() {
        this.render();
    }

    open() {
        this.viewSite = SITE;
        this.render();
        this._opAt = Date.now();
        this.overlay.classList.add('open');
    }

    close() {
        this.overlay.classList.remove('open');
    }

    render() {
        const l = L();
        pultTitle(this.title, icons.eyeOff, l.hd_title);
        this.list.replaceChildren();
        const frag = document.createDocumentFragment();
        this.renderCats(frag, l);
        this.renderRules(frag, l);
        this.list.append(frag);
    }

    renderCats(frag, l) {
        const tx = k => l[k] ?? '';
        const plat = detectPlatform();
        const vs = this.viewSite || SITE;
        const own = vs === SITE;
        const cats = HIDE_CATS.filter(c => c.site === vs && (!own || c.plat === 'b' || c.plat === plat));
        const rows = [];
        const grpOf = c => HIDE_GRP_CHAT.test(c.id) ? 'chat' : HIDE_GRP_WIN.test(c.id) ? 'win' : 'site';
        const sumTx = mk('span');
        const sum = htmlEl('rs-sum', icons.eyeOff);
        sum.appendChild(sumTx);
        const dt = mk('button', 'hp-dt');
        dt.type = 'button';
        dt.innerHTML = '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6h16M4 12h10M4 18h16"/></svg>';
        dt.appendChild(textEl('', tx('hd_details'), 'span'));
        const det = () => storeOn('bc_hide_det');
        const sw = mk('div', 'rs-chips hp-pr hp-sites');
        HIDE_SITES.forEach(([k, nm]) => {
            const b = mk('button', `bm-tbtn${k === vs ? ' on' : ''}`);
            b.type = 'button';
            b.textContent = nm;
            b.onclick = () => { this.viewSite = k; this.render(); };
            sw.append(b);
        });
        const paintDet = () => {
            dt.classList.toggle('on', det());
            dt.setAttribute('aria-pressed', String(det()));
            this.list.classList.toggle('hp-cmp', !det());
        };
        dt.onclick = () => { Store.set('bc_hide_det', !det()); paintDet(); };
        const top = mk('div', 'hp-top');
        top.append(sum, dt);

        const counters = [];
        const refresh = () => {
            const cur = hideCfgGet(vs);
            sumTx.textContent = tx('hd_hidden').replace('{n}', cats.filter(c => catOn(c, cur)).length).replace('{m}', cats.length);
            counters.forEach(({ list, el }) => { el.textContent = `${list.filter(c => catOn(c, cur)).length} / ${list.length}`; });
        };
        this._refresh = refresh;

        const setAll = fn => {
            const cur = hideCfgGet(vs);
            cats.forEach(c => { cur[c.id] = fn(c); });
            hideCfgSet(vs, cur);
            rows.forEach(({ c, row }) => row.classList.toggle('on', catOn(c, cur)));
            if (own) applyHideCSS();
            refresh();
        };
        const pr = mk('div', 'rs-chips hp-pr');
        [[l.hd_p_min, c => HIDE_PRESETS.min.includes(c.id)], [l.hd_p_clean, c => HIDE_PRESETS.clean.includes(c.id)], [l.hd_all, () => true], [l.hd_off, () => false]].forEach(([label, fn]) => {
            const b = textEl('bm-tbtn', label, 'button');
            b.onclick = () => setAll(fn);
            pr.append(b);
        });
        const g0 = mk('div', 'rs-g');
        g0.append(textEl('hp-hs', l.hd_cats), pr);
        if (own) g0.append(textEl('rs-note', l.hd_peek));
        frag.append(sw, top, g0);

        const cfg = hideCfgGet(vs);
        [['site', 'hd_g_site'], ['chat', 'hd_g_chat'], ['win', 'hd_g_win']].forEach(([gk, tk]) => {
            const list = cats.filter(c => grpOf(c) === gk);
            if (!list.length) return;
            const cnt = mk('span');
            counters.push({ list, el: cnt });
            const hs = mk('div', 'hp-hs');
            hs.append(textEl('', tx(tk), 'span'), cnt);
            const box = mk('div', 'hp-list');
            list.forEach(c => {
                const row = this.catRow(c, cfg, l, vs);
                rows.push({ c, row });
                box.append(row);
            });
            const g = mk('div', 'rs-g');
            g.append(hs, box);
            frag.append(g);
        });
        paintDet();
        refresh();
    }

    catRow(c, cfg, l, vs = SITE) {
        const own = vs === SITE;
        const found = !own || !!firstMatch(c);
        const pair = HIDE_L[AppState.lang]?.[c.id] || (AppState.lang === 'RU' ? [c.ru, c.dru] : [c.en, c.den]);
        const row = mk('div', `hp-row${catOn(c, cfg) ? ' on' : ''}${found ? '' : ' na'}`);
        const txt = mk('span', 'hp-tx');
        const name = mk('b');
        name.append(document.createTextNode(pair[0]));
        if (!found) name.append(textEl('hp-nf', l.hd_na, 'span'));
        txt.append(name);
        if (pair[1]) txt.append(textEl('', pair[1], 'small'));
        row.append(txt, mk('span', 'hp-sw'));
        this.bindCatRow(row, c, vs);
        return row;
    }

    bindCatRow(row, c, vs = SITE) {
        const own = vs === SITE;
        let timer = null, peeking = false, savedY = 0;
        const peekOn = () => {
            if (!own) return;
            const el = firstMatch(c);
            if (!el) return;
            peeking = true;
            savedY = window.scrollY;
            applyHideCSS(c.id);
            let st = document.getElementById('bc-unchained-peek');
            if (!st) { st = mk('style'); st.id = 'bc-unchained-peek'; (document.head || document.documentElement).appendChild(st); }
            st.textContent = c.sel.map(x => `${x}{outline:3px solid #ff2d95!important;outline-offset:-3px!important}`).join('\n');
            this.overlay.classList.add('peek');
            const r = el.getBoundingClientRect();
            if (r.bottom < 0 || r.top > window.innerHeight) el.scrollIntoView({ block: 'center' });
        };
        const peekOff = () => {
            clearTimeout(timer);
            if (!peeking) return;
            document.getElementById('bc-unchained-peek')?.remove();
            applyHideCSS();
            this.overlay.classList.remove('peek');
            if (Math.abs(window.scrollY - savedY) > 1) window.scrollTo(0, savedY);
            setTimeout(() => { peeking = false; }, 350);
        };
        row.addEventListener('pointerdown', () => { clearTimeout(timer); timer = setTimeout(peekOn, 450); });
        ['pointerup', 'pointercancel', 'pointerleave'].forEach(ev => row.addEventListener(ev, peekOff));
        row.addEventListener('contextmenu', e => e.preventDefault());
        row.onclick = () => {
            if (peeking) return;
            const cur = hideCfgGet(vs);
            cur[c.id] = !catOn(c, cur);
            hideCfgSet(vs, cur);
            row.classList.toggle('on', cur[c.id]);
            if (own) applyHideCSS();
            this._refresh?.();
        };
    }

    renderRules(frag, l) {
        const vs = this.viewSite || SITE;
        const own = vs === SITE;
        const g = mk('div', 'rs-g');
        if (!own) {
            const reset = textEl('hp-btn', l.hd_reset, 'button');
            reset.onclick = () => { resetHideCats(vs); this.render(); };
            g.append(reset);
            frag.append(g);
            return;
        }
        g.append(textEl('hp-hs', l.hd_rules));
        const rules = getCustom();
        if (!rules.length) g.append(textEl('hp-empty', l.hd_none));
        rules.forEach(sel => {
            const row = mk('div', 'hp-row hp-rule');
            const t = textEl('hp-sel', sel, 'span');
            t.title = sel;
            const del = htmlEl('hp-del', icons.del, 'button');
            del.onclick = () => { removeCustom(sel); applyHideCSS(); this.render(); };
            row.append(t, del);
            g.append(row);
        });
        const pick = htmlEl('hp-btn pm', `${icons.target}<span>${escHTML(l.hd_pick)}</span>`, 'button');
        pick.onclick = () => { this.close(); this.picker.start(); };
        g.append(pick);
        if (rules.length) {
            const clr = textEl('hp-btn', l.hd_clear, 'button');
            clr.onclick = () => { setCustom([]); applyHideCSS(); this.render(); };
            g.append(clr);
        }
        const reset = textEl('hp-btn', l.hd_reset, 'button');
        reset.onclick = () => {
            resetHideCats(SITE);
            setCustom([]);
            applyHideCSS();
            this.render();
        };
        g.append(reset);
        frag.append(g);
    }
}

let HS_AV_TRIED = null, HS_AV_Q = Promise.resolve();
const histOn = () => storeOn('bc_hist_on');
const histLoad = () => {
    const h = Store.get('bc_hist', {});
    return {
        m: isPlain(h?.m) ? h.m : {},
        s: h && Array.isArray(h.s) ? h.s : []
    };
};
const histPos = v => (Number.isFinite(v) && v > 0 ? v : 0);
const newHistModel = e => ({ n: clip(e.n, 100), u: e.u, a: toHttps(e.a), s: histPos(e.s), v: histPos(e.v), f: histPos(e.f), l: histPos(e.l) });
const updateHistModel = (c, e) => {
    const num = histPos;
    c.s = Math.max(c.s, num(e.s));
    c.v = Math.max(c.v, num(e.v));
    c.f = Math.min(c.f || Infinity, num(e.f) || Infinity);
    if (!Number.isFinite(c.f)) c.f = 0;
    if (num(e.l) > c.l) { c.l = num(e.l); c.n = clip(e.n, 100); c.u = e.u; if (e.a) c.a = toHttps(e.a); }
};
const mergeHistModel = (h, k, e) => {
    if (!e || !isStr(e.n) || !isStr(e.u)) return;
    const c = h.m[k];
    if (c) updateHistModel(c, e);
    else h.m[k] = newHistModel(e);
};
const mergeHistSessions = (h, list) => {
    const seen = new Set(h.s.map(x => `${x.k}|${x.t}`));
    (Array.isArray(list) ? list : []).forEach(x => {
        if (!x || !isStr(x.k) || !histPos(x.t) || seen.has(`${x.k}|${x.t}`)) return;
        h.s.push({ k: x.k, n: clip(x.n, 100), t: x.t, d: histPos(x.d) });
    });
};
const histMerge = src => {
    const h = histLoad();
    const m = isPlain(src.m) ? src.m : {};
    Object.keys(m).forEach(k => mergeHistModel(h, k, m[k]));
    mergeHistSessions(h, src.s);
    h.s.sort((a, b) => a.t - b.t);
    Store.set('bc_hist', h);
};
const fmtDur = sec => {
    const s = Math.max(0, Math.round(sec) || 0), h = Math.floor(s / 3600), m = Math.floor(s % 3600 / 60), p = pad2;
    return h ? `${h}:${p(m)}:${p(s % 60)}` : `${m}:${p(s % 60)}`;
};
const fmtStamp = ts => {
    if (!ts) return '';
    const d = new Date(ts), p = pad2;
    return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
};
const csvCell = v => {
    let s = String(v ?? '');
    if (s[0] === '-' && s.length <= 250 && !/[\r\n\t]/.test(s)) {
        return `"${`="${s.replace(/"/g, '""')}"`.replace(/"/g, '""')}"`;
    }
    if (/^[=+\-@\t\r]/.test(s)) s = `'${s}`;
    return `"${s.replace(/"/g, '""')}"`;
};
const csvText = rows => `\ufeff${rows.map(r => r.map(csvCell).join(',')).join('\r\n')}`;

const askConfirm = (ui, text, onYes, icon = icons.del) => {
    const ov = mk('div', 'cf-ov'), card = mk('div', 'cf-card'), btns = mk('div', 'cf-btns');
    const unbindEsc = bindEscape(() => close());
    const close = () => { ov.remove(); unbindEsc(); };
    const no = textEl('', L().cf_cancel, 'button'), yes = textEl('cf-yes', L().cf_ok, 'button');
    no.onclick = close;
    yes.onclick = () => { close(); onYes(); };
    btns.append(no, yes);
    const head = mk('div', 'cf-head'), ic = mk('div', 'cf-ic');
    ic.innerHTML = icon;
    const tx = textEl('cf-txt', text);
    tx.style.whiteSpace = 'pre-line';
    head.append(ic, tx);
    card.append(head, btns);
    card.onclick = e => e.stopPropagation();
    ov.onclick = close;
    ov.append(card);
    ui.shadow.appendChild(ov);
};

let pageToastEl = null, pageToastT = 0;
const pageToast = (text, ok = true) => {
    try {
        if (!pageToastEl || !pageToastEl.isConnected) {
            pageToastEl = document.createElement('div');
            pageToastEl.style.cssText = 'position:fixed;left:50%;bottom:28px;transform:translateX(-50%);z-index:2147483647;max-width:min(92vw,520px);padding:10px 18px;border-radius:12px;font:600 13px/1.35 system-ui,sans-serif;color:#fff;border:1px solid rgba(255,255,255,.18);box-shadow:0 8px 28px rgba(0,0,0,.4);pointer-events:none;white-space:pre-line;text-align:center;transition:opacity .25s';
            (document.body || document.documentElement).appendChild(pageToastEl);
        }
        pageToastEl.textContent = text;
        pageToastEl.style.background = ok ? '#1f9d5c' : '#c0392b';
        pageToastEl.style.opacity = '1';
        clearTimeout(pageToastT);
        pageToastT = setTimeout(() => { if (pageToastEl) pageToastEl.style.opacity = '0'; }, 3200);
    } catch (e) { dbg(e, 'pageToast'); }
};

const tagRows = (m, pim) => [...m.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([v, n]) => ({ v, t: v, n, ic: ptImg(pim.get(v), 'cal-tic') }));
const CAL_SITES = ['bc', 'cb', 'sc'];
const CAL_SITE_NAMES = { bc: 'BongaCams', cb: 'Chaturbate', sc: 'Stripchat' };
const PRES_KEY = 'bc_cal_pres', PRES_SITES = new Set(['cb', 'sc']), PRES_EVERY = 600000, PRES_MIN_N = 2, PRES_MIN_P = 0.5;
const keySite = k => String(k).split('|')[0];
const presOn = () => Store.get('bc_cal_pres_on', true) !== false;
const presFresh = () => ({ o: new Array(168).fill(0), t: new Array(168).fill(0), h: 0, on: 0 });
const presOk = e => isPlain(e) && Array.isArray(e.o) && Array.isArray(e.t) && e.o.length === 168 && e.t.length === 168;
const presLoad = () => { const p = Store.get(PRES_KEY, null); return { m: isPlain(p?.m) ? p.m : {} }; };
const presLocalHour = ts => Math.floor((ts - new Date(ts).getTimezoneOffset() * 60000) / 3600000);
const presCell = ts => { const d = new Date(ts); return ((d.getDay() + 6) % 7) * 24 + d.getHours(); };
const presMark = (p, k, ts, online, seed) => {
    let e = p.m[k];
    if (!presOk(e)) e = p.m[k] = presFresh();
    const c = presCell(ts);
    if (seed) { e.t[c]++; e.o[c]++; return; }
    const hi = presLocalHour(ts);
    if (e.h !== hi) { e.h = hi; e.on = online ? 1 : 0; e.t[c]++; if (online) e.o[c]++; return; }
    if (online && !e.on) { e.on = 1; e.o[c]++; }
};
const presSeed = () => {
    if (Store.get('bc_cal_pres_seed', false)) return;
    Store.set('bc_cal_pres_seed', true);
    try {
        const p = presLoad(), seen = new Set();
        histLoad().s.forEach(x => {
            if (!PRES_SITES.has(keySite(x.k)) || !(x.d > 0)) return;
            const h0 = presLocalHour(x.t), h1 = presLocalHour(x.t + Math.max(x.d, 60) * 1000);
            for (let hi = h0, n = 0; hi <= h1 && n < 48; hi++, n++) {
                const id = `${x.k}|${hi}`;
                if (seen.has(id)) continue;
                seen.add(id);
                presMark(p, x.k, x.t + (hi - h0) * 3600000, true, true);
            }
        });
        Store.set(PRES_KEY, p);
    } catch (e) { dbg(e, 'presSeed'); }
};
const presQueue = [];
let presTimer = 0;
const presFlush = () => {
    presTimer = 0;
    const q = presQueue.splice(0);
    if (!q.length) return;
    try {
        presSeed();
        const p = presLoad();
        q.forEach(([k, ts, on]) => presMark(p, k, ts, !!on));
        if (AppState.bookmarks?.length) {
            const alive = new Set(AppState.bookmarks.filter(hasUrl).map(b => bmKey(b.url)));
            Object.keys(p.m).forEach(k => { if (!alive.has(k)) delete p.m[k]; });
        }
        Store.set(PRES_KEY, p);
    } catch (e) { dbg(e, 'presFlush'); }
};
const presNote = (k, online) => {
    presQueue.push([k, Date.now(), online ? 1 : 0]);
    if (!presTimer) presTimer = setTimeout(presFlush, 4000);
};
const presDay = (e, wd) => Array.from({ length: 24 }, (_, h) => {
    const i = wd * 24 + h, n = e.t[i] | 0;
    return { n, c: n ? (e.o[i] | 0) / n : 0 };
});
const presGood = x => x.n >= PRES_MIN_N && x.c >= PRES_MIN_P;
const presRanges = hrs => {
    const out = [];
    for (let h = 0; h < 24; h++) {
        if (!presGood(hrs[h])) continue;
        let e = h;
        while (e + 1 < 24 && presGood(hrs[e + 1])) e++;
        out.push(`${pad2(h)}:00-${pad2((e + 1) % 24)}:00`);
        h = e;
    }
    return out.join(',');
};

class CalendarManager {
    constructor(ui, bm) {
        this.ui = ui;
        this.bm = bm;
        const now = new Date();
        this.y = now.getFullYear();
        this.m = now.getMonth();
        this.sel = this.dkey(now);
        Object.assign(this, CalendarManager.loadPrefs());
        this.pick = false;
        this.dd = null;
        this.wOpen = false;
        this.pane = mk('div', 'cal-pane');
        this.bm.modal.appendChild(this.pane);
        this.sk = '';
        this.sn = 0;
        setInterval(() => this.scan(), 8000);
        setTimeout(() => this.sample(), 20000);
        setInterval(() => this.sample(), 60000);
        onRemoteChange('bc_hist', () => { if (this.isOpen()) this.render(); });
    }

    static loadPrefs() {
        const lay = Store.get('bc_cal_lay', 3);
        const wf = Store.get('bc_cal_wf', -1);
        const ms = Store.get('bc_cal_models', []);
        return {
            lay: Number.isInteger(lay) && lay >= 0 && lay <= 3 ? lay : 3,
            site: CAL_SITES.includes(SITE) ? SITE : 'bc',
            wf: [0, 1].includes(wf) ? wf : null,
            grp: Store.get('bc_cal_grp', '') || '',
            tag: Store.get('bc_cal_tag', '') || '',
            models: new Set(Array.isArray(ms) ? ms.filter(isStr) : [])
        };
    }

    scan() {
        const key = pageKey();
        if (!key || realHidden()) return;
        if (this.sk !== key) { this.sk = key; this.sn = 0; }
        if (this.sn >= 6) return;
        this.sn++;
        if (this.bm.scanPage()) this.sn = 99;
    }

    isOpen() { return this.bm.isOpen() && this.bm.mode === 'cal'; }

    show() {
        this.render();
        this.bm.checkStatuses().then(() => { if (this.isOpen()) this.render(); }).catch(() => {});
        (async () => {
            for (let i = 0; i < 60 && this.isOpen(); i++) {
                const r = await this.bm.bgExtra(true);
                if (r === 'none') break;
                if (r === 'busy') { await wait(1500); continue; }
                if (r && this.isOpen()) this.render();
            }
        })();
    }

    dkey(d) { return fmtStamp(d).slice(0, 10); }

    fmt(opts, d) {
        let s;
        try { s = new Intl.DateTimeFormat((AppState.lang || 'en').toLowerCase(), opts).format(d); }
        catch { s = new Intl.DateTimeFormat('en', opts).format(d); }
        return s;
    }

    cap(s) { return s ? s.charAt(0).toUpperCase() + s.slice(1) : s; }

    dur(sec) {
        const l = L();
        if (sec < 60) return fmtDur(sec);
        const m = Math.round(sec / 60), h = Math.floor(m / 60);
        return h ? `${h} ${l.u_h} ${m % 60} ${l.u_m}` : `${m} ${l.u_m}`;
    }

    ib(cls, svg, text, tag = 'button') {
        const e = htmlEl(cls, text ? `${svg}<span>${escHTML(text)}</span>` : svg, tag);
        return e;
    }

    span(x) {
        const m = String(x).match(/^(\d{1,2}):(\d{2})-(\d{1,2}):(\d{2})/);
        if (!m) return [0, 0];
        const s = +m[1] * 60 + +m[2];
        let e = +m[3] * 60 + +m[4];
        if (e <= s) e = 1440;
        return [s, e];
    }

    hm(min) { const p = pad2; return `${p(Math.floor(min / 60))}:${p(min % 60)}`; }

    rangesText(r, l) {
        return r.split(',').map(x => {
            const [s, e] = this.span(x);
            return s === 0 && e >= 1440 ? l.cal_allday : `${this.hm(s)}\u2013${this.hm(e)}`;
        }).join(' \u00b7 ');
    }

    bar(r, nowMin) {
        const bar = mk('div', 'cal-bar');
        r.split(',').forEach(x => {
            const [s, e] = this.span(x);
            if (e <= s) return;
            const seg = mk('i', 'cal-seg');
            seg.style.left = `${s / 14.4}%`;
            seg.style.width = `${Math.max((e - s) / 14.4, 1)}%`;
            bar.append(seg);
        });
        if (nowMin != null) { const n = mk('i', 'cal-now'); n.style.left = `${nowMin / 14.4}%`; bar.append(n); }
        return bar;
    }

    startMin(r) { const m = String(r).match(/^(\d{1,2}):(\d{2})/); return m ? +m[1] * 60 + +m[2] : 0; }

    siteBm() { return AppState.bookmarks.filter(b => hasUrl(b) && urlSite(b.url) === this.site); }

    siteModels() { return new Set([...this.models].filter(k => keySite(k) === this.site)); }

    sites() {
        const out = ['bc', 'cb'];
        const has = AppState.bookmarks.some(b => hasUrl(b) && urlSite(b.url) === 'sc') || histLoad().s.some(x => keySite(x.k) === 'sc');
        if (SITE === 'sc' || has) out.push('sc');
        return out;
    }

    sample() {
        if (!presOn() || !AppState.bookmarks.some(b => hasUrl(b) && PRES_SITES.has(urlSite(b.url)))) return;
        const now = Date.now(), last = +Store.get('bc_cal_pres_ts', 0) || 0;
        if (now - last < PRES_EVERY - 20000) return;
        Store.set('bc_cal_pres_ts', now);
        this.bm.checkStatuses(PRES_SITES).catch(() => {});
    }

    favs(hist) {
        const ms = this.siteModels();
        return this.siteBm().filter(b => (this.wf == null || bmWatch(b, hist) === this.wf)
            && (!this.grp || b.g === this.grp)
            && bmTagMatch(b, this.tag)
            && (!ms.size || ms.has(bmKey(b.url))));
    }

    filtersOn() { return this.wf != null || !!this.grp || !!this.tag || this.siteModels().size > 0; }

    collect() {
        const hist = histLoad();
        const favs = this.favs(hist), fk = new Set(favs.map(b => bmKey(b.url)));
        const on = this.filtersOn();
        const sess = (this.lay & 1) ? this.groupSessions(hist, on, fk) : new Map();
        const sched = (this.lay & 2) ? this.buildSched(favs) : [[], [], [], [], [], [], []];
        const anySched = this.siteBm().some(b => Array.isArray(b.sch) && b.sch.some(x => this.clean(x)));
        return { hist, sess, sched, anySched };
    }

    groupSessions(hist, on, fk) {
        const sess = new Map();
        for (const s of hist.s) {
            if (keySite(s.k) !== this.site) continue;
            if (on && !fk.has(s.k)) continue;
            const dk = this.dkey(new Date(s.t));
            if (!sess.has(dk)) sess.set(dk, []);
            sess.get(dk).push(s);
        }
        return sess;
    }

    buildSched(favs) {
        if (PRES_SITES.has(this.site)) return this.buildPres(favs);
        const sched = [[], [], [], [], [], [], []];
        for (const b of favs) if (Array.isArray(b.sch)) b.sch.forEach((x, i) => {
            const r = this.clean(x);
            if (r && i < 7) sched[i].push({ b, r, s: this.startMin(r) });
        });
        sched.forEach(a => a.sort((x, y) => x.s - y.s));
        return sched;
    }

    clean(r) {
        return String(r || '').split(',').filter(x => {
            const m = x.match(/^(\d{1,2}):(\d{2})-(\d{1,2}):(\d{2})/);
            return m && !(+m[1] === +m[3] && m[2] === m[4]);
        }).join(',');
    }

    shift(d) { const x = new Date(this.y, this.m + d, 1); this.y = x.getFullYear(); this.m = x.getMonth(); this.render(); }

    save(k, v) { Store.set(k, v); this.render(); }

    render() {
        const l = L(), { hist, sess, sched, anySched } = this.collect();
        const pane = this.pane, keepTop = pane.scrollTop;
        pane.replaceChildren();
        const head = this.renderHead(l);
        const filters = this.renderFilters(l, hist);
        const now = new Date(), tk = this.dkey(now);
        const dock = mk('div', 'cal-dock');
        dock.append(head, this.dockLayers);
        pane.append(filters, this.renderGrid(sess, sched, now, tk), this.renderDetail(l, hist, sess, sched, anySched, now, tk), dock);
        pane.scrollTop = keepTop;
    }

    renderHead(l) {
        const head = mk('div', 'cal-head');
        const prev = this.ib('cal-nav', icons.chevL, ''), next = this.ib('cal-nav', icons.chevR, '');
        prev.onclick = () => this.shift(-1);
        next.onclick = () => this.shift(1);
        const today = textEl('cal-today', l.cal_today, 'button');
        today.onclick = () => { const n = new Date(); this.y = n.getFullYear(); this.m = n.getMonth(); this.sel = this.dkey(n); this.render(); };
        head.append(prev, textEl('cal-title', this.cap(this.fmt({ month: 'long', year: 'numeric' }, new Date(this.y, this.m, 1)))), next, today);
        return head;
    }

    filterRow1(l) {
        const r1 = mk('div', 'cal-frow');
        const layers = mk('div', 'bm-seg');
        [[1, icons.eye, l.cal_hist], [2, icons.clock, PRES_SITES.has(this.site) ? l.cal_pres_btn : l.cal_sched]].forEach(([bit, ic, t]) => {
            const b = this.ib(`bm-tbtn${this.lay & bit ? ' on' : ''}`, ic, t);
            b.onclick = () => { const v = this.lay ^ bit; this.lay = v; this.save('bc_cal_lay', v); };
            layers.append(b);
        });
        const wseg = mk('div', 'bm-seg');
        WMARKS.forEach(([v, ic, key]) => {
            const b = this.ib(`bm-tbtn${this.wf === v ? ' on' : ''}`, icons[ic], l[key]);
            b.title = l[key];
            b.onclick = () => { this.wf = this.wf === v ? null : v; this.save('bc_cal_wf', this.wf == null ? -1 : this.wf); };
            wseg.append(b);
        });
        this.dockLayers = layers;
        r1.append(wseg);
        return r1;
    }

    mkDD(kind, icon, label, on) {
        const b = this.ib(`cal-dd${on ? ' on' : ''}${this.dd === kind ? ' open' : ''}`, icon, label);
        b.onclick = () => { this.dd = this.dd === kind ? null : kind; this.pick = false; this.render(); };
        return b;
    }

    mkPanel(opts, cur, set) {
        const box = mk('div', 'cal-panel');
        opts.forEach(o => {
            if (o.sep) { box.append(mk('div', 'cal-sep')); return; }
            const c = this.ib(`cal-opt${o.v === cur ? ' on' : ''}${o.all ? ' all' : ''}`, o.ic || '', o.t);
            if (o.n) c.insertAdjacentHTML('beforeend', `<i>${o.n}</i>`);
            c.onclick = () => { this.dd = null; set(o.v); };
            box.append(c);
        });
        return box;
    }

    grpDropdown(l, r2) {
        const sb = this.siteBm();
        const groups = bmGroups().filter(g => sb.some(b => b.g === g));
        if (this.grp && !groups.includes(this.grp)) this.grp = '';
        if (!groups.length) return null;
        r2.append(this.mkDD('grp', icons.folder, this.grp || l.grp_all, !!this.grp));
        if (this.dd !== 'grp') return null;
        return this.mkPanel([{ v: '', t: l.grp_all, all: true }, ...groups.map(g => ({ v: g, t: g }))], this.grp, v => { this.grp = v; this.save('bc_cal_grp', v); });
    }

    countTags(hidePT) {
        const noteTags = new Map(), profTags = new Map();
        this.siteBm().forEach(b => {
            noteHashTags(b).forEach(x => countInto(noteTags, x));
            if (!hidePT) (b.ptags || []).forEach(x => countInto(profTags, x));
        });
        return { noteTags, profTags };
    }

    tagDropdown(l, r2) {
        const hidePT = storeOn('bc_bm_hide_ptags');
        const pim = ptIcoMap();
        const { noteTags, profTags } = this.countTags(hidePT);
        if (this.tag && !noteTags.has(this.tag) && !profTags.has(this.tag)) this.tag = '';
        if (!(noteTags.size + profTags.size)) return null;
        r2.append(this.mkDD('tag', ptImg(pim.get(this.tag), 'cal-tic') || icons.tag, this.tag || l.cal_all_tag, !!this.tag));
        if (this.dd !== 'tag') return null;
        return this.mkPanel(this.tagItems(l, noteTags, profTags, pim), this.tag, v => { this.tag = v; this.save('bc_cal_tag', v); });
    }

    tagItems(l, noteTags, profTags, pim) {
        const srt = m => tagRows(m, pim);
        const sep = noteTags.size && profTags.size ? [{ sep: true }] : [];
        return [{ v: '', t: l.cal_all_tag, all: true }, ...srt(noteTags), ...sep, ...srt(profTags)];
    }

    modelPicker(l, hist) {
        const box = mk('div', 'cal-models');
        if (this.siteModels().size) {
            const rs = this.ib('cal-mrow reset', icons.undo, l.cal_reset);
            rs.onclick = () => { [...this.models].forEach(k => { if (keySite(k) === this.site) this.models.delete(k); }); this.save('bc_cal_models', [...this.models]); };
            box.append(rs);
        }
        const nk = s => String(s).replace(/^[^\p{L}\p{N}]+/u, '');
        this.siteBm().slice()
            .sort((a, b) => nk(a.name).localeCompare(nk(b.name), undefined, { sensitivity: 'base', numeric: true }))
            .forEach(b => {
                const k = bmKey(b.url), on = this.models.has(k);
                const r = mk('button', `cal-mrow${on ? ' on' : ''}`);
                const img = mk('img', 'cal-ava sm');
                bindAvatar(img, b.name, () => [toHttps(b.avatar), toHttps(hist.m[k]?.a)]);
                r.append(img, textEl('', b.name, 'span'));
                if (on) r.insertAdjacentHTML('beforeend', icons.check);
                r.onclick = () => { if (on) this.models.delete(k); else this.models.add(k); this.save('bc_cal_models', [...this.models]); };
                box.append(r);
            });
        return box;
    }

    renderFilters(l, hist) {
        const flt = mk('div', 'cal-filters');
        const r2 = mk('div', 'cal-frow');
        const grpPanel = this.grpDropdown(l, r2);
        const tagPanel = this.tagDropdown(l, r2);
        const ddPanel = tagPanel || grpPanel;
        const r1 = this.filterRow1(l);
        flt.append(this.siteTabs(), r1);
        if (r2.children.length) flt.append(r2);
        if (ddPanel) flt.append(ddPanel);
        const mn = this.siteModels().size;
        const mb = textEl(`cal-pill${mn ? ' on' : ''}`, `${l.cal_models}${mn ? ` (${mn})` : ''}`, 'button');
        mb.onclick = () => { this.pick = !this.pick; this.dd = null; this.render(); };
        r1.append(mb);
        if (this.pick) flt.append(this.modelPicker(l, hist));
        return flt;
    }

    renderGrid(sess, sched, now, tk) {
        const grid = mk('div', 'cal-grid');
        for (let i = 0; i < 7; i++) grid.append(textEl('cal-wd', this.fmt({ weekday: 'short' }, new Date(2024, 0, 1 + i))));
        const first = new Date(this.y, this.m, 1), off = (first.getDay() + 6) % 7, dim = new Date(this.y, this.m + 1, 0).getDate();
        const t0 = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
        for (let i = 0; i < off; i++) grid.append(mk('div', 'cal-day blank'));
        for (let d = 1; d <= dim; d++) grid.append(this.dayCell(d, sess, sched, tk, t0));
        return grid;
    }

    dayCell(d, sess, sched, tk, t0) {
        const dt = new Date(this.y, this.m, d), key = this.dkey(dt), wd = (dt.getDay() + 6) % 7;
        const cell = mk('button', `cal-day${key === tk ? ' today' : ''}${key === this.sel ? ' sel' : ''}${dt.getTime() < t0 ? ' past' : ''}`);
        cell.type = 'button';
        cell.append(textEl('cal-n', String(d), 'span'));
        const bs = mk('div', 'cal-bs'), sn = new Set((sess.get(key) || []).map(s => s.k)).size, sc = sched[wd].length;
        if (sn) bs.append(textEl('cal-b', String(sn), 'i'));
        if (sc) bs.append(textEl('cal-b s', String(sc), 'i'));
        cell.append(bs);
        cell.onclick = () => { this.sel = key; this.render(); };
        return cell;
    }

    watchedBox(l, hist, sess, ctx) {
        const wbox = mk('div', 'cal-wsec');
        const agg = new Map();
        (sess.get(this.sel) || []).forEach(s => {
            const e = agg.get(s.k) || { k: s.k, n: s.n, first: s.t, d: 0, c: 0 };
            e.d += s.d || 0;
            e.c++;
            e.first = Math.min(e.first, s.t);
            agg.set(s.k, e);
        });
        const list = [...agg.values()].sort((a, b) => a.first - b.first);
        const total = list.reduce((s, x) => s + x.d, 0);
        const fh = this.ib(`cal-sec cal-fold${this.wOpen ? ' open' : ''}`, '', `${l.cal_watched}${total ? ` · ${this.dur(total)}` : ''}`);
        fh.onclick = () => { this.wOpen = !this.wOpen; this.render(); };
        wbox.append(fh);
        if (!this.wOpen) return wbox;
        if (!list.length) wbox.append(textEl('cal-muted', l.cal_none));
        else wbox.append(this.watchedTable(l, hist, list, total, ctx));
        return wbox;
    }

    watchedTable(l, hist, list, total, { favMap, href, ava }) {
        const tbl = mk('div', 'cal-tbl');
        const hd = mk('div', 'cal-trow cal-thead');
        [l.cal_c_model, l.cal_c_time, l.cal_c_dur, l.cal_c_n].forEach(x => hd.append(textEl('', x, 'span')));
        tbl.append(hd);
        list.forEach(e => {
            const b = favMap.get(e.k), row = mk('div', 'cal-trow'), name = b ? b.name : e.n;
            const who = mk('div', 'cal-who');
            const a = textEl('', name, 'a');
            a.href = href(b ? b.url : hist.m[e.k]?.u);
            who.append(ava(b, e.k, name), a);
            row.append(who, textEl('', fmtStamp(e.first).slice(11, 16), 'span'), textEl('', this.dur(e.d), 'span'), textEl('', `×${e.c}`, 'span'));
            tbl.append(row);
        });
        const ft = mk('div', 'cal-trow cal-tfoot');
        ft.append(textEl('', `${l.cal_total} · ${list.length}`, 'span'), textEl('', '', 'span'), textEl('', this.dur(total), 'span'), textEl('', `×${list.reduce((s, x) => s + x.c, 0)}`, 'span'));
        tbl.append(ft);
        return tbl;
    }

    schedRow(l, b, r, nowMin, { href, ava }) {
        const row = mk('div', 'cal-row'), line = mk('div', 'cal-line'), body = mk('div', 'cal-body');
        if (nowMin != null && (b.status ?? 0) > 0) line.append(mk('span', 'cal-dot'));
        const a = textEl('', b.name, 'a');
        a.href = href(b.url);
        line.append(a, textEl('cal-t', this.rangesText(r, l), 'span'));
        body.append(line, this.bar(r, nowMin));
        row.append(ava(b, bmKey(b.url), b.name), body);
        return row;
    }

    appendSched(det, l, list, anySched, nowMin, ctx) {
        det.append(textEl('cal-sec', l.cal_sched));
        if (!list.length) det.append(textEl('cal-muted', anySched ? l.cal_none : l.cal_no_sched));
        else {
            const ax = mk('div', 'cal-axis');
            ['00', '06', '12', '18', '24'].forEach(h => ax.append(textEl('', h, 'span')));
            det.append(ax);
        }
        list.forEach(({ b, r }) => det.append(this.schedRow(l, b, r, nowMin, ctx)));
        const bc = AppState.bookmarks.filter(b => hasUrl(b) && urlSite(b.url) === 'bc');
        const got = bc.filter(b => Array.isArray(b.sch)).length;
        if (bc.length && got < bc.length) det.append(textEl('cal-muted', l.cal_loaded.replace('{a}', got).replace('{b}', bc.length)));
    }

    siteTabs() {
        const row = mk('div', 'cal-frow'), seg = mk('div', 'bm-seg cal-sites');
        this.sites().forEach(st => {
            const n = AppState.bookmarks.filter(b => hasUrl(b) && urlSite(b.url) === st).length;
            const btn = this.ib(`bm-tbtn${this.site === st ? ' on' : ''}`, '', `${CAL_SITE_NAMES[st]}${n ? ` \u00b7 ${n}` : ''}`);
            btn.onclick = () => {
                if (this.site === st) return;
                this.site = st;
                this.dd = null;
                this.pick = false;
                this.render();
            };
            seg.append(btn);
        });
        row.append(seg);
        return row;
    }

    buildPres(favs) {
        const sched = [[], [], [], [], [], [], []];
        const p = presLoad();
        for (const b of favs) {
            const e = p.m[bmKey(b.url)];
            if (!presOk(e)) continue;
            for (let wd = 0; wd < 7; wd++) {
                const heat = presDay(e, wd), r = presRanges(heat);
                if (!r) continue;
                const on = heat.filter(presGood);
                sched[wd].push({
                    b, r, heat, s: this.startMin(r),
                    pct: Math.round(on.reduce((a, x) => a + x.c, 0) / on.length * 100)
                });
            }
        }
        sched.forEach(a => a.sort((x, y) => x.s - y.s));
        return sched;
    }

    presRow(l, it, nowMin, { href, ava }) {
        const { b, r, heat, pct } = it;
        const row = mk('div', 'cal-row'), line = mk('div', 'cal-line'), body = mk('div', 'cal-body');
        if (nowMin != null && (b.status ?? 0) > 0) line.append(mk('span', 'cal-dot'));
        const a = textEl('', b.name, 'a');
        a.href = href(b.url);
        line.append(a, textEl('cal-t', `${this.rangesText(r, l)} \u00b7 ${pct}%`, 'span'));
        const strip = mk('div', 'cal-heat'), grid = mk('div', 'cal-hg');
        heat.forEach((x, h) => {
            const c = mk('i', x.n ? '' : 'nd');
            if (x.n) {
                c.style.opacity = String(+(0.14 + 0.86 * x.c * Math.min(1, x.n / 3)).toFixed(2));
                c.title = `${pad2(h)}:00 \u00b7 ${Math.round(x.c * 100)}% \u00b7 n=${x.n}`;
            }
            grid.append(c);
        });
        strip.append(grid);
        if (nowMin != null) { const n = mk('i', 'cal-now'); n.style.left = `${nowMin / 14.4}%`; strip.append(n); }
        body.append(line, strip);
        row.append(ava(b, bmKey(b.url), b.name), body);
        return row;
    }

    appendPres(det, l, list, nowMin, ctx) {
        det.append(textEl('cal-sec', l.cal_pres_h));
        if (!list.length) det.append(textEl('cal-muted', l.cal_pres_none));
        else {
            const ax = mk('div', 'cal-axis');
            ['00', '06', '12', '18', '24'].forEach(h => ax.append(textEl('', h, 'span')));
            det.append(ax);
        }
        list.forEach(it => det.append(this.presRow(l, it, nowMin, ctx)));
        const sb = this.siteBm(), p = presLoad();
        const got = sb.filter(b => presOk(p.m[bmKey(b.url)])).length;
        if (sb.length) det.append(textEl('cal-muted', l.cal_pres_info.replace('{a}', got).replace('{b}', sb.length)));
        const on = presOn();
        const sw = mk('button', `bm-sw${on ? ' on' : ''}`);
        sw.onclick = () => { Store.set('bc_cal_pres_on', !on); this.render(); };
        const prow = mk('div', 'cal-pres-row');
        prow.append(textEl('', l.cal_pres_on, 'span'), sw);
        det.append(prow);
    }

    renderDetail(l, hist, sess, sched, anySched, now, tk) {
        const sp = this.sel.split('-').map(Number), sd = new Date(sp[0], sp[1] - 1, sp[2]), swd = (sd.getDay() + 6) % 7;
        const det = mk('div', 'cal-det');
        det.append(textEl('cal-dtitle', this.cap(this.fmt({ weekday: 'long', day: 'numeric', month: 'long' }, sd))));
        const favMap = new Map(AppState.bookmarks.filter(b => hasUrl(b)).map(b => [bmKey(b.url), b]));
        const href = u => (u && isSafeUrl(u) ? linkFor(u) : '#');
        const ava = (b, k, name) => {
            const img = mk('img', 'cal-ava');
            bindAvatar(img, name, () => [toHttps(b?.avatar), toHttps(hist.m[k]?.a)]);
            return img;
        };
        const ctx = { favMap, href, ava };
        const wbox = (this.lay & 1) ? this.watchedBox(l, hist, sess, ctx) : null;
        if (this.lay & 2) {
            const nowMin = this.sel === tk ? now.getHours() * 60 + now.getMinutes() : null;
            if (PRES_SITES.has(this.site)) this.appendPres(det, l, sched[swd], nowMin, ctx);
            else this.appendSched(det, l, sched[swd], anySched, nowMin, ctx);
        }
        if (wbox) det.append(wbox);
        return det;
    }
}

class HistoryManager {
    constructor(ui, bookmarks) {
        this.ui = ui;
        this.bm = bookmarks;
        this.cur = null;
        this.pend = 0;
        this.fresh = false;
        this.lastTick = Date.now();
        const sort = Store.get('bc_hist_sort', 'last');
        this.sort = ['last', 'time', 'visits'].includes(sort) ? sort : 'last';
        this.build();
        setInterval(() => this.tick(), 5000);
        onPageEvent('pagehide', () => this.endSession(), window);
        onPageEvent('visibilitychange', () => { if (realHidden()) this.flush(); });
        onRemoteChange('bc_hist', () => { if (this.isOpen()) this.render(); });
    }

    build() {
        this.list = mk('div', 'hs-pane');
        this.bm.modal.appendChild(this.list);
    }

    isOpen() {
        return this.bm.isOpen() && this.bm.mode === 'hist';
    }

    show() {
        this.flush();
        this.render();
    }

    open() {
        this.bm.open('hist');
    }

    hide() {
        this.bm.hide();
    }

    updateLabels() {
        if (this.isOpen()) this.render();
    }

    tick() {
        const now = Date.now(), dt = Math.min(65, (now - this.lastTick) / 1000);
        this.lastTick = now;
        if (!histOn()) return;
        const key = pageKey(), name = key ? currentSite.getName() : '';
        if (!key || !name) { this.endSession(); return; }
        this.ensureSession(key, name, now);
        if (isPlaying(selectorVideo())) this.pend += dt;
        if (this.pend >= 15) this.flush();
    }

    ensureSession(key, name, now) {
        if (!this.cur || this.cur.k !== key) {
            this.endSession();
            this.cur = { k: key, t: now, name, url: normUrl(location.href), avatar: '' };
            this.fresh = true;
        }
        this.cur.name = name;
        if (!this.cur.avatar) this.cur.avatar = toHttps(pageAvatar(name)) || bmByKey(key)?.avatar || '';
    }

    flush() {
        const c = this.cur, secs = Math.round(this.pend);
        if (!c || secs < 1) return;
        this.pend = 0;
        const h = histLoad();
        this.recordSession(h, c, secs, Date.now());
        Store.set('bc_hist', h);
        if (this.isOpen()) this.render();
    }

    recordSession(h, c, secs, now) {
        const e = h.m[c.k] || (h.m[c.k] = { n: c.name, u: c.url, a: '', s: 0, v: 0, f: c.t, l: c.t });
        e.n = c.name;
        e.u = c.url;
        if (c.avatar) e.a = c.avatar;
        e.s += secs;
        e.l = now;
        if (this.fresh) { e.v++; this.fresh = false; }
        let s = h.s.find(x => x.k === c.k && x.t === c.t);
        if (!s) { s = { k: c.k, n: c.name, t: c.t, d: 0 }; h.s.push(s); }
        s.d += secs;
    }

    endSession() {
        this.flush();
        this.cur = null;
        this.pend = 0;
    }

    remove(k) {
        const h = histLoad();
        delete h.m[k];
        h.s = h.s.filter(x => x.k !== k);
        Store.set('bc_hist', h);
        if (this.cur?.k === k) { this.cur = null; this.pend = 0; }
        this.render();
    }

    clear() {
        askConfirm(this.ui, L().hist_clear_q, () => {
            Store.set('bc_hist', { m: {}, s: [] });
            this.cur = null;
            this.pend = 0;
            this.render();
        });
    }

    exportCsv(sessions) {
        const h = histLoad();
        const rows = sessions
            ? [['name', 'url', 'start', 'seconds', 'duration'],
                ...h.s.slice().sort((a, b) => b.t - a.t).map(x => [x.n, h.m[x.k]?.u || '', fmtStamp(x.t), x.d, fmtDur(x.d)])]
            : [['name', 'url', 'seconds', 'duration', 'visits', 'first_seen', 'last_seen'],
                ...Object.keys(h.m).map(k => h.m[k]).sort((a, b) => b.l - a.l).map(e => [e.n, e.u, e.s, fmtDur(e.s), e.v, fmtStamp(e.f), fmtStamp(e.l)])];
        Helpers.download(new Blob([csvText(rows)], { type: 'text/csv;charset=utf-8' }), `BC_${sessions ? 'Sessions' : 'History'}_${Helpers.getTimestamp().slice(0, 10)}.csv`);
    }

    loadAvatar(e, img) {
        HS_AV_TRIED ||= new Set();
        if (!e.u || HS_AV_TRIED.has(e.k)) return;
        HS_AV_TRIED.add(e.k);
        HS_AV_Q = HS_AV_Q.then(async () => {
            const av = extractAvatar((await withBackoff(() => requestOnce(e.u))) || '');
            if (!av) return;
            const h = histLoad();
            if (h.m[e.k]) { h.m[e.k].a = av; Store.set('bc_hist', h); }
            if (img.isConnected) bindAvatar(img, e.n || '?', () => [av]);
        }).catch(() => {});
    }

    row(e, l) {
        const row = mk('div', 'hs-row');
        const img = mk('img', 'hs-ava');
        bindAvatar(img, e.n || '?', () => {
            const bmk = bmByKey(e.k);
            return [toHttps(e.a), toHttps(bmk?.avatar)];
        }, () => this.loadAvatar(e, img));
        const info = mk('div', 'hs-info');
        info.append(textEl('hs-name', e.n || e.k), textEl('hs-meta', `${fmtDur(e.s)} · ${e.v} ${l.hist_visits} · ${fmtAgo(e.l)}`));
        const fav = isFavKey(e.k);
        const favBtn = mk('button', `hs-btn${fav ? ' on' : ''}`);
        favBtn.innerHTML = icons.star;
        favBtn.title = fav ? l.star_del : l.star_add;
        favBtn.onclick = ev => {
            ev.stopPropagation();
            if (fav) return this.bm.removeKey(e.k, () => this.render());
            this.bm.addEntry({ name: e.n, url: e.u, avatar: e.a });
            this.render();
        };
        const delBtn = htmlEl('hs-btn del', icons.del, 'button');
        delBtn.title = l.hist_del;
        delBtn.onclick = ev => { ev.stopPropagation(); this.remove(e.k); };
        row.onclick = () => { if (isSafeUrl(e.u)) location.href = linkFor(e.u); };
        if (fav) row.classList.add('fav');
        row.append(img, info, favBtn, mk('span', 'hs-div'), delBtn);
        return row;
    }

    vbars(vals, labels, tips) {
        const max = Math.max(...vals, 1), bars = mk('div', 'ch-bars'), axis = mk('div', 'ch-axis');
        vals.forEach((v, i) => {
            const col = mk('div', 'ch-col'), bar = mk('div', `ch-bar${v > 0 ? '' : ' zero'}`);
            bar.style.height = `${v > 0 ? Math.max(3, Math.round(v / max * 100)) : 0}%`;
            col.title = tips[i];
            col.append(bar);
            bars.append(col);
            axis.append(textEl('', labels[i], 'span'));
        });
        return [bars, axis];
    }

    chartBuckets(h) {
        const DAY = 864e5, HOUR = 36e5, now = Date.now();
        const base = new Date(); base.setHours(0, 0, 0, 0);
        const start0 = base.getTime() - 13 * DAY;
        const days = new Array(14).fill(0), hours = new Array(24).fill(0);
        h.s.forEach(x => {
            let a = x.t, rem = x.d * 1000;
            while (rem > 0 && a <= now + DAY) {
                const dt = new Date(a), hs = new Date(a);
                hs.setMinutes(0, 0, 0);
                const part = Math.min(rem, hs.getTime() + HOUR - a);
                hours[dt.getHours()] += part;
                dt.setHours(0, 0, 0, 0);
                const di = Math.round((dt.getTime() - start0) / DAY);
                if (di >= 0 && di < 14) days[di] += part;
                a += part;
                rem -= part;
            }
        });
        return { days, hours, start0 };
    }

    chartTop(box, entries, l) {
        const top = entries.slice().sort((x, y) => y.s - x.s).slice(0, 5);
        if (!top.length) return null;
        const b3 = box(l.ch_top), tMax = Math.max(top[0].s, 1);
        top.forEach(e => {
            const row = mk('div', 'ch-row'), track = mk('div', 'ch-track'), fill = mk('div', 'ch-fill');
            fill.style.width = `${Math.max(2, Math.round(e.s / tMax * 100))}%`;
            track.append(fill);
            row.append(textEl('ch-name', e.n || e.k, 'span'), track, textEl('ch-val', fmtDur(e.s), 'span'));
            b3.append(row);
        });
        return b3;
    }

    charts(h, entries, l) {
        const DAY = 864e5;
        const { days, hours, start0 } = this.chartBuckets(h);
        const sec = ms => Math.round(ms / 1000);
        const box = (title, extra) => {
            const b = mk('div', 'ch-box'), t = mk('div', 'ch-ttl');
            t.append(textEl('', title, 'span'));
            if (extra) t.append(textEl('', extra, 'span'));
            b.append(t);
            return b;
        };
        const wrap = document.createDocumentFragment();

        const dayLbl = days.map((_, i) => new Date(start0 + i * DAY + DAY / 2).getDate());
        const dayTip = days.map((v, i) => `${this.dayName(start0 + i * DAY + DAY / 2)}: ${fmtDur(sec(v))}`);
        const avg = days.reduce((a, v) => a + v, 0) / 14;
        const b1 = box(l.ch_days, `${l.ch_avg}: ${fmtDur(sec(avg))}`);
        b1.append(...this.vbars(days, dayLbl, dayTip));
        wrap.append(b1);

        const hMax = Math.max(...hours);
        const peak = hMax > 0 ? hours.indexOf(hMax) : -1;
        const hrLbl = hours.map((_, i) => (i % 6 === 0 ? String(i) : ''));
        const hrTip = hours.map((v, i) => `${pad2(i)}:00: ${fmtDur(sec(v))}`);
        const b2 = box(l.ch_hours, peak >= 0 ? `${l.ch_peak}: ${pad2(peak)}:00` : '');
        b2.append(...this.vbars(hours, hrLbl, hrTip));
        wrap.append(b2);
        const b3 = this.chartTop(box, entries, l);
        if (b3) wrap.append(b3);
        return wrap;
    }

    dayName(ts) {
        try { return new Date(ts).toLocaleDateString(undefined, { weekday: 'short', day: 'numeric', month: 'short' }); } catch { return ''; }
    }

    render() {
        const l = L(), h = histLoad(), keep = this.list.scrollTop;
        const entries = Object.keys(h.m).map(k => ({ k, ...h.m[k] }));
        const total = entries.reduce((a, e) => a + e.s, 0);
        const cmps = {
            last: (x, y) => y.l - x.l,
            time: (x, y) => y.s - x.s,
            visits: (x, y) => (y.v - x.v) || (y.s - x.s)
        };
        entries.sort(cmps[this.sort]);
        const frag = document.createDocumentFragment();
        const sorts = mk('div', 'st-chips hs-seg');
        [['last', l.hist_s_last], ['time', l.hist_s_time], ['visits', l.hist_s_vis]].forEach(([id, t]) => {
            sorts.append(chipBtn(t, this.sort === id, () => { this.sort = id; Store.set('bc_hist_sort', id); this.render(); }));
        });
        const chOn = Store.get('bc_hist_ch', false) === true;
        sorts.append(chipBtn(l.ch_btn, chOn, () => { Store.set('bc_hist_ch', !chOn); this.render(); }));
        const clr = htmlEl('hs-trash', icons.del, 'button');
        clr.title = l.hist_clear;
        clr.setAttribute('aria-label', l.hist_clear);
        clr.onclick = () => this.clear();
        const top = mk('div', 'st-chips hs-top');
        top.append(textEl('hd-hint', l.hist_total.replace('{n}', entries.length).replace('{t}', fmtDur(total))), clr);
        frag.append(sorts, top);
        if (!entries.length) frag.append(textEl('bm-empty', l.hist_empty));
        else if (chOn) frag.append(this.charts(h, entries, l));
        entries.slice(0, 300).forEach(e => frag.append(this.row(e, l)));
        this.list.replaceChildren(frag);
        this.list.scrollTop = keep;
    }
}

const STAR_SVG = '<svg viewBox="0 0 24 24"><path d="' + STAR_D + '"/></svg>';
const THUMB_CFG = {
    bc: { links: ['a.thm_a', 'a.lst_link'], box: '.mls_item, .ls_thumb', views: '.bage_viewers, .lst_viewers', name: '' },
    sc: { links: ['a[data-model-list-link="true"]'], box: '[data-testid="model-list-item"]', views: '[class*="iewers"]', name: '[data-testid="model-list-item-username"]' },
    cb: { links: ['a.RoomCardThumbnail', 'a[data-testid="room-card-image-anchor"]'], box: '.RoomCard, li.room_list_room, li.roomCard', views: '.viewers', name: '[data-testid="room-card-username"], .username a' }
}[SITE] || { links: [], box: '', views: '', name: '' };
const THUMB_LINKS = THUMB_CFG.links;
const THUMB_SEL = THUMB_LINKS.join(',');
const THUMB_BOX = THUMB_CFG.box;
const viewersOf = a => {
    const t = THUMB_CFG.views ? ((THUMB_BOX && a.closest(THUMB_BOX)) || a).querySelector(THUMB_CFG.views)?.textContent || '' : '';
    const n = parseCount(t);
    return Number.isFinite(n) ? n : null;
};

class ThumbStars {
    constructor(bookmarks) {
        this.bm = bookmarks;
        this.on = storeOn('bc_star');
        this.timer = 0;
        mkStyle(document.head || document.documentElement, STAR_CSS, 'bcu-star-css');
        this.bindGlobal();
        const resync = () => {
            try { syncBookmarks(); } catch (e) { dbg(e, 'ThumbStars.resync'); }
            this._idx = null;
            this.scan();
        };
        window.addEventListener('pageshow', e => { if (e.persisted) resync(); });
        onPageEvent('visibilitychange', () => { if (!realHidden()) resync(); });
        DomBus.subscribe((recs, isOwn) => { if (!isOwn()) this.schedule(); });
        setInterval(() => { if (!realHidden()) this.scan(); if (!realHidden()) this.checkPage(); }, 6000);
        this.schedule();
    }

    checkPage() {
        const key = pageKey();
        if (!key || !isFavKey(key)) return;
        const v = document.querySelector('video');
        if (v && isPlaying(v)) this.bm.markLive(key);
    }

    bindGlobal() {
        const starOf = e => (e.target && e.target.closest ? e.target.closest('.bcu-star') : null);
        const opt = { capture: true, passive: false };
        let down = null;
        const fire = star => {
            const a = star.parentElement;
            if (a) this.toggle(a);
        };
        const block = swallow;
        ['mousedown', 'mouseup', 'dblclick', 'auxclick', 'contextmenu'].forEach(t =>
            window.addEventListener(t, e => { if (starOf(e)) block(e); }, opt));
        ['touchstart', 'touchend', 'touchmove'].forEach(t =>
            window.addEventListener(t, e => { if (starOf(e)) { e.stopImmediatePropagation(); e.stopPropagation(); } }, { capture: true, passive: true }));
        window.addEventListener('pointerdown', e => {
            const st = starOf(e); if (!st) return;
            down = { st, x: e.clientX, y: e.clientY };
            block(e);
        }, opt);
        window.addEventListener('pointerup', e => {
            const st = starOf(e); if (!st) return;
            block(e);
            const ok = down && down.st === st && Math.abs(e.clientX - down.x) < 12 && Math.abs(e.clientY - down.y) < 12;
            down = null;
            if (!ok || (e.button != null && e.button > 0)) return;
            st._t = Date.now();
            fire(st);
        }, opt);
        window.addEventListener('click', e => {
            const st = starOf(e); if (!st) return;
            block(e);
            if (Date.now() - (st._t || 0) > 800) fire(st);
        }, opt);
    }

    schedule() {
        if (this.timer || !this.on) return;
        this.timer = setTimeout(() => { this.timer = 0; this.scan(); }, 350);
    }

    setEnabled(v) {
        this.on = !!v;
        Store.set('bc_star', this.on);
        this._idx = null;
        if (this.on) return this.scan();
        document.querySelectorAll('.bcu-star,.bcu-ring').forEach(x => x.remove());
        THUMB_LINKS.forEach(sel => document.querySelectorAll(sel).forEach(a => { a._bcu = null; a.classList.remove('bcu-fav'); }));
    }

    scan() {
        if (!this.on) return;
        if (!THUMB_LINKS.length) return;
        const bms = AppState.bookmarks;
        if (!this._idx || this._idxRef !== bms || this._idxLen !== bms.length) {
            this._idx = new Map(bms.map(b => [bmKey(b.url), b]));
            this._idxRef = bms; this._idxLen = bms.length;
        }
        const idx = this._idx;
        const links = document.querySelectorAll(THUMB_SEL);
        const pos = Array.from(links, a => (a._bcu ? null : getComputedStyle(a).position));
        links.forEach((a, i) => this.sync(a, idx, pos[i]));
    }

    keyOf(a) {
        const h = a.href;
        if (a._bcuH === h) return a._bcuK;
        const s = modelSlug(h);
        a._bcuK = s ? `${SITE}|${s.toLowerCase()}` : '';
        a._bcuH = h;
        return a._bcuK;
    }

    sync(a, idx, pos) {
        const key = this.keyOf(a);
        if (!key) return;
        const st = this.ensureStar(a, key, pos);
        const b = idx.get(key);
        this.paintFav(a, st, !!b);
        if (b) this.syncViewers(a, key, b);
    }

    ensureStar(a, key, pos) {
        const st = a._bcu;
        if (st && st.key === key && st.star.isConnected) return st;
        if (st) { st.star.remove(); st.ring.remove(); }
        return (a._bcu = this.make(a, key, pos ?? getComputedStyle(a).position));
    }

    paintFav(a, st, fav) {
        if (st.fav !== fav) {
            st.fav = fav;
            st.star.classList.toggle('on', fav);
            a.classList.toggle('bcu-fav', fav);
        }
        const tip = fav ? L().star_del : L().star_add;
        if (st.star.title !== tip) st.star.title = tip;
    }

    syncViewers(a, key, b) {
        const n = viewersOf(a);
        if (n == null) return;
        this.bm.markLive(key, b);
        if (n !== b.viewers) this.bm.setViewers(key, n, b);
    }

    make(a, key, pos) {
        if (pos === 'static') a.classList.add('bcu-host');
        const ring = document.createElement('span');
        ring.className = 'bcu-ring';
        const star = document.createElement('span');
        star.className = 'bcu-star';
        star.setAttribute('role', 'button');
        star.innerHTML = STAR_SVG;
        ['pointerdown', 'pointerup', 'mousedown', 'mouseup', 'touchstart', 'touchend', 'dblclick', 'contextmenu'].forEach(ev => star.addEventListener(ev, e => e.stopPropagation()));
        star.addEventListener('click', e => { swallow(e); this.toggle(a); });
        a.append(ring, star);
        return { key, star, ring, fav: null };
    }

    thumbImg(a) {
        return a.querySelector('img:not([class*="Blur"])') || a.querySelector('img');
    }

    attrName(a) {
        return a.getAttribute('data-name') || a.dataset.room || a.closest('[data-name]')?.getAttribute('data-name');
    }

    boxName(a) {
        const box = THUMB_BOX ? a.closest(THUMB_BOX) : null;
        return THUMB_CFG.name && box?.querySelector(THUMB_CFG.name)?.textContent.trim();
    }

    thumbName(a, img) {
        return this.attrName(a) || this.boxName(a) || (IS_BC && img?.alt) || modelSlug(a.href);
    }

    toggle(a) {
        const key = this.keyOf(a);
        if (!key) return;
        if (isFavKey(key)) return this.bm.removeKey(key, () => this.scan());
        const img = this.thumbImg(a);
        this.bm.addEntry({ name: this.thumbName(a, img), url: a.href, avatar: '', viewers: viewersOf(a) });
        this.scan();
    }
}

const THEME_SWATCHES_CLASSIC = ['rgba(220, 20, 60, 0.9)', 'rgba(233, 30, 99, 0.9)', 'rgba(156, 39, 176, 0.9)', 'rgba(63, 81, 181, 0.9)', 'rgba(3, 155, 229, 0.9)', 'rgba(0, 150, 136, 0.9)', 'rgba(67, 160, 71, 0.9)', 'rgba(245, 124, 0, 0.9)', 'rgba(96, 125, 139, 0.9)'];
const TILE_ICONS = {
    bBg: 'bgOn', bMute: 'vol', bShot: 'snap', bRec: 'rec', vol: 'volMed', bQual: 'qual', bPip: 'pip', bFs: 'fs', bThtr: 'thtr',
    bSearch: 'search', bHls: 'hls', bCopy: 'copy', bFav: 'fav', stats: 'eye', bHome: 'home', bRel: 'rel',
    bColor: 'color', bHideMgr: 'eyeOff', scale: 'grid', bDark: 'moon', bBkEx: 'dl', bBkIm: 'ul', bDon: 'coffee', hints: 'menu'
};
const colorToHsl = c => {
    try {
        const s = document.createElement('span');
        s.style.color = c;
        document.documentElement.appendChild(s);
        const v = getComputedStyle(s).color;
        s.remove();
        const m = v.match(/(\d+)\D+(\d+)\D+(\d+)/);
        if (!m) return [348, 83, 47];
        const r = m[1] / 255, g = m[2] / 255, b = m[3] / 255;
        const mx = Math.max(r, g, b), mn = Math.min(r, g, b), d = mx - mn, l = (mx + mn) / 2;
        let h = 0, sat = 0;
        if (d) {
            sat = d / (1 - Math.abs(2 * l - 1));
            h = mx === r ? ((g - b) / d) % 6 : mx === g ? (b - r) / d + 2 : (r - g) / d + 4;
            h = (h * 60 + 360) % 360;
        }
        return [Math.round(h), Math.round(sat * 100), Math.round(l * 100)];
    } catch { return [348, 83, 47]; }
};

const noFade = (...els) => {
    const list = els.filter(Boolean);
    list.forEach(e => e.classList.add('bm-nofade'));
    requestAnimationFrame(() => requestAnimationFrame(() => list.forEach(e => e.classList.remove('bm-nofade'))));
};

const stBack = (ui, overlay) => {
    if (!overlay) return;
    const modal = overlay.querySelector('.bm-modal'), hdr = overlay.querySelector('.bm-hdr');
    if (!modal || !hdr) return;
    if (!overlay._bk) {
        const btn = htmlEl('bm-close bm-back', icons.back, 'button');
        btn.style.display = 'none';
        const sync = () => {
            btn.style.display = overlay._fromSt ? 'flex' : 'none';
            modal.classList.toggle('has-back', !!overlay._fromSt);
            btn.title = L().st_back || '';
        };
        btn.onclick = e => {
            e.stopPropagation();
            overlay._fromSt = false;
            sync();
            noFade(overlay, ui.settingsManager?.overlay);
            ui.settingsManager?.open();
            overlay.classList.remove('open');
        };
        overlay.addEventListener('click', e => {
            if (!overlay._fromSt) return;
            const t = e.target;
            const leave = t === overlay
                ? Date.now() - (overlay._stAt || 0) > 400
                : !!(t.closest && t.closest('.bm-close') && !t.closest('.bm-back'));
            if (leave) { overlay._fromSt = false; sync(); }
        }, true);
        hdr.appendChild(btn);
        overlay._bk = sync;
    }
    overlay._fromSt = true;
    overlay._stAt = Date.now();
    overlay._bk();
};

const ST_CSS = `.rs-pult.st-modal .bm-list>* {
  flex: none
}
.rs-pult.st-modal .hd-hint {
  padding: 0 4px;
  font-size: 12px;
  line-height: 1.4;
  color: #fff;
  opacity: .6
}
.rs-pult.st-modal .st-tabs {
  order: 0;
  flex: none;
  display: flex;
  gap: 6px;
  margin: 8px 8px 0;
  padding: 8px;
  border-radius: 14px;
  background: rgba(0,0,0,.34)
}
.rs-pult.st-modal .st-tab {
  flex: 1 1 0;
  min-width: 0;
  min-height: 44px;
  margin: 0;
  padding: 0 4px;
  border-radius: 12px;
  border: 1px solid var(--pl);
  background: var(--pt);
  color: #fff;
  font: 500 13px/1.2 Onest,system-ui,sans-serif;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: pointer;
  transition: background .12s,transform .12s
}
.rs-pult.st-modal .st-tab.on {
  background: var(--th);
  border-color: transparent;
  color: var(--pon);
  font-weight: 600
}
.rs-pult.st-modal .st-tab:active {
  transform: scale(.97)
}
.rs-pult.st-modal .st-tab:focus-visible,
.rs-pult.st-modal .st-pill:focus-visible,
.rs-pult.st-modal .st-tile:focus-visible,
.rs-pult.st-modal .st-swatch:focus-visible {
  outline: 2px solid var(--th);
  outline-offset: 2px
}
.rs-pult.st-modal .st-row {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 48px;
  padding: 0 10px 0 12px;
  border-radius: 12px;
  border: 1px solid var(--pl);
  background: var(--pt);
  color: #fff;
  font-size: 14px
}
.rs-pult.st-modal .st-row:has(.rs-chips) {
  flex-wrap: wrap;
  padding: 8px 10px 8px 12px
}
.rs-pult.st-modal .st-row:has(.rs-chips) .st-lbl {
  flex: 1 0 100%
}
.rs-pult.st-modal .st-row .rs-chips {
  flex: 1 1 100%
}
.rs-pult.st-modal .st-lbl {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 14px;
  line-height: 1.25
}
.rs-pult.st-modal .st-ic {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  color: var(--th);
  filter: brightness(1.4)
}
.rs-pult.st-modal .st-ic svg {
  display: block;
  width: 20px;
  height: 20px;
  margin: 0!important
}
.rs-pult.st-modal .st-pill {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 36px;
  margin: 0;
  padding: 0 12px;
  border-radius: 10px;
  border: 1px solid var(--pl);
  background: var(--pb);
  color: #fff;
  font: 500 13px/1.2 Onest,system-ui,sans-serif;
  cursor: pointer;
  transition: background .12s,transform .12s
}
.rs-pult.st-modal .st-pill:hover {
  background: color-mix(in srgb,var(--pb) 85%,#fff)
}
.rs-pult.st-modal .st-pill:active {
  transform: scale(.97)
}
.rs-pult.st-modal .st-pill svg {
  display: block;
  width: 18px;
  height: 18px;
  margin: 0!important
}
.rs-pult.st-modal .st-wide {
  display: flex;
  width: 100%;
  min-height: 44px;
  border-radius: 12px;
  font-size: 14px
}
.rs-pult.st-modal .rs-chips .st-pill {
  flex: 1 1 auto;
  min-height: 44px;
  border-radius: 12px;
  background: var(--pt)
}
.rs-pult.st-modal .st-row .bm-sw {
  position: relative;
  flex: none;
  width: 46px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 11px;
  background: rgba(255,255,255,.18);
  cursor: pointer
}
.rs-pult.st-modal .st-row .bm-sw::after {
  content: "";
  position: absolute;
  top: 3px;
  left: 3px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #fff
}
.rs-pult.st-modal .st-row .bm-sw.on {
  background: var(--th)
}
.rs-pult.st-modal .st-row .bm-sw.on::after {
  left: 23px
}
.rs-pult.st-modal .st-block {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 10px 12px;
  border-radius: 12px;
  border: 1px solid var(--pl);
  background: var(--pt);
  color: #fff;
  font-size: 14px
}
.rs-pult.st-modal .st-sw-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px
}
.rs-pult.st-modal .st-swatch {
  flex: none;
  width: 24px;
  height: 24px;
  margin: 0;
  padding: 0;
  border-radius: 50%;
  border: 2px solid transparent;
  cursor: pointer
}
.rs-pult.st-modal .st-swatch.sel {
  border-color: #fff
}
.rs-pult.st-modal .st-swatch.st-custom {
  background: conic-gradient(#f00,#ff0,#0f0,#0ff,#00f,#f0f,#f00)
}
.rs-pult.st-modal .st-hsl {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding-top: 4px
}
.rs-pult.st-modal .st-hrow {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 11px;
  opacity: .8
}
.rs-pult.st-modal .st-hsl input,
.rs-pult.st-modal .st-range {
  -webkit-appearance: none;
  appearance: none;
  margin: 0;
  outline: none;
  border-radius: 6px
}
.rs-pult.st-modal .st-hsl input {
  width: 100%;
  height: 14px
}
.rs-pult.st-modal .st-range {
  flex: 1.5 1 0;
  min-width: 60px;
  height: 6px;
  background: rgba(255,255,255,.18)
}
.rs-pult.st-modal .st-hsl input::-webkit-slider-thumb,
.rs-pult.st-modal .st-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: #fff;
  border: 2px solid rgba(0,0,0,.45);
  box-shadow: 0 1px 5px rgba(0,0,0,.55);
  cursor: pointer
}
.rs-pult.st-modal .st-hsl input::-moz-range-thumb,
.rs-pult.st-modal .st-range::-moz-range-thumb {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: #fff;
  border: 2px solid rgba(0,0,0,.45);
  cursor: pointer
}
.rs-pult.st-modal .st-prev {
  height: 8px;
  border-radius: 3px;
  background: var(--th)
}
.rs-pult.st-modal .st-val {
  flex: none;
  min-width: 40px;
  text-align: right;
  font-size: 13px;
  opacity: .7
}
.rs-pult.st-modal .st-tiles {
  display: grid;
  grid-template-columns: repeat(4,minmax(0,1fr));
  gap: 6px
}
.rs-pult.st-modal .st-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  min-height: 62px;
  margin: 0;
  padding: 6px 2px;
  border-radius: 12px;
  border: 1px solid var(--pl);
  background: var(--pt);
  color: #fff;
  font: 500 11px/1.15 Onest,system-ui,sans-serif;
  text-align: center;
  opacity: .5;
  cursor: pointer;
  transition: opacity .12s,transform .12s
}
.rs-pult.st-modal .st-tile.on {
  opacity: 1;
  border-color: var(--th)
}
.rs-pult.st-modal .st-tile:active {
  transform: scale(.97)
}
.rs-pult.st-modal .st-tile .st-ic {
  filter: none;
  color: #fff
}
.rs-pult.st-modal .st-tile .st-tl {
  max-width: 100%;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden
}`;

const applyTheme = (host, c) => { AppState.theme = c; Store.set('bc_theme', c); host.style.setProperty('--th', c); };
const randThemeColor = () => {
    const r = (a, b) => Math.floor(a + Math.random() * (b - a));
    return `hsla(${r(0, 360)},${r(35, 55)}%,${r(35, 50)}%,0.85)`;
};

const AudioFx = {
    vids: new Set(), ctx: null,
    cfg() {
        const c = Store.get('bc_afx', null), o = isPlain(c) ? c : {};
        return { on: o.on === true, boost: clamp(Number(o.boost) || 2, 1, 6), voice: clamp(Number.isFinite(+o.voice) ? +o.voice : 4, 0, 12), comp: o.comp !== false, hp: o.hp !== false, lim: o.lim !== false };
    },
    save(p) { Store.set('bc_afx', { ...this.cfg(), ...p }); this.sync(); },
    watch(v) {
        if (!v || this.vids.has(v)) return;
        this.vids.add(v);
        v.addEventListener('playing', () => this.apply(v), { passive: true });
        this.apply(v);
    },
    curve(c) {
        const fs = 48000, TP = 2 * Math.PI;
        const bq = (hp, f0, Q, dB) => {
            const w0 = TP * f0 / fs, al = Math.sin(w0) / (2 * Q), co = Math.cos(w0), A = 10 ** (dB / 40);
            return hp ? [(1 + co) / 2, -(1 + co), (1 + co) / 2, 1 + al, -2 * co, 1 - al] : [1 + al * A, -2 * co, 1 - al * A, 1 + al / A, -2 * co, 1 - al / A];
        };
        const fl = [c.hp && bq(true, 90, 0.7, 0), bq(false, 450, 1, -c.voice * 0.4), bq(false, 3200, 1, c.voice * 0.8)].filter(Boolean);
        const pts = [];
        for (let i = 0; i <= 60; i++) {
            const w = TP * 20 * 1000 ** (i / 60) / fs, cw = Math.cos(w), sw = Math.sin(w), c2 = Math.cos(2 * w), s2 = Math.sin(2 * w);
            let db = 0;
            for (const [b0, b1, b2, a0, a1, a2] of fl) {
                const nr = b0 + b1 * cw + b2 * c2, ni = b1 * sw + b2 * s2, dr = a0 + a1 * cw + a2 * c2, di = a1 * sw + a2 * s2;
                db += 10 * Math.log10((nr * nr + ni * ni) / (dr * dr + di * di));
            }
            pts.push(`${(i / 60 * 340).toFixed(1)},${(45 - clamp(db, -20, 20) * 2.2).toFixed(1)}`);
        }
        return pts.join(' ');
    },
    sync() { for (const v of this.vids) { if (v.isConnected) this.apply(v); else this.vids.delete(v); } },
    build(v) {
        const A = window.AudioContext || window.webkitAudioContext;
        if (!A || !(v.srcObject || /^blob:/.test(v.currentSrc || ''))) return null;
        try {
            const ctx = this.ctx && this.ctx.state !== 'closed' ? this.ctx : (this.ctx = new A());
            const bq = (type, hz, q) => { const n = ctx.createBiquadFilter(); n.type = type; n.frequency.value = hz; n.Q.value = q; return n; };
            const dc = (th, kn, ra, at, re) => { const n = ctx.createDynamicsCompressor(); n.threshold.value = th; n.knee.value = kn; n.ratio.value = ra; n.attack.value = at; n.release.value = re; return n; };
            return {
                ctx, src: ctx.createMediaElementSource(v), comp: dc(-20, 25, 4, 0.003, 0.25), hp: bq('highpass', 90, 0.7),
                low: bq('peaking', 450, 1), pres: bq('peaking', 3200, 1), gain: ctx.createGain(), lim: dc(-3, 0, 20, 0.001, 0.1), sig: ''
            };
        } catch (e) { dbg(e, 'AudioFx.build'); return null; }
    },
    apply(v) {
        const c = this.cfg();
        let g = v._afx;
        if (!g) {
            if (!c.on) return;
            g = v._afx = this.build(v);
            if (!g) return;
        }
        try {
            g.gain.gain.value = c.boost;
            g.low.gain.value = -c.voice * 0.4;
            g.pres.gain.value = c.voice * 0.8;
            const sig = `${c.on}${c.comp}${c.hp}${c.lim}`;
            if (g.sig !== sig) {
                for (const n of [g.src, g.comp, g.hp, g.low, g.pres, g.gain, g.lim]) n.disconnect();
                const ns = c.on ? [g.src, c.comp && g.comp, c.hp && g.hp, g.low, g.pres, g.gain, c.lim && g.lim, g.ctx.destination].filter(Boolean) : [g.src, g.ctx.destination];
                ns.reduce((a, b) => a.connect(b));
                g.sig = sig;
            }
            if (g.ctx.state === 'suspended') g.ctx.resume().catch(() => {});
        } catch (e) { dbg(e, 'AudioFx.apply'); }
    }
};

class SettingsManager {
    constructor(ui) {
        this.ui = ui;
        this.hslOpen = false;
        this.tab = 'look';
        this.build();
    }

    build() {
        const sh = buildPultShell(this.ui.shadow, RS_PULT_CSS + ST_CSS, ' st-modal');
        this.overlay = sh.overlay;
        this.title = sh.title;
        this.body = sh.body;
        this.tabsEl = mk('div', 'st-tabs');
        sh.modal.insertBefore(this.tabsEl, sh.body);
        bindOutsideClose(this, this.overlay, () => this.close());
        sh.closeBtn.onclick = () => this.close();
    }

    open() {
        this.render();
        this._opAt = Date.now();
        this.overlay.classList.add('open');
    }

    close() {
        this.overlay.classList.remove('open');
    }

    go(open, getOverlay) {
        const prev = this.overlay;
        noFade(prev);
        open();
        const next = getOverlay();
        noFade(next);
        prev.classList.remove('open');
        stBack(this.ui, next);
    }

    updateLabels() {
        if (this.overlay.classList.contains('open')) this.render();
        this.ui.hideManager?.overlay?._bk?.();
        this.ui.recorder?._rs?.overlay?._bk?.();
        this.ui.sleepTimer?.modal?.overlay?._bk?.();
        this.ui.bookmarkManager?.overlay?._bk?.();
    }

    setTheme(c) { applyTheme(this.ui.host, c); }

    randomTheme() { this.setTheme(randThemeColor()); }

    hslPicker(l, swRow) {
        const [h0, s0, l0] = colorToHsl(AppState.theme);
        const st = { h: h0, s: s0, l: clamp(l0, 15, 85) };
        const box = mk('div', 'st-hsl');
        const prev = mk('div', 'st-prev');
        const tracks = [];
        const slider = (label, key, max, min = 0) => {
            const wrap = mk('div', 'st-hrow');
            const inp = mk('input');
            inp.type = 'range';
            inp.min = String(min);
            inp.max = String(max);
            inp.step = '1';
            inp.value = String(st[key]);
            inp.oninput = () => {
                st[key] = +inp.value;
                this.setTheme(`hsla(${st.h},${st.s}%,${st.l}%,0.9)`);
                swRow.querySelectorAll('.st-swatch.sel').forEach(x => x.classList.remove('sel'));
                swRow.querySelector('.st-custom')?.classList.add('sel');
                paint();
            };
            wrap.append(textEl('', label, 'span'), inp);
            tracks.push([key, inp]);
            return wrap;
        };
        const paint = () => {
            tracks.forEach(([key, inp]) => {
                inp.style.background = key === 'h'
                    ? 'linear-gradient(90deg,#f00,#ff0,#0f0,#0ff,#00f,#f0f,#f00)'
                    : key === 's'
                        ? `linear-gradient(90deg,hsl(${st.h},0%,${st.l}%),hsl(${st.h},100%,${st.l}%))`
                        : `linear-gradient(90deg,hsl(${st.h},${st.s}%,15%),hsl(${st.h},${st.s}%,50%),hsl(${st.h},${st.s}%,85%))`;
            });
        };
        box.append(slider(l.st_hue, 'h', 360), slider(l.st_sat, 's', 100), slider(l.st_lig, 'l', 85, 15), prev);
        paint();
        return box;
    }

    renderKit(root) {
        let cur = root;
        const frag = { append: (...n) => cur.append(...n) };
        const ic = k => `<span class="st-ic">${icons[k] || ''}</span>`;
        const sec = (k, t) => {
            const grp = mk('div', 'rs-g');
            grp.append(textEl('hd-sec', t));
            root.append(grp);
            cur = grp;
        };
        const lbl = (k, t) => {
            const e = htmlEl('st-lbl', `${ic(k)}<span>${escHTML(t)}</span>`, 'span');
            return e;
        };
        const row = (k, label, ...ctl) => {
            const r = mk('div', 'st-row');
            r.append(lbl(k, label), ...ctl);
            frag.append(r);
        };
        const pill = (t, fn, k, extra = '') => {
            const b = mk('button', `st-pill${extra}`);
            b.innerHTML = `${k ? icons[k] : ''}<span>${escHTML(t)}</span>`;
            b.onclick = fn;
            return b;
        };
        const sw = (on, fn) => {
            const b = mk('button', `bm-sw${on ? ' on' : ''}`);
            b.onclick = () => { on = !on; b.classList.toggle('on', on); fn(on); };
            return b;
        };
        return { frag, ic, sec, lbl, row, pill, sw };
    }

    stThemeBlock(k, l) {
        const { frag, lbl, pill } = k;
        const colBox = mk('div', 'st-block');
        const swRow = mk('div', 'st-sw-row');
        const swatches = THEME_SWATCHES_CLASSIC;
        const known = swatches.includes(AppState.theme);
        swatches.forEach(c => {
            const s = mk('button', `st-swatch${AppState.theme === c ? ' sel' : ''}`);
            s.style.background = c;
            s.onclick = () => { this.setTheme(c); this.hslOpen = false; this.render(); };
            swRow.append(s);
        });
        const custom = mk('button', `st-swatch st-custom${!known ? ' sel' : ''}`);
        custom.title = l.st_custom;
        custom.onclick = () => { this.hslOpen = !this.hslOpen; this.render(); };
        swRow.append(custom, pill(l.st_color_rnd, () => { this.randomTheme(); this.hslOpen = false; this.render(); }, 'dice'));
        colBox.append(lbl('color', l.st_color), swRow);
        if (this.hslOpen) colBox.append(this.hslPicker(l, swRow));
        frag.append(colBox);
    }

    stLook(k, l, ui) {
        const { sec, row, pill, sw } = k;
        sec('color', l.st_look);
        row('globe', l.st_lang, pill(`${AppState.lang} · ${LANG_NAMES[AppState.lang] || ''}`, () => ui.openLangPicker()));
        this.stThemeBlock(k, l);
        const rng = mk('input', 'st-range');
        rng.type = 'range';
        rng.min = '60';
        rng.max = '150';
        rng.step = '10';
        rng.value = String(Math.round(AppState.scale * 100));
        const val = textEl('st-val', `${rng.value}%`, 'span');
        rng.oninput = () => { val.textContent = `${rng.value}%`; ui.setScale(rng.value / 100); };
        row('grid', l.st_scale, rng, val);
        row('moon', l.st_site_dark, sw(AppState.dark, v => ui.toggleDark(v)));
        row('eyeOff', l.hd_title, pill(l.st_open, () => this.go(() => ui.hideManager?.open(), () => ui.hideManager?.overlay)));
    }

    stButtons(k, l, ui) {
        const { frag, ic, sec, pill } = k;
        sec('grid', l.st_btns);
        frag.append(textEl('hd-hint', l.st_btns_hint));
        const tiles = mk('div', 'st-tiles');
        const hid = ui.hiddenButtons();
        ui.panelItems().forEach(([id, el, key]) => {
            if (!el) return;
            const name = l[key] || key;
            const t = mk('button', `st-tile${hid[id] ? '' : ' on'}`);
            t.title = name;
            t.innerHTML = `${ic(TILE_ICONS[id])}<span class="st-tl">${escHTML(name)}</span>`;
            t.onclick = () => {
                ui.setButtonHidden(id, t.classList.contains('on'));
                t.classList.toggle('on');
            };
            tiles.append(t);
        });
        frag.append(tiles, pill(l.st_btns_reset, () => { ui.resetButtons(); this.render(); }, 'rel', ' st-wide'));
    }

    stTools(k, l, ui) {
        const { sec, row, pill, sw } = k;
        sec('rec', l.st_rec);
        row('rec', l.rs_title, pill(l.st_open, () => this.go(() => ui.recorder?.openSettings(), () => ui.recorder?._rs?.overlay)));

        sec('grid', mvT('mv_title'));
        row('grid', mvT('mv_title'), pill(l.st_open, () => this.go(() => ui.multiView?.open(), () => ui.bookmarkManager?.overlay)));
        row('grid', mvT('mv_auto_all'), sw(storeOn('bc_mv_auto'), v => ui.multiView?.setAuto(v)));
        row('grid', mvT('mv_bg'), sw(mvBg(), v => { Store.set('bc_mv_bg', v); if (ui.multiView?.pane) ui.multiView.labels(); }));

        sec('sleep', l.sleep_title);
        const slp = ui.sleepTimer;
        row('sleep', slp?.end ? `${l.sleep_left}: ${slp.fmt()}` : l.rs_off, pill(l.st_open, () => this.go(() => ui.sleepTimer?.open(), () => ui.sleepTimer?.modal?.overlay)));

        sec('target', l.rad_title);
        row('target', l.rad_title, sw(ui.radial?.on === true, v => ui.radial?.setEnabled(v)));
        row('rel', l.rad_reset, pill(l.cal_reset, () => ui.radial?.resetPos()));
    }

    stAudio(k) {
        const { frag, sec, row, pill, sw } = k;
        const c = AudioFx.cfg();
        sec('vol', nxT('snd'));
        row('vol', nxT('snd_bv'), sw(c.on, v => { AudioFx.save({ on: v }); this.render(); }));
        if (!c.on) return;
        const box = mk('div', 'st-block');
        const draw = () => { box.querySelector('polyline').setAttribute('points', AudioFx.curve(AudioFx.cfg())); };
        const range = (min, max, step, val, fmt, key) => {
            const r = mk('input', 'st-range');
            r.type = 'range';
            r.min = String(min);
            r.max = String(max);
            r.step = String(step);
            r.value = String(val);
            const t = textEl('st-val', fmt(val), 'span');
            r.oninput = () => { t.textContent = fmt(+r.value); AudioFx.save({ [key]: +r.value }); draw(); };
            return [r, t];
        };
        row('vol', nxT('snd_boost'), ...range(1, 6, 0.5, c.boost, x => `×${x}`, 'boost'));
        row('volMed', nxT('snd_voice'), ...range(0, 12, 1, c.voice, x => String(x), 'voice'));
        box.innerHTML = `<svg viewBox="0 0 340 90" width="100%" height="90" role="img" aria-label="EQ"><line x1="0" y1="45" x2="340" y2="45" stroke="currentColor" stroke-opacity=".3" stroke-dasharray="3 4"/><polyline fill="none" stroke="${AppState.theme}" stroke-width="2.5" points="${AudioFx.curve(c)}"/></svg><div style="display:flex;justify-content:space-between;font-size:11px;opacity:.7"><span>20 ${nxT('hz')}</span><span>90</span><span>450</span><span>3.2k</span><span>20k</span></div>`;
        frag.append(box);
        const tg = (key, label, ico) => row(ico, label, sw(c[key], v => { AudioFx.save({ [key]: v }); this.render(); }));
        tg('comp', nxT('snd_comp'), 'volMed');
        tg('hp', nxT('snd_hp'), 'volLow');
        tg('lim', nxT('snd_lim'), 'mute');
        row('rel', nxT('snd_reset'), pill(L().cal_reset, () => { AudioFx.save({ boost: 2, voice: 4, comp: true, hp: true, lim: true }); this.render(); }));
        frag.append(textEl('hd-hint', nxT('snd_hint')));
    }

    stPreview(k) {
        const api = SITE === 'cb' ? CBHP.api : cbhpRemoteApi();
        if (!api) return;
        const { sec, row, sw } = k;
        const t = ruT;
        const labels = { off: t('Выкл', 'Off'), anim: t('Обложка', 'Cover'), video: t('Видео', 'Video') };
        const chips = (items, cur, pick) => {
            const box = mk('div', 'rs-chips');
            items.forEach(([val, text]) => {
                box.append(chipBtn(text, val === cur, () => { pick(val); this.render(); }));
            });
            return box;
        };
        sec('eye', t('Превью при наведении', 'Hover preview'));
        row('eye', t('Режим', 'Mode'), chips(api.modes.map(m => [m, labels[m]]), api.cfg.mode, api.setMode));
        row('vol', t('Звук', 'Sound'), sw(api.cfg.sound, v => api.setSound(v)));
        row('qual', t('Качество видео', 'Video quality'), chips(api.heights.map(h => [h, `${h}p`]), api.cfg.maxH, api.setMaxH));
    }

    stGeo(k) {
        if (SITE !== 'cb') return;
        const { frag, sec, row, pill } = k;
        const t = ruT, cur = cbGeoList();
        const codes = [...new Set([...CB_GEO_POP, ...cur, ...(this.geoAll ? cbGeoSeen() : [])])];
        if (this.geoAll) codes.sort();
        const save = list => { Store.set(CB_GEO_KEY, list); applyHideCSS(); this.render(); };
        sec('globe', t('Фильтр ленты', 'Feed filter'));
        const box = mk('div', 'rs-chips');
        codes.forEach(c => box.append(chipBtn(c.toUpperCase(), cur.includes(c), () => save(cur.includes(c) ? cur.filter(x => x !== c) : [...cur, c]))));
        row('globe', t('Скрывать страны', 'Hide countries'), box,
            pill(this.geoAll ? t('Свернуть', 'Collapse') : t('Все страны', 'All countries'), () => { this.geoAll = !this.geoAll; this.render(); }));
        frag.append(textEl('hd-hint', t('Модели из ваших подписок остаются видны. Применяется сразу.', 'Models you follow stay visible. Applies instantly.')));
    }

    stFavHist(k, l, ui) {
        const { sec, row, pill, sw } = k;
        sec('fav', l.st_fav);
        row('star', l.st_star, sw(ui.thumbStars?.on !== false, v => ui.thumbStars?.setEnabled(v)));
        row('bookmark', l.st_list, pill(l.st_open, () => this.go(() => ui.bookmarkManager?.open(), () => ui.bookmarkManager?.overlay)));

        sec('history', l.st_hist);
        row('eye', l.st_hist_on, sw(histOn(), v => Store.set('bc_hist_on', v)));
        row('list', l.st_list, pill(l.st_open, () => this.go(() => ui.historyManager?.open(), () => ui.bookmarkManager?.overlay)));
        row('cal', l.cal_title, pill(l.st_open, () => this.go(() => ui.bookmarkManager?.open('cal'), () => ui.bookmarkManager?.overlay)));
    }

    stData(k, l, ui) {
        const { frag, sec, lbl, row, pill } = k;
        sec('dl', l.st_data);
        const bkBox = mk('div', 'st-block');
        const bkChips = mk('div', 'rs-chips');
        const bkParts = Backup.parts();
        [['s', l.bk_p_set], ['f', l.bk_p_fav], ['h', l.bk_p_hist]].forEach(([key, t]) => {
            bkChips.append(chipBtn(t, bkParts[key], () => { if (Backup.setPart(key, !bkParts[key])) this.render(); }));
        });
        bkBox.append(lbl('dl', l.bk_parts), bkChips);
        frag.append(bkBox);
        row('dl', l.bk_exp, pill('JSON', () => Backup.export()));
        row('ul', l.bk_imp, pill('JSON', () => ui.backupImport()));
        row('star', ruT('Рекомендуемые значения', 'Recommended values'), pill(ruT('Применить', 'Apply'), () => ui.applyRecommended()));
        frag.append(textEl('hd-hint', ruT('Совет: файл резервной копии можно перетащить прямо на страницу сайта.', 'Tip: you can drag a backup file straight onto the site page.')));
        const csvBox = mk('div', 'st-block');
        const csvChips = mk('div', 'rs-chips');
        csvChips.append(
            pill(l.bk_csv_fav, () => Backup.exportFavCsv()),
            pill(l.bk_csv_hist, () => ui.historyManager?.exportCsv(false)),
            pill(l.bk_csv_ses, () => ui.historyManager?.exportCsv(true))
        );
        csvBox.append(lbl('table', l.bk_csv), csvChips);
        frag.append(csvBox);

        const pst = ParserDiag.stats();
        sec('list', pdT('diag'));
        row('list', `${pdT('title')}${pst.n ? ` (${pst.u}/${pst.n} ${pdT('nostatus')})` : ''}`, pill(l.st_open, () => ParserReport.open()));
    }

    render() {
        const l = L(), ui = this.ui;
        const keep = this._tabSw ? 0 : this.body.scrollTop;
        this._tabSw = false;
        pultTitle(this.title, icons.gear, l.settings);
        const tabs = [['look', ruT('Вид', 'Look')], ['btns', ruT('Кнопки', 'Buttons')], ['tools', ruT('Функции', 'Tools')], ['data', ruT('Данные', 'Data')]];
        if (!tabs.some(x => x[0] === this.tab)) this.tab = 'look';
        this.tabsEl.replaceChildren(...tabs.map(([id, text]) => {
            const b = textEl(`st-tab${id === this.tab ? ' on' : ''}`, text, 'button');
            b.type = 'button';
            b.onclick = () => { if (this.tab === id) return; this.tab = id; this._tabSw = true; this.render(); };
            return b;
        }));
        const frag = document.createDocumentFragment();
        const k = this.renderKit(frag);
        if (this.tab === 'look') this.stLook(k, l, ui);
        else if (this.tab === 'btns') this.stButtons(k, l, ui);
        else if (this.tab === 'tools') { this.stTools(k, l, ui); this.stAudio(k); this.stPreview(k); this.stGeo(k); this.stFavHist(k, l, ui); }
        else this.stData(k, l, ui);
        this.body.replaceChildren(frag);
        this.body.scrollTop = keep;
    }
}

const clampBarX = x => `${clamp(parseFloat(x) || 0, 0, Math.max(0, window.innerWidth - 40))}px`;
const clampBarB = v => `${clamp(Math.round(parseFloat(v) || 0), 0, Math.max(0, window.innerHeight - 40))}px`;

class UIController {
    constructor() {
        this.elements = {};
        this.hasMoved = false;
        this.buildShell();

        this.buildHeader();
        this.buildMediaRow();
        this.buildVolume();
        this.buildControls();
        this.buildMisc();
        this.buildSettings();
        this.applyButtons();
        this.watchLabelFit();

        this.setupDrag();
        this.setupResize();
        this.applyPosition();
        this.setupFilterDrag();

        document.documentElement.appendChild(this.host);
    }

    buildShell() {
        this.host = document.createElement('div');
        this.host.id = 'bc-unchained-host';
        this.shadow = this.host.attachShadow({ mode: 'open' });
        this.shadow.addEventListener('error', e => {
            const t = e.target;
            if (!t || t.tagName !== 'IMG' || !t.dataset || t.dataset.pti !== '1') return;
            if (!tryGm(t, t.getAttribute('src'), () => { t.style.display = 'none'; })) t.style.display = 'none';
        }, true);
        if (document.documentElement.classList.contains('bc-dark-fb')) this.host.setAttribute('data-dfb', '');

        mkStyle(this.shadow, CSS_UI + CSS_X + CSS_NOSEL + CSS_POLISH + CSS_COMPACT + CSS_PULT);

        this.panel = mk('div', 'panel');
        this.panel.style.transform = `scale(${AppState.scale})`;
        this.shadow.appendChild(this.panel);

        this.ghostBtn = htmlEl('ghost', icons.menu);
        this.ghostBtn.onclick = () => this.toggleHide(false);
        this.shadow.appendChild(this.ghostBtn);

        this.restoreBtn = htmlEl('restore', icons.menu);
        this.restoreBtn.onclick = () => { if (!this.hasMoved) this.toggleMinimize(false); };
        this.restoreBtn.onpointerdown = e => this.onDragStart(e);
        this.panel.appendChild(this.restoreBtn);

        this.fullContainer = mk('div', 'col');
        this.panel.appendChild(this.fullContainer);
    }

    fitLabels() {
        this.panel.querySelectorAll('.btn sub').forEach(sub => {
            const btn = sub.closest('.btn');
            if (!btn || !btn.clientWidth) return;
            const sig = `${btn.clientWidth}|${sub.textContent}`;
            if (sub._fitSig === sig) return;
            sub.style.removeProperty('font-size');
            sub.style.setProperty('white-space', 'nowrap', 'important');
            let fs = parseFloat(getComputedStyle(sub).fontSize) || 10.5;
            const room = btn.clientWidth - 4;
            while (sub.scrollWidth > room && fs > 6.5) {
                fs -= 0.5;
                sub.style.setProperty('font-size', `${fs}px`, 'important');
            }
            sub._fitSig = sig;
        });
    }

    watchLabelFit() {
        let raf = 0;
        const run = () => { cancelAnimationFrame(raf); raf = requestAnimationFrame(() => this.fitLabels()); };
        new MutationObserver(run).observe(this.panel, { childList: true, subtree: true, characterData: true });
        window.addEventListener('resize', run);
        run();
        setTimeout(run, 400);
    }

    setupFilterDrag() {
        const fs = { hasMoved: false, dragTarget: null, iL: 0, iT: 0, cFx: Store.get('bc_fx', null), cFb: readFb() };
        const applyPos = this.filterBarPositioner(fs);
        this.bindFilterBarDrag(fs, applyPos);
    }

    filterBarPositioner(fs) {
        const setImp = (el, props) => {
            const st = el.style;
            for (const k in props) {
                if (st.getPropertyValue(k) !== props[k] || st.getPropertyPriority(k) !== 'important') st.setProperty(k, props[k], 'important');
            }
        };
        const applyPos = () => {
            const b = document.querySelector('.bc_sticky_bar');
            if (!b || fs.dragTarget || realHidden() || window.innerHeight < 200) return;
            if (!(fs.cFx && fs.cFb)) return setImp(b, { left: '50%', transform: 'translateX(-50%)' });
            const x = clampBarX(fs.cFx), v = clampBarB(fs.cFb);
            setImp(b, { left: x, top: 'auto', bottom: v, right: 'auto', transform: 'none', margin: '0px', width: 'max-content' });
            if (!b.hasAttribute('data-bcu')) b.setAttribute('data-bcu', '1');
            if (!watched.has(b)) { watched.add(b); barObs.observe(b, { attributes: true, attributeFilter: ['style', 'class'] }); }
        };
        const watched = new WeakSet();
        const schedule = rafOnce(() => applyPos());
        const barObs = new MutationObserver(schedule);
        DomBus.subscribe(muts => {
            for (const m of muts) for (const n of m.addedNodes) {
                if (n.nodeType === 1 && (n.matches?.('.bc_sticky_bar') || n.querySelector?.('.bc_sticky_bar'))) return schedule();
            }
        });
        const css0 = document.getElementById('bc-unchained-site-css');
        if (fs.cFx && fs.cFb && css0 && !css0.textContent.includes('data-bcu')) { css0.remove(); injectSiteCSS(); }
        setInterval(() => { if (!realHidden()) applyPos(); }, 5000);
        window.addEventListener('resize', applyPos, { passive: true });
        applyPos();
        return applyPos;
    }

    bindFilterBarDrag(fs, applyPos) {
        window.addEventListener('pointerdown', e => {
            if (!isPrimaryPress(e)) return;
            const handle = e.target.closest('.js-filter_button, .bc_sticky_bar'), bar = document.querySelector('.bc_sticky_bar');
            if (!handle || !bar) return;
            if (e.pointerType !== 'mouse') e.preventDefault();
            fs.dragTarget = bar;
            const r = bar.getBoundingClientRect();
            fs.iL = r.left; fs.iT = r.top;
            bar.style.cssText += `;transform:none!important;margin:0!important;bottom:auto!important;right:auto!important;width:max-content!important;left:${fs.iL}px!important;top:${fs.iT}px!important;`;
            handle.style.setProperty('cursor', 'grabbing', 'important');

            Helpers.trackDrag(e, {
                minDist: 4,
                onMove: (dx, dy) => {
                    if (!fs.dragTarget.isConnected) return;
                    fs.dragTarget.style.setProperty('left', `${fs.iL + dx}px`, 'important');
                    fs.dragTarget.style.setProperty('top', `${fs.iT + dy}px`, 'important');
                },
                onEnd: (moved) => {
                    fs.hasMoved = moved;
                    if (moved && fs.dragTarget?.isConnected) {
                        const r = fs.dragTarget.getBoundingClientRect();
                        fs.cFx = clampBarX(r.left);
                        fs.cFb = clampBarB(window.innerHeight - r.bottom);
                        Store.set('bc_fx', fs.cFx); Store.set('bc_fb', fs.cFb);
                    }
                    handle.style.setProperty('cursor', 'grab', 'important');
                    fs.dragTarget = null;
                    applyPos();
                }
            });
        }, { passive: false, capture: true });

        window.addEventListener('click', e => {
            if (fs.hasMoved && e.target.closest('.js-filter_button, .bc_sticky_bar')) {
                swallow(e);
            }
        }, { capture: true });
    }

    buildHeader() {
        const hdr = mk('div', 'hdr');
        this.elements.dragZone = mk('div', 'drag');
        let dClicks = 0, dTimer = null;
        hdr.addEventListener('pointerdown', e => {
            if (e.target.closest('button, .stats')) return;
            dClicks++;
            if (dClicks === 1) dTimer = setTimeout(() => dClicks = 0, 500);
            else if (dClicks === 3) {
                clearTimeout(dTimer); dClicks = 0;
                AppState.posX = '15px'; AppState.posY = '10%'; AppState.hasPos = false;
                Store.set('bc_pos', false); this.applyPosition();
            }
            this.onDragStart(e);
        });
        this.elements.stats = htmlEl('stats', `${icons.eye}<span>–</span>`);
        const makeHBtn = (html, cls, click) => {
            const b = mk('button', `h-btn ${cls}`.trim());
            b.innerHTML = html;
            b.onclick = click;
            return b;
        };
        this.elements.bLang = makeHBtn(icons.gear, 'lang', () => this.settingsManager?.open());
        this.elements.bHome = makeHBtn(icons.home, '', () => location.href = '/');
        this.elements.bRel = makeHBtn(icons.rel, '', () => location.reload());
        this.elements.bMin = makeHBtn(icons.min, '', () => this.toggleMinimize(true));
        this.elements.bHide = makeHBtn(icons.close, 'close', () => this.toggleHide(true));
        const btns = mk('div', 'hdr-btns');
        btns.append(this.elements.stats, mk('span', 'hsep'), this.elements.bLang, mk('span', 'hsep'), this.elements.bHome, this.elements.bRel, mk('span', 'hsep'), this.elements.bMin, this.elements.bHide);
        hdr.append(this.elements.dragZone, btns);
        this.fullContainer.appendChild(hdr);
    }

    buildMediaRow() {
        const row = mk('div', 'grid-4');

        this.elements.bBg = Helpers.createButton(AppState.bgPlay ? icons.bgOn : icons.bgOff, '');
        if (AppState.bgPlay) this.elements.bBg.classList.add('on');
        this.elements.bBg.onclick = () => {
            AppState.bgPlay = !AppState.bgPlay;
            Store.set('bc_bg', AppState.bgPlay);
            this.elements.bBg.firstChild.innerHTML = AppState.bgPlay ? icons.bgOn : icons.bgOff;
            this.elements.bBg.querySelector('sub').textContent = L().bg;
            this.elements.bBg.classList.toggle('on', AppState.bgPlay);
            this.sleepTimer?.paint();
            this.enforceBackgroundPlay();
        };

        this.elements.bMute = Helpers.createButton(icons.vol, '');
        this.elements.bShot = Helpers.createButton(icons.snap, '');
        this.elements.bRec = Helpers.createButton(icons.rec, '');

        row.append(this.elements.bBg, this.elements.bMute, this.elements.bShot, this.elements.bRec);
        this.fullContainer.appendChild(row);
    }

    buildVolume() {
        const row = mk('div', 'v-cap');

        this.elements.vIco = htmlEl('vico', icons.vol, 'span');

        this.elements.vSlider = mk('input', 'vslider');
        this.elements.vSlider.type = 'range';
        this.elements.vSlider.min = '0';
        this.elements.vSlider.max = '1';
        this.elements.vSlider.step = '0.02';
        this.elements.vSlider.value = '1';

        this.elements.vVal = textEl('vval', '100%', 'span');

        row.append(this.elements.vIco, this.elements.vSlider, this.elements.vVal);
        this.fullContainer.appendChild(row);
    }

    buildControls() {
        this.fullContainer.appendChild(mk('div', 'sep'));
        const row = mk('div', 'grid-4');

        this.elements.bQual = Helpers.createButton(icons.qual, '');
        this.elements.bQual.onclick = () => this.toggleQuality();

        this.elements.bPip = Helpers.createButton(icons.pip, '');
        this.elements.bFs = Helpers.createButton(icons.fs, '');
        this.elements.bThtr = Helpers.createButton(icons.thtr, '');

        row.append(this.elements.bQual, this.elements.bPip, this.elements.bFs, this.elements.bThtr);
        this.fullContainer.appendChild(row);
    }

    bindQualityClose(qBlock) {
        if (qBlock._bcCloseBound) return;
        qBlock._bcCloseBound = true;
        qBlock.addEventListener('click', e => {
            if (!e.target.closest('.js-qsb_close, .qsb_close, .qsb_list_wrp, [class*="qsb"]')) return;
            setTimeout(() => { qBlock.classList.add('__hidden'); qBlock.style.display = 'none'; }, 50);
        });
    }

    clickQualityFallback() {
        const q = document.querySelector('#quality-btn, .player-resolution, button.vjs-icon-hd, .vjs-resolution-button, .vjs-cog-menu-button, .vjs-icon-cog, [class*="quality"], [class*="resolution"]');
        return q ? q.click() : this.flash(this.elements.bQual, `! ${L().err}`);
    }

    cbQualityMenu() {
        const old = document.getElementById('bc-cb-qual');
        if (old) { old.remove(); return true; }
        const btn = document.querySelector('button.vjs-icon-hd');
        const items = btn?.parentElement ? [...btn.parentElement.querySelectorAll('.vjs-menu-item')] : [];
        if (!items.length) return false;
        const th = AppState.theme;
        const mixOk = typeof CSS !== 'undefined' && CSS.supports?.('color', 'color-mix(in srgb, red 10%, blue)');
        const mix = (pct, base) => (mixOk ? `color-mix(in srgb, ${th} ${pct}%, ${base})` : base);
        const pb = mix(7, '#0b0c0f'), pc = mix(11, '#15171b'), pl = mix(38, '#3a3e46'), pt = mix(14, '#22252b');
        const font = 'Onest,system-ui,-apple-system,sans-serif';
        const el = (css, tag = 'div') => { const e = document.createElement(tag); e.style.cssText = css; return e; };
        const wrap = el('position:fixed;inset:0;z-index:2147483646;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.45)');
        wrap.id = 'bc-cb-qual';
        const card = el(`width:min(86vw,320px);max-height:84vh;display:flex;flex-direction:column;overflow:hidden;border-radius:18px;background:${pb};border:1px solid ${pl};box-shadow:0 14px 36px rgba(0,0,0,.6);color:#fff;font-family:${font};box-sizing:border-box`);
        const body = el('flex:1 1 auto;min-height:0;overflow-y:auto;padding:8px;box-sizing:border-box');
        const grp = el('display:flex;flex-direction:column;gap:6px;padding:8px;border-radius:14px;background:rgba(0,0,0,.34)');
        items.forEach(li => {
            const on = li.classList.contains('vjs-selected') || li.getAttribute('aria-checked') === 'true';
            const b = el(`min-height:52px;padding:0 16px;display:flex;align-items:center;border-radius:12px;border:1px solid ${on ? 'transparent' : pl};background:${on ? th : pt};color:#fff;font:500 16px/1.2 ${font};text-align:left;cursor:pointer;box-sizing:border-box`, 'button');
            b.type = 'button';
            b.textContent = (li.querySelector('.vjs-menu-item-text') || li).textContent.trim();
            b.onclick = e => { e.stopPropagation(); li.click(); wrap.remove(); };
            grp.appendChild(b);
        });
        body.appendChild(grp);
        const foot = el(`flex:none;display:flex;align-items:center;gap:8px;padding:8px;background:${pc};border-top:1px solid ${pl};box-sizing:border-box`);
        const ic = el(`flex:none;width:40px;height:40px;display:flex;align-items:center;justify-content:center;border-radius:12px;background:${th};color:#fff`);
        ic.innerHTML = icons.qual;
        ic.querySelector('svg')?.setAttribute('width', '22');
        ic.querySelector('svg')?.setAttribute('height', '22');
        const ttl = el('flex:1 1 auto;min-width:0;font-size:15px;font-weight:500', 'span');
        ttl.textContent = ({ RU: 'Качество', UK: 'Якість' })[AppState.lang] || 'Quality';
        const x = el(`flex:none;width:44px;height:44px;margin:0;padding:0;display:flex;align-items:center;justify-content:center;border-radius:12px;background:${pb};border:1px solid ${pl};color:#fff;cursor:pointer`, 'button');
        x.type = 'button';
        x.innerHTML = ICON_CLOSE;
        x.onclick = e => { e.stopPropagation(); wrap.remove(); };
        foot.append(ic, ttl, x);
        card.append(body, foot);
        wrap.onclick = e => { if (e.target === wrap) wrap.remove(); };
        wrap.appendChild(card);
        if (document.documentElement.classList.contains('bc-dark-fb')) wrap.style.filter = 'invert(1) hue-rotate(180deg)';
        document.documentElement.appendChild(wrap);
        return true;
    }

    toggleQuality() {
        const qBlock = document.querySelector('#quality_settings_block, .js-quality_settings_block');
        if (!qBlock) return this.cbQualityMenu() || this.clickQualityFallback();
        this.bindQualityClose(qBlock);
        const vis = isQualityShown(qBlock);
        qBlock.classList.toggle('__hidden', vis);
        qBlock.style.display = vis ? 'none' : 'block';
        if (vis) return;
        qBlock.style.zIndex = '2147483645';
        try { jq()?.('#quality-btn').trigger('click'); } catch (e) { dbg(e, 'UIController.toggleQuality'); }
    }

    buildMisc() {
        const row = mk('div', 'grid-4');
        this.elements.bSearch = Helpers.createButton(icons.search, '');
        this.elements.bHls = Helpers.createButton(icons.hls, 'HLS');
        const hlsFlash = ok => this.flash(this.elements.bHls, ok ? 'OK' : 'ERR');
        Helpers.bindLongPress(this.elements.bHls,
            () => copyHls(getStreamUrl(), hlsFlash),
            () => shareHls(getStreamUrl(), `${currentSite.getName()} Stream`, hlsFlash)
        );
        this.elements.bCopy = Helpers.createButton(icons.copy, '');
        this.elements.bCopy.onclick = () => {
            const name = currentSite.getName();
            if (!name) return this.flash(this.elements.bCopy, 'ERR');
            const link = modelSlug(location.href) ? linkFor(location.href) : `${location.origin}${location.pathname}`;
            Helpers.copyCb(link, ok => this.flash(this.elements.bCopy, ok ? 'OK' : 'ERR'));
        };
        this.elements.bFav = Helpers.createButton(icons.fav, '');
        row.append(this.elements.bSearch, this.elements.bHls, this.elements.bCopy, this.elements.bFav);
        this.fullContainer.appendChild(row);
        const navRow = mk('div', 'favnav');
        this.elements.bFavPrev = Helpers.createButton(icons.favPrev, '');
        this.elements.bFavNext = Helpers.createButton(icons.favNext, '');
        navRow.append(this.elements.bFavPrev, this.elements.bFavNext);
        this.fullContainer.appendChild(navRow);
        this.elements.favNavRow = navRow;
        FavNav.hook = on => navRow.classList.toggle('on', on);
        FavNav.apply();
    }

    buildSettings() {
        this.fullContainer.appendChild(mk('div', 'sep'));
        this.buildLookRow();
        this.buildActionRow();
    }

    buildScaleCtrl() {
        const scaleCtrl = mk('div', 'scale-ctrl');
        const btnZoomOut = htmlEl('z-btn', '−', 'button');
        const btnZoomIn = htmlEl('z-btn', '+', 'button');
        const scaleVal = textEl('s-val', `${Math.round(AppState.scale * 100)}%`, 'span');

        const setScale = s => {
            s = clamp(s, 0.6, 1.5);
            AppState.scale = s;
            Store.set('bc_scale', s);
            scaleVal.textContent = `${Math.round(s * 100)}%`;
            this.panel.style.transform = `scale(${s})`;
            if (AppState.hasPos) this.clampPosition();
        };
        this.setScale = setScale;
        this.elements.scaleCtrl = scaleCtrl;
        btnZoomOut.onclick = () => setScale(AppState.scale - 0.1);
        btnZoomIn.onclick = () => setScale(AppState.scale + 0.1);

        scaleCtrl.append(btnZoomOut, scaleVal, btnZoomIn);
        return scaleCtrl;
    }

    buildLookRow() {
        const row = mk('div', 'row-4');
        const btnCol = htmlEl('z-btn', icons.color, 'button');
        btnCol.style.cssText = 'width:100%;height:24px;';
        this.elements.bColor = btnCol;
        btnCol.onclick = () => {
            applyTheme(this.host, randThemeColor());
            btnCol.classList.remove('col-go');
            void btnCol.offsetWidth;
            btnCol.classList.add('col-go');
            clearTimeout(btnCol._goT);
            btnCol._goT = setTimeout(() => btnCol.classList.remove('col-go'), 550);
        };

        const scaleCtrl = this.buildScaleCtrl();
        const btnHide = htmlEl('z-btn', icons.eyeOff, 'button');
        btnHide.style.cssText = 'width:100%;height:24px;';
        btnHide.onclick = () => this.hideManager?.open();
        this.elements.bHideMgr = btnHide;
        row.style.cssText = 'display:grid;grid-template-columns:repeat(4,1fr);gap:6px;align-items:center';
        scaleCtrl.style.cssText = 'grid-column:span 2;justify-content:space-between';
        row.append(btnCol, btnHide, scaleCtrl);
        this.fullContainer.appendChild(row);
    }

    buildActionRow() {
        const row2 = mk('div', 'row-2');
        row2.style.cssText = 'display:flex;gap:6px';
        const sBtn = (key, fn) => {
            const b = mk('button', 'z-btn');
            b.style.cssText = 'flex:1;height:24px;font-size:10px;font-weight:700;display:flex;align-items:center;justify-content:center;white-space:nowrap';
            b.onclick = fn;
            this.elements[key] = b;
            return b;
        };
        row2.append(
            sBtn('bDark', () => this.toggleDark()),
            sBtn('bBkEx', () => { Backup.export(); this.flash(this.elements.bBkEx, 'OK'); }),
            sBtn('bBkIm', () => this.backupImport()),
            sBtn('bDon', () => this.openDonate())
        );
        this.fullContainer.appendChild(row2);

        this.elements.hints = mk('div', 'hotkeys');
        this.fullContainer.appendChild(this.elements.hints);
    }

    panelItems() {
        const e = this.elements;
        return [
            ['bBg', e.bBg, 'bg'], ['bMute', e.bMute, 'sound'], ['bShot', e.bShot, 'snap'], ['bRec', e.bRec, 'rec'],
            ['vol', e.vIco?.parentElement, 'bt_vol'], ['bQual', e.bQual, 'qual'], ['bPip', e.bPip, 'pip'], ['bFs', e.bFs, 'fs'], ['bThtr', e.bThtr, 'thtr'],
            ['bSearch', e.bSearch, 'search'], ['bHls', e.bHls, 'HLS'], ['bCopy', e.bCopy, 'copy'], ['bFav', e.bFav, 'favs'],
            ['stats', e.stats, 'bt_stats'], ['bHome', e.bHome, 'home'], ['bRel', e.bRel, 'rel'],
            ['bColor', e.bColor, 'st_color'], ['bHideMgr', e.bHideMgr, 'hd_title'], ['scale', e.scaleCtrl, 'st_scale'],
            ['bDark', e.bDark, 'st_site_dark'], ['bBkEx', e.bBkEx, 'bk_exp'], ['bBkIm', e.bBkIm, 'bk_imp'], ['bDon', e.bDon, 'don_tip'],
            ['hints', e.hints, 'bt_hints']
        ];
    }

    hiddenButtons() {
        const o = Store.get('bc_btnhide', {});
        return isPlain(o) ? o : {};
    }

    setButtonHidden(id, hide) {
        const o = { ...this.hiddenButtons() };
        if (hide) o[id] = true; else delete o[id];
        Store.set('bc_btnhide', o);
        this.applyButtons();
    }

    resetButtons() {
        Store.set('bc_btnhide', {});
        this.applyButtons();
    }

    applyButtons() {
        const hid = this.hiddenButtons();
        this.panelItems().forEach(([id, el]) => { if (el) el.classList.toggle('off', hid[id] === true); });
        [...this.fullContainer.children].filter(c => c.matches('.grid-4,.row-4,.row-2')).forEach(r => {
            const n = [...r.children].filter(c => !c.classList.contains('off')).length;
            r.classList.toggle('off', n === 0);
            if (r.matches('.grid-4')) r.style.gridTemplateColumns = n > 0 && n < 4 ? `repeat(${n},minmax(0,1fr))` : '';
        });
    }

    openLangPicker() {
        if (!this._lp) {
            const { overlay, modal, title, closeBtn, body } = buildPultShell(this.shadow, RS_PULT_CSS + SRCH_CSS + LP_CSS);
            closeBtn.onclick = () => overlay.classList.remove('open');
            overlay.onclick = e => { if (e.target === overlay && Date.now() - (this._lpAt || 0) > 400) overlay.classList.remove('open'); };

            const { box, input, clear } = srBox();
            input.autocomplete = 'off';
            const gSearch = mk('div', 'rs-g');
            gSearch.append(box);
            const gAuto = mk('div', 'rs-g');
            const gLang = mk('div', 'rs-g');
            body.replaceChildren(gSearch, gAuto, gLang);
            this._lp = { overlay, title, input, clear, gAuto, gLang };
        }
        const { overlay, title, input, clear, gAuto, gLang } = this._lp;
        const l = L();
        pultTitle(title, icons.globe, l.lang_title);
        input.placeholder = l.lp_find || ruT('Найти язык', 'Find language');
        input.value = '';
        const choose = (code, auto) => {
            AppState.langAuto = auto;
            Store.set('bc_lang_auto', auto);
            AppState.lang = auto ? detectLang() : code;
            if (!auto) Store.set('bc_lang', code);
            this.updateLabels();
            overlay.classList.remove('open');
        };
        const item = (code, text, on, fn, extra) => {
            const b = mk('button', `lp-btn${on ? ' sel' : ''}${extra ? ' ' + extra : ''}`);
            b.type = 'button';
            b.innerHTML = `<span class="lp-code">${code}</span><span class="lp-name">${escHTML(text)}</span>${extra === 'lp-auto' && on ? `<span class="lp-ck">${LP_CHECK}</span>` : ''}`;
            b.onclick = fn;
            b._q = `${code} ${text}`.toLowerCase();
            return b;
        };
        const dl = detectLang();
        const autoBtn = item(dl, `${l.lang_auto} · ${LANG_NAMES[dl]}`, AppState.langAuto, () => choose('', true), 'lp-auto');
        const btns = LANGS.map(c => item(c, LANG_NAMES[c], !AppState.langAuto && AppState.lang === c, () => choose(c, false)));
        const grid = mk('div', 'lp-grid');
        grid.append(...btns);
        const cnt = mk('span');
        const hs = textEl('hd-sec lp-hs', '');
        hs.append(textEl('', l.lang_title, 'span'), cnt);
        const empty = textEl('lp-empty', ruT('Ничего не найдено', 'No matches'));
        gAuto.replaceChildren(textEl('hd-sec', ruT('Автоопределение', 'Auto-detect')), autoBtn);
        gLang.replaceChildren(hs, grid, empty);
        const apply = () => {
            const q = input.value.trim().toLowerCase();
            let n = 0;
            btns.forEach(b => { const ok = !q || b._q.includes(q); b.hidden = !ok; if (ok) n++; });
            const aOk = !q || autoBtn._q.includes(q);
            autoBtn.hidden = !aOk;
            gAuto.hidden = !aOk;
            cnt.textContent = n;
            empty.hidden = n > 0 || aOk;
            gLang.hidden = n === 0 && aOk;
        };
        input.oninput = apply;
        input.onkeydown = e => {
            e.stopPropagation();
            if (e.key === 'Enter') { const f = btns.find(b => !b.hidden); if (f) f.click(); }
        };
        clear.onclick = () => { input.value = ''; apply(); input.focus(); };
        apply();
        this._lpAt = Date.now();
        overlay.classList.add('open');
    }

    updateLabels() {
        const l = L();
        this.host.toggleAttribute('data-rtl', RTL_LANGS.includes(AppState.lang));
        this.elements.dragZone.textContent = '';
        this.elements.bLang.title = l.settings;
        this.elements.bHome.title = l.home;
        this.elements.bRel.title = l.rel;
        this.elements.bMin.title = l.min;
        this.elements.bHide.title = l.hide;

        const subs = [
            ['bBg', 'bg'], ['bMute', 'sound'], ['bShot', 'snap'], ['bRec', 'rec'],
            ['bQual', 'qual'], ['bPip', 'pip'], ['bFs', 'fs'], ['bThtr', 'thtr'],
            ['bSearch', 'search'], ['bCopy', 'copy'], ['bFav', 'favs']
        ];
        subs.forEach(([k, lk]) => {
            const s = this.elements[k]?.querySelector('sub');
            if (s) s.textContent = l[lk];
        });

        this.elements.hints.innerHTML = l.hints;
        if (this.elements.bRec) this.elements.bRec.title = l.rec_tip;
        const e = this.elements;
        if (e.bDark) { e.bDark.innerHTML = (AppState.dark ? icons.moon : icons.sun); e.bDark.title = l.dark; e.bDark.style.background = AppState.dark ? 'linear-gradient(145deg,var(--th),rgba(255,255,255,.14))' : ''; e.bDark.style.boxShadow = AppState.dark ? 'inset 0 0 0 1px rgba(255,255,255,.22)' : ''; }
        [['bBkEx', 'dl', 'bk_exp'], ['bBkIm', 'ul', 'bk_imp'], ['bDon', 'coffee', 'don_tip']].forEach(([k, ico, tk]) => {
            if (e[k]) { e[k].innerHTML = icons[ico]; e[k].title = l[tk]; }
        });
        if (e.bDon) e.bDon.style.color = '#ffb347';
        this.bookmarkManager?.updateLabels();
        this.hideManager?.updateLabels();
        this.settingsManager?.updateLabels();
        this.historyManager?.updateLabels();
    }

    toggleDark(on = !AppState.dark) {
        AppState.dark = !!on;
        Store.set('bc_dark', AppState.dark);
        applyDark();
        this.updateLabels();
        const b = this.elements.bDark, dark = AppState.dark;
        if (b) {
            b.innerHTML = themeMorph(dark);
            setTimeout(() => { if (b.isConnected && AppState.dark === dark) b.innerHTML = dark ? icons.moon : icons.sun; }, 620);
        }
    }

    backupImport() { Backup.pick(f => this.backupFile(f)); }

    backupFile(file) {
        const t = ruT, fail = msg => pageToast(msg, false);
        Backup.read(file).then(data => {
            const rep = Backup.inspect(data);
            if (!Backup.useful(rep)) { fail(t('В файле нет подходящих данных', 'The file has no usable data')); return; }
            askConfirm(this, Backup.summary(rep), () => {
                try {
                    const r = Backup.apply(data, rep);
                    pageToast(`${t('Импортировано', 'Imported')}: ${t('настроек', 'settings')} ${r.n}, ${t('закладок', 'bookmarks')} +${r.added}`);
                    setTimeout(() => location.reload(), 900);
                } catch (e) { dbg(e, 'backupFile'); fail(t('Ошибка импорта', 'Import failed')); }
            }, icons.rel);
        }).catch(() => fail(t('Это не резервная копия Cams Unchained', 'Not a Cams Unchained backup')));
    }

    applyRecommended() {
        const t = ruT;
        askConfirm(this, t('Применить рекомендуемые значения? Закладки, история, тема, язык и позиции не меняются. Страница перезагрузится.', 'Apply recommended values? Bookmarks, history, theme, language and positions are not changed. The page will reload.'), () => {
            Backup.applyRecommended();
            pageToast(t('Рекомендуемые значения применены', 'Recommended values applied'));
            setTimeout(() => location.reload(), 900);
        }, icons.rel);
    }

    setBlurMute(on) {
        AppState.blurMute = !!on;
        Store.set('bc_blur_mute', AppState.blurMute);
        const vc = this.videoController;
        vc?.applyBlur(vc.cachedVideo);
        this.multiView?.tiles?.forEach(t => t.applyAudio());
    }

    donateWallets() {
        return [
            { name: 'TON', net: 'Gram · USDT', sym: 'T', svg: '<svg viewBox="12 14 32 32" width="26" height="26" xmlns="http://www.w3.org/2000/svg" style="display:block;filter:drop-shadow(0 1px 2px rgba(0,0,0,.3))"><path fill="#fff" d="M37.56 15.63H18.44c-3.52 0-5.75 3.79-3.98 6.86l11.8 20.45c.77 1.34 2.7 1.34 3.47 0l11.8-20.45c1.77-3.06-.46-6.86-3.97-6.86zM26.25 36.81l-2.57-4.98-6.2-11.09c-.41-.71.1-1.62.95-1.62h7.82v17.69zm12.26-16.07-6.2 11.1-2.57 4.97V19.12h7.82c.86 0 1.36.91.95 1.62z"/></svg>', c1: '#2aa6f0', c2: '#0b6fc2', addr: 'UQAhsfD61tpWIWI_VUnvJzDXE5LkfZQd1uZoWFO0uDEY81Nq' },
            { name: 'Litecoin', net: 'LTC', sym: 'Ł', svg: '<svg viewBox="0 0 24 24" width="26" height="26" xmlns="http://www.w3.org/2000/svg" style="display:block;filter:drop-shadow(0 1px 2px rgba(0,0,0,.3))"><path d="M10.8 4.8 8.6 16.4h7.4" fill="none" stroke="#fff" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/><path d="M6.4 12.9l8.2-3.3" fill="none" stroke="#fff" stroke-width="2.1" stroke-linecap="round"/></svg>', c1: '#9aa7b8', c2: '#4b5a70', addr: 'ltc1q7kdhehwusk6qupe325trqe9xjhp6qq4rpgy88j' },
            { name: 'BNB Smart Chain', net: 'BEP-20 · USDT · BNB', sym: 'B', svg: '<svg viewBox="0 0 24 24" width="26" height="26" xmlns="http://www.w3.org/2000/svg" style="display:block;filter:drop-shadow(0 1px 2px rgba(0,0,0,.3))"><g fill="#fff"><path d="M12 3 7.5 7.5l1.7 1.7L12 6.4l2.8 2.8 1.7-1.7zM12 21l-4.5-4.5 1.7-1.7 2.8 2.8 2.8-2.8 1.7 1.7z"/><path d="M5.2 9.9 7.3 12l-2.1 2.1L3.1 12zM18.8 9.9 20.9 12l-2.1 2.1-2.1-2.1zM12 9.2 14.8 12 12 14.8 9.2 12z"/></g></svg>', c1: '#f8cf4d', c2: '#c9930a', addr: '0xC1602C8CE455A4f7E694DF8524f59317de375310' },
            { name: 'Ethereum', net: 'ERC-20 · ETH', sym: 'Ξ', svg: '<svg viewBox="0 0 256 417" width="17" height="28" xmlns="http://www.w3.org/2000/svg" style="display:block;filter:drop-shadow(0 1px 2px rgba(0,0,0,.3))"><path fill="#fff" fill-opacity=".6" d="M127.96 0v154.16l128 56.16z"/><path fill="#fff" d="M127.96 0 0 210.32l127.96-56.16z"/><path fill="#fff" fill-opacity=".6" d="M127.96 312.19V417L256 236.59z"/><path fill="#fff" d="M127.96 417V312.19L0 236.59z"/><path fill="#fff" fill-opacity=".2" d="m127.96 287.96 128-75.64-128-58.16z"/><path fill="#fff" fill-opacity=".6" d="m0 212.32 127.96 75.64V154.16z"/></svg>', c1: '#8a9bff', c2: '#4a5bd0', addr: '0xC1602C8CE455A4f7E694DF8524f59317de375310' },
            { name: 'Solana', net: 'SOL · USDT', sym: 'S', svg: '<svg viewBox="0 0 398 312" width="24" height="19" xmlns="http://www.w3.org/2000/svg" style="display:block;filter:drop-shadow(0 1px 2px rgba(0,0,0,.3))"><g fill="#fff"><path d="M64.6 237.9c2.4-2.4 5.7-3.8 9.2-3.8h317.4c5.8 0 8.7 7 4.6 11.1l-62.7 62.7c-2.4 2.4-5.7 3.8-9.2 3.8H6.5c-5.8 0-8.7-7-4.6-11.1l62.7-62.7z"/><path d="M64.6 3.8C67.1 1.4 70.4 0 73.8 0h317.4c5.8 0 8.7 7 4.6 11.1l-62.7 62.7c-2.4 2.4-5.7 3.8-9.2 3.8H6.5c-5.8 0-8.7-7-4.6-11.1L64.6 3.8z"/><path d="M333.1 120.1c-2.4-2.4-5.7-3.8-9.2-3.8H6.5c-5.8 0-8.7 7-4.6 11.1l62.7 62.7c2.4 2.4 5.7 3.8 9.2 3.8h317.4c5.8 0 8.7-7 4.6-11.1l-62.7-62.7z"/></g></svg>', c1: '#9945ff', c2: '#14c9a0', addr: '7gp6seGi5aVJiKbMKxztPX8NBbtFsySRPfwmWmDEFM6P' }
        ];
    }

    donateStyle() {
        const css = document.createElement('style');
        css.textContent = `@keyframes dnPop {
  0% {
    transform: translateY(10px) scale(.98);
    opacity: 0
  }
  100% {
    transform: none;
    opacity: 1
  }
}
@keyframes dnSteam {
  0% {
    transform: translateY(5px) scaleX(.8);
    opacity: 0
  }
  25% {
    opacity: .6
  }
  60% {
    transform: translateY(-5px) scaleX(1.15);
    opacity: .35
  }
  100% {
    transform: translateY(-13px) scaleX(.9);
    opacity: 0
  }
}
@keyframes dnBeat {
  0%,
  100% {
    transform: scale(1)
  }
  50% {
    transform: scale(1.18)
  }
}
.dn-modal {
  --pb:#0e0f12;
  --pc:#17191d;
  --pl:#2a2d33;
  --pt:#22252b;
  --pon:#fff;
  display: flex!important;
  flex-direction: column;
  font-family: Onest,system-ui,-apple-system,sans-serif;
  color: #fff;
  background: var(--pb)!important;
  border: 1px solid var(--pl)!important;
  border-radius: 18px!important;
  box-shadow: 0 14px 36px rgba(0,0,0,.6)!important;
  -webkit-backdrop-filter: none!important;
  backdrop-filter: none!important;
  padding: 0;
  overflow: hidden
}
${pultVars('.dn-modal')}.dn-modal * {
  box-sizing: border-box
}
.dn-grp {
  background: rgba(0,0,0,.34);
  border-radius: 14px;
  padding: 4px;
  display: flex;
  flex-direction: column;
  gap: 6px
}
.dn-hdr {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 4px
}
.dn-cup {
  position: relative;
  flex: 0 0 48px;
  width: 48px;
  height: 48px;
  border-radius: 12px;
  background: var(--th);
  color: var(--pon);
  display: flex;
  align-items: center;
  justify-content: center
}
.dn-cup svg {
  display: block;
  overflow: visible
}
.dn-cup .dn-s {
  animation: dnWisp 3s ease-in-out infinite
}
.dn-cup .dn-s2 {
  animation-delay: 1.1s
}
@keyframes dnWisp {
  0%,
  100% {
    opacity: 1;
    transform: translateY(0)
  }
  50% {
    opacity: .35;
    transform: translateY(-1.5px)
  }
}
.dn-steam {
  position: absolute;
  left: 0;
  right: 0;
  top: 9px;
  height: 0;
  pointer-events: none
}
.dn-steam i {
  position: absolute;
  bottom: 100%;
  width: 2px;
  height: 11px;
  border-radius: 1px;
  background: linear-gradient(rgba(255,255,255,0),rgba(255,255,255,.7) 55%,rgba(255,255,255,0));
  animation: dnSteam 3.4s cubic-bezier(.45,0,.55,1) infinite
}
.dn-top {
  display: flex;
  flex-direction: column;
  gap: 2px
}
.dn-xrow {
  display: flex;
  justify-content: flex-end
}
.dn-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 3px;
  padding: 0 8px 6px;
  margin-top: -6px
}
.dn-hero .dn-cup {
  flex: none;
  width: 64px;
  height: 64px;
  border-radius: 18px;
  margin-bottom: 8px
}
.dn-hero .dn-t1 {
  font-size: 17px
}
.dn-hero .dn-t2 {
  font-size: 12px
}
.dn-heart {
  position: absolute;
  right: -6px;
  top: -6px;
  width: 20px;
  height: 20px;
  pointer-events: none;
  filter: drop-shadow(0 2px 4px rgba(0,0,0,.5));
  animation: dnBeat 1.8s ease-in-out infinite
}
.dn-ttl {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px
}
.dn-t1 {
  font-size: 15px;
  font-weight: 700;
  line-height: 1.2;
  color: #fff
}
.dn-t2 {
  font-size: 11.5px;
  font-weight: 500;
  line-height: 1.3;
  color: #fff;
  opacity: .72
}
.dn-x {
  flex: 0 0 52px;
  width: 52px;
  height: 52px;
  border-radius: 14px;
  border: 1px solid var(--pl);
  background: var(--pt);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  padding: 0;
  font: inherit;
  transition: background .12s,border-color .12s,transform .12s;
  -webkit-tap-highlight-color: transparent
}
.dn-x:hover {
  background: rgba(231,76,60,.9);
  border-color: transparent
}
.dn-x:active {
  transform: scale(.95)
}
.dn-lbl {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 6px 0;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .5px;
  text-transform: uppercase;
  color: #fff;
  opacity: .62
}
.dn-lbl::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--pl);
  opacity: .7
}
.dn-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px;
  min-height: 64px;
  border-radius: 14px;
  background: var(--pc);
  border: 1px solid var(--pl);
  transition: background .15s,border-color .15s,transform .12s;
  -webkit-tap-highlight-color: transparent
}
.dn-row:active {
  transform: scale(.985)
}
.dn-list {
  background: var(--pc);
  border: 1px solid var(--pl);
  border-radius: 14px;
  overflow: hidden
}
.dn-list .dn-row {
  border: 0;
  border-radius: 0;
  background: transparent;
  min-height: 60px
}
.dn-list .dn-row+.dn-row {
  border-top: 1px solid var(--pl)
}
.dn-list .dn-ic {
  flex-basis: 38px;
  width: 38px;
  height: 38px;
  border-radius: 50%
}
.dn-list .dn-row:hover:not(.dn-ok) {
  background: rgba(255,255,255,.04)
}
.dn-cta {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px;
  border-radius: 14px;
  background: var(--th);
  color: var(--pon);
  cursor: pointer;
  transition: filter .12s,transform .12s;
  -webkit-tap-highlight-color: transparent
}
.dn-cta:hover {
  filter: brightness(1.08)
}
.dn-cta:active {
  transform: scale(.985)
}
.dn-cta .dn-ic {
  border-radius: 10px;
  flex-basis: 38px;
  width: 38px;
  height: 38px
}
.dn-cta .dn-mid {
  gap: 1px
}
.dn-cta .dn-nm {
  color: var(--pon);
  font-size: 14px
}
.dn-cta .dn-ad {
  font: 500 12px/1.3 Onest,system-ui,sans-serif;
  color: var(--pon);
  opacity: .9;
  filter: none;
  cursor: pointer
}
.dn-cta>svg,
.dn-cta>i {
  flex: none;
  display: block
}
.dn-row.dn-ok {
  background: rgba(61,220,132,.12)
}
.dn-ic {
  flex: 0 0 40px;
  width: 40px;
  height: 40px;
  border-radius: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-weight: 800;
  font-size: 17px;
  box-shadow: inset 0 1px rgba(255,255,255,.18)
}
.dn-mid {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px
}
.dn-nm {
  display: flex;
  align-items: baseline;
  gap: 6px;
  flex-wrap: wrap;
  font-size: 13.5px;
  font-weight: 700;
  line-height: 1.2;
  color: #fff
}
.dn-nt {
  font-size: 10px;
  font-weight: 600;
  color: #fff;
  opacity: .55;
  letter-spacing: .2px
}
.dn-ad {
  font: 12px/1.35 ui-monospace,Menlo,Consolas,monospace;
  color: #fff;
  opacity: .82;
  cursor: pointer;
  word-break: break-all;
  -webkit-tap-highlight-color: transparent
}
.dn-ad.dn-sub {
  font: 500 12px/1.35 Onest,system-ui,sans-serif;
  word-break: normal;
  color: var(--th);
  opacity: 1;
  filter: brightness(1.35)
}
.dn-cp {
  flex: 0 0 44px;
  width: 44px;
  height: 44px;
  border-radius: 12px;
  border: 1px solid var(--pl);
  background: var(--pt);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  padding: 0;
  transition: background .12s,border-color .12s,transform .12s;
  -webkit-tap-highlight-color: transparent
}
.dn-cp:hover {
  background: color-mix(in srgb,var(--pt) 78%,#fff)
}
.dn-cp:active {
  transform: scale(.94)
}
.dn-cp svg {
  display: block;
  margin: 0!important
}
.dn-cp.on {
  background: var(--th);
  color: var(--pon);
  border-color: transparent
}
.dn-cp.go {
  background: var(--th);
  color: var(--pon);
  border-color: transparent
}
.dn-note {
  padding: 6px 10px 4px;
  font-size: 11px;
  line-height: 1.5;
  color: #fff;
  opacity: .62;
  text-align: center
}
.dn-hint {
  padding: 4px 8px 6px;
  font-size: 11px;
  font-weight: 500;
  color: #fff;
  opacity: .62;
  text-align: center
}
.dn-x:focus-visible,
.dn-cp:focus-visible,
.dn-row:focus-visible {
  outline: 2px solid var(--th);
  outline-offset: 2px
}
@media (max-width:520px) {
  .bm-overlay:has(.dn-modal) {
    align-items: stretch
  }
  .bm-modal.dn-modal {
    height: 100%!important;
    max-height: 100%!important;
    width: 100vw!important;
    max-width: 100vw!important;
    border-radius: 0!important;
    border-left: 0!important;
    border-right: 0!important;
    border-bottom: 0!important;
    align-self: stretch!important
  }
}
.dn-body {
  order: 1;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px;
  scrollbar-width: thin;
  scrollbar-color: var(--pl) transparent
}
.dn-body>* {
  flex-shrink: 0
}
.dn-bar {
  order: 2;
  flex: none;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 8px calc(8px + env(safe-area-inset-bottom,0px));
  background: var(--pc);
  border-top: 1px solid var(--pl)
}
.dn-bar .dn-cup {
  flex: 0 0 44px;
  width: 44px;
  height: 44px;
  border-radius: 12px
}
.dn-bar .dn-ttl {
  padding: 0 2px
}
.dn-bar .dn-x {
  flex: 0 0 44px;
  width: 44px;
  height: 44px;
  border-radius: 12px;
  background: var(--pb)
}
.dn-bar .dn-heart {
  right: -6px;
  top: -6px;
  width: 18px;
  height: 18px
}`;
        return css;
    }

    donateHead(tx) {
        const bar = mk('div', 'dn-bar');
        const cup = htmlEl('dn-cup', '<svg viewBox="0 0 24 24" width="28" height="28" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><path class="dn-s" d="M8 3a2.4 2.4 0 0 0-1 2 2.4 2.4 0 0 0 1 2"/><path class="dn-s dn-s2" d="M12 3a2.4 2.4 0 0 0-1 2 2.4 2.4 0 0 0 1 2"/><path d="M3 10h14v5a6 6 0 0 1-6 6H9a6 6 0 0 1-6-6v-5z"/><path d="M16.75 16.73a3 3 0 1 0 .25-5.56"/></svg>');
        const heart = htmlEl('dn-heart', '<svg viewBox="0 0 24 24" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg"><path d="M12 21s-8-5.2-8-11a4.6 4.6 0 0 1 8-3.1A4.6 4.6 0 0 1 20 10c0 5.8-8 11-8 11z" fill="#ff5d73"/><path d="M7.5 8.5c.4-1.2 1.4-1.8 2.5-1.7" fill="none" stroke="#fff" stroke-width="1.4" stroke-linecap="round" opacity=".6"/></svg>');
        cup.appendChild(heart);
        const ttl = mk('div', 'dn-ttl');
        const t1 = mk('div', 'dn-t1'); t1.textContent = tx.title;
        const t2 = mk('div', 'dn-t2'); t2.textContent = tx.sub;
        ttl.append(t1, t2);
        const x = htmlEl('dn-x', ICON_CLOSE, 'button');
        bar.append(cup, ttl, x);
        return { el: bar, closeBtn: x };
    }

    donateRow(w, tx, toast) {
        const icCopy = '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
        const icOk = '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>';
        const short = a => a.length > 20 ? `${a.slice(0, 8)}…${a.slice(-8)}` : a;

        const row = mk('div', 'dn-row');
        row.style.setProperty('--c', w.c1);
        const badge = mk('div', 'dn-ic');
        badge.style.background = `linear-gradient(145deg,${w.c1},${w.c2})`;
        if (w.svg) badge.innerHTML = w.svg; else badge.textContent = w.sym;
        const mid = mk('div', 'dn-mid');
        const nm = textEl('dn-nm', w.name);
        if (w.net) { const nt = mk('span', 'dn-nt'); nt.textContent = w.net; nm.appendChild(nt); }
        const ad = mk('div', 'dn-ad');
        let full = false;
        ad.textContent = short(w.addr);
        ad.onclick = () => { full = !full; ad.textContent = full ? w.addr : short(w.addr); };
        mid.append(nm, ad);
        const cp = htmlEl('dn-cp', icCopy, 'button');
        cp.onclick = () => Helpers.copy(w.addr, () => {
            cp.innerHTML = icOk;
            cp.classList.add('on');
            row.classList.add('dn-ok');
            toast(tx.copied, true);
            setTimeout(() => { if (cp.isConnected) { cp.innerHTML = icCopy; cp.classList.remove('on'); row.classList.remove('dn-ok'); } }, 1400);
        }, () => toast(tx.fail, false));
        row.append(badge, mid, cp);
        return row;
    }

    donateToast() {
        const toastEl = mk('div');
        toastEl.style.cssText = 'position:sticky;bottom:0;height:0;display:flex;justify-content:center;overflow:visible;z-index:3';
        const toastIn = mk('div');
        toastIn.style.cssText = 'position:absolute;bottom:14px;padding:9px 16px;border-radius:12px;font-size:12.5px;font-weight:600;color:#fff;background:#1f9d5c;border:1px solid rgba(255,255,255,.18);box-shadow:0 6px 18px rgba(0,0,0,.5);opacity:0;transform:translateY(8px);transition:.25s;pointer-events:none;white-space:nowrap';
        toastEl.appendChild(toastIn);
        let tt = 0;
        const toast = (msg, ok) => {
            toastIn.textContent = msg;
            toastIn.style.background = ok ? '#1f9d5c' : '#c0392b';
            toastIn.style.opacity = '1';
            toastIn.style.transform = 'none';
            clearTimeout(tt);
            tt = setTimeout(() => { toastIn.style.opacity = '0'; toastIn.style.transform = 'translateY(8px)'; }, 1500);
        };
        return { el: toastEl, toast };
    }

    openDonate() {
        const l = L();
        const tx = { title: l.don_title, sub: l.don_sub, copied: l.don_copied, fail: l.don_fail, tap: l.don_tap };
        if (this._donate && this._donate.lang !== AppState.lang) { this._donate.overlay.remove(); this._donate = null; }
        if (this._donate) {
            this._donate.overlay.classList.add('open');
            return;
        }
        const overlay = mk('div', 'bm-overlay');
        overlay.onclick = ev => { if (ev.target === overlay) overlay.classList.remove('open'); };
        overlay.appendChild(this.donateStyle());

        const modal = mk('div', 'bm-modal');
        modal.classList.add('dn-modal');
        modal.style.cssText = 'position:relative;height:auto;min-height:0;max-height:92vh;width:400px;max-width:94vw;animation:dnPop .22s ease';

        modal.style.setProperty('background', 'var(--pb)', 'important');
        const head = this.donateHead(tx);
        head.closeBtn.onclick = () => overlay.classList.remove('open');
        const { el: toastEl, toast } = this.donateToast();

        const icOpen = '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 4h6v6"/><path d="M20 4l-9 9"/><path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/></svg>';
        const refRow = mk('div', 'dn-cta');
        const refIc = mk('div', 'dn-ic');
        refIc.style.background = 'rgba(0,0,0,.28)';
        refIc.textContent = 'C';
        const refMid = mk('div', 'dn-mid');
        const refNm = mk('div', 'dn-nm'); refNm.textContent = 'Chaturbate';
        const refSub = mk('div', 'dn-ad'); refSub.textContent = supT('sub');
        refMid.append(refNm, refSub);
        const refOpen = mk('div');
        refOpen.style.cssText = 'flex:none;display:flex';
        refOpen.innerHTML = icOpen.replace('width="18" height="18"', 'width="22" height="22"');
        refRow.title = supT('btn');
        refRow.onclick = openSupportLink;
        refRow.append(refIc, refMid, refOpen);
        const refNote = textEl('dn-note', supT('note'));

        const walLbl = textEl('dn-lbl', l.don_tip);
        const list = mk('div', 'dn-list');
        this.donateWallets().forEach(w => list.appendChild(this.donateRow(w, tx, toast)));

        const hint = textEl('dn-hint', tx.tap);

        const bodyEl = mk('div', 'dn-body');
        bodyEl.append(refRow, refNote, walLbl, list, hint, toastEl);
        modal.append(bodyEl, head.el);
        overlay.appendChild(modal);
        this.shadow.appendChild(overlay);
        this._donate = { overlay, lang: AppState.lang };
        requestAnimationFrame(() => overlay.classList.add('open'));
    }

    flash(btn, text) {
        const target = btn.querySelector('sub') || btn;
        const pending = !!target._bcFlashT;
        clearTimeout(target._bcFlashT);
        if (!pending) {
            target._bcOrig = target.innerHTML;
            target._bcPrevCss = target.style.cssText;
            const w = target.offsetWidth, h = target.offsetHeight;
            if (h > 0) for (const k of ['height', 'min-height', 'max-height']) target.style.setProperty(k, `${h}px`, 'important');
            if (w > 0) for (const k of ['width', 'min-width', 'max-width']) target.style.setProperty(k, `${w}px`, 'important');
            target.style.setProperty('overflow', 'visible', 'important');
            target.style.setProperty('align-self', 'auto', 'important');
        }
        const sz = target.tagName === 'SUB' ? 12 : 16;
        target.innerHTML = text === 'OK'
            ? `<svg viewBox="0 0 24 24" width="${sz}" height="${sz}" stroke="#2ecc71" fill="none" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" style="display:inline-block;vertical-align:middle;margin:0 auto"><polyline points="20 6 9 17 4 12"/></svg>`
            : text;
        target._bcFlashT = setTimeout(() => {
            target._bcFlashT = null;
            if (!target.isConnected) return;
            target.innerHTML = target._bcOrig;
            target.style.cssText = target._bcPrevCss || '';
        }, 1500);
    }

    ensureVisible() {
        try {
            if (!this.host) return;
            if (!this.host.isConnected) document.documentElement.appendChild(this.host);
            const cs = getComputedStyle(this.host);
            if (cs.display === 'none' || cs.visibility === 'hidden' || cs.opacity === '0') {
                ['display:block', 'visibility:visible', 'opacity:1'].forEach(d => { const [k, v] = d.split(':'); this.host.style.setProperty(k, v, 'important'); });
            }
            if (bcPtrDown || realHidden() || window.innerWidth < 100 || window.innerHeight < 200) return;
            const el = AppState.isHidden ? this.ghostBtn : AppState.isMinimized ? this.restoreBtn : this.panel;
            const r = el.getBoundingClientRect();
            const off = r.width < 1 || r.height < 1 || r.right < 24 || r.bottom < 24 || r.left > window.innerWidth - 24 || r.top > window.innerHeight - 24;
            if (off) this.resetPlacement();
        } catch (e) { dbg(e, 'UIController.ensureVisible'); }
    }

    resetPlacement() {
        AppState.hasPos = false;
        Store.set('bc_pos', false);
        this.applyPosition();
    }

    resetPanel() {
        this.toggleHide(false);
        this.toggleMinimize(false, true);
        AppState.scale = 1;
        Store.set('bc_scale', 1);
        this.panel.style.transform = 'scale(1)';
        this.resetPlacement();
        setTimeout(() => this.ensureVisible(), 300);
    }

    toggleHide(hide) {
        AppState.isHidden = hide;
        Store.set('bc_hid', hide);
        this.panel.classList.toggle('hidden', hide);
        this.ghostBtn.style.display = hide ? 'flex' : 'none';
        if (!hide && AppState.hasPos) this.clampPosition();
    }

    savePos(x, y) {
        AppState.posX = x; AppState.posY = y; AppState.hasPos = true;
        Store.set('bc_x', x); Store.set('bc_y', y); Store.set('bc_pos', true);
    }

    toggleMinimize(minimized, initial = false) {
        AppState.isMinimized = minimized;
        Store.set('bc_min', minimized);

        if (!initial) {
            const rect = this.panel.getBoundingClientRect();
            const k = (minimized ? 1 : -1) * AppState.scale;
            const at = (p, d) => `${(parseFloat(this.panel.style[p]) || (p === 'top' ? rect.top - this.hostTop() : rect[p])) + k * d}px`;
            const newLeft = at('left', MINI_SHIFT_X), newTop = at('top', MINI_SHIFT_Y);
            this.panel.style.cssText += `;left:${newLeft};top:${newTop};right:auto;bottom:auto`;
            this.savePos(newLeft, newTop);
        }

        this.fullContainer.style.display = minimized ? 'none' : 'flex';
        this.restoreBtn.style.display = minimized ? 'flex' : 'none';
        this.restoreBtn.style.transformOrigin = 'top left';
        this.restoreBtn.style.transform = `scale(${1 / AppState.scale})`;
        this.panel.className = minimized ? 'panel mini' : 'panel';
        if (!initial) this.clampPosition();
    }

    hostTop() { return this.host.getBoundingClientRect().top || 0; }

    onDragStart(e) {
        if (!isPrimaryPress(e)) return;
        e.preventDefault();
        const rect = this.panel.getBoundingClientRect();
        const iL = rect.left, iT = rect.top - this.hostTop();
        this.panel.style.cssText += `;left:${iL}px;top:${iT}px;right:auto;bottom:auto`;
        this.panel.classList.add('dragging');

        Helpers.trackDrag(e, {
            minDist: 2,
            capEl: this.panel,
            onMove: (dx, dy) => {
                this.hasMoved = true;
                this.panel.style.left = `${iL + dx}px`;
                this.panel.style.top = `${iT + dy}px`;
            },
            onEnd: (moved) => {
                this.panel.classList.remove('dragging');
                this.hasMoved = moved;
                if (moved) {
                    this.clampPosition();
                    this.savePos(this.panel.style.left, this.panel.style.top);
                }
            }
        });
    }

    setupDrag() {
        let swallowClick = false;
        this.panel.addEventListener('click', ev => {
            if (!swallowClick) return;
            swallowClick = false;
            ev.stopPropagation();
            ev.preventDefault();
        }, true);
        this.panel.addEventListener('pointerdown', e => {
            if (AppState.isMinimized) return;
            if (!isPrimaryPress(e)) return;
            if (e.target.closest('input, textarea, .vslider, .resize-handle, .bm-overlay')) return;
            if (!e.target.closest('button, a, .btn, .h-btn, .z-btn')) { this.onDragStart(e); return; }
            const sx = e.clientX, sy = e.clientY, pid = e.pointerId, tgt = e.target;
            const stop = () => {
                window.removeEventListener('pointermove', mv, true);
                window.removeEventListener('pointerup', stop, true);
                window.removeEventListener('pointercancel', stop, true);
            };
            const mv = ev => {
                if (ev.pointerId !== pid || Math.hypot(ev.clientX - sx, ev.clientY - sy) < 10) return;
                stop();
                swallowClick = true;
                window.addEventListener('pointerup', () => setTimeout(() => { swallowClick = false; }, 350), { once: true, capture: true });
                try { tgt.dispatchEvent(new PointerEvent('pointercancel', { pointerId: pid, bubbles: true })); } catch (e) { dbg(e, 'setupDrag.mv'); }
                this.onDragStart(e);
            };
            window.addEventListener('pointermove', mv, { capture: true, passive: true });
            window.addEventListener('pointerup', stop, true);
            window.addEventListener('pointercancel', stop, true);
        });
    }

    buildResizeHandles() {
        this.resizeHandle = mk('div', 'resize-handle');
        this.resizeHandle.dataset.edge = 'se';
        const edges = ['n', 's', 'e', 'w', 'nw', 'ne', 'sw'].map(k => {
            const d = mk('div', `rs-edge rs-${k}`);
            d.dataset.edge = k;
            return d;
        });
        this.panel.append(this.resizeHandle, ...edges);
        return [this.resizeHandle, ...edges];
    }

    startResize(e) {
        if (!isPrimaryPress(e)) return;
        e.preventDefault();
        e.stopPropagation();
        const edge = e.currentTarget.dataset.edge;
        const hasN = edge.includes('n'), hasS = edge.includes('s'), hasW = edge.includes('w'), hasE = edge.includes('e');
        const rect = this.panel.getBoundingClientRect();
        const s0 = AppState.scale, w0 = rect.width, h0 = rect.height;
        const scaleVal = this.shadow.querySelector('.s-val');
        const ht = this.hostTop();
        this.panel.style.left = `${rect.left}px`;
        this.panel.style.top = `${rect.top - ht}px`;
        this.panel.style.right = 'auto';
        this.panel.style.bottom = 'auto';

        let raf = 0, ns = s0;
        const paint = () => {
            raf = 0;
            const f = ns / s0;
            this.panel.style.transform = `scale(${ns})`;
            this.panel.style.left = `${hasW ? rect.right - w0 * f : rect.left}px`;
            this.panel.style.top = `${(hasN ? rect.bottom - h0 * f : rect.top) - ht}px`;
            if (scaleVal) scaleVal.textContent = `${Math.round(ns * 100)}%`;
        };
        Helpers.trackDrag(e, {
            minDist: 1,
            onMove: (dx, dy) => {
                const kx = axisK(hasE, hasW, w0, dx), ky = axisK(hasS, hasN, h0, dy);
                ns = clamp(s0 * (kx !== null && ky !== null ? (kx + ky) / 2 : kx ?? ky), 0.6, 1.5);
                AppState.scale = ns;
                if (!raf) raf = requestAnimationFrame(paint);
            },
            onEnd: moved => {
                if (!moved) return;
                if (raf) { cancelAnimationFrame(raf); paint(); }
                Store.set('bc_scale', ns);
                this.clampPosition();
                this.savePos(this.panel.style.left, this.panel.style.top);
            }
        });
    }

    setupResize() {
        this.buildResizeHandles().forEach(el => el.addEventListener('pointerdown', e => this.startResize(e)));
    }

    clampPosition(gap = 10) {
        if (this.panel.classList.contains('hidden')) return;
        if (realHidden() || window.innerHeight < 200 || window.innerWidth < 100) return;
        const rect = this.panel.getBoundingClientRect();
        const l = parseFloat(this.panel.style.left), t = parseFloat(this.panel.style.top);
        if (isNaN(l) || isNaN(t)) return;
        this.panel.style.left = `${clamp(l, gap, Math.max(gap, window.innerWidth - gap - rect.width))}px`;
        const vh = window.innerHeight;
        this.panel.style.top = `${clamp(t, gap - vh, Math.max(gap - vh, -gap - rect.height))}px`;
    }

    applyPosition() {
        if (AppState.hasPos && AppState.posX && AppState.posY) {
            const py = parseFloat(AppState.posY);
            if (py >= 0 && window.innerHeight >= 200) { AppState.posY = `${Math.round(py - window.innerHeight)}px`; Store.set('bc_y', AppState.posY); }
            this.panel.style.left = AppState.posX;
            this.panel.style.top = AppState.posY;
            this.panel.style.right = 'auto';
            this.panel.style.bottom = 'auto';
        } else {
            this.panel.style.right = '15px';
            this.panel.style.bottom = '10vh';
            this.panel.style.left = 'auto';
            this.panel.style.top = 'auto';
        }
    }

    enforceBackgroundPlay() {
        if (this._pbBound) return;
        this._pbBound = true;
        const pb = e => {
            if (!(AppState.bgPlay && e.isTrusted)) return;
            e.stopImmediatePropagation();
            const subs = BG_SUBS[e.type];
            if (subs && !e._bcRelayed) {
                e._bcRelayed = true;
                subs.forEach(fn => { try { fn(e); } catch (e) { dbg(e, 'enforceBackgroundPlay.pb'); } });
            }
        };
        ['blur', 'visibilitychange', 'webkitvisibilitychange', 'pagehide'].forEach(evt => {
            window.addEventListener(evt, pb, true);
            document.addEventListener(evt, pb, true);
        });
        const hid = Object.getOwnPropertyDescriptor(Document.prototype, 'hidden');
        const vis = Object.getOwnPropertyDescriptor(Document.prototype, 'visibilityState');
        try {
            Object.defineProperties(document, {
                hidden: { get: () => AppState.bgPlay ? false : hid.get.call(document), configurable: true },
                visibilityState: { get: () => AppState.bgPlay ? 'visible' : vis.get.call(document), configurable: true }
            });
        } catch (e) { dbg(e, 'UIController.enforceBackgroundPlay'); }
    }

    injectSiteCSS() {
        injectSiteCSS();
        ensureHideCSS();
    }
}

class BgSaver {
    constructor(videoController, recorder) {
        this.vc = videoController;
        this.rec = recorder;
        this.timer = 0;
        this.paused = null;
        this.lowered = null;
        onPageEvent('visibilitychange', () => this.onVis());
    }

    busy() {
        return !!document.pictureInPictureElement || this.rec?.mRec?.state === 'recording';
    }

    onVis() {
        clearTimeout(this.timer);
        if (realHidden()) {
            this.timer = setTimeout(() => this.enter(), 10000);
        } else {
            this.leave();
        }
    }

    enter() {
        if (!realHidden() || this.busy()) return;
        try {
            if (AppState.bgPlay) {
                this.setLowest();
            } else {
                const v = this.vc.cachedVideo;
                if (v && !v.paused) { v.pause(); this.paused = v; }
            }
        } catch (e) { dbg(e, 'BgSaver.enter'); }
    }

    leave() {
        try {
            const v = this.paused;
            this.paused = null;
            if (v?.isConnected) {
                const b = v.buffered;
                if (b.length) {
                    const end = b.end(b.length - 1);
                    if (end - v.currentTime > 3) v.currentTime = end - 0.5;
                }
                v.play().catch(() => {});
            }
            if (this.lowered) { const h = this.lowered.prev; this.lowered = null; this.pickQuality(h); }
        } catch (e) { dbg(e, 'BgSaver.leave'); }
    }

    qualityItems() {
        const out = [];
        document.querySelectorAll('.js-qsb_list_wrp *, [class*="qsb_"] *, [class*="rendition"] *, [class*="quality_"] *, .vjs-menu-item *, [class*="resolution"] *, [class*="Resolution"] *, [role="menu"] *, [role="listbox"] *').forEach(el => {
            if (el.children.length || el.closest('button.player-resolution, button.vjs-icon-hd')) return;
            const m = (el.textContent || '').trim().match(/^(\d{3,4})\s*p?$/i);
            if (m) {
                const row = el.closest('li,a,button,div') || el;
                const mark = /(^|[\s_-])(active|selected|current|checked|__selected|__active)($|[\s_-])/;
                out.push({ el, h: +m[1], sel: mark.test(row.className) || mark.test(el.className) });
            }
        });
        return out;
    }

    openQualityMenu() {
        document.querySelector('.controls_current_rendition, #quality-btn, .player-resolution, button.vjs-icon-hd')?.click();
    }

    setLowest() {
        this.openQualityMenu();
        setTimeout(() => {
            try {
                const items = this.qualityItems();
                if (items.length < 2) return;
                const low = items.reduce((a, b) => b.h < a.h ? b : a);
                const cur = items.find(i => i.sel);
                if (cur && cur.h === low.h) return;
                this.lowered = { prev: cur ? cur.h : Math.max(...items.map(i => i.h)) };
                low.el.click();
            } catch (e) { dbg(e, 'BgSaver.setLowest'); }
        }, 500);
    }

    pickQuality(h) {
        this.openQualityMenu();
        setTimeout(() => {
            try { this.qualityItems().find(i => i.h === h)?.el.click(); } catch (e) { dbg(e, 'BgSaver.pickQuality'); }
        }, 500);
    }
}

const autoAge = () => {
    if (!BC_RE.test(location.hostname)) return;
    const sel = '.js-18_plus_warning a.js-close_warning, #popup_18_plus a.button_18_plus';
    let clicked = false;
    const cleanup = () => {
        try { document.body?.classList.remove('bd_18plus'); document.documentElement.classList.remove('ht_18plus'); } catch (e) { dbg(e, 'autoAge.cleanup'); }
    };
    const accept = () => {
        const a = document.querySelector(sel);
        if (!a) return false;
        if (clicked) return true;
        clicked = true;
        a.addEventListener('click', e => e.preventDefault(), { once: true, capture: true });
        try { a.click(); } catch (e) { dbg(e, 'autoAge.accept'); }
        const done = () => setTimeout(cleanup, 150);
        try { fetch(a.href, { credentials: 'include', cache: 'no-store' }).then(done, done); } catch { done(); }
        return true;
    };
    if (accept()) return;
    const mo = new MutationObserver(() => { if (accept()) mo.disconnect(); });
    mo.observe(document.documentElement, { childList: true, subtree: true });
    setTimeout(() => mo.disconnect(), 20000);
};
autoAge();

const VIEWERS_SEL = '#bChatHeader .js-oc_count, .chat-header-inner .js-oc_count, #online-chat .js-oc_count, .chat-info-viewers, .v-viewers, .js-oc_count, .oc_count, .player-viewers-count, .HeaderRoomInfo__numViewers, [data-testid="viewers-count"]';
const VIEWERS_FALLBACK = '[class*="numViewers" i], [class*="viewerCount" i], [class*="viewersCount" i], [class*="viewers-count" i], [data-testid*="viewer" i]';
const readViewers = () => {
    const pick = sel => {
        try {
            for (const el of document.querySelectorAll(sel)) {
                const t = (el.textContent || '').trim();
                if (t && t.length < 60 && /\d/.test(t)) return t;
            }
        } catch (e) { dbg(e, 'readViewers.pick'); }
        return '';
    };
    return pick(VIEWERS_SEL) || pick(VIEWERS_FALLBACK);
};
const markFav = (btn, on) => {
    if (!btn) return;
    btn.style.background = '';
    btn.style.borderColor = '';
    btn.classList.toggle('fav-on', !!on);
};

const SLEEP_OPTS = [0, 15, 30, 45, 60, 90, 120];
const SLEEP_CSS = `.rs-pult .sl-r {
  display: grid;
  grid-template-columns: repeat(3,minmax(0,1fr));
  gap: 6px
}
.rs-pult .sl-f {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  min-width: 0;
  padding: 8px 6px;
  border-radius: 12px;
  border: 1px solid var(--pl);
  background: var(--pt)
}
.rs-pult .sl-f span {
  font-size: 12px;
  opacity: .62
}
.rs-pult .sl-f input {
  width: 100%;
  min-width: 0;
  margin: 0;
  padding: 2px 0;
  border: 0;
  outline: 0;
  background: transparent;
  color: #fff;
  text-align: center;
  font: 600 26px/1.15 Onest,system-ui,sans-serif;
  -moz-appearance: textfield;
  appearance: textfield
}
.rs-pult .sl-f input::-webkit-outer-spin-button,
.rs-pult .sl-f input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0
}
.rs-pult .sl-pm {
  display: flex;
  gap: 6px;
  width: 100%
}
.rs-pult .sl-pm button {
  flex: 1;
  min-height: 40px;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 9px;
  background: var(--pb);
  color: #fff;
  font: 500 20px/1 Onest,system-ui,sans-serif;
  cursor: pointer;
  transition: transform .12s
}
.rs-pult .sl-pm button:active,
.rs-pult .sl-go:active {
  transform: scale(.96)
}
.rs-pult .sl-go {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 48px;
  margin: 4px 0 0;
  padding: 0 12px;
  border: 0;
  border-radius: 12px;
  background: var(--th);
  color: var(--pon);
  font: 600 15px/1.2 Onest,system-ui,sans-serif;
  cursor: pointer;
  transition: transform .12s
}
.rs-pult .sl-pm button:focus-visible,
.rs-pult .sl-go:focus-visible,
.rs-pult .sl-f input:focus-visible {
  outline: 2px solid var(--th);
  outline-offset: 2px
}`;

class SleepTimer {
    constructor(ui, rec, bg) {
        this.ui = ui;
        this.rec = rec;
        this.bg = bg;
        this.lock = false;
        this.guard = 0;
        this.cover = null;
        this.mo = null;
        this.end = 0;
        this.mins = 0;
        this.t = 0;
        this.iv = 0;
        this.modal = null;
        this.at = 0;
        const end = +Store.get('bc_sleep_end', 0) || 0;
        if (end > Date.now()) {
            this.mins = +Store.get('bc_sleep_min', 0) || 0;
            this.arm(end);
        } else if (end) {
            Store.set('bc_sleep_end', 0);
            Store.set('bc_sleep_min', 0);
        }
        const lockAt = +Store.get('bc_sleep_lock', 0) || 0;
        if (lockAt && Date.now() - lockAt < 7200000) this.engage(); else if (lockAt) Store.set('bc_sleep_lock', 0);
        ['play', 'playing', 'loadstart', 'loadedmetadata', 'canplay'].forEach(t => document.addEventListener(t, e => this.onPlay(e), true));
        document.addEventListener('timeupdate', () => this.check(), true);
        onPageEvent('visibilitychange', () => this.check());
    }

    left() { return Math.max(0, this.end - Date.now()); }

    fmt() {
        const t = Math.ceil(this.left() / 1000), p = pad2;
        return t >= 3600 ? `${Math.floor(t / 3600)}:${p(Math.floor(t / 60) % 60)}:${p(t % 60)}` : Helpers.formatTime(t);
    }

    set(min) { this.setMs(min * 60000); }

    setMs(ms) {
        if (!(ms > 0)) return this.cancel();
        ms = Math.min(Math.round(ms), 86400000);
        this.mins = ms / 60000;
        const end = Date.now() + ms;
        Store.set('bc_sleep_end', end);
        Store.set('bc_sleep_min', this.mins);
        this.arm(end);
    }

    arm(end) {
        clearTimeout(this.t);
        clearInterval(this.iv);
        this.end = end;
        this.t = setTimeout(() => this.check(), Math.max(0, end - Date.now()) + 50);
        this.iv = setInterval(() => { this.paint(); this.check(); }, 1000);
        this.paint();
    }

    cancel() {
        clearTimeout(this.t);
        clearInterval(this.iv);
        this.t = this.iv = 0;
        this.end = 0;
        this.mins = 0;
        Store.set('bc_sleep_end', 0);
        Store.set('bc_sleep_min', 0);
        this.paint();
    }

    check() {
        if (this.end && Date.now() >= this.end) this.fire();
    }

    engage() {
        this.lock = true;
        this.strip();
        this.showCover();
        clearInterval(this.guard);
        this.guard = setInterval(() => this.strip(), 2000);
        if (!this.mo) {
            const hasMedia = n => n.nodeType === 1 && (n.matches('video,audio') || !!n.querySelector('video,audio'));
            this.mo = new MutationObserver(recs => {
                for (const r of recs) for (const n of r.addedNodes) if (hasMedia(n)) return this.strip();
            });
            this.mo.observe(document.documentElement, { childList: true, subtree: true });
        }
    }

    unlock() {
        this.lock = false;
        clearInterval(this.guard);
        this.guard = 0;
        this.mo?.disconnect();
        this.mo = null;
        this.cover?.remove();
        this.cover = null;
        Store.set('bc_sleep_lock', 0);
    }

    wake() {
        this.unlock();
        location.reload();
    }

    strip() {
        try { window.dispatchEvent(new Event('bc_sleep_kill')); } catch (e) { dbg(e, 'SleepTimer.strip'); }
        try {
            document.querySelectorAll('video,audio').forEach(v => {
                try {
                    stopTracks(v.srcObject);
                    v.srcObject = null;
                    unloadMedia(v);
                } catch (e) { dbg(e, 'SleepTimer.strip'); }
                v.remove();
            });
        } catch (e) { dbg(e, 'SleepTimer.strip'); }
    }

    showCover() {
        if (this.cover?.isConnected) return;
        const c = mk('div', '');
        c.style.cssText = 'position:fixed;inset:0;z-index:0;background:#000;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;text-align:center;padding:24px;color:#8a8a90;font-size:15px;pointer-events:auto;touch-action:manipulation;cursor:pointer';
        String(L().sleep_wake).split('\n').forEach(t => c.append(textEl('', t, 'div')));
        c.onclick = () => this.wake();
        this.ui.shadow.insertBefore(c, this.ui.shadow.firstChild);
        this.cover = c;
    }

    onPlay(e) {
        if (!this.lock || !(e.target instanceof HTMLMediaElement)) return;
        this.strip();
    }

    fire() {
        this.cancel();
        Store.set('bc_sleep_lock', Date.now());
        let wasRec = false;
        try {
            const btn = this.ui.elements.bRec;
            if (this.rec?.mRec && this.rec.mRec.state !== 'inactive') { wasRec = true; this.rec.stop(btn, btn.querySelector('sub')); }
        } catch (e) { dbg(e, 'SleepTimer.fire'); }
        if (this.bg) this.bg.paused = null;
        try { this.ui.flash(this.ui.elements.bBg, 'OK'); } catch (e) { dbg(e, 'SleepTimer.fire'); }
        if (wasRec) setTimeout(() => { if (Store.get('bc_sleep_lock', 0)) this.engage(); }, 2000);
        else this.engage();
    }

    paint() {
        const bg = this.ui.elements.bBg;
        if (bg) {
            bg.classList.toggle('slp', !!this.end);
            const arc = bg.querySelector('.slp-arc');
            if (arc && this.end) {
                const tot = this.mins * 60000 || this.left();
                const f = tot > 0 ? Math.min(1, this.left() / tot) : 0;
                arc.setAttribute('stroke-dashoffset', String(+(100 * (1 - f)).toFixed(2)));
            }
        }
        const sub = this.ui.elements.bBg?.querySelector('sub');
        if (sub && !sub._bcFlashT) {
            if (this.end) {
                sub._slp = true;
                sub.textContent = this.fmt();
            } else if (sub._slp) {
                sub._slp = false;
                sub.textContent = L().bg;
            }
        }
        this.paintModal();
    }

    clock(ms) {
        return fmtStamp(Date.now() + ms).slice(11, 16);
    }

    paintModal() {
        const el = this.modal?.sumTx;
        if (!el || !el.isConnected) return;
        const l = L();
        el.textContent = this.end ? `${l.sleep_left}: ${this.fmt()} · → ${this.clock(this.left())}` : l.rs_off;
    }

    open() {
        if (!this.modal) {
            const { overlay, modal, title, closeBtn, body } = buildPultShell(this.ui.shadow, RS_PULT_CSS + SLEEP_CSS);
            const close = () => { overlay.classList.remove('open'); overlay._fromSt = false; overlay._bk?.(); };
            closeBtn.onclick = close;
            overlay.onclick = e => { if (e.target === overlay && Date.now() - this.at > 400) close(); };
            this.modal = { overlay, title, body, close, sumTx: null };
        }
        this.at = Date.now();
        this.render();
        this.modal.overlay.classList.add('open');
    }

    sleepField(unit, max) {
        const wrap = mk('div', 'sl-f');
        const inp = mk('input', '');
        inp.type = 'number';
        inp.min = '0';
        inp.max = String(max);
        inp.step = '1';
        inp.value = '0';
        inp.inputMode = 'numeric';
        inp.onfocus = () => inp.select();
        const num = () => clamp(parseInt(inp.value, 10) || 0, 0, max);
        const mkBtn = (txt, d) => {
            const b = mk('button', '');
            b.type = 'button';
            b.textContent = txt;
            b.onclick = () => { inp.value = String(clamp(num() + d, 0, max)); inp.dispatchEvent(new Event('input')); };
            return b;
        };
        const pm = mk('div', 'sl-pm');
        pm.append(mkBtn('−', -1), mkBtn('+', 1));
        wrap.append(textEl('', unit, 'span'), inp, pm);
        return { wrap, inp, num };
    }

    render() {
        const m = this.modal;
        if (!m) return;
        const l = L();
        pultTitle(m.title, icons.sleep, l.sleep_title);

        const sum = htmlEl('rs-sum', icons.sleep);
        m.sumTx = mk('span');
        sum.appendChild(m.sumTx);

        const off = mk('div', 'rs-chips');
        off.append(chipBtn(l.rs_off, !this.end, () => { this.set(0); m.close(); }));

        const qs = mk('div', 'rs-qs');
        SLEEP_OPTS.filter(v => v).forEach(v => {
            const hrs = v >= 60 && v % 60 === 0;
            const b = mk('button', `bm-btn${this.end && v === this.mins ? ' sel' : ''}`);
            const r1 = mk('b'); r1.textContent = hrs ? v / 60 : v;
            const r2 = mk('span'); r2.textContent = hrs ? l.u_h : l.u_m;
            const r3 = mk('small'); r3.textContent = `→ ${this.clock(v * 60000)}`;
            b.append(r1, r2, r3);
            b.onclick = () => { this.set(v); m.close(); };
            qs.append(b);
        });

        const fh = this.sleepField(l.u_h, 24), fm = this.sleepField(l.u_m, 1440), fs = this.sleepField(l.sleep_sec, 86400);
        const row = mk('div', 'sl-r');
        row.append(fh.wrap, fm.wrap, fs.wrap);
        const go = mk('button', 'sl-go');
        go.type = 'button';
        const total = () => (fh.num() * 3600 + fm.num() * 60 + fs.num()) * 1000;
        const paintGo = () => {
            const ms = Math.min(total(), 86400000), t = Math.round(ms / 1000), p = pad2;
            go.textContent = ms ? `${l.sleep_start} · ${p(Math.floor(t / 3600))}:${p(Math.floor(t / 60) % 60)}:${p(t % 60)} → ${this.clock(ms)}` : l.sleep_start;
        };
        const start = () => {
            const ms = total();
            if (!ms) return;
            this.setMs(ms);
            m.close();
        };
        go.onclick = start;
        [fh, fm, fs].forEach(f => {
            f.inp.oninput = paintGo;
            f.inp.onkeydown = e => { e.stopPropagation(); if (e.key === 'Enter') { e.preventDefault(); start(); } };
        });
        paintGo();

        const grp = (...kids) => { const g = mk('div', 'rs-g'); g.append(...kids); return g; };
        m.body.replaceChildren(
            sum,
            grp(off, qs),
            grp(textEl('hd-sec', l.sleep_custom), row, go),
            textEl('rs-note', l.sleep_hint)
        );
        this.paintModal();
    }
}

const RAD = { NODE: 42, R0: 84, RSTEP: 62, DEAD: 26, ARC: 0.44, HOLD: 380, FAB: 48 };
const radDiff = (a, b) => {
    let d = (a - b) % (Math.PI * 2);
    if (d > Math.PI) d -= Math.PI * 2;
    if (d < -Math.PI) d += Math.PI * 2;
    return d;
};
const RAD_CSS = `.rm-fab {
  position: fixed;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  display: none;
  align-items: center;
  justify-content: center;
  color: #fff;
  background: var(--th);
  border: 2px solid rgba(255,255,255,.35);
  box-shadow: 0 4px 15px rgba(0,0,0,.6);
  pointer-events: auto;
  touch-action: none!important;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  cursor: pointer;
  opacity: .85;
  z-index: 2147483640;
  transition: transform .15s,opacity .15s,box-shadow .15s
}
.rm-fab.on {
  display: flex
}
.rm-fab:hover {
  opacity: 1
}
.rm-fab.moving {
  transform: scale(1.25);
  opacity: 1;
  box-shadow: 0 0 0 6px rgba(255,255,255,.2),0 8px 24px rgba(0,0,0,.7)
}
.rm-fab svg {
  width: 24px;
  height: 24px;
  pointer-events: none
}
.rm-ov {
  position: fixed;
  inset: 0;
  display: none;
  pointer-events: none;
  touch-action: none;
  z-index: 2147483645;
  background: rgba(0,0,0,.38)
}
.rm-ov.open {
  display: block
}
.rm-ov.sticky {
  pointer-events: auto
}
.rm-stage {
  position: absolute;
  width: 0;
  height: 0;
  transform-origin: 0 0
}
.rm-ring {
  position: absolute;
  border-radius: 50%;
  box-sizing: content-box;
  border-style: solid;
  border-color: rgba(255,255,255,.07);
  transform: translate(-50%,-50%);
  pointer-events: none;
  z-index: 0
}
.rm-hub {
  position: absolute;
  z-index: 1;
  width: 64px;
  height: 64px;
  margin: -32px 0 0 -32px;
  border-radius: 50%;
  background: rgba(15,15,18,.95);
  border: 2px solid rgba(255,255,255,.25);
  color: #fff;
  font: 700 10px/1.1 system-ui,sans-serif;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 4px;
  overflow: hidden;
  word-break: break-word;
  pointer-events: auto;
  cursor: pointer;
  transition: background .1s
}
.rm-hub.lit {
  background: var(--th)
}
.rm-hub svg {
  width: 24px;
  height: 24px
}
.rm-node {
  position: absolute;
  z-index: 2;
  width: 42px;
  height: 42px;
  margin: -21px 0 0 -21px;
  border-radius: 50%;
  background: rgba(15,15,18,.95);
  border: 1px solid rgba(255,255,255,.2);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: auto;
  cursor: pointer;
  box-shadow: 0 3px 10px rgba(0,0,0,.5);
  animation: rmPop .14s ease-out;
  transition: transform .1s,background .1s
}
.rm-node svg {
  width: 20px;
  height: 20px;
  pointer-events: none
}
.rm-node.grp::after {
  content: '';
  position: absolute;
  right: 2px;
  bottom: 2px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--th);
  border: 1px solid #fff
}
.rm-node.hot {
  background: var(--th);
  transform: scale(1.28);
  border-color: #fff;
  z-index: 3
}
@keyframes rmPop {
  from {
    transform: scale(.3);
    opacity: 0
  }
  to {
    transform: scale(1);
    opacity: 1
  }
}`;

const nearestAngle = (pos, ang) => {
    let best = -1, bd = 1e9;
    pos.forEach((p, i) => { const df = Math.abs(radDiff(ang, p.a)); if (df < bd) { bd = df; best = i; } });
    return { best, bd };
};
class RadialMenu {
    constructor(ui, app) {
        this.ui = ui;
        this.app = app;
        this.on = Store.get('bc_rad', false) === true;
        this.mode = '';
        this.path = [];
        this.lv = [];
        this.items = [];
        this.scale = 1;
        mkStyle(ui.shadow, RAD_CSS);
        this.fab = htmlEl('rm-fab', icons.target);
        this.ov = mk('div', 'rm-ov');
        this.stage = mk('div', 'rm-stage');
        this.hubEl = mk('div', 'rm-hub');
        this.ov.appendChild(this.stage);
        ui.shadow.append(this.ov, this.fab);
        this.bind();
        this.placeFab();
        this.fab.classList.toggle('on', this.on);
        window.addEventListener('resize', () => { this.close(); this.placeFab(); }, { passive: true });
        window.addEventListener('keydown', e => { if (e.key === 'Escape' && this.mode) this.close(); }, true);
    }

    setEnabled(v) {
        this.on = !!v;
        Store.set('bc_rad', this.on);
        this.fab.classList.toggle('on', this.on);
        if (!this.on) this.close();
        this.placeFab();
    }

    resetPos() {
        Store.set('bc_rad_x', null);
        Store.set('bc_rad_y', null);
        this.placeFab();
    }

    fabPos() {
        const W = window.innerWidth, H = window.innerHeight, S = RAD.FAB;
        const x = parseFloat(Store.get('bc_rad_x', null)), y = parseFloat(Store.get('bc_rad_y', null));
        return {
            x: clamp(Number.isFinite(x) ? x : W - S - 14, 4, Math.max(4, W - S - 4)),
            y: clamp(Number.isFinite(y) ? y : Math.round(H * 0.55), 4, Math.max(4, H - S - 4))
        };
    }

    placeFab() {
        const p = this.fabPos();
        this.fab.style.left = `${p.x}px`;
        this.fab.style.top = `${p.y}px`;
    }

    treeCtx() {
        const l = L(), ui = this.ui, app = this.app, vc = app.videoController, el = ui.elements;
        const press = b => {
            if (!b) return;
            if (b.onpointerup) {
                const o = { button: 0, pointerType: 'touch', isPrimary: true, bubbles: true };
                b.dispatchEvent(new PointerEvent('pointerdown', o));
                b.dispatchEvent(new PointerEvent('pointerup', o));
            } else b.click();
        };
        const vol = d => () => { const v = vc.cachedVideo; if (v) vc.setVolume(v.volume + d); };
        const addFav = () => {
            const name = currentSite.getName();
            if (name && !isFavName(name)) { app.bookmarks.add(); ui.flash(el.bFav, 'OK'); app.updateViewersCount(); }
            else ui.flash(el.bFav, 'ERR');
        };
        return { l, ui, app, vc, el, press, vol, addFav };
    }

    treeMedia(c) {
        const { l, app, vc, el, press, vol } = c;
        return { icon: icons.vol, label: l.rad_media, kids: [
            { icon: icons.bgOn, label: l.bg, run: () => press(el.bBg) },
            { icon: icons.mute, label: l.sound, run: () => press(el.bMute) },
            { icon: icons.volMed, label: l.rad_vup, run: vol(0.1) },
            { icon: icons.volLow, label: l.rad_vdn, run: vol(-0.1) },
            { icon: icons.snap, label: l.snap, run: () => vc.takeScreenshot() },
            { icon: icons.rec, label: l.rec, run: () => app.recorder.toggle() }
        ] };
    }

    treeView(c) {
        const { l, ui, vc } = c;
        return { icon: icons.fs, label: l.rad_view, kids: [
            { icon: icons.qual, label: l.qual, run: () => ui.toggleQuality() },
            { icon: icons.pip, label: l.pip, run: () => vc.togglePiP() },
            { icon: icons.fs, label: l.fs, run: () => vc.toggleFullscreen() },
            { icon: icons.thtr, label: l.thtr, run: () => vc.toggleTheater() }
        ] };
    }

    treeNav(c) {
        const { l, app, el, addFav } = c;
        return { icon: icons.fav, label: l.rad_nav, kids: [
            { icon: icons.search, label: l.search, run: () => app.search.open() },
            { icon: icons.bookmark, label: l.favs, run: () => app.bookmarks.open() },
            { icon: icons.fav, label: l.bm_add, run: addFav },
            { icon: icons.favPrev, label: l.rad_prev, run: () => el.bFavPrev?.click() },
            { icon: icons.favNext, label: l.rad_next, run: () => el.bFavNext?.click() },
            { icon: icons.copy, label: l.copy, run: () => el.bCopy?.click() }
        ] };
    }

    treePage(c) {
        const { l, ui, app } = c;
        return { icon: icons.grid, label: l.rad_page, kids: [
            { icon: icons.home, label: l.home, run: () => { location.href = '/'; } },
            { icon: icons.rel, label: l.rel, run: () => location.reload() },
            { icon: AppState.dark ? icons.sun : icons.moon, label: l.dark, run: () => ui.toggleDark() },
            { icon: icons.sleep, label: l.sleep_title, run: () => app.sleep.open() },
            { icon: icons.history, label: l.hist_title, run: () => app.history.open() },
            { icon: icons.min, label: l.min, run: () => ui.toggleMinimize(true) },
            { icon: icons.close, label: l.hide, run: () => ui.toggleHide(true) }
        ] };
    }

    tree() {
        const c = this.treeCtx(), { l, app } = c;
        return [
            this.treeMedia(c),
            this.treeView(c),
            this.treeNav(c),
            this.treePage(c),
            { icon: icons.gear, label: l.settings, run: () => app.settings.open() }
        ];
    }

    fan(r) {
        const { x: hx, y: hy } = this.hub, W = window.innerWidth, H = window.innerHeight, M = RAD.NODE / 2 + 4, b = this.base;
        const ok = a => { const x = hx + Math.cos(a) * r, y = hy + Math.sin(a) * r; return x >= M && x <= W - M && y >= M && y <= H - M; };
        let lo = 0, hi = 0;
        const st = 0.04;
        while (hi < Math.PI && ok(b + hi + st)) hi += st;
        while (lo > -Math.PI && ok(b + lo - st)) lo -= st;
        return { lo, hi, full: hi - lo >= Math.PI * 2 - 0.12 };
    }

    spread(c, n, step) {
        return Array.from({ length: n }, (_, i) => c + (i - (n - 1) / 2) * step);
    }

    layoutRoot(n, f, b) {
        if (f.full) {
            const step = (Math.PI * 2) / n;
            return { step, angles: Array.from({ length: n }, (_, i) => -Math.PI / 2 + i * step) };
        }
        const step = n > 1 ? Math.min(1.3, (f.hi - f.lo) / (n - 1)) : 0;
        return { step, angles: this.spread(b + (f.lo + f.hi) / 2, n, step) };
    }

    layoutKids(n, f, b, parentAng) {
        if (f.full) return { step: RAD.ARC, angles: this.spread(parentAng, n, RAD.ARC) };
        const span = f.hi - f.lo;
        let step = RAD.ARC;
        if ((n - 1) * step > span) step = n > 1 ? span / (n - 1) : 0;
        const w = ((n - 1) * step) / 2;
        const c = b + clamp(radDiff(parentAng, b), f.lo + w, f.hi - w);
        return { step, angles: this.spread(c, n, step) };
    }

    layout(list, d, parentAng) {
        const r = RAD.R0 + d * RAD.RSTEP, n = list.length, b = this.base;
        const { step, angles } = d === 0
            ? this.layoutRoot(n, this.f0, b)
            : this.layoutKids(n, this.f1, b, parentAng);
        const out = angles.map(a => ({ a, x: Math.cos(a) * r, y: Math.sin(a) * r }));
        out.step = step;
        return out;
    }

    kidsHit(list, pos, ang, dist) {
        if (dist < RAD.R0 + RAD.RSTEP - RAD.NODE / 2 - 8) return false;
        const c = this.path[0], cn = c != null ? list[c] : null;
        if (!cn || !cn.kids) return false;
        const kp = this.layout(cn.kids, 1, pos[c].a), tol = Math.max(kp.step, 0.2) * 0.6;
        return kp.some(p => Math.abs(radDiff(ang, p.a)) <= tol);
    }

    beyondRing(d, dist) {
        return d > 0 && dist < RAD.R0 + d * RAD.RSTEP - RAD.NODE / 2 - 8;
    }

    tooFar(d, pos, bd) {
        return d > 0 && bd > Math.max(pos.step, 0.2) * 0.6;
    }

    pick(vx, vy) {
        const dist = Math.hypot(vx, vy);
        if (dist < RAD.DEAD) return [];
        const ang = Math.atan2(vy, vx), path = [];
        let list = this.items, parentAng = null;
        for (let d = 0; list && !this.beyondRing(d, dist); d++) {
            const pos = this.layout(list, d, parentAng);
            let { best, bd } = nearestAngle(pos, ang);
            if (d === 0 && this.kidsHit(list, pos, ang, dist)) best = this.path[0];
            if (this.tooFar(d, pos, bd)) break;
            path.push(best);
            const node = list[best];
            if (!node.kids) break;
            parentAng = pos[best].a;
            list = node.kids;
        }
        return path;
    }

    moveFab(f, e) {
        const S = RAD.FAB;
        f.style.left = `${clamp(e.clientX - S / 2, 4, window.innerWidth - S - 4)}px`;
        f.style.top = `${clamp(e.clientY - S / 2, 4, window.innerHeight - S - 4)}px`;
    }

    dragMove(st, e) {
        const vx = e.clientX - st.ox, vy = e.clientY - st.oy;
        if (!st.moved && Math.hypot(vx, vy) > 10) { st.moved = true; clearTimeout(st.timer); }
        if (st.moved) this.setPath(this.pick(vx / this.scale, vy / this.scale));
        if (e.cancelable) e.preventDefault();
    }

    endMove(f) {
        f.classList.remove('moving');
        this.mode = '';
        Store.set('bc_rad_x', f.style.left);
        Store.set('bc_rad_y', f.style.top);
    }

    endDrag(e, s) {
        if (e.type === 'pointercancel') return this.close();
        const node = this.nodeAtPath(this.path);
        if (node && !node.kids) { this.close(); this.run(node); return; }
        if (node || !s.moved) return this.sticky();
        this.close();
    }

    nodeAtPath(path) {
        let list = this.items, node = null;
        for (const i of path) { node = list && list[i] || null; list = node && node.kids; }
        return node;
    }

    openMenu(c, mode) {
        this.items = this.tree();
        this.path = [];
        this.scale = 1;
        this.hub = { x: c.x, y: c.y };
        const cx = window.innerWidth / 2, cy = window.innerHeight / 2;
        this.base = Math.hypot(cx - c.x, cy - c.y) < 20 ? -Math.PI / 2 : Math.atan2(cy - c.y, cx - c.x);
        this.f0 = this.fan(RAD.R0);
        this.f1 = this.fan(RAD.R0 + RAD.RSTEP);
        this.stage.style.cssText = `left:${c.x}px;top:${c.y}px`;
        this.lv = [];
        this.stage.replaceChildren(this.hubEl);
        this.mode = mode;
        this.ov.className = `rm-ov open${mode === 'sticky' ? ' sticky' : ''}`;
        this.render();
        this.updateHub();
    }

    buildLevel({ list, pos, key, d }) {
        const el = mk('div', 'rm-lvl');
        const r = RAD.R0 + d * RAD.RSTEP, band = RAD.NODE + 12;
        const ring = mk('div', 'rm-ring');
        ring.style.cssText = `width:${2 * r - band}px;height:${2 * r - band}px;border-width:${band}px`;
        el.appendChild(ring);
        const nodes = list.map((it, i) => {
            const n = mk('div', `rm-node${it.kids ? ' grp' : ''}`);
            n.innerHTML = it.icon;
            n.title = it.label;
            n.dataset.d = d;
            n.dataset.i = i;
            n.style.left = `${pos[i].x}px`;
            n.style.top = `${pos[i].y}px`;
            el.appendChild(n);
            return n;
        });
        this.stage.appendChild(el);
        return { key, list, nodes, el };
    }

    render() {
        const levels = [];
        let list = this.items, parentAng = null, key = '';
        for (let d = 0; list; d++) {
            const pos = this.layout(list, d, parentAng);
            levels.push({ list, pos, key, d });
            const i = this.path[d];
            if (i == null || !list[i] || !list[i].kids) break;
            parentAng = pos[i].a;
            key += `/${i}`;
            list = list[i].kids;
        }
        let from = levels.findIndex((lv, d) => !this.lv[d] || this.lv[d].key !== lv.key);
        if (from < 0) from = levels.length;
        while (this.lv.length > from) this.lv.pop().el.remove();
        for (let d = from; d < levels.length; d++) this.lv.push(this.buildLevel(levels[d]));
        this.lv.forEach((lv, d) => lv.nodes.forEach((n, i) => n.classList.toggle('hot', this.path[d] === i)));
    }

    updateHub() {
        const n = this.nodeAtPath(this.path);
        this.hubEl.classList.toggle('lit', !!n);
        if (n) this.hubEl.textContent = n.label; else this.hubEl.innerHTML = icons.target;
    }

    setPath(p) {
        if (p.length === this.path.length && p.every((v, i) => v === this.path[i])) return;
        this.path = p;
        this.render();
        this.updateHub();
        if (p.length && navigator.vibrate) navigator.vibrate(8);
    }

    sticky() {
        this._stAt = Date.now();
        this.mode = 'sticky';
        this.ov.classList.add('sticky');
    }

    close() {
        if (!this.mode) return;
        this.mode = '';
        this.ov.className = 'rm-ov';
        this.path = [];
        this.lv = [];
        this.stage.replaceChildren();
    }

    run(it) {
        try { it.run(); } catch (err) { console.warn('[BCU radial]', err); }
    }

    bind() {
        this.bindFabGuards();
        this.bindFabPointer();
    }

    bindFabGuards() {
        const f = this.fab;
        f.oncontextmenu = e => e.preventDefault();
        ['touchstart', 'touchmove', 'touchend'].forEach(t => f.addEventListener(t, e => { if (e.cancelable) e.preventDefault(); }, { passive: false }));
        ['touchstart', 'touchmove'].forEach(t => this.ov.addEventListener(t, e => { if (e.cancelable) e.preventDefault(); }, { passive: false }));
        window.addEventListener('contextmenu', e => {
            const path = e.composedPath ? e.composedPath() : [];
            if (this.mode || path.includes(f) || path.includes(this.ov)) { e.preventDefault(); e.stopImmediatePropagation(); }
        }, true);
    }

    bindFabPointer() {
        const f = this.fab;
        let st = null;
        f.onpointerdown = e => {
            if (!isPrimaryPress(e)) return;
            swallow(e);
            this.close();
            try { f.setPointerCapture(e.pointerId); } catch (e) { dbg(e, 'RadialMenu.bindFabPointer'); }
            const p = this.fabPos();
            st = { id: e.pointerId, ox: e.clientX, oy: e.clientY, moved: false, timer: 0 };
            this.openMenu({ x: p.x + RAD.FAB / 2, y: p.y + RAD.FAB / 2 }, 'drag');
            st.timer = setTimeout(() => {
                if (!st || st.moved || this.mode !== 'drag') return;
                this.close();
                this.mode = 'move';
                f.classList.add('moving');
                if (navigator.vibrate) navigator.vibrate(15);
            }, RAD.HOLD);
        };
        f.onpointermove = e => {
            if (!st || e.pointerId !== st.id) return;
            if (this.mode === 'move') return this.moveFab(f, e);
            if (this.mode !== 'drag') return;
            this.dragMove(st, e);
        };
        const end = e => {
            if (!st || e.pointerId !== st.id) return;
            clearTimeout(st.timer);
            const s = st;
            st = null;
            try { f.releasePointerCapture(e.pointerId); } catch (e) { dbg(e, 'bindFabPointer.end'); }
            if (this.mode === 'move') return this.endMove(f);
            if (this.mode !== 'drag') return;
            this.endDrag(e, s);
        };
        f.onpointerup = end;
        f.onpointercancel = end;
        f.onlostpointercapture = e => { if (st && e.pointerId === st.id) end({ type: 'pointercancel', pointerId: e.pointerId }); };
        window.addEventListener('blur', () => { if (st) end({ type: 'pointercancel', pointerId: st.id }); });
        document.addEventListener('visibilitychange', () => { if (document.hidden && st) end({ type: 'pointercancel', pointerId: st.id }); });
        this.bindStickyUp();
    }

    bindStickyUp() {
        this.ov.onpointerup = e => {
            if (this.mode !== 'sticky' || !isPrimaryPress(e) || Date.now() - (this._stAt || 0) < 150) return;
            swallow(e);
            const n = e.target.closest ? e.target.closest('.rm-node') : null;
            if (!n) return this.close();
            this.stickyPick(+n.dataset.d, +n.dataset.i);
        };
    }

    stickyPick(d, i) {
        const it = this.lv[d] && this.lv[d].list[i];
        if (!it) return;
        if (!it.kids) { this.close(); this.run(it); return; }
        const same = this.path[d] === i && this.path.length === d + 1;
        this.setPath(same ? this.path.slice(0, d) : this.path.slice(0, d).concat(i));
    }
}

class App {
    constructor() {
        this.createModules();
        const vc = this.videoController, el = this.ui.elements;
        this.bindMediaButtons(vc, el);
        this.bindFavButtons(vc, el);
        this.restoreUiState();
        this.bindRemoteAndWindow(vc);
        this.startTimers();
    }

    createModules() {
        this.ui = new UIController();
        this.videoController = new VideoController(this.ui);
        this.ui.videoController = this.videoController;
        this.recorder = new MediaRecorderManager(this.videoController, this.ui);
        this.bgSaver = new BgSaver(this.videoController, this.recorder);
        this.bookmarks = new BookmarkManager(this.ui);
        this.search = new SearchManager(this.ui);

        this.ui.bookmarkManager = this.bookmarks;
        this.ui.searchManager = this.search;
        this.hideManager = new HideManager(this.ui);
        this.ui.hideManager = this.hideManager;
        this.multiView = new MultiView(this.ui);
        this.ui.multiView = this.multiView;
        bmDropHooks.push(k => this.multiView.dropBmKey(k));
        this.history = new HistoryManager(this.ui, this.bookmarks);
        this.thumbStars = new ThumbStars(this.bookmarks);
        this.ui.historyManager = this.history;
        this.calendar = new CalendarManager(this.ui, this.bookmarks);
        this.ui.calendarManager = this.calendar;
        this.ui.thumbStars = this.thumbStars;
        this.ui.recorder = this.recorder;
        this.settings = new SettingsManager(this.ui);
        this.ui.settingsManager = this.settings;
        this.sleep = new SleepTimer(this.ui, this.recorder, this.bgSaver);
        this.ui.sleepTimer = this.sleep;
        this.ui.radial = new RadialMenu(this.ui, this);
    }

    bindMediaButtons(vc, el) {
        const bgClick = el.bBg.onclick;
        el.bBg.onclick = null;
        Helpers.bindLongPress(el.bBg, () => bgClick.call(el.bBg), () => this.sleep.open());
        Helpers.bindLongPress(el.bMute,
            () => {
                if (AppState.gmute) setGMute(false);
                vc.toggleMute();
            },
            () => {
                setGMute(true);
                const v = vc.cachedVideo;
                if (v) hardMute(v);
                vc.syncUI();
                if (!v) vc.paintMuteBtn(el.bMute, true, true);
            }
        );
        el.bPip.onclick = () => vc.togglePiP();
        el.bFs.onclick = () => vc.toggleFullscreen();
        el.bThtr.onclick = () => vc.toggleTheater();
        el.bShot.onclick = () => vc.takeScreenshot();
        el.vIco.onclick = () => vc.toggleMute();
        Helpers.bindLongPress(el.bRec, () => this.recorder.toggle(), () => this.recorder.openSettings());
        el.bSearch.onclick = () => this.search.open();
    }

    async goFav(dir, btn) {
        if (btn._busy) return;
        btn._busy = true;
        btn.style.opacity = '.5';
        try {
            await this.bookmarks.checkStatuses();
            const here = siteKey(location.hostname);
            const curName = currentSite.getName();
            const list = AppState.bookmarks
                .filter(b => b.status === 1 && isSafeUrl(b.url) && urlSite(b.url) === here)
                .sort((x, y) => (!!y.pin - !!x.pin) || x.name.localeCompare(y.name));
            const idx = list.findIndex(b => String(b.name).toLowerCase() === String(curName).toLowerCase());
            if (!list.length || (list.length === 1 && idx === 0)) return this.ui.flash(btn, 'ERR');
            const next = idx < 0 ? (dir > 0 ? list[0] : list[list.length - 1]) : list[(idx + dir + list.length) % list.length];
            location.href = linkFor(next.url);
        } catch { this.ui.flash(btn, 'ERR'); }
        finally { btn._busy = false; btn.style.opacity = ''; }
    }

    bindFavButtons(vc, el) {
        el.bFavPrev.onclick = () => this.goFav(-1, el.bFavPrev);
        el.bFavNext.onclick = () => this.goFav(1, el.bFavNext);
        el.bFavPrev.title = el.bFavNext.title = '';
        el.vSlider.oninput = e => vc.setVolume(parseFloat(e.target.value));
        el.vSlider.onwheel = e => {
            e.preventDefault();
            if (vc.cachedVideo) vc.setVolume(clamp(vc.cachedVideo.volume + wheelVol(e), 0, 1));
        };
        Helpers.bindLongPress(el.bFav,
            () => setTimeout(() => this.bookmarks.open(), 50),
            () => {
                const name = currentSite.getName();
                if (name && !isFavName(name)) {
                    this.bookmarks.add();
                    this.ui.flash(el.bFav, 'OK');
                    this.updateViewersCount();
                } else this.ui.flash(el.bFav, 'ERR');
            }
        );
        el.bFav.onclick = e => { halt(e); };
    }

    restoreUiState() {
        this.ui.updateLabels();
        this.ui.injectSiteCSS();
        if (AppState.bgPlay) this.ui.enforceBackgroundPlay();
        if (AppState.isMinimized) this.ui.toggleMinimize(true, true);
        if (AppState.hasPos) this.ui.clampPosition();
        if (AppState.isHidden) this.ui.toggleHide(true);
        setTimeout(() => this.ui.ensureVisible(), 1500);
        setTimeout(() => this.ui.ensureVisible(), 5000);
        gmMenu('Сбросить панель / Reset panel', () => this.ui.resetPanel());
        gmMenu(pdT('title'), () => ParserReport.open());
        gmMenu('Копировать лог ошибок / Copy error log', () => copyErrorLog());
        let evT = 0;
        const later = () => { clearTimeout(evT); evT = setTimeout(() => this.ui.ensureVisible(), 700); };
        ['resize', 'orientationchange'].forEach(t => window.addEventListener(t, later, { passive: true }));
        ['fullscreenchange', 'webkitfullscreenchange'].forEach(t => document.addEventListener(t, later, true));
        setTimeout(() => this.multiView?.bootResume(), 2500);
    }

    bindBackupDrop() {
        if (window.top !== window) return;
        const ui = this.ui, t = ruT;
        let ov = null, hideT = 0;
        const hide = () => { clearTimeout(hideT); if (ov) { ov.remove(); ov = null; } };
        const hasJson = e => {
            const dt = e.dataTransfer;
            if (!dt || !Array.from(dt.types || []).includes('Files')) return false;
            return Array.from(dt.items || []).some(x => x.kind === 'file' && /json/i.test(x.type));
        };
        const show = () => {
            if (!ov) {
                ov = document.createElement('div');
                ov.style.cssText = 'position:fixed;inset:10px;z-index:2147483647;pointer-events:none;display:flex;align-items:center;justify-content:center;border:3px dashed var(--th,#dc143c);border-radius:18px;background:rgba(0,0,0,.6);color:#fff;font:700 20px/1.3 system-ui,sans-serif;text-align:center;padding:20px';
                ov.textContent = t('Отпустите файл, чтобы импортировать резервную копию', 'Drop the file to import the backup');
                (document.body || document.documentElement).appendChild(ov);
            }
            clearTimeout(hideT);
            hideT = setTimeout(hide, 700);
        };
        window.addEventListener('dragover', e => {
            if (e.defaultPrevented || !hasJson(e)) return;
            e.preventDefault();
            try { e.dataTransfer.dropEffect = 'copy'; } catch (err) { dbg(err, 'bindBackupDrop'); }
            show();
        });
        window.addEventListener('dragend', hide);
        window.addEventListener('drop', e => {
            hide();
            if (e.defaultPrevented) return;
            const f = Array.from(e.dataTransfer?.files || []).find(Backup.isJsonFile);
            if (!f) return;
            e.preventDefault();
            ui.backupFile(f);
        });
    }

    bindRemoteAndWindow(vc) {
        onRemoteChange('bc_bookmarks', v => this.bookmarks.applyRemote(v));
        onRemoteChange('bc_blur_mute', v => {
            try { AppState.blurMute = JSON.parse(v) === true; } catch (e) { dbg(e, 'App.bindRemoteAndWindow'); }
            vc.applyBlur(vc.cachedVideo);
        });
        this.bindBackupDrop();
        onRemoteChange('bc_gmute', v => {
            try { AppState.gmute = JSON.parse(v) === true; } catch (e) { dbg(e, 'App.bindRemoteAndWindow'); }
            const cv = vc.cachedVideo;
            if (AppState.gmute && cv) hardMute(cv);
            vc.syncUI();
        });

        window.addEventListener('resize', () => {
            if (AppState.hasPos) this.ui.clampPosition();
        }, { passive: true });
        onPageEvent('visibilitychange', () => {
            if (realHidden() || !AppState.hasPos) return;
            setTimeout(() => { this.ui.applyPosition(); this.ui.clampPosition(); }, 150);
        });
    }

    startTimers() {
        let tick = 0;
        setInterval(() => {
            if (realHidden()) return;
            tick++;
            this.ui.injectSiteCSS();
            this.updateViewersCount();
            this.updateQualBadge();
            if (tick % 5 === 0) this.slowTasks();
        }, 3000);
        ['resize', 'loadedmetadata'].forEach(t => document.addEventListener(t, e => { if (e.target && e.target.tagName === 'VIDEO') this.updateQualBadge(); }, true));
        setTimeout(() => this.updateViewersCount(), 1000);
    }

    slowTasks() {
        try { applyDark(); } catch (e) { dbg(e, 'App.slowTasks'); }
        this.ui.ensureVisible();
    }

    updateQualBadge() {
        const btn = this.ui.elements.bQual;
        if (!btn) return;
        const v = this.videoController?.cachedVideo;
        const h = v && v.videoWidth && v.videoHeight ? Math.min(v.videoWidth, v.videoHeight) : 0;
        let b = btn._qBadge;
        if (!h) { if (b) b.style.display = 'none'; return; }
        if (!b || !b.isConnected) {
            b = btn._qBadge = document.createElement('span');
            b.style.cssText = 'position:absolute;z-index:2;padding:0;background:none;border:0;font:700 10px/10px system-ui,sans-serif;letter-spacing:0;pointer-events:none;white-space:nowrap';
            btn.style.position = 'relative';
            btn.appendChild(b);
        }
        const txt = h >= 2000 ? '4K' : h >= 1400 ? '2K' : String(h);
        const gold = h >= 1000 && h < 1400, red = h >= 1400;
        b.style.display = '';
        b.style.color = red ? '#ff4d5e' : gold ? '#F2B93B' : 'rgba(255,255,255,.7)';
        const ic = btn.firstChild;
        const top = ic && ic.offsetHeight ? Math.max(1, ic.offsetTop - 6) : 2;
        b.style.left = 'auto';
        b.style.right = '4px';
        b.style.top = top + 'px';
        if (b.textContent !== txt) b.textContent = txt;
    }

    updateViewersCount() {
        const name = currentSite.getName();
        const { stats, bFav } = this.ui.elements;
        if (stats) stats.style.visibility = name ? 'visible' : 'hidden';
        markFav(bFav, !!name && isFavName(name));
        if (!name) return;

        const txt = stats && readViewers();
        if (txt) paintViewers(stats, txt);
        const vn = parseCount(txt);
        if (Number.isFinite(vn)) this.bookmarks.setViewers(pageKey(), vn);
    }
}

const CBHP = { api: null };
const cbhpRemoteApi = () => {
    const MODES = ['off', 'anim', 'video'], HEIGHTS = [240, 360, 480, 720];
    const rd = (k, d) => {
        try { const v = GM_getValue(`cb:${k}`); return v == null ? d : JSON.parse(v); } catch { return d; }
    };
    const wr = (k, v) => { try { GM_setValue(`cb:${k}`, JSON.stringify(v)); } catch (e) { dbg(e, 'cbhpRemoteApi'); } };
    const mode = rd('cbhp_mode', 'video'), maxH = +rd('cbhp_maxh', 360);
    return {
        modes: MODES,
        heights: HEIGHTS,
        cfg: {
            mode: MODES.includes(mode) ? mode : 'video',
            sound: rd('cbhp_sound', false) === true,
            maxH: HEIGHTS.includes(maxH) ? maxH : 360
        },
        setMode: m => { if (MODES.includes(m)) wr('cbhp_mode', m); },
        setSound: on => wr('cbhp_sound', on === true),
        setMaxH: h => { if (HEIGHTS.includes(+h)) wr('cbhp_maxh', +h); }
    };
};
const HLSJS_SRC = `(function __HLS_WORKER_BUNDLE__(__IN_WORKER__){
(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Hls = factory());
})(this, (function () { 'use strict';

  function _arrayLikeToArray(r, a) {
    (null == a || a > r.length) && (a = r.length);
    for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
    return n;
  }
  function _construct(t, e, r) {
    if (_isNativeReflectConstruct()) return Reflect.construct.apply(null, arguments);
    var o = [null];
    o.push.apply(o, e);
    var p = new (t.bind.apply(t, o))();
    return r && _setPrototypeOf(p, r.prototype), p;
  }
  function _defineProperties(e, r) {
    for (var t = 0; t < r.length; t++) {
      var o = r[t];
      o.enumerable = o.enumerable || false, o.configurable = true, "value" in o && (o.writable = true), Object.defineProperty(e, _toPropertyKey(o.key), o);
    }
  }
  function _createClass(e, r, t) {
    return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", {
      writable: false
    }), e;
  }
  function _createForOfIteratorHelperLoose(r, e) {
    var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
    if (t) return (t = t.call(r)).next.bind(t);
    if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e) {
      t && (r = t);
      var o = 0;
      return function () {
        return o >= r.length ? {
          done: true
        } : {
          done: false,
          value: r[o++]
        };
      };
    }
    throw new TypeError("Invalid attempt to iterate non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
  }
  function _defineProperty(e, r, t) {
    return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
      value: t,
      enumerable: true,
      configurable: true,
      writable: true
    }) : e[r] = t, e;
  }
  function _extends() {
    return _extends = Object.assign ? Object.assign.bind() : function (n) {
      for (var e = 1; e < arguments.length; e++) {
        var t = arguments[e];
        for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
      }
      return n;
    }, _extends.apply(null, arguments);
  }
  function _getPrototypeOf(t) {
    return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) {
      return t.__proto__ || Object.getPrototypeOf(t);
    }, _getPrototypeOf(t);
  }
  function _inheritsLoose(t, o) {
    t.prototype = Object.create(o.prototype), t.prototype.constructor = t, _setPrototypeOf(t, o);
  }
  function _isNativeFunction(t) {
    try {
      return -1 !== Function.toString.call(t).indexOf("[native code]");
    } catch (n) {
      return "function" == typeof t;
    }
  }
  function _isNativeReflectConstruct() {
    try {
      var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
    } catch (t) {}
    return (_isNativeReflectConstruct = function () {
      return !!t;
    })();
  }
  function ownKeys(e, r) {
    var t = Object.keys(e);
    if (Object.getOwnPropertySymbols) {
      var o = Object.getOwnPropertySymbols(e);
      r && (o = o.filter(function (r) {
        return Object.getOwnPropertyDescriptor(e, r).enumerable;
      })), t.push.apply(t, o);
    }
    return t;
  }
  function _objectSpread2(e) {
    for (var r = 1; r < arguments.length; r++) {
      var t = null != arguments[r] ? arguments[r] : {};
      r % 2 ? ownKeys(Object(t), true).forEach(function (r) {
        _defineProperty(e, r, t[r]);
      }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
        Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
      });
    }
    return e;
  }
  function _setPrototypeOf(t, e) {
    return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) {
      return t.__proto__ = e, t;
    }, _setPrototypeOf(t, e);
  }
  function _toPrimitive(t, r) {
    if ("object" != typeof t || !t) return t;
    var e = t[Symbol.toPrimitive];
    if (void 0 !== e) {
      var i = e.call(t, r);
      if ("object" != typeof i) return i;
      throw new TypeError("@@toPrimitive must return a primitive value.");
    }
    return ("string" === r ? String : Number)(t);
  }
  function _toPropertyKey(t) {
    var i = _toPrimitive(t, "string");
    return "symbol" == typeof i ? i : i + "";
  }
  function _unsupportedIterableToArray(r, a) {
    if (r) {
      if ("string" == typeof r) return _arrayLikeToArray(r, a);
      var t = {}.toString.call(r).slice(8, -1);
      return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
    }
  }
  function _wrapNativeSuper(t) {
    var r = "function" == typeof Map ? new Map() : void 0;
    return _wrapNativeSuper = function (t) {
      if (null === t || !_isNativeFunction(t)) return t;
      if ("function" != typeof t) throw new TypeError("Super expression must either be null or a function");
      if (void 0 !== r) {
        if (r.has(t)) return r.get(t);
        r.set(t, Wrapper);
      }
      function Wrapper() {
        return _construct(t, arguments, _getPrototypeOf(this).constructor);
      }
      return Wrapper.prototype = Object.create(t.prototype, {
        constructor: {
          value: Wrapper,
          enumerable: false,
          writable: true,
          configurable: true
        }
      }), _setPrototypeOf(Wrapper, t);
    }, _wrapNativeSuper(t);
  }

  function isArrayBufferLike(value) {
    return value instanceof ArrayBuffer || typeof SharedArrayBuffer !== "undefined" && value instanceof SharedArrayBuffer;
  }

  var UTF_16 = "utf-16";

  var UTF_16_BE = "utf-16be";

  var UTF_16_LE = "utf-16le";

  var UTF_8 = "utf-8";

  function decodeText(data, options) {
    if (options === void 0) {
      options = {};
    }
    var view;
    if (isArrayBufferLike(data)) view = new DataView(data);else view = new DataView(data.buffer, data.byteOffset, data.byteLength);
    var byteOffset = 0;
    var _options = options,
      encoding = _options.encoding;
    if (!encoding) {
      var first = view.getUint8(0);
      var second = view.getUint8(1);
      if (first == 239 && second == 187 && view.getUint8(2) == 191) {
        encoding = UTF_8;
        byteOffset = 3;
      } else if (first == 254 && second == 255) {
        encoding = UTF_16_BE;
        byteOffset = 2;
      } else if (first == 255 && second == 254) {
        encoding = UTF_16_LE;
        byteOffset = 2;
      } else encoding = UTF_8;
    }
    if (typeof TextDecoder !== "undefined") return new TextDecoder(encoding).decode(view);
    var _view = view,
      byteLength = _view.byteLength;
    var endian = encoding !== UTF_16_BE;
    var str = "";
    var char;
    while (byteOffset < byteLength) {
      switch (encoding) {
        case UTF_8:
          char = view.getUint8(byteOffset);
          if (char < 128) byteOffset++;else if (char >= 194 && char <= 223) {
            if (byteOffset + 1 < byteLength) {
              var byte2 = view.getUint8(byteOffset + 1);
              if (byte2 >= 128 && byte2 <= 191) {
                char = (char & 31) << 6 | byte2 & 63;
                byteOffset += 2;
              } else byteOffset++;
            } else byteOffset++;
          } else if (char >= 224 && char <= 239) {
            if (byteOffset + 2 <= byteLength - 1) {
              var _byte = view.getUint8(byteOffset + 1);
              var byte3 = view.getUint8(byteOffset + 2);
              if (_byte >= 128 && _byte <= 191 && byte3 >= 128 && byte3 <= 191) {
                char = (char & 15) << 12 | (_byte & 63) << 6 | byte3 & 63;
                byteOffset += 3;
              } else byteOffset++;
            } else byteOffset++;
          } else if (char >= 240 && char <= 244) {
            if (byteOffset + 3 <= byteLength - 1) {
              var _byte2 = view.getUint8(byteOffset + 1);
              var _byte3 = view.getUint8(byteOffset + 2);
              var byte4 = view.getUint8(byteOffset + 3);
              if (_byte2 >= 128 && _byte2 <= 191 && _byte3 >= 128 && _byte3 <= 191 && byte4 >= 128 && byte4 <= 191) {
                char = (char & 7) << 18 | (_byte2 & 63) << 12 | (_byte3 & 63) << 6 | byte4 & 63;
                byteOffset += 4;
              } else byteOffset++;
            } else byteOffset++;
          } else byteOffset++;
          break;
        case UTF_16_BE:
        case UTF_16:
        case UTF_16_LE:
          char = view.getUint16(byteOffset, endian);
          byteOffset += 2;
          break;
      }
      str += String.fromCodePoint(char);
    }
    return str;
  }

  function uuid() {
    try {
      return crypto.randomUUID();
    } catch (error) {
      try {
        var url = URL.createObjectURL(new Blob());
        var uuid$1 = url.toString();
        URL.revokeObjectURL(url);
        return uuid$1.slice(uuid$1.lastIndexOf("/") + 1);
      } catch (error$1) {
        var dt = ( new Date()).getTime();
        return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
          var r = (dt + Math.random() * 16) % 16 | 0;
          dt = Math.floor(dt / 16);
          return (c == "x" ? r : r & 3 | 8).toString(16);
        });
      }
    }
  }

  function getDefaultExportFromCjs (x) {
  	return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
  }

  var eventemitter3 = {exports: {}};

  var hasRequiredEventemitter3;

  function requireEventemitter3 () {
  	if (hasRequiredEventemitter3) return eventemitter3.exports;
  	hasRequiredEventemitter3 = 1;
  	(function (module) {

  		var has = Object.prototype.hasOwnProperty
  		  , prefix = '~';

  		function Events() {}

  		if (Object.create) {
  		  Events.prototype = Object.create(null);

  		  if (!new Events().__proto__) prefix = false;
  		}

  		function EE(fn, context, once) {
  		  this.fn = fn;
  		  this.context = context;
  		  this.once = once || false;
  		}

  		function addListener(emitter, event, fn, context, once) {
  		  if (typeof fn !== 'function') {
  		    throw new TypeError('The listener must be a function');
  		  }

  		  var listener = new EE(fn, context || emitter, once)
  		    , evt = prefix ? prefix + event : event;

  		  if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++;
  		  else if (!emitter._events[evt].fn) emitter._events[evt].push(listener);
  		  else emitter._events[evt] = [emitter._events[evt], listener];

  		  return emitter;
  		}

  		function clearEvent(emitter, evt) {
  		  if (--emitter._eventsCount === 0) emitter._events = new Events();
  		  else delete emitter._events[evt];
  		}

  		function EventEmitter() {
  		  this._events = new Events();
  		  this._eventsCount = 0;
  		}

  		EventEmitter.prototype.eventNames = function eventNames() {
  		  var names = []
  		    , events
  		    , name;

  		  if (this._eventsCount === 0) return names;

  		  for (name in (events = this._events)) {
  		    if (has.call(events, name)) names.push(prefix ? name.slice(1) : name);
  		  }

  		  if (Object.getOwnPropertySymbols) {
  		    return names.concat(Object.getOwnPropertySymbols(events));
  		  }

  		  return names;
  		};

  		EventEmitter.prototype.listeners = function listeners(event) {
  		  var evt = prefix ? prefix + event : event
  		    , handlers = this._events[evt];

  		  if (!handlers) return [];
  		  if (handlers.fn) return [handlers.fn];

  		  for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) {
  		    ee[i] = handlers[i].fn;
  		  }

  		  return ee;
  		};

  		EventEmitter.prototype.listenerCount = function listenerCount(event) {
  		  var evt = prefix ? prefix + event : event
  		    , listeners = this._events[evt];

  		  if (!listeners) return 0;
  		  if (listeners.fn) return 1;
  		  return listeners.length;
  		};

  		EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) {
  		  var evt = prefix ? prefix + event : event;

  		  if (!this._events[evt]) return false;

  		  var listeners = this._events[evt]
  		    , len = arguments.length
  		    , args
  		    , i;

  		  if (listeners.fn) {
  		    if (listeners.once) this.removeListener(event, listeners.fn, undefined, true);

  		    switch (len) {
  		      case 1: return listeners.fn.call(listeners.context), true;
  		      case 2: return listeners.fn.call(listeners.context, a1), true;
  		      case 3: return listeners.fn.call(listeners.context, a1, a2), true;
  		      case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true;
  		      case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true;
  		      case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true;
  		    }

  		    for (i = 1, args = new Array(len -1); i < len; i++) {
  		      args[i - 1] = arguments[i];
  		    }

  		    listeners.fn.apply(listeners.context, args);
  		  } else {
  		    var length = listeners.length
  		      , j;

  		    for (i = 0; i < length; i++) {
  		      if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true);

  		      switch (len) {
  		        case 1: listeners[i].fn.call(listeners[i].context); break;
  		        case 2: listeners[i].fn.call(listeners[i].context, a1); break;
  		        case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break;
  		        case 4: listeners[i].fn.call(listeners[i].context, a1, a2, a3); break;
  		        default:
  		          if (!args) for (j = 1, args = new Array(len -1); j < len; j++) {
  		            args[j - 1] = arguments[j];
  		          }

  		          listeners[i].fn.apply(listeners[i].context, args);
  		      }
  		    }
  		  }

  		  return true;
  		};

  		EventEmitter.prototype.on = function on(event, fn, context) {
  		  return addListener(this, event, fn, context, false);
  		};

  		EventEmitter.prototype.once = function once(event, fn, context) {
  		  return addListener(this, event, fn, context, true);
  		};

  		EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) {
  		  var evt = prefix ? prefix + event : event;

  		  if (!this._events[evt]) return this;
  		  if (!fn) {
  		    clearEvent(this, evt);
  		    return this;
  		  }

  		  var listeners = this._events[evt];

  		  if (listeners.fn) {
  		    if (
  		      listeners.fn === fn &&
  		      (!once || listeners.once) &&
  		      (!context || listeners.context === context)
  		    ) {
  		      clearEvent(this, evt);
  		    }
  		  } else {
  		    for (var i = 0, events = [], length = listeners.length; i < length; i++) {
  		      if (
  		        listeners[i].fn !== fn ||
  		        (once && !listeners[i].once) ||
  		        (context && listeners[i].context !== context)
  		      ) {
  		        events.push(listeners[i]);
  		      }
  		    }

  		    if (events.length) this._events[evt] = events.length === 1 ? events[0] : events;
  		    else clearEvent(this, evt);
  		  }

  		  return this;
  		};

  		EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) {
  		  var evt;

  		  if (event) {
  		    evt = prefix ? prefix + event : event;
  		    if (this._events[evt]) clearEvent(this, evt);
  		  } else {
  		    this._events = new Events();
  		    this._eventsCount = 0;
  		  }

  		  return this;
  		};

  		EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
  		EventEmitter.prototype.addListener = EventEmitter.prototype.on;

  		EventEmitter.prefixed = prefix;

  		EventEmitter.EventEmitter = EventEmitter;

  		{
  		  module.exports = EventEmitter;
  		} 
  	} (eventemitter3));
  	return eventemitter3.exports;
  }

  var eventemitter3Exports = requireEventemitter3();
  var EventEmitter = getDefaultExportFromCjs(eventemitter3Exports);

  var urlToolkit = {exports: {}};

  var hasRequiredUrlToolkit;

  function requireUrlToolkit () {
  	if (hasRequiredUrlToolkit) return urlToolkit.exports;
  	hasRequiredUrlToolkit = 1;
  	(function (module, exports) {

  		(function (root) {
  		  var URL_REGEX =
  		    /^(?=((?:[a-zA-Z0-9+\\-.]+:)?))\\1(?=((?:\\/\\/[^\\/?#]*)?))\\2(?=((?:(?:[^?#\\/]*\\/)*[^;?#\\/]*)?))\\3((?:;[^?#]*)?)(\\?[^#]*)?(#[^]*)?$/;
  		  var FIRST_SEGMENT_REGEX = /^(?=([^\\/?#]*))\\1([^]*)$/;
  		  var SLASH_DOT_REGEX = /(?:\\/|^)\\.(?=\\/)/g;
  		  var SLASH_DOT_DOT_REGEX = /(?:\\/|^)\\.\\.\\/(?!\\.\\.\\/)[^\\/]*(?=\\/)/g;

  		  var URLToolkit = {
  		    buildAbsoluteURL: function (baseURL, relativeURL, opts) {
  		      opts = opts || {};
  		      baseURL = baseURL.trim();
  		      relativeURL = relativeURL.trim();
  		      if (!relativeURL) {
  		        if (!opts.alwaysNormalize) {
  		          return baseURL;
  		        }
  		        var basePartsForNormalise = URLToolkit.parseURL(baseURL);
  		        if (!basePartsForNormalise) {
  		          throw new Error('Error trying to parse base URL.');
  		        }
  		        basePartsForNormalise.path = URLToolkit.normalizePath(
  		          basePartsForNormalise.path
  		        );
  		        return URLToolkit.buildURLFromParts(basePartsForNormalise);
  		      }
  		      var relativeParts = URLToolkit.parseURL(relativeURL);
  		      if (!relativeParts) {
  		        throw new Error('Error trying to parse relative URL.');
  		      }
  		      if (relativeParts.scheme) {
  		        if (!opts.alwaysNormalize) {
  		          return relativeURL;
  		        }
  		        relativeParts.path = URLToolkit.normalizePath(relativeParts.path);
  		        return URLToolkit.buildURLFromParts(relativeParts);
  		      }
  		      var baseParts = URLToolkit.parseURL(baseURL);
  		      if (!baseParts) {
  		        throw new Error('Error trying to parse base URL.');
  		      }
  		      if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') {
  		        var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path);
  		        baseParts.netLoc = pathParts[1];
  		        baseParts.path = pathParts[2];
  		      }
  		      if (baseParts.netLoc && !baseParts.path) {
  		        baseParts.path = '/';
  		      }
  		      var builtParts = {
  		        scheme: baseParts.scheme,
  		        netLoc: relativeParts.netLoc,
  		        path: null,
  		        params: relativeParts.params,
  		        query: relativeParts.query,
  		        fragment: relativeParts.fragment,
  		      };
  		      if (!relativeParts.netLoc) {
  		        builtParts.netLoc = baseParts.netLoc;
  		        if (relativeParts.path[0] !== '/') {
  		          if (!relativeParts.path) {
  		            builtParts.path = baseParts.path;
  		            if (!relativeParts.params) {
  		              builtParts.params = baseParts.params;
  		              if (!relativeParts.query) {
  		                builtParts.query = baseParts.query;
  		              }
  		            }
  		          } else {
  		            var baseURLPath = baseParts.path;
  		            var newPath =
  		              baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) +
  		              relativeParts.path;
  		            builtParts.path = URLToolkit.normalizePath(newPath);
  		          }
  		        }
  		      }
  		      if (builtParts.path === null) {
  		        builtParts.path = opts.alwaysNormalize
  		          ? URLToolkit.normalizePath(relativeParts.path)
  		          : relativeParts.path;
  		      }
  		      return URLToolkit.buildURLFromParts(builtParts);
  		    },
  		    parseURL: function (url) {
  		      var parts = URL_REGEX.exec(url);
  		      if (!parts) {
  		        return null;
  		      }
  		      return {
  		        scheme: parts[1] || '',
  		        netLoc: parts[2] || '',
  		        path: parts[3] || '',
  		        params: parts[4] || '',
  		        query: parts[5] || '',
  		        fragment: parts[6] || '',
  		      };
  		    },
  		    normalizePath: function (path) {
  		      path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, '');
  		      while (
  		        path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length
  		      ) {}
  		      return path.split('').reverse().join('');
  		    },
  		    buildURLFromParts: function (parts) {
  		      return (
  		        parts.scheme +
  		        parts.netLoc +
  		        parts.path +
  		        parts.params +
  		        parts.query +
  		        parts.fragment
  		      );
  		    },
  		  };

  		  module.exports = URLToolkit;
  		})(); 
  	} (urlToolkit));
  	return urlToolkit.exports;
  }

  var urlToolkitExports = requireUrlToolkit();

  var isFiniteNumber = Number.isFinite || function (value) {
    return typeof value === 'number' && isFinite(value);
  };

  var isSafeInteger = Number.isSafeInteger || function (value) {
    return typeof value === 'number' && Math.abs(value) <= MAX_SAFE_INTEGER;
  };
  var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;

  var ErrorTypes = function (ErrorTypes) {
    ErrorTypes["NETWORK_ERROR"] = "networkError";
    ErrorTypes["MEDIA_ERROR"] = "mediaError";
    ErrorTypes["KEY_SYSTEM_ERROR"] = "keySystemError";
    ErrorTypes["MUX_ERROR"] = "muxError";
    ErrorTypes["OTHER_ERROR"] = "otherError";
    return ErrorTypes;
  }({});
  var ErrorDetails = function (ErrorDetails) {
    ErrorDetails["KEY_SYSTEM_NO_KEYS"] = "keySystemNoKeys";
    ErrorDetails["KEY_SYSTEM_NO_ACCESS"] = "keySystemNoAccess";
    ErrorDetails["KEY_SYSTEM_NO_SESSION"] = "keySystemNoSession";
    ErrorDetails["KEY_SYSTEM_NO_CONFIGURED_LICENSE"] = "keySystemNoConfiguredLicense";
    ErrorDetails["KEY_SYSTEM_LICENSE_REQUEST_FAILED"] = "keySystemLicenseRequestFailed";
    ErrorDetails["KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED"] = "keySystemServerCertificateRequestFailed";
    ErrorDetails["KEY_SYSTEM_SERVER_CERTIFICATE_UPDATE_FAILED"] = "keySystemServerCertificateUpdateFailed";
    ErrorDetails["KEY_SYSTEM_SESSION_UPDATE_FAILED"] = "keySystemSessionUpdateFailed";
    ErrorDetails["KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED"] = "keySystemStatusOutputRestricted";
    ErrorDetails["KEY_SYSTEM_STATUS_INTERNAL_ERROR"] = "keySystemStatusInternalError";
    ErrorDetails["KEY_SYSTEM_DESTROY_MEDIA_KEYS_ERROR"] = "keySystemDestroyMediaKeysError";
    ErrorDetails["KEY_SYSTEM_DESTROY_CLOSE_SESSION_ERROR"] = "keySystemDestroyCloseSessionError";
    ErrorDetails["KEY_SYSTEM_DESTROY_REMOVE_SESSION_ERROR"] = "keySystemDestroyRemoveSessionError";
    ErrorDetails["MANIFEST_LOAD_ERROR"] = "manifestLoadError";
    ErrorDetails["MANIFEST_LOAD_TIMEOUT"] = "manifestLoadTimeOut";
    ErrorDetails["MANIFEST_PARSING_ERROR"] = "manifestParsingError";
    ErrorDetails["MANIFEST_INCOMPATIBLE_CODECS_ERROR"] = "manifestIncompatibleCodecsError";
    ErrorDetails["LEVEL_EMPTY_ERROR"] = "levelEmptyError";
    ErrorDetails["PLAYLIST_UNCHANGED_ERROR"] = "playlistUnchangedError";
    ErrorDetails["LEVEL_LOAD_ERROR"] = "levelLoadError";
    ErrorDetails["LEVEL_LOAD_TIMEOUT"] = "levelLoadTimeOut";
    ErrorDetails["LEVEL_PARSING_ERROR"] = "levelParsingError";
    ErrorDetails["LEVEL_SWITCH_ERROR"] = "levelSwitchError";
    ErrorDetails["AUDIO_TRACK_LOAD_ERROR"] = "audioTrackLoadError";
    ErrorDetails["AUDIO_TRACK_LOAD_TIMEOUT"] = "audioTrackLoadTimeOut";
    ErrorDetails["SUBTITLE_LOAD_ERROR"] = "subtitleTrackLoadError";
    ErrorDetails["SUBTITLE_TRACK_LOAD_TIMEOUT"] = "subtitleTrackLoadTimeOut";
    ErrorDetails["FRAG_LOAD_ERROR"] = "fragLoadError";
    ErrorDetails["FRAG_LOAD_TIMEOUT"] = "fragLoadTimeOut";
    ErrorDetails["FRAG_DECRYPT_ERROR"] = "fragDecryptError";
    ErrorDetails["FRAG_PARSING_ERROR"] = "fragParsingError";
    ErrorDetails["FRAG_GAP"] = "fragGap";
    ErrorDetails["REMUX_ALLOC_ERROR"] = "remuxAllocError";
    ErrorDetails["KEY_LOAD_ERROR"] = "keyLoadError";
    ErrorDetails["KEY_LOAD_TIMEOUT"] = "keyLoadTimeOut";
    ErrorDetails["BUFFER_ADD_CODEC_ERROR"] = "bufferAddCodecError";
    ErrorDetails["BUFFER_INCOMPATIBLE_CODECS_ERROR"] = "bufferIncompatibleCodecsError";
    ErrorDetails["BUFFER_APPEND_ERROR"] = "bufferAppendError";
    ErrorDetails["BUFFER_APPENDING_ERROR"] = "bufferAppendingError";
    ErrorDetails["BUFFER_APPEND_NO_PROGRESS"] = "bufferAppendNoProgress";
    ErrorDetails["BUFFER_STALLED_ERROR"] = "bufferStalledError";
    ErrorDetails["BUFFER_FULL_ERROR"] = "bufferFullError";
    ErrorDetails["BUFFER_SEEK_OVER_HOLE"] = "bufferSeekOverHole";
    ErrorDetails["BUFFER_NUDGE_ON_STALL"] = "bufferNudgeOnStall";
    ErrorDetails["ASSET_LIST_LOAD_ERROR"] = "assetListLoadError";
    ErrorDetails["ASSET_LIST_LOAD_TIMEOUT"] = "assetListLoadTimeout";
    ErrorDetails["ASSET_LIST_PARSING_ERROR"] = "assetListParsingError";
    ErrorDetails["INTERSTITIAL_ASSET_ITEM_ERROR"] = "interstitialAssetItemError";
    ErrorDetails["INTERNAL_EXCEPTION"] = "internalException";
    ErrorDetails["INTERNAL_ABORTED"] = "aborted";
    ErrorDetails["ATTACH_MEDIA_ERROR"] = "attachMediaError";
    ErrorDetails["MEDIA_SOURCE_REQUIRES_RESET"] = "mediaSourceRequiresReset";
    ErrorDetails["UNKNOWN"] = "unknown";
    return ErrorDetails;
  }({});

  var Events = function (Events) {
    Events["MEDIA_ATTACHING"] = "hlsMediaAttaching";
    Events["MEDIA_ATTACHED"] = "hlsMediaAttached";
    Events["MEDIA_DETACHING"] = "hlsMediaDetaching";
    Events["MEDIA_DETACHED"] = "hlsMediaDetached";
    Events["MEDIA_ENDED"] = "hlsMediaEnded";
    Events["STALL_RESOLVED"] = "hlsStallResolved";
    Events["BUFFER_RESET"] = "hlsBufferReset";
    Events["BUFFER_CODECS"] = "hlsBufferCodecs";
    Events["BUFFER_CREATED"] = "hlsBufferCreated";
    Events["BUFFER_APPENDING"] = "hlsBufferAppending";
    Events["BUFFER_APPENDED"] = "hlsBufferAppended";
    Events["BUFFER_EOS"] = "hlsBufferEos";
    Events["BUFFERED_TO_END"] = "hlsBufferedToEnd";
    Events["BUFFER_FLUSHING"] = "hlsBufferFlushing";
    Events["BUFFER_FLUSHED"] = "hlsBufferFlushed";
    Events["MANIFEST_LOADING"] = "hlsManifestLoading";
    Events["MANIFEST_LOADED"] = "hlsManifestLoaded";
    Events["MANIFEST_PARSED"] = "hlsManifestParsed";
    Events["LEVEL_SWITCHING"] = "hlsLevelSwitching";
    Events["LEVEL_SWITCHED"] = "hlsLevelSwitched";
    Events["LEVEL_LOADING"] = "hlsLevelLoading";
    Events["LEVEL_LOADED"] = "hlsLevelLoaded";
    Events["LEVEL_UPDATED"] = "hlsLevelUpdated";
    Events["LEVEL_PTS_UPDATED"] = "hlsLevelPtsUpdated";
    Events["LEVELS_UPDATED"] = "hlsLevelsUpdated";
    Events["AUDIO_TRACKS_UPDATED"] = "hlsAudioTracksUpdated";
    Events["AUDIO_TRACK_SWITCHING"] = "hlsAudioTrackSwitching";
    Events["AUDIO_TRACK_SWITCHED"] = "hlsAudioTrackSwitched";
    Events["AUDIO_TRACK_LOADING"] = "hlsAudioTrackLoading";
    Events["AUDIO_TRACK_LOADED"] = "hlsAudioTrackLoaded";
    Events["AUDIO_TRACK_UPDATED"] = "hlsAudioTrackUpdated";
    Events["SUBTITLE_TRACKS_UPDATED"] = "hlsSubtitleTracksUpdated";
    Events["SUBTITLE_TRACKS_CLEARED"] = "hlsSubtitleTracksCleared";
    Events["SUBTITLE_TRACK_SWITCH"] = "hlsSubtitleTrackSwitch";
    Events["SUBTITLE_TRACK_LOADING"] = "hlsSubtitleTrackLoading";
    Events["SUBTITLE_TRACK_LOADED"] = "hlsSubtitleTrackLoaded";
    Events["SUBTITLE_TRACK_UPDATED"] = "hlsSubtitleTrackUpdated";
    Events["SUBTITLE_FRAG_PROCESSED"] = "hlsSubtitleFragProcessed";
    Events["CUES_PARSED"] = "hlsCuesParsed";
    Events["NON_NATIVE_TEXT_TRACKS_FOUND"] = "hlsNonNativeTextTracksFound";
    Events["INIT_PTS_FOUND"] = "hlsInitPtsFound";
    Events["FRAG_LOADING"] = "hlsFragLoading";
    Events["FRAG_LOAD_EMERGENCY_ABORTED"] = "hlsFragLoadEmergencyAborted";
    Events["FRAG_LOADED"] = "hlsFragLoaded";
    Events["FRAG_DECRYPTED"] = "hlsFragDecrypted";
    Events["FRAG_PARSING_INIT_SEGMENT"] = "hlsFragParsingInitSegment";
    Events["FRAG_PARSING_USERDATA"] = "hlsFragParsingUserdata";
    Events["FRAG_PARSING_METADATA"] = "hlsFragParsingMetadata";
    Events["FRAG_PARSED"] = "hlsFragParsed";
    Events["FRAG_BUFFERED"] = "hlsFragBuffered";
    Events["FRAG_CHANGED"] = "hlsFragChanged";
    Events["FPS_DROP"] = "hlsFpsDrop";
    Events["FPS_DROP_LEVEL_CAPPING"] = "hlsFpsDropLevelCapping";
    Events["MAX_AUTO_LEVEL_UPDATED"] = "hlsMaxAutoLevelUpdated";
    Events["ERROR"] = "hlsError";
    Events["DESTROYING"] = "hlsDestroying";
    Events["KEY_LOADING"] = "hlsKeyLoading";
    Events["KEY_LOADED"] = "hlsKeyLoaded";
    Events["LIVE_BACK_BUFFER_REACHED"] = "hlsLiveBackBufferReached";
    Events["BACK_BUFFER_REACHED"] = "hlsBackBufferReached";
    Events["STEERING_MANIFEST_LOADED"] = "hlsSteeringManifestLoaded";
    Events["ASSET_LIST_LOADING"] = "hlsAssetListLoading";
    Events["ASSET_LIST_LOADED"] = "hlsAssetListLoaded";
    Events["INTERSTITIALS_UPDATED"] = "hlsInterstitialsUpdated";
    Events["INTERSTITIALS_BUFFERED_TO_BOUNDARY"] = "hlsInterstitialsBufferedToBoundary";
    Events["INTERSTITIAL_ASSET_PLAYER_CREATED"] = "hlsInterstitialAssetPlayerCreated";
    Events["INTERSTITIAL_STARTED"] = "hlsInterstitialStarted";
    Events["INTERSTITIAL_ASSET_STARTED"] = "hlsInterstitialAssetStarted";
    Events["INTERSTITIAL_ASSET_ENDED"] = "hlsInterstitialAssetEnded";
    Events["INTERSTITIAL_ASSET_ERROR"] = "hlsInterstitialAssetError";
    Events["INTERSTITIAL_ENDED"] = "hlsInterstitialEnded";
    Events["INTERSTITIALS_PRIMARY_RESUMED"] = "hlsInterstitialsPrimaryResumed";
    Events["PLAYOUT_LIMIT_REACHED"] = "hlsPlayoutLimitReached";
    Events["EVENT_CUE_ENTER"] = "hlsEventCueEnter";
    return Events;
  }({});

  var LoadStats = function LoadStats() {
    this.aborted = false;
    this.loaded = 0;
    this.retry = 0;
    this.total = 0;
    this.chunkCount = 0;
    this.bwEstimate = 0;
    this.loading = {
      start: 0,
      first: 0,
      end: 0
    };
    this.parsing = {
      start: 0,
      end: 0
    };
    this.buffering = {
      start: 0,
      first: 0,
      end: 0
    };
  };

  var LoaderContextType = function (LoaderContextType) {
    LoaderContextType["MANIFEST"] = "manifest";
    LoaderContextType["LEVEL"] = "level";
    LoaderContextType["AUDIO_TRACK"] = "audioTrack";
    LoaderContextType["SUBTITLE_TRACK"] = "subtitleTrack";
    LoaderContextType["MEDIA_FRAGMENT"] = "media-fragment";
    LoaderContextType["KEY"] = "key";
    LoaderContextType["STEERING_MANIFEST"] = "steering-manifest";
    LoaderContextType["SERVER_CERTIFICATE"] = "server-certificate";
    LoaderContextType["INTERSTITIAL_ASSET_LIST"] = "interstitial-asset-list";
    return LoaderContextType;
  }({});
  var PlaylistLevelType = function (PlaylistLevelType) {
    PlaylistLevelType["MAIN"] = "main";
    PlaylistLevelType["AUDIO"] = "audio";
    PlaylistLevelType["SUBTITLE"] = "subtitle";
    return PlaylistLevelType;
  }({});

  var ElementaryStreamTypes = {
    AUDIO: "audio",
    VIDEO: "video",
    AUDIOVIDEO: "audiovideo"
  };
  var BaseSegment = function () {
    function BaseSegment(base) {
      this._byteRange = null;
      this._url = null;
      this._stats = null;
      this._streams = null;
      this.base = void 0;
      this.relurl = void 0;
      if (typeof base === 'string') {
        base = {
          url: base
        };
      }
      this.base = base;
      makeEnumerable(this, 'stats');
    }

    var _proto = BaseSegment.prototype;
    _proto.setByteRange = function setByteRange(value, previous) {
      var params = value.split('@', 2);
      var start;
      if (params.length === 1) {
        start = (previous == null ? void 0 : previous.byteRangeEndOffset) || 0;
      } else {
        start = parseInt(params[1]);
      }
      this._byteRange = [start, parseInt(params[0]) + start];
    };
    _proto.clearElementaryStreamInfo = function clearElementaryStreamInfo() {
      var elementaryStreams = this.elementaryStreams;
      elementaryStreams[ElementaryStreamTypes.AUDIO] = null;
      elementaryStreams[ElementaryStreamTypes.VIDEO] = null;
      elementaryStreams[ElementaryStreamTypes.AUDIOVIDEO] = null;
      this.url = null;
    };
    return _createClass(BaseSegment, [{
      key: "baseurl",
      get: function get() {
        return this.base.url;
      }
    }, {
      key: "byteRange",
      get: function get() {
        if (this._byteRange === null) {
          return [];
        }
        return this._byteRange;
      }
    }, {
      key: "byteRangeStartOffset",
      get: function get() {
        return this.byteRange[0];
      }
    }, {
      key: "byteRangeEndOffset",
      get: function get() {
        return this.byteRange[1];
      }
    }, {
      key: "elementaryStreams",
      get: function get() {
        if (this._streams === null) {
          var _this$_streams;
          this._streams = (_this$_streams = {}, _this$_streams[ElementaryStreamTypes.AUDIO] = null, _this$_streams[ElementaryStreamTypes.VIDEO] = null, _this$_streams[ElementaryStreamTypes.AUDIOVIDEO] = null, _this$_streams);
        }
        return this._streams;
      },
      set: function set(value) {
        this._streams = value;
      }
    }, {
      key: "hasStats",
      get: function get() {
        return this._stats !== null;
      }
    }, {
      key: "hasStreams",
      get: function get() {
        return this._streams !== null;
      }
    }, {
      key: "stats",
      get: function get() {
        if (this._stats === null) {
          this._stats = new LoadStats();
        }
        return this._stats;
      },
      set: function set(value) {
        this._stats = value;
      }
    }, {
      key: "url",
      get: function get() {
        if (!this._url && this.relurl) {
          this._url = urlToolkitExports.buildAbsoluteURL(this.baseurl, this.relurl, {
            alwaysNormalize: true
          });
        }
        return this._url || '';
      },
      set: function set(value) {
        this._url = value;
      }
    }]);
  }();
  function isMediaFragment(frag) {
    return frag.sn !== 'initSegment';
  }
  function mediaFragmentsAreEqual(frag, mediaFragment) {
    return frag.sn === (mediaFragment == null ? void 0 : mediaFragment.sn) && frag.level === mediaFragment.level;
  }
  function fragmentsAreEqual(frag, otherFrag) {
    return frag.sn === (otherFrag == null ? void 0 : otherFrag.sn) && frag.level === otherFrag.level && frag.cc === otherFrag.cc
  ;
  }

  var Fragment = function (_BaseSegment2) {
    function Fragment(type, base) {
      var _this;
      _this = _BaseSegment2.call(this, base) || this;
      _this._decryptdata = null;
      _this._programDateTime = null;
      _this._ref = null;
      _this._bitrate = void 0;
      _this.rawProgramDateTime = null;
      _this.tagList = [];
      _this.duration = 0;
      _this.sn = 0;
      _this.levelkeys = void 0;
      _this.type = void 0;
      _this.loader = null;
      _this.keyLoader = null;
      _this.level = -1;
      _this.cc = 0;
      _this.startPTS = void 0;
      _this.endPTS = void 0;
      _this.startDTS = void 0;
      _this.endDTS = void 0;
      _this.start = 0;
      _this.playlistOffset = 0;
      _this.deltaPTS = void 0;
      _this.maxStartPTS = void 0;
      _this.minEndPTS = void 0;
      _this.data = void 0;
      _this.bitrateTest = false;
      _this.title = null;
      _this.initSegment = null;
      _this.endList = void 0;
      _this.gap = void 0;
      _this.urlId = 0;
      _this.type = type;
      return _this;
    }
    _inheritsLoose(Fragment, _BaseSegment2);
    var _proto2 = Fragment.prototype;
    _proto2.addStart = function addStart(value) {
      this.setStart(this.start + value);
    };
    _proto2.setStart = function setStart(value) {
      this.start = value;
      if (this._ref) {
        this._ref.start = value;
      }
    };
    _proto2.setDuration = function setDuration(value) {
      this.duration = value;
      if (this._ref) {
        this._ref.duration = value;
      }
    };
    _proto2.setKeyFormat = function setKeyFormat(keyFormat) {
      var levelkeys = this.levelkeys;
      if (levelkeys) {
        var _this$_decryptdata;
        var key = levelkeys[keyFormat];
        if (key && !((_this$_decryptdata = this._decryptdata) != null && _this$_decryptdata.keyId)) {
          this._decryptdata = key.getDecryptData(this.sn, levelkeys);
        }
      }
    };
    _proto2.abortRequests = function abortRequests() {
      var _this$loader, _this$keyLoader;
      (_this$loader = this.loader) == null || _this$loader.abort();
      (_this$keyLoader = this.keyLoader) == null || _this$keyLoader.abort();
    };
    _proto2.setElementaryStreamInfo = function setElementaryStreamInfo(type, startPTS, endPTS, startDTS, endDTS, partial) {
      if (partial === void 0) {
        partial = false;
      }
      var elementaryStreams = this.elementaryStreams;
      var info = elementaryStreams[type];
      if (!info) {
        elementaryStreams[type] = {
          startPTS: startPTS,
          endPTS: endPTS,
          startDTS: startDTS,
          endDTS: endDTS,
          partial: partial
        };
        return;
      }
      info.startPTS = Math.min(info.startPTS, startPTS);
      info.endPTS = Math.max(info.endPTS, endPTS);
      info.startDTS = Math.min(info.startDTS, startDTS);
      info.endDTS = Math.max(info.endDTS, endDTS);
    };
    return _createClass(Fragment, [{
      key: "byteLength",
      get: function get() {
        if (this.hasStats) {
          var total = this.stats.total;
          if (total) {
            return total;
          }
        }
        if (this.byteRange.length) {
          var start = this.byteRange[0];
          var end = this.byteRange[1];
          if (isFiniteNumber(start) && isFiniteNumber(end)) {
            return end - start;
          }
        }
        return null;
      }
    }, {
      key: "bitrate",
      get: function get() {
        if (this.byteLength) {
          return this.byteLength * 8 / this.duration;
        }
        if (this._bitrate) {
          return this._bitrate;
        }
        return null;
      },
      set: function set(value) {
        this._bitrate = value;
      }
    }, {
      key: "decryptdata",
      get: function get() {
        var _this$_decryptdata2;
        var levelkeys = this.levelkeys;
        if (!levelkeys || levelkeys.NONE) {
          return null;
        }
        if (levelkeys.identity) {
          if (!this._decryptdata) {
            this._decryptdata = levelkeys.identity.getDecryptData(this.sn);
          }
        } else if (!((_this$_decryptdata2 = this._decryptdata) != null && _this$_decryptdata2.keyId)) {
          var keyFormats = Object.keys(levelkeys);
          if (keyFormats.length === 1) {
            var levelKey = this._decryptdata = levelkeys[keyFormats[0]] || null;
            if (levelKey) {
              this._decryptdata = levelKey.getDecryptData(this.sn, levelkeys);
            }
          }
        }
        return this._decryptdata;
      }
    }, {
      key: "end",
      get: function get() {
        return this.start + this.duration;
      }
    }, {
      key: "endProgramDateTime",
      get: function get() {
        if (this.programDateTime === null) {
          return null;
        }
        var duration = !isFiniteNumber(this.duration) ? 0 : this.duration;
        return this.programDateTime + duration * 1000;
      }
    }, {
      key: "encrypted",
      get: function get() {
        var _this$_decryptdata3;
        if ((_this$_decryptdata3 = this._decryptdata) != null && _this$_decryptdata3.encrypted) {
          return true;
        } else if (this.levelkeys) {
          var _this$levelkeys$keyFo;
          var keyFormats = Object.keys(this.levelkeys);
          var len = keyFormats.length;
          if (len > 1 || len === 1 && (_this$levelkeys$keyFo = this.levelkeys[keyFormats[0]]) != null && _this$levelkeys$keyFo.encrypted) {
            return true;
          }
        }
        return false;
      }
    }, {
      key: "programDateTime",
      get: function get() {
        if (this._programDateTime === null && this.rawProgramDateTime) {
          this.programDateTime = Date.parse(this.rawProgramDateTime);
        }
        return this._programDateTime;
      },
      set: function set(value) {
        if (!isFiniteNumber(value)) {
          this._programDateTime = this.rawProgramDateTime = null;
          return;
        }
        this._programDateTime = value;
      }
    }, {
      key: "ref",
      get: function get() {
        if (!isMediaFragment(this)) {
          return null;
        }
        if (!this._ref) {
          this._ref = {
            base: this.base,
            start: this.start,
            duration: this.duration,
            sn: this.sn,
            programDateTime: this.programDateTime
          };
        }
        return this._ref;
      }
    }]);
  }(BaseSegment);

  var Part = function (_BaseSegment3) {
    function Part(partAttrs, frag, base, index, previous) {
      var _this2;
      _this2 = _BaseSegment3.call(this, base) || this;
      _this2.fragOffset = 0;
      _this2.duration = 0;
      _this2.independent = false;
      _this2.relurl = void 0;
      _this2.fragment = void 0;
      _this2.index = void 0;
      _this2.gap = false;
      _this2.duration = partAttrs.decimalFloatingPoint('DURATION');
      _this2.gap = partAttrs.bool('GAP');
      _this2.independent = partAttrs.bool('INDEPENDENT');
      _this2.relurl = partAttrs.enumeratedString('URI');
      _this2.fragment = frag;
      _this2.index = index;
      var byteRange = partAttrs.enumeratedString('BYTERANGE');
      if (byteRange) {
        _this2.setByteRange(byteRange, previous);
      }
      if (previous) {
        _this2.fragOffset = previous.fragOffset + previous.duration;
      }
      return _this2;
    }
    _inheritsLoose(Part, _BaseSegment3);
    return _createClass(Part, [{
      key: "start",
      get: function get() {
        return this.fragment.start + this.fragOffset;
      }
    }, {
      key: "end",
      get: function get() {
        return this.start + this.duration;
      }
    }, {
      key: "loaded",
      get: function get() {
        var elementaryStreams = this.elementaryStreams;
        return !!(elementaryStreams.audio || elementaryStreams.video || elementaryStreams.audiovideo ||
        this.fragment.type === PlaylistLevelType.SUBTITLE && this.stats.loading.end > 0);
      }
    }]);
  }(BaseSegment);
  function getOwnPropertyDescriptorFromPrototypeChain(object, property) {
    var prototype = Object.getPrototypeOf(object);
    if (prototype) {
      var propertyDescriptor = Object.getOwnPropertyDescriptor(prototype, property);
      if (propertyDescriptor) {
        return propertyDescriptor;
      }
      return getOwnPropertyDescriptorFromPrototypeChain(prototype, property);
    }
  }
  function makeEnumerable(object, property) {
    var d = getOwnPropertyDescriptorFromPrototypeChain(object, property);
    if (d) {
      d.enumerable = true;
      Object.defineProperty(object, property, d);
    }
  }

  function isTimeoutError(error) {
    switch (error.details) {
      case ErrorDetails.FRAG_LOAD_TIMEOUT:
      case ErrorDetails.KEY_LOAD_TIMEOUT:
      case ErrorDetails.LEVEL_LOAD_TIMEOUT:
      case ErrorDetails.MANIFEST_LOAD_TIMEOUT:
      case ErrorDetails.AUDIO_TRACK_LOAD_TIMEOUT:
      case ErrorDetails.SUBTITLE_TRACK_LOAD_TIMEOUT:
      case ErrorDetails.ASSET_LIST_LOAD_TIMEOUT:
        return true;
    }
    return false;
  }
  function isKeyError(error) {
    return error.details.startsWith('key');
  }
  function isUnusableKeyError(error) {
    return isKeyError(error) && !!error.frag && !error.frag.decryptdata;
  }
  function getRetryConfig(loadPolicy, error) {
    var isTimeout = isTimeoutError(error);
    return loadPolicy.default[(isTimeout ? 'timeout' : 'error') + "Retry"];
  }
  function getRetryDelay(retryConfig, retryCount) {
    var backoffFactor = retryConfig.backoff === 'linear' ? 1 : Math.pow(2, retryCount);
    return Math.min(backoffFactor * retryConfig.retryDelayMs, retryConfig.maxRetryDelayMs);
  }
  function getLoaderConfigWithoutReties(loderConfig) {
    return _objectSpread2(_objectSpread2({}, loderConfig), {
      errorRetry: null,
      timeoutRetry: null
    });
  }
  function shouldRetry(retryConfig, retryCount, isTimeout, loaderResponse) {
    if (!retryConfig) {
      return false;
    }
    var httpStatus = loaderResponse == null ? void 0 : loaderResponse.code;
    var retry = retryCount < retryConfig.maxNumRetry && (retryForHttpStatus(httpStatus) || !!isTimeout);
    return retryConfig.shouldRetry ? retryConfig.shouldRetry(retryConfig, retryCount, isTimeout, loaderResponse, retry) : retry;
  }
  function retryForHttpStatus(httpStatus) {
    return offlineHttpStatus(httpStatus) || !!httpStatus && (httpStatus < 400 || httpStatus > 499);
  }
  function offlineHttpStatus(httpStatus) {
    return httpStatus === 0 && navigator.onLine === false;
  }
  function isPenaltyExpired(level, expireMs) {
    return expireMs > 0 && level.loadError > 0 && level.loadErrorTime !== 0 && self.performance.now() - level.loadErrorTime > expireMs;
  }

  var EWMA = function () {
    function EWMA(halfLife, estimate, weight) {
      if (estimate === void 0) {
        estimate = 0;
      }
      if (weight === void 0) {
        weight = 0;
      }
      this.halfLife = void 0;
      this.alpha_ = void 0;
      this.estimate_ = void 0;
      this.totalWeight_ = void 0;
      this.halfLife = halfLife;
      this.alpha_ = halfLife ? Math.exp(Math.log(0.5) / halfLife) : 0;
      this.estimate_ = estimate;
      this.totalWeight_ = weight;
    }
    var _proto = EWMA.prototype;
    _proto.sample = function sample(weight, value) {
      var adjAlpha = Math.pow(this.alpha_, weight);
      this.estimate_ = value * (1 - adjAlpha) + adjAlpha * this.estimate_;
      this.totalWeight_ += weight;
    };
    _proto.getTotalWeight = function getTotalWeight() {
      return this.totalWeight_;
    };
    _proto.getEstimate = function getEstimate() {
      if (this.alpha_) {
        var zeroFactor = 1 - Math.pow(this.alpha_, this.totalWeight_);
        if (zeroFactor) {
          return this.estimate_ / zeroFactor;
        }
      }
      return this.estimate_;
    };
    return EWMA;
  }();

  var EwmaBandWidthEstimator = function () {
    function EwmaBandWidthEstimator(slow, fast, defaultEstimate, defaultTTFB) {
      if (defaultTTFB === void 0) {
        defaultTTFB = 100;
      }
      this.defaultEstimate_ = void 0;
      this.minWeight_ = void 0;
      this.minDelayMs_ = void 0;
      this.slow_ = void 0;
      this.fast_ = void 0;
      this.defaultTTFB_ = void 0;
      this.ttfb_ = void 0;
      this.defaultEstimate_ = defaultEstimate;
      this.minWeight_ = 0.001;
      this.minDelayMs_ = 50;
      this.slow_ = new EWMA(slow);
      this.fast_ = new EWMA(fast);
      this.defaultTTFB_ = defaultTTFB;
      this.ttfb_ = new EWMA(slow);
    }
    var _proto = EwmaBandWidthEstimator.prototype;
    _proto.update = function update(slow, fast) {
      var slow_ = this.slow_,
        fast_ = this.fast_,
        ttfb_ = this.ttfb_;
      if (slow_.halfLife !== slow) {
        this.slow_ = new EWMA(slow, slow_.getEstimate(), slow_.getTotalWeight());
      }
      if (fast_.halfLife !== fast) {
        this.fast_ = new EWMA(fast, fast_.getEstimate(), fast_.getTotalWeight());
      }
      if (ttfb_.halfLife !== slow) {
        this.ttfb_ = new EWMA(slow, ttfb_.getEstimate(), ttfb_.getTotalWeight());
      }
    };
    _proto.sample = function sample(durationMs, numBytes) {
      if (numBytes <= 0) {
        return;
      }
      var minDelay = Math.min(this.minDelayMs_, numBytes / 100000);
      var numBits = 8 * numBytes;
      var durationS = Math.max(durationMs, minDelay) / 1000;
      var bandwidthInBps = numBits / durationS;
      this.fast_.sample(durationS, bandwidthInBps);
      this.slow_.sample(durationS, bandwidthInBps);
    };
    _proto.sampleTTFB = function sampleTTFB(ttfb) {
      var seconds = ttfb / 1000;
      var weight = Math.sqrt(2) * Math.exp(-Math.pow(seconds, 2) / 2);
      this.ttfb_.sample(weight, Math.max(ttfb, 5));
    };
    _proto.canEstimate = function canEstimate() {
      return this.fast_.getTotalWeight() >= this.minWeight_;
    };
    _proto.getEstimate = function getEstimate() {
      if (this.canEstimate()) {
        return Math.min(this.fast_.getEstimate(), this.slow_.getEstimate());
      } else {
        return this.defaultEstimate_;
      }
    };
    _proto.getEstimateTTFB = function getEstimateTTFB() {
      if (this.ttfb_.getTotalWeight() >= this.minWeight_) {
        return this.ttfb_.getEstimate();
      } else {
        return this.defaultTTFB_;
      }
    };
    _proto.destroy = function destroy() {};
    return _createClass(EwmaBandWidthEstimator, [{
      key: "defaultEstimate",
      get: function get() {
        return this.defaultEstimate_;
      }
    }]);
  }();

  var Logger = function Logger(label, logger) {
    this.trace = void 0;
    this.debug = void 0;
    this.log = void 0;
    this.warn = void 0;
    this.info = void 0;
    this.error = void 0;
    var lb = "[" + label + "]:";
    this.trace = noop;
    this.debug = logger.debug.bind(null, lb);
    this.log = logger.log.bind(null, lb);
    this.warn = logger.warn.bind(null, lb);
    this.info = logger.info.bind(null, lb);
    this.error = logger.error.bind(null, lb);
  };
  var noop = function noop() {};
  var fakeLogger = {
    trace: noop,
    debug: noop,
    log: noop,
    warn: noop,
    info: noop,
    error: noop
  };
  function createLogger() {
    return _extends({}, fakeLogger);
  }

  function consolePrintFn(type, id) {
    var func = self.console[type];
    return func ? func.bind(self.console, (id ? '[' + id + '] ' : '') + "[" + type + "] >") : noop;
  }
  function getLoggerFn(key, debugConfig, id) {
    return debugConfig[key] ? debugConfig[key].bind(debugConfig) : consolePrintFn(key, id);
  }
  var exportedLogger = createLogger();
  function enableLogs(debugConfig, context, id) {
    var newLogger = createLogger();
    if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
      var keys = [
      'debug', 'log', 'info', 'warn', 'error'];
      keys.forEach(function (key) {
        newLogger[key] = getLoggerFn(key, debugConfig, id);
      });
      try {
        newLogger.log("Debug logs enabled for \\"" + context + "\\" in hls.js version " + "1.7.3");
      } catch (e) {
        return createLogger();
      }
      keys.forEach(function (key) {
        exportedLogger[key] = getLoggerFn(key, debugConfig);
      });
    } else {
      _extends(exportedLogger, newLogger);
    }
    return newLogger;
  }
  var logger = exportedLogger;

  var empty;
  var hasRequiredEmpty;
  function requireEmpty() {
    if (hasRequiredEmpty) return empty;
    hasRequiredEmpty = 1;
    empty = undefined;
    return empty;
  }

  var emptyExports = requireEmpty();
  var Cues = getDefaultExportFromCjs(emptyExports);

  function getMediaSource(preferManagedMediaSource) {
    if (preferManagedMediaSource === void 0) {
      preferManagedMediaSource = true;
    }
    if (typeof self === 'undefined') return undefined;
    var mms = (preferManagedMediaSource || !self.MediaSource) && self.ManagedMediaSource;
    return mms || self.MediaSource || self.WebKitMediaSource;
  }
  function isManagedMediaSource(source) {
    return typeof self !== 'undefined' && source === self.ManagedMediaSource;
  }
  function isCompatibleTrackChange(currentTracks, requiredTracks) {
    var trackNames = Object.keys(currentTracks);
    var requiredTrackNames = Object.keys(requiredTracks);
    var trackCount = trackNames.length;
    var requiredTrackCount = requiredTrackNames.length;
    return !trackCount || !requiredTrackCount || trackCount === requiredTrackCount && !trackNames.some(function (name) {
      return requiredTrackNames.indexOf(name) === -1;
    });
  }

  function isId3Header(data, offset) {
    if (offset + 10 <= data.length) {
      if (data[offset] === 73 && data[offset + 1] === 68 && data[offset + 2] === 51) {
        if (data[offset + 3] < 255 && data[offset + 4] < 255) {
          if (data[offset + 6] < 128 && data[offset + 7] < 128 && data[offset + 8] < 128 && data[offset + 9] < 128) return true;
        }
      }
    }
    return false;
  }

  function readId3Size(data, offset) {
    var size = 0;
    size = (data[offset] & 127) << 21;
    size |= (data[offset + 1] & 127) << 14;
    size |= (data[offset + 2] & 127) << 7;
    size |= data[offset + 3] & 127;
    return size;
  }

  function canParseId3(data, offset) {
    return isId3Header(data, offset) && readId3Size(data, offset + 6) + 10 <= data.length - offset;
  }

  function isId3Footer(data, offset) {
    if (offset + 10 <= data.length) {
      if (data[offset] === 51 && data[offset + 1] === 68 && data[offset + 2] === 73) {
        if (data[offset + 3] < 255 && data[offset + 4] < 255) {
          if (data[offset + 6] < 128 && data[offset + 7] < 128 && data[offset + 8] < 128 && data[offset + 9] < 128) return true;
        }
      }
    }
    return false;
  }

  function getId3Data(data, offset) {
    var front = offset;
    var length = 0;
    while (isId3Header(data, offset)) {
      length += 10;
      var size = readId3Size(data, offset + 6);
      length += size;
      if (isId3Footer(data, offset + 10)) length += 10;
      offset += length;
    }
    if (length > 0) return data.subarray(front, front + length);
  }

  function toArrayBuffer(view$1) {
    if (isArrayBufferLike(view$1)) return view$1;else {
      if (view$1.byteOffset == 0 && view$1.byteLength == view$1.buffer.byteLength) return view$1.buffer;
      return new Uint8Array(view$1).buffer;
    }
  }

  function toUint8(data, offset, length) {
    if (offset === void 0) {
      offset = 0;
    }
    if (length === void 0) {
      length = Infinity;
    }
    return view(data, offset, length, Uint8Array);
  }
  function view(data, offset, length, Type) {
    var buffer = unsafeGetArrayBuffer(data);
    var bytesPerElement = 1;
    if ("BYTES_PER_ELEMENT" in Type) bytesPerElement = Type.BYTES_PER_ELEMENT;
    var dataOffset = isArrayBufferView(data) ? data.byteOffset : 0;
    var dataEnd = (dataOffset + data.byteLength) / bytesPerElement;
    var rawStart = (dataOffset + offset) / bytesPerElement;
    var start = Math.floor(Math.max(0, Math.min(rawStart, dataEnd)));
    return new Type(buffer, start, Math.floor(Math.min(start + Math.max(length, 0), dataEnd)) - start);
  }
  function unsafeGetArrayBuffer(view$1) {
    if (isArrayBufferLike(view$1)) return view$1;else return view$1.buffer;
  }
  function isArrayBufferView(obj) {
    return obj && isArrayBufferLike(obj.buffer) && obj.byteLength !== void 0 && obj.byteOffset !== void 0;
  }

  function utf8ArrayToStr(array, exitOnNull) {
    if (exitOnNull === void 0) {
      exitOnNull = false;
    }
    var byteLength = exitOnNull ? array.indexOf(0) : array.length;
    var result = decodeText(new DataView(array.buffer, array.byteOffset, byteLength), {
      encoding: UTF_8
    });
    return exitOnNull ? result : result.replace(/\\0/g, "");
  }

  function decodeId3ImageFrame(frame) {
    var metadataFrame = {
      key: frame.type,
      description: "",
      data: "",
      mimeType: null,
      pictureType: null
    };
    var utf8Encoding = 3;
    if (frame.size < 2) return;
    if (frame.data[0] !== utf8Encoding) {
      console.log("Ignore frame with unrecognized character encoding");
      return;
    }
    var mimeTypeEndIndex = frame.data.subarray(1).indexOf(0);
    if (mimeTypeEndIndex === -1) return;
    var mimeType = utf8ArrayToStr(toUint8(frame.data, 1, mimeTypeEndIndex));
    var pictureType = frame.data[2 + mimeTypeEndIndex];
    var descriptionEndIndex = frame.data.subarray(3 + mimeTypeEndIndex).indexOf(0);
    if (descriptionEndIndex === -1) return;
    var description = utf8ArrayToStr(toUint8(frame.data, 3 + mimeTypeEndIndex, descriptionEndIndex));
    var data;
    if (mimeType === "-->") data = utf8ArrayToStr(toUint8(frame.data, 4 + mimeTypeEndIndex + descriptionEndIndex));else data = toArrayBuffer(frame.data.subarray(4 + mimeTypeEndIndex + descriptionEndIndex));
    metadataFrame.mimeType = mimeType;
    metadataFrame.pictureType = pictureType;
    metadataFrame.description = description;
    metadataFrame.data = data;
    return metadataFrame;
  }

  function decodeId3PrivFrame(frame) {
    if (frame.size < 2) return;
    var owner = utf8ArrayToStr(frame.data, true);
    var privateData = new Uint8Array(frame.data.subarray(owner.length + 1));
    return {
      key: frame.type,
      info: owner,
      data: privateData.buffer
    };
  }

  function decodeId3TextFrame(frame) {
    if (frame.size < 2) return;
    if (frame.type === "TXXX") {
      var index = 1;
      var data = frame.data;
      var description = utf8ArrayToStr(data.subarray(index), true);
      index += description.length + 1;
      var value = utf8ArrayToStr(data.subarray(index));
      return {
        key: frame.type,
        info: description,
        data: value
      };
    }
    var text = utf8ArrayToStr(frame.data.subarray(1));
    return {
      key: frame.type,
      info: "",
      data: text
    };
  }

  function decodeId3UrlFrame(frame) {
    if (frame.type === "WXXX") {
      if (frame.size < 2) return;
      var index = 1;
      var description = utf8ArrayToStr(frame.data.subarray(index), true);
      index += description.length + 1;
      var value = utf8ArrayToStr(frame.data.subarray(index));
      return {
        key: frame.type,
        info: description,
        data: value
      };
    }
    var url = utf8ArrayToStr(frame.data);
    return {
      key: frame.type,
      info: "",
      data: url
    };
  }

  function decodeId3Frame(frame) {
    if (frame.type === "PRIV") return decodeId3PrivFrame(frame);else if (frame.type[0] === "W") return decodeId3UrlFrame(frame);else if (frame.type === "APIC") return decodeId3ImageFrame(frame);
    return decodeId3TextFrame(frame);
  }

  function getId3FrameData(data) {
    var type = String.fromCharCode(data[0], data[1], data[2], data[3]);
    var size = readId3Size(data, 4);
    var offset = 10;
    return {
      type: type,
      size: size,
      data: data.subarray(offset, offset + size)
    };
  }

  var HEADER_FOOTER_SIZE = 10;
  var FRAME_SIZE = 10;
  function getId3Frames(id3Data) {
    var offset = 0;
    var frames = [];
    while (isId3Header(id3Data, offset)) {
      var size = readId3Size(id3Data, offset + 6);
      if (id3Data[offset + 5] >> 6 & 1) offset += HEADER_FOOTER_SIZE;
      offset += HEADER_FOOTER_SIZE;
      var end = offset + size;
      while (offset + FRAME_SIZE < end) {
        var frameData = getId3FrameData(id3Data.subarray(offset));
        var frame = decodeId3Frame(frameData);
        if (frame) frames.push(frame);
        offset += frameData.size + HEADER_FOOTER_SIZE;
      }
      if (isId3Footer(id3Data, offset)) offset += HEADER_FOOTER_SIZE;
    }
    return frames;
  }

  function isId3TimestampFrame(frame) {
    return frame && frame.key === "PRIV" && frame.info === "com.apple.streaming.transportStreamTimestamp";
  }

  function readId3Timestamp(timeStampFrame) {
    if (timeStampFrame.data.byteLength === 8) {
      var data = new Uint8Array(timeStampFrame.data);
      var pts33Bit = data[3] & 1;
      var timestamp = (data[4] << 23) + (data[5] << 15) + (data[6] << 7) + data[7];
      timestamp /= 45;
      if (pts33Bit) timestamp += 47721858.84;
      return Math.round(timestamp);
    }
  }

  function getId3Timestamp(data) {
    var frames = getId3Frames(data);
    for (var _iterator = _createForOfIteratorHelperLoose(frames), _step; !(_step = _iterator()).done;) {
      var frame = _step.value;
      if (isId3TimestampFrame(frame)) return readId3Timestamp(frame);
    }
  }

  function arrayToHex(array) {
    var str = '';
    for (var i = 0; i < array.length; i++) {
      var h = array[i].toString(16);
      if (h.length < 2) {
        h = '0' + h;
      }
      str += h;
    }
    return str;
  }
  function hexToArrayBuffer(str) {
    return Uint8Array.from(str.replace(/^0x/, '').replace(/([\\da-fA-F]{2}) ?/g, '0x$1 ').replace(/ +$/, '').split(' ')).buffer;
  }

  function bin2str(data) {
    return String.fromCharCode.apply(null, data);
  }

  var UINT32_MAX = Math.pow(2, 32) - 1;
  var types = {
    avc1: 0x61766331,
    avcC: 0x61766343,
    hvc1: 0x68766331,
    hvcC: 0x68766343,
    btrt: 0x62747274,
    dinf: 0x64696e66,
    dref: 0x64726566,
    esds: 0x65736473,
    free: 0x66726565,
    ftyp: 0x66747970,
    hdlr: 0x68646c72,
    mdat: 0x6d646174,
    mdhd: 0x6d646864,
    mdia: 0x6d646961,
    mfhd: 0x6d666864,
    minf: 0x6d696e66,
    moof: 0x6d6f6f66,
    moov: 0x6d6f6f76,
    mp4a: 0x6d703461,
    '.mp3': 0x2e6d7033,
    dac3: 0x64616333,
    'ac-3': 0x61632d33,
    mvex: 0x6d766578,
    mvhd: 0x6d766864,
    pasp: 0x70617370,
    sdtp: 0x73647470,
    stbl: 0x7374626c,
    stco: 0x7374636f,
    stsc: 0x73747363,
    stsd: 0x73747364,
    stsz: 0x7374737a,
    stts: 0x73747473,
    tfdt: 0x74666474,
    tfhd: 0x74666864,
    traf: 0x74726166,
    trak: 0x7472616b,
    trun: 0x7472756e,
    trex: 0x74726578,
    tkhd: 0x746b6864,
    vmhd: 0x766d6864,
    smhd: 0x736d6864
  };
  var RemuxerTrackIdConfig = {
    video: 1,
    audio: 2,
    id3: 3,
    text: 4
  };
  function readUint16(buffer, offset) {
    var val = buffer[offset] << 8 | buffer[offset + 1];
    return val < 0 ? 65536 + val : val;
  }
  function readUint32(buffer, offset) {
    var val = buffer[offset] << 24 | buffer[offset + 1] << 16 | buffer[offset + 2] << 8 | buffer[offset + 3];
    return val < 0 ? 4294967296 + val : val;
  }
  function readUint64(buffer, offset) {
    var result = readUint32(buffer, offset);
    result *= Math.pow(2, 32);
    result += readUint32(buffer, offset + 4);
    return result;
  }

  function readCString(buffer, offset) {
    var result = '';
    for (var i = offset; i < buffer.length; i++) {
      result += String.fromCharCode(buffer[i]);
      if (buffer[i] === 0) {
        return result;
      }
    }
    return null;
  }
  function readSint32(buffer, offset) {
    return buffer[offset] << 24 | buffer[offset + 1] << 16 | buffer[offset + 2] << 8 | buffer[offset + 3];
  }
  function writeUint32(buffer, offset, value) {
    buffer[offset] = value >> 24;
    buffer[offset + 1] = value >> 16 & 0xff;
    buffer[offset + 2] = value >> 8 & 0xff;
    buffer[offset + 3] = value & 0xff;
  }
  function hasBoxData(data, type) {
    var end = data.byteLength;
    for (var i = 0; i < end;) {
      var size = readUint32(data, i);
      if (size > 8 && readUint32(data, i + 4) === types[type]) {
        return true;
      }
      i = size > 1 ? i + size : end;
    }
    return false;
  }

  function findBox(data, path) {
    var results = [];
    if (!path.length) {
      return results;
    }
    findBoxPath(data, path, 0, 0, data.byteLength, results);
    return results;
  }
  function findBoxPath(data, path, pathIndex, start, end, results) {
    var pathType = path[pathIndex];
    if ((pathType == null ? void 0 : pathType.length) !== 4) {
      return;
    }
    var type0 = pathType.charCodeAt(0);
    var type1 = pathType.charCodeAt(1);
    var type2 = pathType.charCodeAt(2);
    var type3 = pathType.charCodeAt(3);
    var lastPathIndex = path.length - 1;
    for (var i = start; i < end;) {
      var hasHeader = i + 8 <= end;
      var size = i + 4 <= end ? data[i] * 0x1000000 + (data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]) : readUint32(data, i);
      var endbox = size > 1 ? i + size : end;
      if (hasHeader && data[i + 4] === type0 && data[i + 5] === type1 && data[i + 6] === type2 && data[i + 7] === type3) {
        var boxEnd = Math.min(endbox, end);
        if (pathIndex === lastPathIndex) {
          results.push(data.subarray(i + 8, boxEnd));
        } else {
          findBoxPath(data, path, pathIndex + 1, i + 8, boxEnd, results);
        }
      }
      i = endbox;
    }
  }
  function parseSegmentIndex(sidx) {
    var references = [];
    var version = sidx[0];

    var index = 8;
    var timescale = readUint32(sidx, index);
    index += 4;
    var earliestPresentationTime = 0;
    var firstOffset = 0;
    if (version === 0) {
      earliestPresentationTime = readUint32(sidx, index);
      firstOffset = readUint32(sidx, index + 4);
      index += 8;
    } else {
      earliestPresentationTime = readUint64(sidx, index);
      firstOffset = readUint64(sidx, index + 8);
      index += 16;
    }

    index += 2;
    var startByte = sidx.length + firstOffset;
    var referencesCount = readUint16(sidx, index);
    index += 2;
    for (var i = 0; i < referencesCount; i++) {
      var referenceIndex = index;
      var referenceInfo = readUint32(sidx, referenceIndex);
      referenceIndex += 4;
      var referenceSize = referenceInfo & 0x7fffffff;
      var referenceType = (referenceInfo & 0x80000000) >>> 31;
      if (referenceType === 1) {
        return null;
      }
      var subsegmentDuration = readUint32(sidx, referenceIndex);
      referenceIndex += 4;
      references.push({
        referenceSize: referenceSize,
        subsegmentDuration: subsegmentDuration,
        info: {
          duration: subsegmentDuration / timescale,
          start: startByte,
          end: startByte + referenceSize - 1
        }
      });
      startByte += referenceSize;

      referenceIndex += 4;

      index = referenceIndex;
    }
    return {
      earliestPresentationTime: earliestPresentationTime,
      timescale: timescale,
      version: version,
      referencesCount: referencesCount,
      references: references
    };
  }

  function parseInitSegment(initSegment) {
    var result = [];
    var traks = findBox(initSegment, ['moov', 'trak']);
    for (var i = 0; i < traks.length; i++) {
      var trak = traks[i];
      var tkhd = findBox(trak, ['tkhd'])[0];
      if (tkhd) {
        var version = tkhd[0];
        var trackId = readUint32(tkhd, version === 0 ? 12 : 20);
        var mdhd = findBox(trak, ['mdia', 'mdhd'])[0];
        if (mdhd) {
          version = mdhd[0];
          var timescale = readUint32(mdhd, version === 0 ? 12 : 20);
          var hdlr = findBox(trak, ['mdia', 'hdlr'])[0];
          if (hdlr) {
            var hdlrType = bin2str(hdlr.subarray(8, 12));
            var type = {
              soun: ElementaryStreamTypes.AUDIO,
              vide: ElementaryStreamTypes.VIDEO
            }[hdlrType];
            var stsdBox = findBox(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0];
            var stsd = parseStsd(stsdBox);
            if (type) {
              result[trackId] = {
                timescale: timescale,
                type: type,
                stsd: stsd
              };
              result[type] = _objectSpread2({
                timescale: timescale,
                id: trackId
              }, stsd);
            } else {
              result[trackId] = {
                timescale: timescale,
                type: hdlrType,
                stsd: stsd
              };
            }
          }
        }
      }
    }
    var trex = findBox(initSegment, ['moov', 'mvex', 'trex']);
    trex.forEach(function (trex) {
      var trackId = readUint32(trex, 4);
      var track = result[trackId];
      if (track) {
        track.default = {
          duration: readUint32(trex, 12),
          sampleSize: readUint32(trex, 16),
          flags: readUint32(trex, 20)
        };
      }
    });
    return result;
  }
  function parseStsd(stsd) {
    var sampleEntries = stsd.subarray(8);
    var sampleEntriesEnd = sampleEntries.subarray(8 + 78);
    var fourCC = bin2str(sampleEntries.subarray(4, 8));
    var codec = fourCC;
    var supplemental;
    var encrypted = fourCC === 'enca' || fourCC === 'encv';
    if (encrypted) {
      var encBox = findBox(sampleEntries, [fourCC])[0];
      var encBoxChildren = encBox.subarray(fourCC === 'enca' ? 28 : 78);
      var sinfs = findBox(encBoxChildren, ['sinf']);
      sinfs.forEach(function (sinf) {
        var schm = findBox(sinf, ['schm'])[0];
        if (schm) {
          var scheme = bin2str(schm.subarray(4, 8));
          if (scheme === 'cbcs' || scheme === 'cenc') {
            var frma = findBox(sinf, ['frma'])[0];
            if (frma) {
              codec = bin2str(frma);
            }
          }
        }
      });
    }
    var codecFourCC = codec;
    switch (codec) {
      case 'avc1':
      case 'avc2':
      case 'avc3':
      case 'avc4':
        {
          var avcCBox = findBox(sampleEntriesEnd, ['avcC'])[0];
          if (avcCBox && avcCBox.length > 3) {
            codec += '.' + toHex(avcCBox[1]) + toHex(avcCBox[2]) + toHex(avcCBox[3]);
            supplemental = parseSupplementalDoViCodec(codecFourCC === 'avc1' ? 'dva1' : 'dvav', sampleEntriesEnd);
          }
          break;
        }
      case 'mp4a':
        {
          var codecBox = findBox(sampleEntries, [fourCC])[0];
          var esdsBox = findBox(codecBox.subarray(28), ['esds'])[0];
          if (esdsBox && esdsBox.length > 7) {
            var i = 4;
            if (esdsBox[i++] !== 0x03) {
              break;
            }
            i = skipBERInteger(esdsBox, i);
            i += 2;
            var flags = esdsBox[i++];
            if (flags & 0x80) {
              i += 2;
            }
            if (flags & 0x40) {
              i += esdsBox[i++];
            }
            if (esdsBox[i++] !== 0x04) {
              break;
            }
            i = skipBERInteger(esdsBox, i);
            var objectType = esdsBox[i++];
            if (objectType === 0x40) {
              codec += '.' + toHex(objectType);
            } else {
              break;
            }
            i += 12;
            if (esdsBox[i++] !== 0x05) {
              break;
            }
            i = skipBERInteger(esdsBox, i);
            var firstByte = esdsBox[i++];
            var audioObjectType = (firstByte & 0xf8) >> 3;
            if (audioObjectType === 31) {
              audioObjectType += 1 + ((firstByte & 0x7) << 3) + ((esdsBox[i] & 0xe0) >> 5);
            }
            codec += '.' + audioObjectType;
          }
          break;
        }
      case 'hvc1':
      case 'hev1':
        {
          var hvcCBox = findBox(sampleEntriesEnd, ['hvcC'])[0];
          if (hvcCBox && hvcCBox.length > 12) {
            var profileByte = hvcCBox[1];
            var profileSpace = ['', 'A', 'B', 'C'][profileByte >> 6];
            var generalProfileIdc = profileByte & 0x1f;
            var profileCompat = readUint32(hvcCBox, 2);
            var tierFlag = (profileByte & 0x20) >> 5 ? 'H' : 'L';
            var levelIDC = hvcCBox[12];
            var constraintIndicator = hvcCBox.subarray(6, 12);
            codec += '.' + profileSpace + generalProfileIdc;
            codec += '.' + reverse32BitInt(profileCompat).toString(16).toUpperCase();
            codec += '.' + tierFlag + levelIDC;
            var constraintString = '';
            for (var _i2 = constraintIndicator.length; _i2--;) {
              var byte = constraintIndicator[_i2];
              if (byte || constraintString) {
                var encodedByte = byte.toString(16).toUpperCase();
                constraintString = '.' + encodedByte + constraintString;
              }
            }
            codec += constraintString;
          }
          supplemental = parseSupplementalDoViCodec(codecFourCC == 'hev1' ? 'dvhe' : 'dvh1', sampleEntriesEnd);
          break;
        }
      case 'dvh1':
      case 'dvhe':
      case 'dvav':
      case 'dva1':
      case 'dav1':
        {
          codec = parseSupplementalDoViCodec(codec, sampleEntriesEnd) || codec;
          break;
        }
      case 'vp09':
        {
          var vpcCBox = findBox(sampleEntriesEnd, ['vpcC'])[0];
          if (vpcCBox && vpcCBox.length > 6) {
            var profile = vpcCBox[4];
            var level = vpcCBox[5];
            var bitDepth = vpcCBox[6] >> 4 & 0x0f;
            codec += '.' + addLeadingZero(profile) + '.' + addLeadingZero(level) + '.' + addLeadingZero(bitDepth);
          }
          break;
        }
      case 'av01':
        {
          var av1CBox = findBox(sampleEntriesEnd, ['av1C'])[0];
          if (av1CBox && av1CBox.length > 2) {
            var _profile = av1CBox[1] >>> 5;
            var _level = av1CBox[1] & 0x1f;
            var _tierFlag = av1CBox[2] >>> 7 ? 'H' : 'M';
            var highBitDepth = (av1CBox[2] & 0x40) >> 6;
            var twelveBit = (av1CBox[2] & 0x20) >> 5;
            var _bitDepth = _profile === 2 && highBitDepth ? twelveBit ? 12 : 10 : highBitDepth ? 10 : 8;
            var monochrome = (av1CBox[2] & 0x10) >> 4;
            var chromaSubsamplingX = (av1CBox[2] & 0x08) >> 3;
            var chromaSubsamplingY = (av1CBox[2] & 0x04) >> 2;
            var chromaSamplePosition = av1CBox[2] & 0x03;
            var colorPrimaries = 1;
            var transferCharacteristics = 1;
            var matrixCoefficients = 1;
            var videoFullRangeFlag = 0;
            codec += '.' + _profile + '.' + addLeadingZero(_level) + _tierFlag + '.' + addLeadingZero(_bitDepth) + '.' + monochrome + '.' + chromaSubsamplingX + chromaSubsamplingY + chromaSamplePosition + '.' + addLeadingZero(colorPrimaries) + '.' + addLeadingZero(transferCharacteristics) + '.' + addLeadingZero(matrixCoefficients) + '.' + videoFullRangeFlag;
            supplemental = parseSupplementalDoViCodec('dav1', sampleEntriesEnd);
          }
          break;
        }
    }
    return {
      codec: codec,
      encrypted: encrypted,
      supplemental: supplemental
    };
  }
  function parseSupplementalDoViCodec(fourCC, sampleEntriesEnd) {
    var dvvCResult = findBox(sampleEntriesEnd, ['dvvC']);
    var dvXCBox = dvvCResult.length ? dvvCResult[0] : findBox(sampleEntriesEnd, ['dvcC'])[0];
    if (dvXCBox) {
      var doViProfile = dvXCBox[2] >> 1 & 0x7f;
      var doViLevel = dvXCBox[2] << 5 & 0x20 | dvXCBox[3] >> 3 & 0x1f;
      return fourCC + '.' + addLeadingZero(doViProfile) + '.' + addLeadingZero(doViLevel);
    }
  }
  function reverse32BitInt(val) {
    var result = 0;
    for (var i = 0; i < 32; i++) {
      result |= (val >> i & 1) << 32 - 1 - i;
    }
    return result >>> 0;
  }
  function skipBERInteger(bytes, i) {
    var limit = i + 5;
    while (bytes[i++] & 0x80 && i < limit) {
    }
    return i;
  }
  function toHex(x) {
    return ('0' + x.toString(16).toUpperCase()).slice(-2);
  }
  function addLeadingZero(num) {
    return (num < 10 ? '0' : '') + num;
  }
  function patchEncyptionData(initSegment, decryptdata) {
    if (!initSegment || !decryptdata) {
      return;
    }
    var keyId = decryptdata.keyId;
    if ((keyId == null ? void 0 : keyId.byteLength) === 16 && decryptdata.isCommonEncryption) {
      applyToTencBoxes(initSegment, function (tenc, isAudio) {
        var tencKeyId = tenc.subarray(8, 24);
        if (!tencKeyId.some(function (b) {
          return b !== 0;
        })) {
          tenc.set(keyId, 8);
        }
      });
    }
  }
  function parseKeyIdsFromTenc(initSegment) {
    var keyIds = [];
    applyToTencBoxes(initSegment, function (tenc) {
      return keyIds.push(tenc.subarray(8, 24));
    });
    return keyIds;
  }
  function applyToTencBoxes(initSegment, predicate) {
    var traks = findBox(initSegment, ['moov', 'trak']);
    traks.forEach(function (trak) {
      var stsd = findBox(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0];
      if (!stsd) return;
      var sampleEntries = stsd.subarray(8);
      var encBoxes = findBox(sampleEntries, ['enca']);
      var isAudio = encBoxes.length > 0;
      if (!isAudio) {
        encBoxes = findBox(sampleEntries, ['encv']);
      }
      encBoxes.forEach(function (enc) {
        var encBoxChildren = isAudio ? enc.subarray(28) : enc.subarray(78);
        var sinfBoxes = findBox(encBoxChildren, ['sinf']);
        sinfBoxes.forEach(function (sinf) {
          var tenc = parseSinf(sinf);
          if (tenc) {
            predicate(tenc, isAudio);
          }
        });
      });
    });
  }
  function parseSinf(sinf) {
    var schm = findBox(sinf, ['schm'])[0];
    if (schm) {
      var scheme = bin2str(schm.subarray(4, 8));
      if (scheme === 'cbcs' || scheme === 'cenc') {
        var tenc = findBox(sinf, ['schi', 'tenc'])[0];
        if (tenc) {
          return tenc;
        }
      }
    }
  }

  var utf8TextDecoder;
  function decodeUtf8UserData(data) {
    if (typeof TextDecoder === 'undefined') {
      return utf8ArrayToStr(data);
    }
    var result = (utf8TextDecoder || (utf8TextDecoder = new TextDecoder('utf-8'))).decode(data);
    return result.replace(/\\0/g, '');
  }
  function findMoofTrafs(data) {
    var end = data.byteLength;
    var trafs = [];
    for (var i = 0; i < end;) {
      var hasHeader = i + 8 <= end;
      var size = i + 4 <= end ? data[i] * 0x1000000 + (data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]) : readUint32(data, i);
      var endbox = size > 1 ? i + size : end;
      if (hasHeader && data[i + 4] === 0x6d && data[i + 5] === 0x6f && data[i + 6] === 0x6f && data[i + 7] === 0x66) {
        var moofEnd = Math.min(endbox, end);
        for (var j = i + 8; j < moofEnd;) {
          var childHasHeader = j + 8 <= moofEnd;
          var childSize = j + 4 <= moofEnd ? data[j] * 0x1000000 + (data[j + 1] << 16 | data[j + 2] << 8 | data[j + 3]) : readUint32(data, j);
          var childEndbox = childSize > 1 ? j + childSize : moofEnd;
          if (childHasHeader && data[j + 4] === 0x74 && data[j + 5] === 0x72 && data[j + 6] === 0x61 && data[j + 7] === 0x66) {
            var trafEnd = Math.min(childEndbox, moofEnd);
            var tfdt = void 0;
            var tfhd = void 0;
            var truns = [];
            for (var k = j + 8; k < trafEnd;) {
              var boxHasHeader = k + 8 <= trafEnd;
              var boxSize = k + 4 <= trafEnd ? data[k] * 0x1000000 + (data[k + 1] << 16 | data[k + 2] << 8 | data[k + 3]) : readUint32(data, k);
              var boxEnd = boxSize > 1 ? k + boxSize : trafEnd;
              if (boxHasHeader && data[k + 4] === 0x74) {
                var payloadEnd = Math.min(boxEnd, trafEnd);
                if (data[k + 5] === 0x66 && data[k + 6] === 0x68 && data[k + 7] === 0x64) {
                  tfhd || (tfhd = data.subarray(k + 8, payloadEnd));
                } else if (data[k + 5] === 0x66 && data[k + 6] === 0x64 && data[k + 7] === 0x74) {
                  tfdt || (tfdt = data.subarray(k + 8, payloadEnd));
                } else if (data[k + 5] === 0x72 && data[k + 6] === 0x75 && data[k + 7] === 0x6e) {
                  truns.push(data.subarray(k + 8, payloadEnd));
                }
              }
              k = boxEnd;
            }
            trafs.push({
              moofOffset: data.byteOffset + i,
              tfdt: tfdt,
              tfhd: tfhd,
              truns: truns
            });
          }
          j = childEndbox;
        }
      }
      i = endbox;
    }
    return trafs;
  }
  function getSampleData(data, initData, chunkMeta, logger, includeSampleDetails) {
    return parseSampleData(data, initData, logger);
  }
  function parseSamplesAndTiming(timeOffset, track, initData, logger, includeSampleDetails) {
    var samples = [];
    var data = track.samples;
    var tracks = parseSampleData(data, initData, logger, includeSampleDetails, {
      isHEVCFlavor: isHEVC(track.codec),
      samples: samples,
      timeOffset: timeOffset,
      timescale: track.timescale,
      trackId: track.id
    });
    return {
      samples: samples,
      sampleData: {
        includeSampleDetails: includeSampleDetails,
        tracks: tracks
      }
    };
  }
  function parseSampleData(data, initData, logger, includeSampleDetails, seiContext) {
    var tracks = {};
    var hasTrackDuration = false;
    var trafs = findMoofTrafs(data);
    var eof = data.byteLength;
    for (var i = 0; i < trafs.length; i++) {
      var _trafs$i = trafs[i],
        moofOffset = _trafs$i.moofOffset,
        tfdt = _trafs$i.tfdt,
        tfhd = _trafs$i.tfhd,
        truns = _trafs$i.truns;
      var id = readUint32(tfhd, 4);
      var track = initData[id];
      if (!track) {
        continue;
      }
      var trackTimes = tracks[id] || (tracks[id] = {
        start: NaN,
        duration: 0,
        sampleCount: 0,
        trun: [],
        timescale: track.timescale,
        type: track.type
      });
      var baseTime = void 0;
      if (tfdt) {
        var version = tfdt[0];
        baseTime = readUint32(tfdt, 4);
        if (version === 1) {
          if (baseTime === UINT32_MAX) {
          } else {
            baseTime *= UINT32_MAX + 1;
            baseTime += readUint32(tfdt, 8);
          }
        }
        if (isFiniteNumber(baseTime) && (!isFiniteNumber(trackTimes.start) || baseTime < trackTimes.start)) {
          trackTimes.start = baseTime;
        }
        if (seiContext && isFiniteNumber(baseTime)) {
          seiContext.timeOffset = baseTime / seiContext.timescale;
        }
      }
      var scanSei = (seiContext == null ? void 0 : seiContext.trackId) === id && track.type === ElementaryStreamTypes.VIDEO;
      var trackDefault = track.default;
      var tfhdFlags = readUint32(tfhd, 0) & 0xffffff;
      var defaultSampleDuration = (trackDefault == null ? void 0 : trackDefault.duration) || 0;
      var defaultSampleSize = (trackDefault == null ? void 0 : trackDefault.sampleSize) || 0;
      var tfhdOptOffset = 8;
      if (tfhdFlags & 0x000001) {
        tfhdOptOffset += 8;
      }
      if (tfhdFlags & 0x000002) {
        tfhdOptOffset += 4;
      }
      if (tfhdFlags & 0x000008) {
        defaultSampleDuration = readUint32(tfhd, tfhdOptOffset);
        tfhd.byteOffset - data.byteOffset + tfhdOptOffset;
        tfhdOptOffset += 4;
      }
      if (tfhdFlags & 0x000010) {
        defaultSampleSize = readUint32(tfhd, tfhdOptOffset);
      }
      var baseDataOffset = 0;
      var baseDataOffsetPresent = (tfhdFlags & 0x000001) !== 0;
      var defaultBaseIsMoof = !baseDataOffsetPresent && (tfhdFlags & 0x020000) !== 0;
      if (baseDataOffsetPresent) {
        baseDataOffset = readUint32(tfhd, 8);
        baseDataOffset *= Math.pow(2, 32);
        baseDataOffset += readUint32(tfhd, 12);
      } else if (defaultBaseIsMoof) {
        baseDataOffset = moofOffset;
      }
      var sampleDTS = baseTime || 0;
      var rawDuration = 0;
      var sampleDuration = defaultSampleDuration;
      for (var j = 0; j < truns.length; j++) {
        var trun = truns[j];
        var sampleCount = readUint32(trun, 4);
        var sampleIndex = trackTimes.sampleCount;
        trackTimes.sampleCount += sampleCount;
        var dataOffsetPresent = trun[3] & 0x01;
        var dataOffset = 0;
        var firstSampleFlagsPresent = trun[3] & 0x04;
        var sampleDurationPresent = trun[2] & 0x01;
        var sampleSizePresent = trun[2] & 0x02;
        var sampleFlagsPresent = trun[2] & 0x04;
        var sampleCompositionTimeOffsetPresent = trun[2] & 0x08;
        var offset = 8;
        if (dataOffsetPresent) {
          dataOffset = readSint32(trun, offset);
          offset += 4;
        }
        if (firstSampleFlagsPresent) {
          var isNonSyncSample = trun[offset + 1] & 0x01;
          if (!isNonSyncSample && trackTimes.keyFrameIndex === undefined) {
            trackTimes.keyFrameIndex = sampleIndex;
          }
          offset += 4;
        }
        var sampleOffset = baseDataOffset + dataOffset;
        if (trun[1] === 0 && trun[2] === 0x06 && trun[3] === 0x01) {
          var _seiContext$timeOffse, _seiContext$timescale;
          var runStartDTS = sampleDTS;
          var runStartOffset = sampleOffset;
          var trunSamplesOffset = offset;
          var firstSyncSample = -1;
          var firstSyncSampleEnd = 0;
          var fittingSampleCount = 0;
          var seiTimeOffset = (_seiContext$timeOffse = seiContext == null ? void 0 : seiContext.timeOffset) != null ? _seiContext$timeOffse : 0;
          var seiSampleDuration = defaultSampleDuration / ((_seiContext$timescale = seiContext == null ? void 0 : seiContext.timescale) != null ? _seiContext$timescale : 1);
          var hevcSei = (seiContext == null ? void 0 : seiContext.isHEVCFlavor) === true;
          var seiHeaderSize = hevcSei ? 2 : 1;
          for (var ix = 0; ix < sampleCount; ix++) {
            var _size = trun[offset] === 0 ? trun[offset + 1] << 16 | trun[offset + 2] << 8 | trun[offset + 3] : readUint32(trun, offset);
            var isFirstSyncSample = firstSyncSample === -1 && trackTimes.keyFrameIndex === undefined && (trun[offset + 5] & 0x01) === 0;
            offset += 8;
            var sampleEnd = sampleOffset + _size;
            if (scanSei) {
              if (_size > 0) {
                var naluOffset = sampleOffset;
                do {
                  var naluSize = data[naluOffset] === 0 ? data[naluOffset + 1] << 16 | data[naluOffset + 2] << 8 | data[naluOffset + 3] : readUint32(data, naluOffset);
                  naluOffset += 4;
                  var naluHeader = data[naluOffset];
                  var naluType = hevcSei ? naluHeader >> 1 & 0x3f : naluHeader & 0x1f;
                  var isSei = hevcSei ? naluType === 39 || naluType === 40 : naluType === 6;
                  if (isSei) {
                    parseSEIMessageFromNALu(data.subarray(naluOffset, naluOffset + naluSize), seiHeaderSize, seiTimeOffset, seiContext.samples);
                  }
                  naluOffset += naluSize;
                } while (naluOffset < sampleEnd);
              }
              seiTimeOffset += seiSampleDuration;
            }
            sampleOffset = sampleEnd;
            if (isFirstSyncSample) {
              firstSyncSample = ix;
              firstSyncSampleEnd = sampleOffset;
            }
          }
          if (sampleOffset <= eof) {
            fittingSampleCount = sampleCount;
          } else {
            var fittingOffset = runStartOffset;
            var fittingTrunOffset = trunSamplesOffset;
            for (var _ix = 0; _ix < sampleCount; _ix++) {
              var _size2 = trun[fittingTrunOffset] === 0 ? trun[fittingTrunOffset + 1] << 16 | trun[fittingTrunOffset + 2] << 8 | trun[fittingTrunOffset + 3] : readUint32(trun, fittingTrunOffset);
              fittingOffset += _size2;
              if (fittingOffset > eof) {
                break;
              }
              fittingSampleCount = _ix + 1;
              fittingTrunOffset += 8;
            }
          }
          if (scanSei) {
            seiContext.timeOffset = seiTimeOffset;
          }
          if (firstSyncSample !== -1 && firstSyncSampleEnd <= eof && trackTimes.keyFrameIndex === undefined) {
            trackTimes.keyFrameIndex = firstSyncSample;
            trackTimes.keyFrameStart = runStartDTS + firstSyncSample * defaultSampleDuration;
          }
          if (fittingSampleCount) {
            if (!isFiniteNumber(trackTimes.ptsMin) || runStartDTS < trackTimes.ptsMin) {
              trackTimes.ptsMin = runStartDTS;
            }
            var lastSampleEnd = runStartDTS + fittingSampleCount * defaultSampleDuration;
            if (!isFiniteNumber(trackTimes.ptsMax) || lastSampleEnd > trackTimes.ptsMax) {
              trackTimes.ptsMax = lastSampleEnd;
            }
          }
          var runDuration = defaultSampleDuration * sampleCount;
          sampleDTS = runStartDTS + runDuration;
          rawDuration += runDuration;
          continue;
        }
        var samples = void 0;
        var fragRun = void 0;
        var size = void 0;
        for (var _ix2 = 0; _ix2 < sampleCount; _ix2++) {
          var thisSampleDurationOffset = void 0;
          if (sampleDurationPresent) {
            sampleDuration = readUint32(trun, offset);
            offset += 4;
          } else {
            sampleDuration = defaultSampleDuration;
          }
          if (sampleSizePresent) {
            size = readUint32(trun, offset);
            offset += 4;
          } else {
            size = defaultSampleSize;
          }
          var flags = void 0;
          var _isNonSyncSample = void 0;
          if (sampleFlagsPresent) {
            _isNonSyncSample = trun[offset + 1] & 0x01 ? 1 : 0;
            offset += 4;
          }
          var cts = 0;
          if (sampleCompositionTimeOffsetPresent) {
            var _version = trun[0];
            cts = _version === 0 ? readUint32(trun, offset) : readSint32(trun, offset);
            offset += 4;
          }
          if (scanSei) {
            var _naluOffset = sampleOffset;
            var naluTotalSize = 0;
            while (naluTotalSize < size) {
              var _naluSize = readUint32(data, _naluOffset);
              _naluOffset += 4;
              if (isSEIMessage(seiContext.isHEVCFlavor, data[_naluOffset])) {
                parseSEIMessageFromNALu(data.subarray(_naluOffset, _naluOffset + _naluSize), seiContext.isHEVCFlavor ? 2 : 1, seiContext.timeOffset + cts / seiContext.timescale, seiContext.samples);
              }
              _naluOffset += _naluSize;
              naluTotalSize += _naluSize + 4;
            }
            seiContext.timeOffset += sampleDuration / seiContext.timescale;
          }
          sampleOffset += size;
          if (sampleOffset <= eof) {
            if (fragRun && thisSampleDurationOffset !== undefined) {
              fragRun.lastSampleDurationOffset = thisSampleDurationOffset;
            }
            if (_isNonSyncSample === 0 && trackTimes.keyFrameIndex === undefined) {
              trackTimes.keyFrameIndex = _ix2;
              trackTimes.keyFrameStart = sampleDTS;
            }
            var pts = sampleDTS + cts;
            if (!isFiniteNumber(trackTimes.ptsMin) || pts < trackTimes.ptsMin) {
              trackTimes.ptsMin = pts;
            }
            if (!isFiniteNumber(trackTimes.ptsMax) || pts + sampleDuration > trackTimes.ptsMax) {
              trackTimes.ptsMax = pts + sampleDuration;
            }
            if (samples) {
              samples[_ix2] = {
                cts: cts,
                duration: sampleDuration,
                flags: flags,
                size: size
              };
            }
          }
          sampleDTS += sampleDuration;
          rawDuration += sampleDuration;
        }
        if (!rawDuration && defaultSampleDuration) {
          rawDuration += defaultSampleDuration * sampleCount;
        }
      }
      trackTimes.duration += rawDuration;
      if (trackTimes.duration) {
        hasTrackDuration = true;
      }
    }
    if (!hasTrackDuration) {
      var sidxMinStart = Infinity;
      var sidxMaxEnd = 0;
      var sidxs = findBox(data, ['sidx']);
      for (var _i3 = 0; _i3 < sidxs.length; _i3++) {
        var sidx = parseSegmentIndex(sidxs[_i3]);
        if (sidx != null && sidx.references) {
          sidxMinStart = Math.min(sidxMinStart, sidx.earliestPresentationTime / sidx.timescale);
          var subSegmentDuration = sidx.references.reduce(function (dur, ref) {
            return dur + ref.info.duration || 0;
          }, 0);
          sidxMaxEnd = Math.max(sidxMaxEnd, subSegmentDuration + sidx.earliestPresentationTime / sidx.timescale);
        }
      }
      if (sidxMaxEnd && isFiniteNumber(sidxMaxEnd)) {
        Object.keys(tracks).forEach(function (trackId) {
          if (!tracks[trackId].duration) {
            tracks[trackId].duration = sidxMaxEnd * tracks[trackId].timescale - tracks[trackId].start;
          }
        });
      }
    }
    return tracks;
  }

  function segmentValidRange(data) {
    var segmentedRange = {
      valid: null,
      remainder: null
    };
    var moofs = findBox(data, ['moof']);
    if (moofs.length < 2) {
      segmentedRange.remainder = data;
      return segmentedRange;
    }
    var last = moofs[moofs.length - 1];
    segmentedRange.valid = data.slice(0, last.byteOffset - 8);
    segmentedRange.remainder = data.slice(last.byteOffset - 8);
    return segmentedRange;
  }
  function appendUint8Array(data1, data2) {
    var temp = new Uint8Array(data1.length + data2.length);
    temp.set(data1);
    temp.set(data2, data1.length);
    return temp;
  }
  function parseSamples(timeOffset, track) {
    var seiSamples = [];
    var videoData = track.samples;
    var timescale = track.timescale;
    var trackId = track.id;
    var isHEVCFlavor = false;
    var moofs = findBox(videoData, ['moof']);
    moofs.map(function (moof) {
      var moofOffset = moof.byteOffset - 8;
      var trafs = findBox(moof, ['traf']);
      trafs.map(function (traf) {
        var baseTime = findBox(traf, ['tfdt']).map(function (tfdt) {
          var version = tfdt[0];
          var result = readUint32(tfdt, 4);
          if (version === 1) {
            result *= Math.pow(2, 32);
            result += readUint32(tfdt, 8);
          }
          return result / timescale;
        })[0];
        if (baseTime !== undefined) {
          timeOffset = baseTime;
        }
        return findBox(traf, ['tfhd']).map(function (tfhd) {
          var id = readUint32(tfhd, 4);
          var tfhdFlags = readUint32(tfhd, 0) & 0xffffff;
          var baseDataOffsetPresent = (tfhdFlags & 0x000001) !== 0;
          var baseDataOffset = 0;
          var sampleDescriptionIndexPresent = (tfhdFlags & 0x000002) !== 0;
          var defaultSampleDurationPresent = (tfhdFlags & 0x000008) !== 0;
          var defaultSampleDuration = 0;
          var defaultSampleSizePresent = (tfhdFlags & 0x000010) !== 0;
          var defaultSampleSize = 0;
          var defaultSampleFlagsPresent = (tfhdFlags & 0x000020) !== 0;
          var defaultBaseIsMoof = !baseDataOffsetPresent && (tfhdFlags & 0x020000) !== 0;
          var tfhdOffset = 8;
          if (id === trackId) {
            if (baseDataOffsetPresent) {
              baseDataOffset = readUint32(tfhd, tfhdOffset);
              tfhdOffset += 4;
              baseDataOffset *= Math.pow(2, 32);
              baseDataOffset += readUint32(tfhd, tfhdOffset);
              tfhdOffset += 4;
            } else if (defaultBaseIsMoof) {
              baseDataOffset = moofOffset;
            }
            if (sampleDescriptionIndexPresent) {
              tfhdOffset += 4;
            }
            if (defaultSampleDurationPresent) {
              defaultSampleDuration = readUint32(tfhd, tfhdOffset);
              tfhdOffset += 4;
            }
            if (defaultSampleSizePresent) {
              defaultSampleSize = readUint32(tfhd, tfhdOffset);
              tfhdOffset += 4;
            }
            if (defaultSampleFlagsPresent) {
              tfhdOffset += 4;
            }
            if (track.type === 'video') {
              isHEVCFlavor = isHEVC(track.codec);
            }
            findBox(traf, ['trun']).map(function (trun) {
              var version = trun[0];
              var flags = readUint32(trun, 0) & 0xffffff;
              var dataOffsetPresent = (flags & 0x000001) !== 0;
              var dataOffset = 0;
              var firstSampleFlagsPresent = (flags & 0x000004) !== 0;
              var sampleDurationPresent = (flags & 0x000100) !== 0;
              var sampleDuration = 0;
              var sampleSizePresent = (flags & 0x000200) !== 0;
              var sampleSize = 0;
              var sampleFlagsPresent = (flags & 0x000400) !== 0;
              var sampleCompositionOffsetsPresent = (flags & 0x000800) !== 0;
              var compositionOffset = 0;
              var sampleCount = readUint32(trun, 4);
              var trunOffset = 8;

              if (dataOffsetPresent) {
                dataOffset = readUint32(trun, trunOffset);
                trunOffset += 4;
              }
              if (firstSampleFlagsPresent) {
                trunOffset += 4;
              }
              var sampleOffset = baseDataOffset + dataOffset;
              for (var ix = 0; ix < sampleCount; ix++) {
                if (sampleDurationPresent) {
                  sampleDuration = readUint32(trun, trunOffset);
                  trunOffset += 4;
                } else {
                  sampleDuration = defaultSampleDuration;
                }
                if (sampleSizePresent) {
                  sampleSize = readUint32(trun, trunOffset);
                  trunOffset += 4;
                } else {
                  sampleSize = defaultSampleSize;
                }
                if (sampleFlagsPresent) {
                  trunOffset += 4;
                }
                if (sampleCompositionOffsetsPresent) {
                  if (version === 0) {
                    compositionOffset = readUint32(trun, trunOffset);
                  } else {
                    compositionOffset = readSint32(trun, trunOffset);
                  }
                  trunOffset += 4;
                }
                if (track.type === ElementaryStreamTypes.VIDEO) {
                  var naluTotalSize = 0;
                  while (naluTotalSize < sampleSize) {
                    var naluSize = readUint32(videoData, sampleOffset);
                    sampleOffset += 4;
                    if (isSEIMessage(isHEVCFlavor, videoData[sampleOffset])) {
                      var data = videoData.subarray(sampleOffset, sampleOffset + naluSize);
                      parseSEIMessageFromNALu(data, isHEVCFlavor ? 2 : 1, timeOffset + compositionOffset / timescale, seiSamples);
                    }
                    sampleOffset += naluSize;
                    naluTotalSize += naluSize + 4;
                  }
                }
                timeOffset += sampleDuration / timescale;
              }
            });
          }
        });
      });
    });
    return seiSamples;
  }
  function isHEVC(codec) {
    if (!codec) {
      return false;
    }
    var baseCodec = codec.substring(0, 4);
    return baseCodec === 'hvc1' || baseCodec === 'hev1' ||
    baseCodec === 'dvh1' || baseCodec === 'dvhe';
  }
  function isSEIMessage(isHEVCFlavor, naluHeader) {
    if (isHEVCFlavor) {
      var naluType = naluHeader >> 1 & 0x3f;
      return naluType === 39 || naluType === 40;
    } else {
      var _naluType = naluHeader & 0x1f;
      return _naluType === 6;
    }
  }
  function parseSEIMessageFromNALu(unescapedData, headerSize, pts, samples) {
    var data = discardEPB(unescapedData);
    var seiPtr = 0;
    seiPtr += headerSize;
    var payloadType = 0;
    var payloadSize = 0;
    var b = 0;
    while (seiPtr < data.length) {
      payloadType = 0;
      do {
        if (seiPtr >= data.length) {
          break;
        }
        b = data[seiPtr++];
        payloadType += b;
      } while (b === 0xff);

      payloadSize = 0;
      do {
        if (seiPtr >= data.length) {
          break;
        }
        b = data[seiPtr++];
        payloadSize += b;
      } while (b === 0xff);
      var leftOver = data.length - seiPtr;
      var payPtr = seiPtr;

      if (payloadSize < leftOver) {
        seiPtr += payloadSize;
      } else if (payloadSize > leftOver) {
        logger.error("Malformed SEI payload. " + payloadSize + " is too small, only " + leftOver + " bytes left to parse.");
        break;
      }
      if (payloadType === 4) {
        var countryCode = data[payPtr++];
        if (countryCode === 181) {
          var providerCode = readUint16(data, payPtr);
          payPtr += 2;
          if (providerCode === 49) {
            var userStructure = readUint32(data, payPtr);
            payPtr += 4;
            if (userStructure === 0x47413934) {
              var userDataType = data[payPtr++];

              if (userDataType === 3) {
                var firstByte = data[payPtr++];
                var totalCCs = 0x1f & firstByte;
                var enabled = 0x40 & firstByte;
                var totalBytes = enabled ? 2 + totalCCs * 3 : 0;
                var byteArray = new Uint8Array(totalBytes);
                if (enabled) {
                  byteArray[0] = firstByte;
                  for (var i = 1; i < totalBytes; i++) {
                    byteArray[i] = data[payPtr++];
                  }
                }
                samples.push({
                  type: userDataType,
                  payloadType: payloadType,
                  pts: pts,
                  bytes: byteArray
                });
              }
            }
          }
        }
      } else if (payloadType === 5) {
        if (payloadSize > 16) {
          var uuidStrArray = [];
          for (var _i4 = 0; _i4 < 16; _i4++) {
            var _b = data[payPtr++].toString(16);
            uuidStrArray.push(_b.length == 1 ? '0' + _b : _b);
            if (_i4 === 3 || _i4 === 5 || _i4 === 7 || _i4 === 9) {
              uuidStrArray.push('-');
            }
          }
          var length = payloadSize - 16;
          var userDataBytes = new Uint8Array(length);
          for (var _i5 = 0; _i5 < length; _i5++) {
            userDataBytes[_i5] = data[payPtr++];
          }
          samples.push({
            payloadType: payloadType,
            pts: pts,
            uuid: uuidStrArray.join(''),
            userData: decodeUtf8UserData(userDataBytes),
            userDataBytes: userDataBytes
          });
        }
      }
    }
  }

  function discardEPB(data) {
    var length = data.byteLength;
    var epbPositions;
    var position = data.indexOf(0x03, 3);

    while (position !== -1) {
      if (data[position - 1] === 0 && data[position - 2] === 0) {
        (epbPositions || (epbPositions = [])).push(position);
      }
      position = data.indexOf(0x03, position + 1);
    }

    if (!epbPositions) {
      return data;
    }

    var newLength = length - epbPositions.length;
    var newData = new Uint8Array(newLength);
    var epbIndex = 0;
    var sourceIndex = 0;
    for (var i = 0; i < newLength; sourceIndex++, i++) {
      if (sourceIndex === epbPositions[epbIndex]) {
        sourceIndex++;
        epbIndex++;
      }
      newData[i] = data[sourceIndex];
    }
    return newData;
  }
  function parseEmsg(data) {
    var version = data[0];
    var schemeIdUri = '';
    var value = '';
    var timeScale = 0;
    var presentationTimeDelta = 0;
    var presentationTime;
    var eventDuration = 0;
    var id = 0;
    var offset = 4;
    if (version === 0) {
      var scheme = readCString(data, offset);
      var schemeValue = scheme !== null ? readCString(data, offset + scheme.length) : null;
      if (scheme !== null && schemeValue !== null) {
        schemeIdUri = scheme;
        value = schemeValue;
        offset += scheme.length + schemeValue.length;
        timeScale = readUint32(data, offset);
        offset += 4;
        presentationTimeDelta = readUint32(data, offset);
        offset += 4;
        eventDuration = readUint32(data, offset);
        offset += 4;
        id = readUint32(data, offset);
        offset += 4;
      } else {
        offset = data.length;
      }
    } else if (version === 1) {
      timeScale = readUint32(data, offset);
      offset += 4;
      var leftPresentationTime = readUint32(data, offset);
      offset += 4;
      var rightPresentationTime = readUint32(data, offset);
      offset += 4;
      presentationTime = Math.pow(2, 32) * leftPresentationTime + rightPresentationTime;
      if (!isSafeInteger(presentationTime)) {
        presentationTime = Number.MAX_SAFE_INTEGER;
      }
      eventDuration = readUint32(data, offset);
      offset += 4;
      id = readUint32(data, offset);
      offset += 4;
      var _scheme = readCString(data, offset);
      var _schemeValue = _scheme !== null ? readCString(data, offset + _scheme.length) : null;
      if (_scheme !== null) {
        schemeIdUri = _scheme;
        offset += _scheme.length;
      }
      if (_schemeValue !== null) {
        value = _schemeValue;
        offset += _schemeValue.length;
      } else {
        offset = data.length;
      }
    }
    var payload = data.subarray(offset, data.byteLength);
    return {
      schemeIdUri: schemeIdUri,
      value: value,
      timeScale: timeScale,
      presentationTime: presentationTime,
      presentationTimeDelta: presentationTimeDelta,
      eventDuration: eventDuration,
      id: id,
      payload: payload
    };
  }

  var ua = getNavigator('userAgent');
  getNavigator('vendor');
  var chromeVersion;
  var firefoxVersion;
  var safariVersion;
  function getNavigator(field) {
    try {
      return navigator[field] || '';
    } catch (e) {
    }
    return '';
  }
  function userAgentSafariVersion() {
    if (safariVersion === undefined) {
      var result = ua.match(/Safari\\/(\\d+)/i);
      safariVersion = result ? parseInt(result[1]) : 0;
    }
    return safariVersion;
  }
  function userAgentChromeVersion() {
    if (chromeVersion === undefined) {
      var result = ua.match(/Chrome\\/(\\d+)/i);
      chromeVersion = result ? parseInt(result[1]) : 0;
    }
    return chromeVersion;
  }
  function userAgentFirefoxVersion() {
    if (firefoxVersion === undefined) {
      var result = ua.match(/Firefox\\/(\\d+)/i);
      firefoxVersion = result ? parseInt(result[1]) : 0;
    }
    return firefoxVersion;
  }
  function userAgentHevcSupportIsInaccurate() {
    return /\\(Windows.+Firefox\\//i.test(ua);
  }
  function userAgentIsAndroidLike() {
    return /Android/.test(ua);
  }

  var sampleEntryCodesISO = {
    audio: {
      a3ds: 1,
      'ac-3': 0.95,
      'ac-4': 1,
      alac: 0.9,
      alaw: 1,
      dra1: 1,
      'dts+': 1,
      'dts-': 1,
      dtsc: 1,
      dtse: 1,
      dtsh: 1,
      'ec-3': 0.9,
      enca: 1,
      fLaC: 0.9,
      flac: 0.9,
      FLAC: 0.9,
      g719: 1,
      g726: 1,
      m4ae: 1,
      mha1: 1,
      mha2: 1,
      mhm1: 1,
      mhm2: 1,
      mlpa: 1,
      mp4a: 1,
      'raw ': 1,
      Opus: 1,
      opus: 1,
      samr: 1,
      sawb: 1,
      sawp: 1,
      sevc: 1,
      sqcp: 1,
      ssmv: 1,
      twos: 1,
      ulaw: 1
    },
    video: {
      avc1: 1,
      avc2: 1,
      avc3: 1,
      avc4: 1,
      avcp: 1,
      av01: 0.8,
      dav1: 0.8,
      drac: 1,
      dva1: 1,
      dvav: 1,
      dvh1: 0.7,
      dvhe: 0.7,
      encv: 1,
      hev1: 0.75,
      hvc1: 0.75,
      mjp2: 1,
      mp4v: 1,
      mvc1: 1,
      mvc2: 1,
      mvc3: 1,
      mvc4: 1,
      resv: 1,
      rv60: 1,
      s263: 1,
      svc1: 1,
      svc2: 1,
      'vc-1': 1,
      vp08: 1,
      vp09: 0.9
    },
    text: {
      stpp: 1,
      wvtt: 1
    },
    image: {
      mjpg: 1
    }
  };
  function isCodecType(codec, type) {
    var typeCodes = sampleEntryCodesISO[type];
    return !!typeCodes && !!typeCodes[codec.slice(0, 4)];
  }
  function areCodecsMediaSourceSupported(codecs, type, preferManagedMediaSource) {
    if (preferManagedMediaSource === void 0) {
      preferManagedMediaSource = true;
    }
    return !codecs.split(',').some(function (codec) {
      return !isCodecMediaSourceSupported(codec, type, preferManagedMediaSource);
    });
  }
  function isCodecMediaSourceSupported(codec, type, preferManagedMediaSource) {
    var _MediaSource$isTypeSu;
    if (preferManagedMediaSource === void 0) {
      preferManagedMediaSource = true;
    }
    var MediaSource = getMediaSource(preferManagedMediaSource);
    return (_MediaSource$isTypeSu = MediaSource == null ? void 0 : MediaSource.isTypeSupported(mimeTypeForCodec(codec, type))) != null ? _MediaSource$isTypeSu : false;
  }
  function mimeTypeForCodec(codec, type) {
    return type + "/mp4;codecs=" + codec;
  }
  function videoCodecPreferenceValue(videoCodec) {
    if (videoCodec) {
      var fourCC = videoCodec.substring(0, 4);
      return sampleEntryCodesISO.video[fourCC];
    }
    return 2;
  }
  function codecsSetSelectionPreferenceValue(codecSet) {
    var limitedHevcSupport = userAgentHevcSupportIsInaccurate();
    return codecSet.split(',').reduce(function (num, fourCC) {
      var lowerPriority = limitedHevcSupport && isHEVC(fourCC);
      var preferenceValue = lowerPriority ? 9 : sampleEntryCodesISO.video[fourCC];
      if (preferenceValue) {
        return (preferenceValue * 2 + num) / (num ? 3 : 2);
      }
      return (sampleEntryCodesISO.audio[fourCC] + num) / (num ? 2 : 1);
    }, 0);
  }
  var CODEC_COMPATIBLE_NAMES = {};
  function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource) {
    if (preferManagedMediaSource === void 0) {
      preferManagedMediaSource = true;
    }
    if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
      return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
    }
    var codecsToCheck = {
      flac: ['flac', 'fLaC', 'FLAC'],
      opus: ['opus', 'Opus'],
      'mp4a.40.34': ['mp3']
    }[lowerCaseCodec];
    for (var i = 0; i < codecsToCheck.length; i++) {
      var _getMediaSource;
      if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
        CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
        return codecsToCheck[i];
      } else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
        return '';
      }
    }
    return lowerCaseCodec;
  }
  var AUDIO_CODEC_REGEXP = /flac|opus|mp4a\\.40\\.34/i;
  function getCodecCompatibleName(codec, preferManagedMediaSource) {
    if (preferManagedMediaSource === void 0) {
      preferManagedMediaSource = true;
    }
    return codec.replace(AUDIO_CODEC_REGEXP, function (m) {
      return getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource);
    });
  }
  function replaceVideoCodec(originalCodecs, newVideoCodec) {
    var codecs = [];
    if (originalCodecs) {
      var allCodecs = originalCodecs.split(',');
      for (var i = 0; i < allCodecs.length; i++) {
        if (!isCodecType(allCodecs[i], 'video')) {
          codecs.push(allCodecs[i]);
        }
      }
    }
    if (newVideoCodec) {
      codecs.push(newVideoCodec);
    }
    return codecs.join(',');
  }
  function pickMostCompleteCodecName(parsedCodec, levelCodec) {
    if (parsedCodec && (parsedCodec.length > 4 || ['ac-3', 'ec-3', 'alac', 'fLaC', 'Opus'].indexOf(parsedCodec) !== -1)) {
      if (isCodecSupportedAsType(parsedCodec, 'audio') || isCodecSupportedAsType(parsedCodec, 'video')) {
        return parsedCodec;
      }
    }
    if (levelCodec) {
      var levelCodecs = levelCodec.split(',');
      if (levelCodecs.length > 1) {
        if (parsedCodec) {
          for (var i = levelCodecs.length; i--;) {
            if (levelCodecs[i].substring(0, 4) === parsedCodec.substring(0, 4)) {
              return levelCodecs[i];
            }
          }
        }
        return levelCodecs[0];
      }
    }
    return levelCodec || parsedCodec;
  }
  function isCodecSupportedAsType(codec, type) {
    return isCodecType(codec, type) && isCodecMediaSourceSupported(codec, type);
  }
  function convertAVC1ToAVCOTI(videoCodecs) {
    var codecs = videoCodecs.split(',');
    for (var i = 0; i < codecs.length; i++) {
      var avcdata = codecs[i].split('.');
      if (avcdata.length > 2 && avcdata[0] === 'avc1') {
        codecs[i] = "avc1." + parseInt(avcdata[1]).toString(16) + ('000' + parseInt(avcdata[2]).toString(16)).slice(-4);
      }
    }
    return codecs.join(',');
  }
  function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
    var MediaSource = getMediaSource(preferManagedMediaSource) || {
      isTypeSupported: function isTypeSupported() {
        return false;
      }
    };
    return {
      mpeg: MediaSource.isTypeSupported('audio/mpeg'),
      mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
      ac3: false
    };
  }
  function getCodecsForMimeType(mimeType) {
    return mimeType.replace(/^.+codecs=["']?([^"']+).*$/, '$1');
  }

  function setQueryParam(url, key, value) {
    var encodedKey = encodeURIComponent(key);
    var param = encodedKey + "=" + encodeURIComponent(value);
    var replaced = false;
    var params = [];
    url.search.substring(1).split('&').forEach(function (p) {
      if (p === encodedKey || p.startsWith(encodedKey + '=')) {
        if (!replaced) {
          params.push(param);
          replaced = true;
        }
      } else if (p) {
        params.push(p);
      }
    });
    if (!replaced) {
      params.push(param);
    }
    url.search = params.join('&');
  }

  var HdcpLevels = ['NONE', 'TYPE-0', 'TYPE-1', null];
  function isHdcpLevel(value) {
    return HdcpLevels.indexOf(value) > -1;
  }
  var VideoRangeValues = ['SDR', 'PQ', 'HLG'];
  function isVideoRange(value) {
    return !!value && VideoRangeValues.indexOf(value) > -1;
  }
  var HlsSkip = {
    No: "",
    Yes: "YES",
    v2: "v2"
  };
  function getSkipValue(details) {
    var canSkipUntil = details.canSkipUntil,
      canSkipDateRanges = details.canSkipDateRanges,
      age = details.age;
    var playlistRecentEnough = age < canSkipUntil / 2;
    if (canSkipUntil && playlistRecentEnough) {
      if (canSkipDateRanges) {
        return HlsSkip.v2;
      }
      return HlsSkip.Yes;
    }
    return HlsSkip.No;
  }
  var HlsUrlParameters = function () {
    function HlsUrlParameters(msn, part, skip) {
      this.msn = void 0;
      this.part = void 0;
      this.skip = void 0;
      this.msn = msn;
      this.part = part;
      this.skip = skip;
    }
    var _proto = HlsUrlParameters.prototype;
    _proto.addDirectives = function addDirectives(uri) {
      var url = new self.URL(uri);
      if (this.msn !== undefined) {
        setQueryParam(url, '_HLS_msn', this.msn.toString());
      }
      if (this.part !== undefined) {
        setQueryParam(url, '_HLS_part', this.part.toString());
      }
      if (this.skip) {
        setQueryParam(url, '_HLS_skip', this.skip);
      }
      return url.href;
    };
    return HlsUrlParameters;
  }();
  var Level = function () {
    function Level(data) {
      this._attrs = void 0;
      this.audioCodec = void 0;
      this.bitrate = void 0;
      this.codecSet = void 0;
      this.url = void 0;
      this.frameRate = void 0;
      this.height = void 0;
      this.id = void 0;
      this.name = void 0;
      this.supplemental = void 0;
      this.videoCodec = void 0;
      this.width = void 0;
      this.imageCodec = void 0;
      this.iframes = void 0;
      this.details = void 0;
      this.fragmentError = 0;
      this.loadError = 0;
      this.loadErrorTime = 0;
      this.loaded = void 0;
      this.realBitrate = 0;
      this.supportedPromise = void 0;
      this.supportedResult = void 0;
      this._avgBitrate = 0;
      this._audioGroups = void 0;
      this._subtitleGroups = void 0;
      this._urlId = 0;
      this.url = [data.url];
      this._attrs = [data.attrs];
      this.bitrate = data.bitrate;
      if (data.details) {
        this.details = data.details;
      }
      this.id = data.id || 0;
      this.name = data.name;
      this.width = data.width || 0;
      this.height = data.height || 0;
      this.frameRate = data.attrs.optionalFloat('FRAME-RATE', 0);
      this._avgBitrate = data.attrs.decimalInteger('AVERAGE-BANDWIDTH');
      this.audioCodec = data.audioCodec;
      this.videoCodec = data.videoCodec;
      if ('imageCodec' in data) {
        this.imageCodec = data.imageCodec;
      }
      this.codecSet = [data.videoCodec, data.audioCodec].filter(function (c) {
        return !!c;
      }).map(function (s) {
        return s.substring(0, 4);
      }).join(',');
      if ('supplemental' in data) {
        var _data$supplemental;
        this.supplemental = data.supplemental;
        var supplementalVideo = (_data$supplemental = data.supplemental) == null ? void 0 : _data$supplemental.videoCodec;
        if (supplementalVideo && supplementalVideo !== data.videoCodec) {
          this.codecSet += "," + supplementalVideo.substring(0, 4);
        }
      }
      if ('iframes' in data && data.iframes) {
        this.iframes = true;
      } else {
        this.addGroupId('audio', data.attrs.AUDIO);
        this.addGroupId('text', data.attrs.SUBTITLES);
      }
    }
    var _proto2 = Level.prototype;
    _proto2.hasAudioGroup = function hasAudioGroup(groupId) {
      return hasGroup(this._audioGroups, groupId);
    };
    _proto2.hasSubtitleGroup = function hasSubtitleGroup(groupId) {
      return hasGroup(this._subtitleGroups, groupId);
    };
    _proto2.addGroupId = function addGroupId(type, groupId) {
      if (!groupId) {
        return;
      }
      if (type === 'audio') {
        var audioGroups = this._audioGroups;
        if (!audioGroups) {
          audioGroups = this._audioGroups = [];
        }
        if (audioGroups.indexOf(groupId) === -1) {
          audioGroups.push(groupId);
        }
      } else if (type === 'text') {
        var subtitleGroups = this._subtitleGroups;
        if (!subtitleGroups) {
          subtitleGroups = this._subtitleGroups = [];
        }
        if (subtitleGroups.indexOf(groupId) === -1) {
          subtitleGroups.push(groupId);
        }
      }
    }

  ;
    _proto2.addFallback = function addFallback() {};
    return _createClass(Level, [{
      key: "maxBitrate",
      get: function get() {
        return Math.max(this.realBitrate, this.bitrate);
      }
    }, {
      key: "averageBitrate",
      get: function get() {
        return this._avgBitrate || this.realBitrate || this.bitrate;
      }
    }, {
      key: "attrs",
      get: function get() {
        return this._attrs[0];
      }
    }, {
      key: "codecs",
      get: function get() {
        return this.attrs.CODECS || '';
      }
    }, {
      key: "pathwayId",
      get: function get() {
        return this.attrs['PATHWAY-ID'] || '.';
      }
    }, {
      key: "videoRange",
      get: function get() {
        return this.attrs['VIDEO-RANGE'] || 'SDR';
      }
    }, {
      key: "score",
      get: function get() {
        return this.attrs.optionalFloat('SCORE', 0);
      }
    }, {
      key: "uri",
      get: function get() {
        return this.url[0] || '';
      }
    }, {
      key: "audioGroups",
      get: function get() {
        return this._audioGroups;
      }
    }, {
      key: "subtitleGroups",
      get: function get() {
        return this._subtitleGroups;
      }
    }, {
      key: "urlId",
      get: function get() {
        return 0;
      },
      set: function set(value) {}
    }, {
      key: "audioGroupIds",
      get: function get() {
        return this.audioGroups ? [this.audioGroupId] : undefined;
      }
    }, {
      key: "textGroupIds",
      get: function get() {
        return this.subtitleGroups ? [this.textGroupId] : undefined;
      }
    }, {
      key: "audioGroupId",
      get: function get() {
        var _this$audioGroups;
        return (_this$audioGroups = this.audioGroups) == null ? void 0 : _this$audioGroups[0];
      }
    }, {
      key: "textGroupId",
      get: function get() {
        var _this$subtitleGroups;
        return (_this$subtitleGroups = this.subtitleGroups) == null ? void 0 : _this$subtitleGroups[0];
      }
    }]);
  }();
  function hasGroup(groups, groupId) {
    if (!groupId || !groups) {
      return false;
    }
    return groups.indexOf(groupId) !== -1;
  }

  function isHdrSupported() {
    if (typeof matchMedia === 'function') {
      var mediaQueryList = matchMedia('(dynamic-range: high)');
      var badQuery = matchMedia('bad query');
      if (mediaQueryList.media !== badQuery.media) {
        return mediaQueryList.matches === true;
      }
    }
    return false;
  }

  function getVideoSelectionOptions(currentVideoRange, videoPreference) {
    var preferHDR = false;
    var allowedVideoRanges = [];
    if (currentVideoRange) {
      preferHDR = currentVideoRange !== 'SDR';
      allowedVideoRanges = [currentVideoRange];
    }
    if (videoPreference) {
      allowedVideoRanges = videoPreference.allowedVideoRanges || VideoRangeValues.slice(0);
      var allowAutoPreferHDR = allowedVideoRanges.join('') !== 'SDR' && !videoPreference.videoCodec;
      preferHDR = videoPreference.preferHDR !== undefined ? videoPreference.preferHDR : allowAutoPreferHDR && isHdrSupported();
      if (!preferHDR) {
        allowedVideoRanges = ['SDR'];
      }
    }
    return {
      preferHDR: preferHDR,
      allowedVideoRanges: allowedVideoRanges
    };
  }

  var omitCircularRefsReplacer = function omitCircularRefsReplacer(replacer) {
    var known = new WeakSet();
    return function (_, value) {
      if (replacer) {
        value = replacer(_, value);
      }
      if (typeof value === 'object' && value !== null) {
        if (known.has(value)) {
          return;
        }
        known.add(value);
      }
      return value;
    };
  };
  var stringify = function stringify(object, replacer) {
    return JSON.stringify(object, omitCircularRefsReplacer(replacer));
  };

  function getStartCodecTier(codecTiers, currentVideoRange, currentBw, audioPreference, videoPreference) {
    var codecSets = Object.keys(codecTiers);
    var channelsPreference = audioPreference == null ? void 0 : audioPreference.channels;
    var audioCodecPreference = audioPreference == null ? void 0 : audioPreference.audioCodec;
    var videoCodecPreference = videoPreference == null ? void 0 : videoPreference.videoCodec;
    var preferStereo = channelsPreference && parseInt(channelsPreference) === 2;
    var hasStereo = false;
    var hasCurrentVideoRange = false;
    var minHeight = Infinity;
    var minFramerate = Infinity;
    var minBitrate = Infinity;
    var minIndex = Infinity;
    var selectedScore = 0;
    var videoRanges = [];
    var _getVideoSelectionOpt = getVideoSelectionOptions(currentVideoRange, videoPreference),
      preferHDR = _getVideoSelectionOpt.preferHDR,
      allowedVideoRanges = _getVideoSelectionOpt.allowedVideoRanges;
    var _loop = function _loop() {
      var tier = codecTiers[codecSets[i]];
      hasStereo || (hasStereo = tier.channels[2] > 0);
      minHeight = Math.min(minHeight, tier.minHeight);
      minFramerate = Math.min(minFramerate, tier.minFramerate);
      minBitrate = Math.min(minBitrate, tier.minBitrate);
      var matchingVideoRanges = allowedVideoRanges.filter(function (range) {
        return tier.videoRanges[range] > 0;
      });
      if (matchingVideoRanges.length > 0) {
        hasCurrentVideoRange = true;
      }
    };
    for (var i = codecSets.length; i--;) {
      _loop();
    }
    minHeight = isFiniteNumber(minHeight) ? minHeight : 0;
    minFramerate = isFiniteNumber(minFramerate) ? minFramerate : 0;
    var maxHeight = Math.max(1080, minHeight);
    var maxFramerate = Math.max(30, minFramerate);
    minBitrate = isFiniteNumber(minBitrate) ? minBitrate : currentBw;
    currentBw = Math.max(minBitrate, currentBw);
    if (!hasCurrentVideoRange) {
      currentVideoRange = undefined;
    }
    var hasMultipleSets = codecSets.length > 1;
    var codecSet = codecSets.reduce(function (selected, candidate) {
      var candidateTier = codecTiers[candidate];
      if (candidate === selected) {
        return selected;
      }
      videoRanges = hasCurrentVideoRange ? allowedVideoRanges.filter(function (range) {
        return candidateTier.videoRanges[range] > 0;
      }) : [];
      if (hasMultipleSets) {
        if (candidateTier.minBitrate > currentBw) {
          logStartCodecCandidateIgnored(candidate, "min bitrate of " + candidateTier.minBitrate + " > current estimate of " + currentBw);
          return selected;
        }
        if (!candidateTier.hasDefaultAudio) {
          logStartCodecCandidateIgnored(candidate, "no renditions with default or auto-select sound found");
          return selected;
        }
        if (audioCodecPreference && candidate.indexOf(audioCodecPreference.substring(0, 4)) % 5 !== 0) {
          logStartCodecCandidateIgnored(candidate, "audio codec preference \\"" + audioCodecPreference + "\\" not found");
          return selected;
        }
        if (channelsPreference && !preferStereo) {
          if (!candidateTier.channels[channelsPreference]) {
            logStartCodecCandidateIgnored(candidate, "no renditions with " + channelsPreference + " channel sound found (channels options: " + Object.keys(candidateTier.channels) + ")");
            return selected;
          }
        } else if ((!audioCodecPreference || preferStereo) && hasStereo && candidateTier.channels['2'] === 0) {
          logStartCodecCandidateIgnored(candidate, "no renditions with stereo sound found");
          return selected;
        }
        if (candidateTier.minHeight > maxHeight) {
          logStartCodecCandidateIgnored(candidate, "min resolution of " + candidateTier.minHeight + " > maximum of " + maxHeight);
          return selected;
        }
        if (candidateTier.minFramerate > maxFramerate) {
          logStartCodecCandidateIgnored(candidate, "min framerate of " + candidateTier.minFramerate + " > maximum of " + maxFramerate);
          return selected;
        }
        if (!videoRanges.some(function (range) {
          return candidateTier.videoRanges[range] > 0;
        })) {
          logStartCodecCandidateIgnored(candidate, "no variants with VIDEO-RANGE of " + stringify(videoRanges) + " found");
          return selected;
        }
        if (videoCodecPreference && candidate.indexOf(videoCodecPreference.substring(0, 4)) % 5 !== 0) {
          logStartCodecCandidateIgnored(candidate, "video codec preference \\"" + videoCodecPreference + "\\" not found");
          return selected;
        }
        if (candidateTier.maxScore < selectedScore) {
          logStartCodecCandidateIgnored(candidate, "max score of " + candidateTier.maxScore + " < selected max of " + selectedScore);
          return selected;
        }
      }
      if (selected && (codecsSetSelectionPreferenceValue(candidate) >= codecsSetSelectionPreferenceValue(selected) || candidateTier.fragmentError > codecTiers[selected].fragmentError)) {
        return selected;
      }
      minIndex = candidateTier.minIndex;
      selectedScore = candidateTier.maxScore;
      return candidate;
    }, undefined);
    return {
      codecSet: codecSet,
      videoRanges: videoRanges,
      preferHDR: preferHDR,
      minFramerate: minFramerate,
      minBitrate: minBitrate,
      minIndex: minIndex
    };
  }
  function logStartCodecCandidateIgnored(codeSet, reason) {
  }
  function getAudioTracksByGroup(allAudioTracks) {
    return allAudioTracks.reduce(function (audioTracksByGroup, track) {
      var trackGroup = audioTracksByGroup.groups[track.groupId];
      if (!trackGroup) {
        trackGroup = audioTracksByGroup.groups[track.groupId] = {
          tracks: [],
          channels: {
            2: 0
          },
          hasDefault: false,
          hasAutoSelect: false
        };
      }
      trackGroup.tracks.push(track);
      var channelsKey = track.channels || '2';
      trackGroup.channels[channelsKey] = (trackGroup.channels[channelsKey] || 0) + 1;
      trackGroup.hasDefault = trackGroup.hasDefault || track.default;
      trackGroup.hasAutoSelect = trackGroup.hasAutoSelect || track.autoselect;
      if (trackGroup.hasDefault) {
        audioTracksByGroup.hasDefaultAudio = true;
      }
      if (trackGroup.hasAutoSelect) {
        audioTracksByGroup.hasAutoSelectAudio = true;
      }
      return audioTracksByGroup;
    }, {
      hasDefaultAudio: false,
      hasAutoSelectAudio: false,
      groups: {}
    });
  }
  function getCodecTiers(levels, audioTracksByGroup, minAutoLevel, maxAutoLevel) {
    return levels.slice(minAutoLevel, maxAutoLevel + 1).reduce(function (tiers, level, index) {
      if (!level.codecSet) {
        return tiers;
      }
      var audioGroups = level.audioGroups;
      var tier = tiers[level.codecSet];
      if (!tier) {
        tiers[level.codecSet] = tier = {
          minBitrate: Infinity,
          minHeight: Infinity,
          minFramerate: Infinity,
          minIndex: index,
          maxScore: 0,
          videoRanges: {
            SDR: 0
          },
          channels: {
            '2': 0
          },
          hasDefaultAudio: !audioGroups,
          fragmentError: 0
        };
      }
      tier.minBitrate = Math.min(tier.minBitrate, level.bitrate);
      var lesserWidthOrHeight = Math.min(level.height, level.width);
      tier.minHeight = Math.min(tier.minHeight, lesserWidthOrHeight);
      tier.minFramerate = Math.min(tier.minFramerate, level.frameRate);
      tier.minIndex = Math.min(tier.minIndex, index);
      tier.maxScore = Math.max(tier.maxScore, level.score);
      tier.fragmentError += level.fragmentError;
      tier.videoRanges[level.videoRange] = (tier.videoRanges[level.videoRange] || 0) + 1;
      return tiers;
    }, {});
  }
  function useAlternateAudio(audioTrackUrl, hls) {
    var _hls$loadLevelObj;
    return !!audioTrackUrl && audioTrackUrl !== ((_hls$loadLevelObj = hls.loadLevelObj) == null ? void 0 : _hls$loadLevelObj.uri);
  }

  var AbrController = function (_Logger) {
    function AbrController(_hls) {
      var _this;
      _this = _Logger.call(this, 'abr', _hls.logger) || this;
      _this.hls = void 0;
      _this.lastLevelLoadSec = 0;
      _this.lastLoadedFragLevel = -1;
      _this.firstSelection = -1;
      _this.firstAutoFloor = -1;
      _this._nextAutoLevel = -1;
      _this.nextAutoLevelKey = '';
      _this.audioTracksByGroup = null;
      _this.codecTiers = null;
      _this.timer = -1;
      _this.fragCurrent = null;
      _this.partCurrent = null;
      _this.bitrateTestDelay = 0;
      _this.rebufferNotice = -1;
      _this.supportedCache = {};
      _this.bwEstimator = void 0;
      _this._abandonRulesCheck = function (levelLoaded) {
        var _ref;
        var _this2 = _this,
          frag = _this2.fragCurrent,
          part = _this2.partCurrent,
          hls = _this2.hls;
        if (!hls) {
          return;
        }
        var autoLevelEnabled = hls.autoLevelEnabled,
          media = hls.media;
        if (!frag || !media) {
          return;
        }
        var now = performance.now();
        var stats = part ? part.stats : frag.stats;
        var duration = part ? part.duration : frag.duration;
        var timeLoading = now - stats.loading.start;
        var minAutoLevel = hls.minAutoLevel;
        var loadingFragForLevel = frag.level;
        var currentAutoLevel = _this._nextAutoLevel;
        if (stats.aborted || stats.loaded && stats.loaded === stats.total || loadingFragForLevel <= minAutoLevel) {
          _this.clearTimer();
          _this._nextAutoLevel = -1;
          return;
        }

        if (!autoLevelEnabled) {
          return;
        }

        var fragBlockingSwitch = currentAutoLevel > -1 && currentAutoLevel !== loadingFragForLevel;
        var levelChange = !!levelLoaded || fragBlockingSwitch;
        if (!levelChange && (media.paused || !media.playbackRate || !media.readyState)) {
          return;
        }
        var bufferInfo = hls.mainForwardBufferInfo;
        if (!levelChange && bufferInfo === null) {
          return;
        }
        var ttfbEstimate = _this.bwEstimator.getEstimateTTFB();
        var playbackRate = Math.abs(media.playbackRate);
        if (timeLoading <= Math.max(ttfbEstimate, 1000 * (duration / (playbackRate * 2)))) {
          return;
        }

        var bufferStarvationDelay = bufferInfo ? bufferInfo.len / playbackRate : 0;
        var ttfb = stats.loading.first ? stats.loading.first - stats.loading.start : -1;
        var loadedFirstByte = stats.loaded && ttfb > -1;
        var bwEstimate = _this.getBwEstimate();
        var levels = hls.levels;
        var level = levels[loadingFragForLevel];
        var expectedLen = Math.max(stats.loaded, Math.round(duration * (frag.bitrate || level.averageBitrate) / 8));
        var timeStreaming = loadedFirstByte ? timeLoading - ttfb : timeLoading;
        if (timeStreaming < 1 && loadedFirstByte) {
          timeStreaming = Math.min(timeLoading, stats.loaded * 8 / bwEstimate);
        }
        var loadRate = loadedFirstByte ? stats.loaded * 1000 / timeStreaming : 0;
        var ttfbSeconds = ttfbEstimate / 1000;
        var fragLoadedDelay = loadRate ? (expectedLen - stats.loaded) / loadRate : expectedLen * 8 / bwEstimate + ttfbSeconds;
        if (fragLoadedDelay <= bufferStarvationDelay) {
          return;
        }
        var bwe = loadRate ? loadRate * 8 : bwEstimate;
        var live = ((_ref = (levelLoaded == null ? void 0 : levelLoaded.details) || hls.latestLevelDetails) == null ? void 0 : _ref.live) === true;
        var abrBandWidthUpFactor = hls.config.abrBandWidthUpFactor;
        var fragLevelNextLoadedDelay = Number.POSITIVE_INFINITY;
        var nextLoadLevel;
        for (nextLoadLevel = loadingFragForLevel - 1; nextLoadLevel > minAutoLevel; nextLoadLevel--) {
          var levelNextBitrate = levels[nextLoadLevel].maxBitrate;
          var requiresLevelLoad = !levels[nextLoadLevel].details || live;
          fragLevelNextLoadedDelay = _this.getTimeToLoadFrag(ttfbSeconds, bwe, duration * levelNextBitrate, requiresLevelLoad);
          if (fragLevelNextLoadedDelay < Math.min(bufferStarvationDelay, duration + ttfbSeconds)) {
            break;
          }
        }
        if (fragLevelNextLoadedDelay >= fragLoadedDelay) {
          return;
        }

        if (fragLevelNextLoadedDelay > duration * 10) {
          return;
        }
        if (loadedFirstByte) {
          _this.bwEstimator.sample(timeLoading - Math.min(ttfbEstimate, ttfb), stats.loaded);
        } else {
          _this.bwEstimator.sampleTTFB(timeLoading);
        }
        var nextLoadLevelBitrate = levels[nextLoadLevel].maxBitrate;
        if (_this.getBwEstimate() * abrBandWidthUpFactor > nextLoadLevelBitrate) {
          _this.firstAutoFloor = Math.min(nextLoadLevel, hls.firstLevel);
          _this.resetEstimator(nextLoadLevelBitrate);
        }
        var bestSwitchLevel = _this.findBestLevel(nextLoadLevelBitrate, minAutoLevel, nextLoadLevel, 0, bufferStarvationDelay, 1, 1);
        if (bestSwitchLevel > -1) {
          nextLoadLevel = bestSwitchLevel;
        }
        hls.nextLoadLevel = hls.nextAutoLevel = nextLoadLevel;
        _this.clearTimer();
        var abortAndSwitch = function abortAndSwitch() {
          _this.clearTimer();
          var hls = _this.hls;
          if (_this.fragCurrent === frag && hls.loadLevel === nextLoadLevel && nextLoadLevel > 0) {
            var _bufferStarvationDelay = _this.getStarvationDelay();
            frag.abortRequests();
            _this.fragCurrent = _this.partCurrent = null;
            if (nextLoadLevel > minAutoLevel) {
              var lowestSwitchLevel = _this.findBestLevel(hls.levels[minAutoLevel].bitrate, minAutoLevel, nextLoadLevel, 0, _bufferStarvationDelay, 1, 1);
              if (lowestSwitchLevel === -1) {
                lowestSwitchLevel = minAutoLevel;
              }
              hls.nextLoadLevel = hls.nextAutoLevel = lowestSwitchLevel;
              _this.firstAutoFloor = Math.min(lowestSwitchLevel, hls.firstLevel);
              _this.resetEstimator(hls.levels[lowestSwitchLevel].bitrate);
            }
          }
        };
        if (fragBlockingSwitch || fragLoadedDelay > fragLevelNextLoadedDelay * 2) {
          abortAndSwitch();
        } else {
          _this.timer = self.setInterval(abortAndSwitch, fragLevelNextLoadedDelay * 1000);
        }
        hls.trigger(Events.FRAG_LOAD_EMERGENCY_ABORTED, {
          frag: frag,
          part: part,
          stats: stats
        });
      };
      _this.hls = _hls;
      _this.bwEstimator = _this.initEstimator();
      _this.registerListeners();
      return _this;
    }
    _inheritsLoose(AbrController, _Logger);
    var _proto = AbrController.prototype;
    _proto.resetEstimator = function resetEstimator(abrEwmaDefaultEstimate) {
      if (!this.hls) {
        return;
      }
      if (abrEwmaDefaultEstimate) {
        this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
      }
      this.firstSelection = -1;
      this.bwEstimator = this.initEstimator();
    };
    _proto.initEstimator = function initEstimator() {
      var config = this.hls.config;
      return new EwmaBandWidthEstimator(config.abrEwmaSlowVoD, config.abrEwmaFastVoD, config.abrEwmaDefaultEstimate);
    };
    _proto.registerListeners = function registerListeners() {
      var hls = this.hls;
      hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
      hls.on(Events.FRAG_LOADING, this.onFragLoading, this);
      hls.on(Events.FRAG_LOADED, this.onFragLoaded, this);
      hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
      hls.on(Events.LEVEL_SWITCHING, this.onLevelSwitching, this);
      hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
      hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
      hls.on(Events.MAX_AUTO_LEVEL_UPDATED, this.onMaxAutoLevelUpdated, this);
      hls.on(Events.ERROR, this.onError, this);
    };
    _proto.unregisterListeners = function unregisterListeners() {
      var hls = this.hls;
      if (!hls) {
        return;
      }
      hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
      hls.off(Events.FRAG_LOADING, this.onFragLoading, this);
      hls.off(Events.FRAG_LOADED, this.onFragLoaded, this);
      hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
      hls.off(Events.LEVEL_SWITCHING, this.onLevelSwitching, this);
      hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
      hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
      hls.off(Events.MAX_AUTO_LEVEL_UPDATED, this.onMaxAutoLevelUpdated, this);
      hls.off(Events.ERROR, this.onError, this);
    };
    _proto.destroy = function destroy() {
      this.unregisterListeners();
      this.clearTimer();
      this.hls = this._abandonRulesCheck = this.supportedCache = null;
      this.audioTracksByGroup = this.codecTiers = this.fragCurrent = this.partCurrent = null;
    };
    _proto.onManifestLoading = function onManifestLoading(event, data) {
      this.lastLoadedFragLevel = -1;
      this.firstSelection = -1;
      this.firstAutoFloor = -1;
      this.lastLevelLoadSec = 0;
      this.supportedCache = {};
      this.fragCurrent = this.partCurrent = null;
      this.onLevelsUpdated();
      this.clearTimer();
    };
    _proto.onLevelsUpdated = function onLevelsUpdated() {
      if (this.lastLoadedFragLevel > -1 && this.fragCurrent) {
        this.lastLoadedFragLevel = this.fragCurrent.level;
      }
      this._nextAutoLevel = -1;
      this.onMaxAutoLevelUpdated();
      this.codecTiers = null;
      this.audioTracksByGroup = null;
    };
    _proto.onMaxAutoLevelUpdated = function onMaxAutoLevelUpdated() {
      this.firstSelection = -1;
      this.nextAutoLevelKey = '';
    };
    _proto.onFragLoading = function onFragLoading(event, data) {
      var frag = data.frag;
      if (this.ignoreFragment(frag)) {
        return;
      }
      if (!frag.bitrateTest) {
        var _data$part;
        this.fragCurrent = frag;
        this.partCurrent = (_data$part = data.part) != null ? _data$part : null;
      }
      this.clearTimer();
      this.timer = self.setInterval(this._abandonRulesCheck, 100);
    };
    _proto.onLevelSwitching = function onLevelSwitching(event, data) {
      this.clearTimer();
    };
    _proto.onError = function onError(event, data) {
      if (data.fatal) {
        return;
      }
      switch (data.details) {
        case ErrorDetails.BUFFER_ADD_CODEC_ERROR:
        case ErrorDetails.BUFFER_APPEND_ERROR:
          this.lastLoadedFragLevel = -1;
          this.firstSelection = -1;
          break;
        case ErrorDetails.FRAG_LOAD_TIMEOUT:
          {
            var frag = data.frag;
            var fragCurrent = this.fragCurrent,
              part = this.partCurrent;
            if (frag && fragmentsAreEqual(frag, fragCurrent)) {
              var now = performance.now();
              var stats = part ? part.stats : frag.stats;
              var timeLoading = now - stats.loading.start;
              var ttfb = stats.loading.first ? stats.loading.first - stats.loading.start : -1;
              var loadedFirstByte = stats.loaded && ttfb > -1;
              if (loadedFirstByte) {
                var ttfbEstimate = this.bwEstimator.getEstimateTTFB();
                this.bwEstimator.sample(timeLoading - Math.min(ttfbEstimate, ttfb), stats.loaded);
              } else {
                this.bwEstimator.sampleTTFB(timeLoading);
              }
            }
            break;
          }
      }
    };
    _proto.getTimeToLoadFrag = function getTimeToLoadFrag(timeToFirstByteSec, bandwidth, fragSizeBits, isSwitch) {
      var fragLoadSec = timeToFirstByteSec + fragSizeBits / bandwidth;
      var playlistLoadSec = isSwitch ? timeToFirstByteSec + this.lastLevelLoadSec : 0;
      return fragLoadSec + playlistLoadSec;
    };
    _proto.onLevelLoaded = function onLevelLoaded(event, data) {
      var config = this.hls.config;
      var loading = data.stats.loading;
      var timeLoadingMs = loading.end - loading.first;
      if (isFiniteNumber(timeLoadingMs)) {
        this.lastLevelLoadSec = timeLoadingMs / 1000;
      }
      if (data.details.live) {
        this.bwEstimator.update(config.abrEwmaSlowLive, config.abrEwmaFastLive);
      } else {
        this.bwEstimator.update(config.abrEwmaSlowVoD, config.abrEwmaFastVoD);
      }
      if (this.timer > -1) {
        this._abandonRulesCheck(data.levelInfo);
      }
    };
    _proto.onFragLoaded = function onFragLoaded(event, data) {
      var _this$hls;
      var frag = data.frag,
        part = data.part;
      var stats = part ? part.stats : frag.stats;
      if (frag.type === PlaylistLevelType.MAIN) {
        this.bwEstimator.sampleTTFB(stats.loading.first - stats.loading.start);
      }
      if (this.ignoreFragment(frag)) {
        return;
      }
      this.clearTimer();
      if (frag.level === this._nextAutoLevel) {
        this._nextAutoLevel = -1;
      }
      this.firstSelection = -1;

      if ((_this$hls = this.hls) != null && _this$hls.config.abrMaxWithRealBitrate) {
        var duration = part ? part.duration : frag.duration;
        var level = this.hls.levels[frag.level];
        var loadedBytes = (level.loaded ? level.loaded.bytes : 0) + stats.loaded;
        var loadedDuration = (level.loaded ? level.loaded.duration : 0) + duration;
        level.loaded = {
          bytes: loadedBytes,
          duration: loadedDuration
        };
        level.realBitrate = Math.round(8 * loadedBytes / loadedDuration);
      }
      if (frag.bitrateTest) {
        var fragBufferedData = {
          stats: stats,
          frag: frag,
          part: part,
          id: frag.type
        };
        this.onFragBuffered(Events.FRAG_BUFFERED, fragBufferedData);
        frag.bitrateTest = false;
      } else {
        this.lastLoadedFragLevel = frag.level;
      }
    };
    _proto.onFragBuffered = function onFragBuffered(event, data) {
      var frag = data.frag,
        part = data.part;
      var stats = part != null && part.stats.loaded ? part.stats : frag.stats;
      if (stats.aborted) {
        return;
      }
      if (this.ignoreFragment(frag)) {
        return;
      }
      var processingEnd = (frag.bitrate || 0) >= 100000 ? stats.parsing.end : stats.loading.end;
      var processingMs = processingEnd - stats.loading.start - Math.min(stats.loading.first - stats.loading.start, this.bwEstimator.getEstimateTTFB());
      this.bwEstimator.sample(processingMs, stats.loaded);
      stats.bwEstimate = this.getBwEstimate();
      if (frag.bitrateTest) {
        this.bitrateTestDelay = processingMs / 1000;
      } else {
        this.bitrateTestDelay = 0;
        if (this.firstAutoFloor > -1 && this.bwEstimator.canEstimate()) {
          this.firstAutoFloor = -1;
        }
      }
    };
    _proto.ignoreFragment = function ignoreFragment(frag) {
      return frag.type !== PlaylistLevelType.MAIN || frag.sn === 'initSegment';
    };
    _proto.clearTimer = function clearTimer() {
      if (this.timer > -1) {
        self.clearInterval(this.timer);
        this.timer = -1;
      }
    };
    _proto.getAutoLevelKey = function getAutoLevelKey() {
      return this.getBwEstimate() + "_" + this.getStarvationDelay().toFixed(2);
    };
    _proto.getNextABRAutoLevel = function getNextABRAutoLevel() {
      var fragCurrent = this.fragCurrent,
        partCurrent = this.partCurrent,
        hls = this.hls;
      if (!hls) {
        return -1;
      }
      if (hls.levels.length <= 1) {
        return hls.loadLevel;
      }
      var maxAutoLevel = hls.maxAutoLevel,
        config = hls.config,
        minAutoLevel = hls.minAutoLevel;
      var currentFragDuration = partCurrent ? partCurrent.duration : fragCurrent ? fragCurrent.duration : 0;
      var avgbw = this.getBwEstimate();
      var bufferStarvationDelay = this.getStarvationDelay();
      var bwFactor = config.abrBandWidthFactor;
      var bwUpFactor = config.abrBandWidthUpFactor;

      if (bufferStarvationDelay) {
        var _bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, 0, bwFactor, bwUpFactor);
        if (_bestLevel >= 0) {
          this.rebufferNotice = -1;
          return _bestLevel;
        }
      }
      var maxStarvationDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxStarvationDelay) : config.maxStarvationDelay;
      if (!bufferStarvationDelay) {
        var bitrateTestDelay = this.bitrateTestDelay;
        if (bitrateTestDelay) {
          var maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay;
          maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
          bwFactor = bwUpFactor = 1;
        }
      }
      var bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor);
      if (this.rebufferNotice !== bestLevel) {
        this.rebufferNotice = bestLevel;
      }
      if (bestLevel > -1) {
        return bestLevel;
      }
      var minLevel = hls.levels[minAutoLevel];
      var autoLevel = hls.loadLevelObj;
      if (autoLevel && (minLevel == null ? void 0 : minLevel.bitrate) < autoLevel.bitrate) {
        return minAutoLevel;
      }
      return hls.loadLevel;
    };
    _proto.getStarvationDelay = function getStarvationDelay() {
      var hls = this.hls;
      var media = hls == null ? void 0 : hls.media;
      if (!media) {
        return Infinity;
      }
      var playbackRate = media && media.playbackRate !== 0 ? Math.abs(media.playbackRate) : 1.0;
      var bufferInfo = hls.mainForwardBufferInfo;
      return (bufferInfo ? bufferInfo.len : 0) / playbackRate;
    };
    _proto.getBwEstimate = function getBwEstimate() {
      return this.bwEstimator.getEstimate();
    };
    _proto.findBestLevel = function findBestLevel(currentBw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor) {
      var _this3 = this;
      var hls = this.hls,
        lastLoadedFragLevel = this.lastLoadedFragLevel,
        fragCurrent = this.fragCurrent,
        partCurrent = this.partCurrent;
      if (!hls) {
        return -1;
      }
      var maxFetchDuration = bufferStarvationDelay + maxStarvationDelay;
      var selectionBaseLevel = lastLoadedFragLevel === -1 ? hls.firstLevel : lastLoadedFragLevel;
      var levels = hls.levels,
        allAudioTracks = hls.allAudioTracks,
        loadLevel = hls.loadLevel,
        latestLevelDetails = hls.latestLevelDetails,
        config = hls.config;
      if (levels.length === 1) {
        return 0;
      }
      var level = levels[selectionBaseLevel];
      var live = !!(latestLevelDetails != null && latestLevelDetails.live);
      var firstSelection = loadLevel === -1 || lastLoadedFragLevel === -1;
      var currentCodecSet;
      var currentVideoRange = 'SDR';
      var currentFrameRate = (level == null ? void 0 : level.frameRate) || 0;
      var audioPreference = config.audioPreference,
        videoPreference = config.videoPreference;
      var audioTracksByGroup = this.audioTracksByGroup || (this.audioTracksByGroup = getAudioTracksByGroup(allAudioTracks));
      var minStartIndex = -1;
      if (firstSelection) {
        if (this.firstSelection !== -1) {
          return this.firstSelection;
        }
        var codecTiers = this.codecTiers || (this.codecTiers = getCodecTiers(levels, audioTracksByGroup, minAutoLevel, maxAutoLevel));
        var startTier = getStartCodecTier(codecTiers, currentVideoRange, currentBw, audioPreference, videoPreference);
        var codecSet = startTier.codecSet,
          videoRanges = startTier.videoRanges,
          minFramerate = startTier.minFramerate,
          minBitrate = startTier.minBitrate,
          minIndex = startTier.minIndex,
          preferHDR = startTier.preferHDR;
        minStartIndex = minIndex;
        currentCodecSet = codecSet;
        currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0];
        currentFrameRate = minFramerate;
        currentBw = Math.max(currentBw, minBitrate);
      } else {
        currentCodecSet = level == null ? void 0 : level.codecSet;
        currentVideoRange = level == null ? void 0 : level.videoRange;
      }
      var currentFragDuration = partCurrent ? partCurrent.duration : fragCurrent ? fragCurrent.duration : 0;
      var ttfbEstimateSec = this.bwEstimator.getEstimateTTFB() / 1000;
      var levelsSkipped = [];
      var _loop = function _loop() {
          var _levelInfo$supportedR;
          var levelInfo = levels[i];
          var upSwitch = i > selectionBaseLevel;
          if (!levelInfo) {
            return 0;
          }

          if (currentCodecSet && levelInfo.codecSet !== currentCodecSet || currentVideoRange && levelInfo.videoRange !== currentVideoRange || upSwitch && currentFrameRate > levelInfo.frameRate || !upSwitch && currentFrameRate > 0 && currentFrameRate < levelInfo.frameRate || (_levelInfo$supportedR = levelInfo.supportedResult) != null && (_levelInfo$supportedR = _levelInfo$supportedR.decodingInfoResults) != null && _levelInfo$supportedR.some(function (info) {
            return info.smooth === false;
          })) {
            if (!firstSelection || i !== minStartIndex) {
              levelsSkipped.push(i);
              return 0;
            }
          }
          var levelDetails = levelInfo.details;
          var avgDuration = (partCurrent ? levelDetails == null ? void 0 : levelDetails.partTarget : levelDetails == null ? void 0 : levelDetails.averagetargetduration) || currentFragDuration || 1;
          var adjustedbw;
          if (!upSwitch) {
            adjustedbw = bwFactor * currentBw;
          } else {
            adjustedbw = bwUpFactor * currentBw;
          }

          var bitrate = currentFragDuration && bufferStarvationDelay >= currentFragDuration * 2 && maxStarvationDelay === 0 ? levelInfo.averageBitrate : levelInfo.maxBitrate;
          var fetchDuration = _this3.getTimeToLoadFrag(ttfbEstimateSec, adjustedbw, bitrate * avgDuration, !levelDetails || levelDetails.live);
          var canSwitchWithinTolerance =
          adjustedbw >= bitrate && (
          i === lastLoadedFragLevel || levelInfo.loadError === 0 && levelInfo.fragmentError === 0 || isPenaltyExpired(levelInfo, config.errorPenaltyExpireMs)) && (
          fetchDuration <= ttfbEstimateSec || !isFiniteNumber(fetchDuration) || live && !_this3.bitrateTestDelay || fetchDuration < maxFetchDuration);
          if (canSwitchWithinTolerance) {
            var forcedAutoLevel = _this3.forcedAutoLevel;
            if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) {
              if (levelsSkipped.length) {
                _this3.trace("Skipped level(s) " + levelsSkipped.join(',') + " of " + maxAutoLevel + " max with CODECS and VIDEO-RANGE:\\"" + levels[levelsSkipped[0]].codecs + "\\" " + levels[levelsSkipped[0]].videoRange + "; not compatible with \\"" + currentCodecSet + "\\" " + currentVideoRange);
              }
            }
            if (firstSelection) {
              _this3.firstSelection = i;
            }
            return {
              v: i
            };
          }
        },
        _ret;
      for (var i = maxAutoLevel; i >= minAutoLevel; i--) {
        _ret = _loop();
        if (_ret === 0) continue;
        if (_ret) return _ret.v;
      }
      return -1;
    };
    _proto.deriveNextAutoLevel = function deriveNextAutoLevel(nextLevel) {
      if (!this.hls) {
        return -1;
      }
      var _this$hls2 = this.hls,
        maxAutoLevel = _this$hls2.maxAutoLevel,
        minAutoLevel = _this$hls2.minAutoLevel;
      return Math.min(Math.max(nextLevel, minAutoLevel), maxAutoLevel);
    };
    return _createClass(AbrController, [{
      key: "firstAutoLevel",
      get: function get() {
        var hls = this.hls;
        if (!hls) {
          return 0;
        }
        var maxAutoLevel = hls.maxAutoLevel,
          minAutoLevel = hls.minAutoLevel;
        var bwEstimate = this.getBwEstimate();
        var bufferStarvationDelay = this.getStarvationDelay();
        var maxStartDelay = hls.config.maxStarvationDelay;
        var abrAutoLevel = this.findBestLevel(bwEstimate, minAutoLevel, maxAutoLevel, bufferStarvationDelay / 2, maxStartDelay, 1, 1);
        if (abrAutoLevel > -1) {
          return abrAutoLevel;
        }
        var firstLevel = hls.firstLevel;
        var firstAutoFloor = this.firstAutoFloor === -1 ? Infinity : this.firstAutoFloor;
        var clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel, firstAutoFloor);
        return clamped;
      }
    }, {
      key: "forcedAutoLevel",
      get: function get() {
        if (this.nextAutoLevelKey) {
          return -1;
        }
        return this._nextAutoLevel;
      }

    }, {
      key: "nextAutoLevel",
      get: function get() {
        if (!this.hls) {
          return -1;
        }
        var forcedAutoLevel = this.forcedAutoLevel;
        var bwEstimator = this.bwEstimator;
        var useEstimate = bwEstimator.canEstimate();
        var loadedFirstFrag = this.lastLoadedFragLevel > -1;
        if (forcedAutoLevel !== -1 && (!useEstimate || !loadedFirstFrag || this.nextAutoLevelKey === this.getAutoLevelKey())) {
          return forcedAutoLevel;
        }

        var nextABRAutoLevel = useEstimate && loadedFirstFrag ? this.getNextABRAutoLevel() : this.firstAutoLevel;

        if (forcedAutoLevel !== -1) {
          var levels = this.hls.levels;
          if (levels.length > Math.max(forcedAutoLevel, nextABRAutoLevel) && levels[forcedAutoLevel].loadError <= levels[nextABRAutoLevel].loadError) {
            return forcedAutoLevel;
          }
        }

        this._nextAutoLevel = nextABRAutoLevel;
        this.nextAutoLevelKey = this.getAutoLevelKey();
        return nextABRAutoLevel;
      },
      set: function set(nextLevel) {
        var value = this.deriveNextAutoLevel(nextLevel);
        if (this._nextAutoLevel !== value) {
          this.nextAutoLevelKey = '';
          this._nextAutoLevel = value;
        }
      }
    }]);
  }(Logger);

  var BufferOperationQueue = function () {
    function BufferOperationQueue(sourceBufferReference) {
      this.tracks = void 0;
      this.queues = {
        video: [],
        audio: [],
        audiovideo: []
      };
      this.tracks = sourceBufferReference;
    }
    var _proto = BufferOperationQueue.prototype;
    _proto.destroy = function destroy() {
      this.tracks = this.queues = null;
    };
    _proto.append = function append(operation, type, pending) {
      if (this.queues === null || this.tracks === null) {
        return;
      }
      var queue = this.queues[type];
      queue.push(operation);
      if (queue.length === 1 && !pending) {
        this.executeNext(type);
      }
    };
    _proto.appendBlocker = function appendBlocker(type) {
      var _this = this;
      return new Promise(function (resolve) {
        var operation = {
          label: 'async-blocker',
          execute: resolve,
          onStart: function onStart() {},
          onComplete: function onComplete() {},
          onError: function onError() {}
        };
        _this.append(operation, type);
      });
    };
    _proto.prependBlocker = function prependBlocker(type) {
      var _this2 = this;
      return new Promise(function (resolve) {
        if (_this2.queues) {
          var operation = {
            label: 'async-blocker-prepend',
            execute: resolve,
            onStart: function onStart() {},
            onComplete: function onComplete() {},
            onError: function onError() {}
          };
          _this2.queues[type].unshift(operation);
        }
      });
    };
    _proto.removeBlockers = function removeBlockers() {
      if (this.queues === null) {
        return;
      }
      [this.queues.video, this.queues.audio, this.queues.audiovideo].forEach(function (queue) {
        var _queue$;
        var label = (_queue$ = queue[0]) == null ? void 0 : _queue$.label;
        if (label === 'async-blocker' || label === 'async-blocker-prepend') {
          queue[0].execute();
          queue.splice(0, 1);
        } else if (label === 'block-audio') {
          queue.splice(0, 1);
        }
      });
    };
    _proto.insertNext = function insertNext(operations, type) {
      var _this$queues$type;
      if (this.queues === null) {
        return;
      }
      (_this$queues$type = this.queues[type]).splice.apply(_this$queues$type, [1, 0].concat(operations));
    };
    _proto.unblockAudio = function unblockAudio() {
      var _this$current;
      if (this.queues === null) {
        return;
      }
      if (((_this$current = this.current('audio')) == null ? void 0 : _this$current.label) === 'block-audio') {
        this.shiftAndExecuteNext('audio');
      }
    };
    _proto.audioBlocking = function audioBlocking() {
      if (this.queues === null) {
        return false;
      }
      return this.queues.audio.some(function (op) {
        return op.label === 'block-audio';
      });
    };
    _proto.executeNext = function executeNext(type) {
      if (this.queues === null || this.tracks === null) {
        return;
      }
      var queue = this.queues[type];
      if (queue.length) {
        var operation = queue[0];
        try {
          operation.execute();
        } catch (error) {
          var _this$tracks$type;
          operation.onError(error);
          if (this.queues === null || this.tracks === null) {
            return;
          }

          var sb = (_this$tracks$type = this.tracks[type]) == null ? void 0 : _this$tracks$type.buffer;
          if (!(sb != null && sb.updating)) {
            this.shiftAndExecuteNext(type);
          }
        }
      }
    };
    _proto.shiftAndExecuteNext = function shiftAndExecuteNext(type) {
      if (this.queues === null) {
        return;
      }
      this.queues[type].shift();
      this.executeNext(type);
    };
    _proto.current = function current(type) {
      var _this$queues;
      return ((_this$queues = this.queues) == null ? void 0 : _this$queues[type][0]) || null;
    };
    _proto.toString = function toString() {
      var queues = this.queues,
        tracks = this.tracks;
      if (queues === null || tracks === null) {
        return "<destroyed>";
      }
      return "\\n" + this.list('video') + "\\n" + this.list('audio') + "\\n" + this.list('audiovideo') + "}";
    };
    _proto.list = function list(type) {
      var _this$queues2, _this$tracks;
      return (_this$queues2 = this.queues) != null && _this$queues2[type] || (_this$tracks = this.tracks) != null && _this$tracks[type] ? type + ": (" + this.listSbInfo(type) + ") " + this.listOps(type) : '';
    };
    _proto.listSbInfo = function listSbInfo(type) {
      var _this$tracks2;
      var track = (_this$tracks2 = this.tracks) == null ? void 0 : _this$tracks2[type];
      var sb = track == null ? void 0 : track.buffer;
      if (!sb) {
        return 'none';
      }
      return "SourceBuffer" + (sb.updating ? ' updating' : '') + (track.ended ? ' ended' : '') + (track.ending ? ' ending' : '');
    };
    _proto.listOps = function listOps(type) {
      var _this$queues3;
      return ((_this$queues3 = this.queues) == null ? void 0 : _this$queues3[type].map(function (op) {
        return op.label;
      }).join(', ')) || '';
    };
    return BufferOperationQueue;
  }();

  var BinarySearch = {
    search: function search(list, comparisonFn) {
      var minIndex = 0;
      var maxIndex = list.length - 1;
      var currentIndex = null;
      var currentElement = null;
      while (minIndex <= maxIndex) {
        currentIndex = (minIndex + maxIndex) / 2 | 0;
        currentElement = list[currentIndex];
        var comparisonResult = comparisonFn(currentElement);
        if (comparisonResult > 0) {
          minIndex = currentIndex + 1;
        } else if (comparisonResult < 0) {
          maxIndex = currentIndex - 1;
        } else {
          return currentElement;
        }
      }
      return null;
    }
  };

  function findFragmentByPDT(fragments, PDTValue, maxFragLookUpTolerance) {
    if (PDTValue === null || !Array.isArray(fragments) || !fragments.length || !isFiniteNumber(PDTValue)) {
      return null;
    }

    var startPDT = fragments[0].programDateTime;
    if (PDTValue < (startPDT || 0)) {
      return null;
    }
    var endPDT = fragments[fragments.length - 1].endProgramDateTime;
    if (PDTValue >= (endPDT || 0)) {
      return null;
    }
    for (var seg = 0; seg < fragments.length; ++seg) {
      var frag = fragments[seg];
      if (pdtWithinToleranceTest(PDTValue, maxFragLookUpTolerance, frag)) {
        return frag;
      }
    }
    return null;
  }

  function findFragmentByPTS(fragPrevious, fragments, bufferEnd, maxFragLookUpTolerance, nextFragLookupTolerance) {
    if (bufferEnd === void 0) {
      bufferEnd = 0;
    }
    if (maxFragLookUpTolerance === void 0) {
      maxFragLookUpTolerance = 0;
    }
    if (nextFragLookupTolerance === void 0) {
      nextFragLookupTolerance = 0.005;
    }
    var fragNext = null;
    if (fragPrevious) {
      fragNext = fragments[1 + fragPrevious.sn - fragments[0].sn] || null;
      var bufferEdgeError = fragPrevious.endDTS - bufferEnd;
      if (bufferEdgeError > 0 && bufferEdgeError < 0.0000015) {
        bufferEnd += 0.0000015;
      }
      if (fragNext && fragPrevious.level !== fragNext.level && fragNext.end <= fragPrevious.end) {
        fragNext = fragments[2 + fragPrevious.sn - fragments[0].sn] || null;
      }
    } else if (bufferEnd === 0 && fragments[0].start === 0) {
      fragNext = fragments[0];
    }
    if (fragNext && ((!fragPrevious || fragPrevious.level === fragNext.level) && fragmentWithinToleranceTest(bufferEnd, maxFragLookUpTolerance, fragNext) === 0 || fragmentWithinFastStartSwitch(fragNext, fragPrevious, Math.min(nextFragLookupTolerance, maxFragLookUpTolerance)))) {
      return fragNext;
    }
    var foundFragment = BinarySearch.search(fragments, fragmentWithinToleranceTest.bind(null, bufferEnd, maxFragLookUpTolerance));
    if (foundFragment && (foundFragment !== fragPrevious || !fragNext)) {
      return foundFragment;
    }
    return fragNext;
  }
  function fragmentWithinFastStartSwitch(fragNext, fragPrevious, nextFragLookupTolerance) {
    if ((fragPrevious == null ? void 0 : fragPrevious.start) === 0 && fragPrevious.level < fragNext.level && (fragPrevious.endPTS || 0) > 0) {
      var firstDuration = fragPrevious.tagList.reduce(function (duration, tag) {
        if (tag[0] === 'INF') {
          duration += parseFloat(tag[1]);
        }
        return duration;
      }, nextFragLookupTolerance);
      return fragNext.start <= firstDuration;
    }
    return false;
  }

  function fragmentWithinToleranceTest(bufferEnd, maxFragLookUpTolerance, candidate) {
    if (bufferEnd === void 0) {
      bufferEnd = 0;
    }
    if (maxFragLookUpTolerance === void 0) {
      maxFragLookUpTolerance = 0;
    }
    if (candidate.start <= bufferEnd && candidate.start + candidate.duration > bufferEnd) {
      return 0;
    }
    var candidateLookupTolerance = Math.min(maxFragLookUpTolerance, candidate.duration + (candidate.deltaPTS ? candidate.deltaPTS : 0));
    if (candidate.start + candidate.duration - candidateLookupTolerance <= bufferEnd) {
      return 1;
    } else if (candidate.start - candidateLookupTolerance > bufferEnd && candidate.start) {
      return -1;
    }
    return 0;
  }

  function pdtWithinToleranceTest(pdtBufferEnd, maxFragLookUpTolerance, candidate) {
    var candidateLookupTolerance = Math.min(maxFragLookUpTolerance, candidate.duration + (candidate.deltaPTS ? candidate.deltaPTS : 0)) * 1000;

    var endProgramDateTime = candidate.endProgramDateTime || 0;
    return endProgramDateTime - candidateLookupTolerance > pdtBufferEnd;
  }
  function findNearestWithCC(details, cc, pos) {
    if (details) {
      if (details.startCC <= cc && details.endCC >= cc) {
        var fragments = details.fragments;
        var fragmentHint = details.fragmentHint;
        if (fragmentHint) {
          fragments = fragments.concat(fragmentHint);
        }
        var closest;
        BinarySearch.search(fragments, function (candidate) {
          if (candidate.cc < cc) {
            return 1;
          }
          if (candidate.cc > cc) {
            return -1;
          }
          closest = candidate;
          if (candidate.end <= pos) {
            return 1;
          }
          if (candidate.start > pos) {
            return -1;
          }
          return 0;
        });
        return closest || null;
      }
    }
    return null;
  }
  function getNextFrag(details, sn) {
    if (sn === details.endSN && details.fragmentHint) {
      return details.fragmentHint;
    }
    return details.fragments[1 + sn - details.startSN] || null;
  }

  var NetworkErrorAction = {
    DoNothing: 0,
    SendAlternateToPenaltyBox: 2,
    RemoveAlternatePermanently: 3,
    RetryRequest: 5
  };
  var ErrorActionFlags = {
    None: 0,
    MoveAllAlternatesMatchingHost: 1,
    MoveAllAlternatesMatchingHDCP: 2,
    MoveAllAlternatesMatchingKey: 4,
    SwitchToSDR: 8,
    ResetMediaSource: 16
  };
  var ErrorController = function (_Logger) {
    function ErrorController(hls) {
      var _this;
      _this = _Logger.call(this, 'error-controller', hls.logger) || this;
      _this.hls = void 0;
      _this.playlistError = 0;
      _this.hls = hls;
      _this.registerListeners();
      return _this;
    }
    _inheritsLoose(ErrorController, _Logger);
    var _proto = ErrorController.prototype;
    _proto.registerListeners = function registerListeners() {
      var hls = this.hls;
      hls.on(Events.ERROR, this.onError, this);
      hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
      hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
    };
    _proto.unregisterListeners = function unregisterListeners() {
      var hls = this.hls;
      if (!hls) {
        return;
      }
      hls.off(Events.ERROR, this.onError, this);
      hls.off(Events.ERROR, this.onErrorOut, this);
      hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
      hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
    };
    _proto.destroy = function destroy() {
      this.unregisterListeners();
      this.hls = null;
    };
    _proto.startLoad = function startLoad(startPosition) {};
    _proto.stopLoad = function stopLoad() {
      this.playlistError = 0;
    };
    _proto.getVariantLevelIndex = function getVariantLevelIndex(frag) {
      if ((frag == null ? void 0 : frag.type) === PlaylistLevelType.MAIN) {
        return frag.level;
      }
      return this.getVariantIndex();
    };
    _proto.getVariantIndex = function getVariantIndex() {
      var _hls$loadLevelObj;
      var hls = this.hls;
      var currentLevel = hls.currentLevel;
      if ((_hls$loadLevelObj = hls.loadLevelObj) != null && _hls$loadLevelObj.details || currentLevel === -1) {
        return hls.loadLevel;
      }
      return currentLevel;
    };
    _proto.variantHasKey = function variantHasKey(level, keyInError) {
      if (level) {
        var _level$details;
        if ((_level$details = level.details) != null && _level$details.hasKey(keyInError)) {
          return true;
        }
        var audioGroupsIds = level.audioGroups;
        if (audioGroupsIds) {
          var audioTracks = this.hls.allAudioTracks.filter(function (track) {
            return audioGroupsIds.indexOf(track.groupId) >= 0;
          });
          return audioTracks.some(function (track) {
            var _track$details;
            return (_track$details = track.details) == null ? void 0 : _track$details.hasKey(keyInError);
          });
        }
      }
      return false;
    };
    _proto.onManifestLoading = function onManifestLoading() {
      this.playlistError = 0;
    };
    _proto.onLevelUpdated = function onLevelUpdated() {
      this.playlistError = 0;
    };
    _proto.onError = function onError(event, data) {
      var _data$frag;
      if (data.fatal) {
        return;
      }
      var hls = this.hls;
      var context = data.context;
      switch (data.details) {
        case ErrorDetails.FRAG_LOAD_ERROR:
        case ErrorDetails.FRAG_LOAD_TIMEOUT:
        case ErrorDetails.KEY_LOAD_ERROR:
        case ErrorDetails.KEY_LOAD_TIMEOUT:
          data.errorAction = this.getFragRetryOrSwitchAction(data);
          return;
        case ErrorDetails.FRAG_PARSING_ERROR:
          if ((_data$frag = data.frag) != null && _data$frag.gap) {
            data.errorAction = createDoNothingErrorAction();
            return;
          }
        case ErrorDetails.FRAG_GAP:
        case ErrorDetails.FRAG_DECRYPT_ERROR:
          {
            data.errorAction = this.getFragRetryOrSwitchAction(data);
            data.errorAction.action = NetworkErrorAction.SendAlternateToPenaltyBox;
            return;
          }
        case ErrorDetails.PLAYLIST_UNCHANGED_ERROR:
        case ErrorDetails.LEVEL_EMPTY_ERROR:
        case ErrorDetails.LEVEL_PARSING_ERROR:
          {
            var _data$context;
            var levelIndex = data.parent === PlaylistLevelType.MAIN ? data.level : hls.loadLevel;
            if (data.details === ErrorDetails.LEVEL_EMPTY_ERROR && !!((_data$context = data.context) != null && (_data$context = _data$context.levelDetails) != null && _data$context.live)) {
              data.errorAction = this.getPlaylistRetryOrSwitchAction(data, levelIndex);
            } else {
              data.levelRetry = false;
              data.errorAction = this.getLevelSwitchAction(data, levelIndex);
            }
          }
          return;
        case ErrorDetails.LEVEL_LOAD_ERROR:
        case ErrorDetails.LEVEL_LOAD_TIMEOUT:
          if (typeof (context == null ? void 0 : context.level) === 'number') {
            data.errorAction = this.getPlaylistRetryOrSwitchAction(data, context.level);
          }
          return;
        case ErrorDetails.AUDIO_TRACK_LOAD_ERROR:
        case ErrorDetails.AUDIO_TRACK_LOAD_TIMEOUT:
        case ErrorDetails.SUBTITLE_LOAD_ERROR:
        case ErrorDetails.SUBTITLE_TRACK_LOAD_TIMEOUT:
          if (context) {
            var level = hls.loadLevelObj;
            if (level && (context.type === LoaderContextType.AUDIO_TRACK && level.hasAudioGroup(context.groupId) || context.type === LoaderContextType.SUBTITLE_TRACK && level.hasSubtitleGroup(context.groupId))) {
              data.errorAction = this.getPlaylistRetryOrSwitchAction(data, hls.loadLevel);
              data.errorAction.action = NetworkErrorAction.SendAlternateToPenaltyBox;
              data.errorAction.flags = ErrorActionFlags.MoveAllAlternatesMatchingHost;
              return;
            }
          }
          return;
        case ErrorDetails.KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED:
          {
            data.errorAction = {
              action: NetworkErrorAction.SendAlternateToPenaltyBox,
              flags: ErrorActionFlags.MoveAllAlternatesMatchingHDCP
            };
          }
          return;
        case ErrorDetails.KEY_SYSTEM_SESSION_UPDATE_FAILED:
        case ErrorDetails.KEY_SYSTEM_STATUS_INTERNAL_ERROR:
        case ErrorDetails.KEY_SYSTEM_NO_SESSION:
          {
            data.errorAction = {
              action: NetworkErrorAction.SendAlternateToPenaltyBox,
              flags: ErrorActionFlags.MoveAllAlternatesMatchingKey
            };
          }
          return;
        case ErrorDetails.BUFFER_ADD_CODEC_ERROR:
        case ErrorDetails.REMUX_ALLOC_ERROR:
        case ErrorDetails.BUFFER_APPEND_ERROR:
          if (!data.errorAction) {
            data.errorAction = this.getLevelSwitchAction(data, data.level);
          }
          return;
        case ErrorDetails.MEDIA_SOURCE_REQUIRES_RESET:
          if (!data.errorAction) {
            data.errorAction = this.getLevelSwitchAction(data, data.level);
          }
          data.errorAction.flags |= ErrorActionFlags.ResetMediaSource | ErrorActionFlags.SwitchToSDR;
          return;
        case ErrorDetails.INTERNAL_EXCEPTION:
        case ErrorDetails.BUFFER_APPENDING_ERROR:
        case ErrorDetails.BUFFER_APPEND_NO_PROGRESS:
        case ErrorDetails.BUFFER_FULL_ERROR:
        case ErrorDetails.LEVEL_SWITCH_ERROR:
        case ErrorDetails.BUFFER_STALLED_ERROR:
        case ErrorDetails.BUFFER_SEEK_OVER_HOLE:
        case ErrorDetails.BUFFER_NUDGE_ON_STALL:
          data.errorAction = createDoNothingErrorAction();
          return;
      }
      if (data.type === ErrorTypes.KEY_SYSTEM_ERROR) {
        data.levelRetry = false;
        data.errorAction = createDoNothingErrorAction();
      }
    };
    _proto.getPlaylistRetryOrSwitchAction = function getPlaylistRetryOrSwitchAction(data, levelIndex) {
      var hls = this.hls;
      var retryConfig = getRetryConfig(hls.config.playlistLoadPolicy, data);
      var retryCount = this.playlistError++;
      var retry = shouldRetry(retryConfig, retryCount, isTimeoutError(data), data.response);
      if (retry) {
        return {
          action: NetworkErrorAction.RetryRequest,
          flags: ErrorActionFlags.None,
          retryConfig: retryConfig,
          retryCount: retryCount
        };
      }
      var errorAction = this.getLevelSwitchAction(data, levelIndex);
      if (retryConfig) {
        errorAction.retryConfig = retryConfig;
        errorAction.retryCount = retryCount;
      }
      return errorAction;
    };
    _proto.getFragRetryOrSwitchAction = function getFragRetryOrSwitchAction(data) {
      var hls = this.hls;
      var variantLevelIndex = this.getVariantLevelIndex(data.frag);
      var level = hls.levels[variantLevelIndex];
      var _hls$config = hls.config,
        fragLoadPolicy = _hls$config.fragLoadPolicy,
        keyLoadPolicy = _hls$config.keyLoadPolicy;
      var retryConfig = getRetryConfig(isKeyError(data) ? keyLoadPolicy : fragLoadPolicy, data);
      var fragmentErrors = hls.levels.reduce(function (acc, level) {
        return acc + level.fragmentError;
      }, 0);
      if (level) {
        if (data.details !== ErrorDetails.FRAG_GAP) {
          level.fragmentError++;
        }
        if (!isUnusableKeyError(data)) {
          var retry = shouldRetry(retryConfig, fragmentErrors, isTimeoutError(data), data.response);
          if (retry) {
            return {
              action: NetworkErrorAction.RetryRequest,
              flags: ErrorActionFlags.None,
              retryConfig: retryConfig,
              retryCount: fragmentErrors
            };
          }
        }
      }
      var errorAction = this.getLevelSwitchAction(data, variantLevelIndex);
      if (retryConfig) {
        errorAction.retryConfig = retryConfig;
        errorAction.retryCount = fragmentErrors;
      }
      return errorAction;
    };
    _proto.getLevelSwitchAction = function getLevelSwitchAction(data, levelIndex) {
      var hls = this.hls;
      if (levelIndex === null || levelIndex === undefined) {
        levelIndex = hls.loadLevel;
      }
      var level = this.hls.levels[levelIndex];
      if (level) {
        var _data$frag2, _data$context2;
        var errorDetails = data.details;
        level.loadError++;
        level.loadErrorTime = self.performance.now();
        if (errorDetails === ErrorDetails.BUFFER_APPEND_ERROR) {
          level.fragmentError++;
        }
        var nextLevel = -1;
        var levels = hls.levels,
          loadLevel = hls.loadLevel,
          minAutoLevel = hls.minAutoLevel,
          maxAutoLevel = hls.maxAutoLevel;
        if (!hls.autoLevelEnabled && !hls.config.preserveManualLevelOnError) {
          hls.loadLevel = -1;
        }
        var fragErrorType = (_data$frag2 = data.frag) == null ? void 0 : _data$frag2.type;
        var isAudioCodecError = fragErrorType === PlaylistLevelType.AUDIO && errorDetails === ErrorDetails.FRAG_PARSING_ERROR || data.sourceBufferName === 'audio' && isCodecRelated(data);
        var findAudioCodecAlternate = isAudioCodecError && levels.some(function (_ref) {
          var audioCodec = _ref.audioCodec;
          return level.audioCodec !== audioCodec;
        });
        var isVideoCodecError = data.sourceBufferName === 'video' && isCodecRelated(data);
        var findVideoCodecAlternate = isVideoCodecError && levels.some(function (_ref2) {
          var codecSet = _ref2.codecSet,
            audioCodec = _ref2.audioCodec;
          return level.codecSet !== codecSet && level.audioCodec === audioCodec;
        });
        var _ref3 = (_data$context2 = data.context) != null ? _data$context2 : {},
          playlistErrorType = _ref3.type,
          playlistErrorGroupId = _ref3.groupId;
        var _loop = function _loop() {
            var candidate = (i + loadLevel) % levels.length;
            if (candidate !== loadLevel && candidate >= minAutoLevel && candidate <= maxAutoLevel && (levels[candidate].loadError === 0 || isPenaltyExpired(levels[candidate], hls.config.errorPenaltyExpireMs))) {
              var _level$audioGroups, _level$subtitleGroups;
              var levelCandidate = levels[candidate];

              if (errorDetails === ErrorDetails.FRAG_GAP && fragErrorType === PlaylistLevelType.MAIN && data.frag) {
                var levelDetails = levels[candidate].details;
                if (levelDetails) {
                  var fragCandidate = findFragmentByPTS(data.frag, levelDetails.fragments, data.frag.start);
                  if (fragCandidate != null && fragCandidate.gap) {
                    return 0;
                  }
                }
              } else if (playlistErrorType === LoaderContextType.AUDIO_TRACK && levelCandidate.hasAudioGroup(playlistErrorGroupId) || playlistErrorType === LoaderContextType.SUBTITLE_TRACK && levelCandidate.hasSubtitleGroup(playlistErrorGroupId)) {
                return 0;
              } else if (fragErrorType === PlaylistLevelType.AUDIO && (_level$audioGroups = level.audioGroups) != null && _level$audioGroups.some(function (groupId) {
                return levelCandidate.hasAudioGroup(groupId);
              }) || fragErrorType === PlaylistLevelType.SUBTITLE && (_level$subtitleGroups = level.subtitleGroups) != null && _level$subtitleGroups.some(function (groupId) {
                return levelCandidate.hasSubtitleGroup(groupId);
              }) || findAudioCodecAlternate && level.audioCodec === levelCandidate.audioCodec || findVideoCodecAlternate && level.codecSet === levelCandidate.codecSet || !findAudioCodecAlternate && level.audioCodec !== levelCandidate.audioCodec) {
                return 0;
              }
              nextLevel = candidate;
              return 1;
            }
          },
          _ret;
        for (var i = levels.length; i--;) {
          _ret = _loop();
          if (_ret === 0) continue;
          if (_ret === 1) break;
        }
        if (nextLevel > -1 && hls.loadLevel !== nextLevel) {
          data.levelRetry = true;
          this.playlistError = 0;
          return {
            action: NetworkErrorAction.SendAlternateToPenaltyBox,
            flags: ErrorActionFlags.None,
            nextAutoLevel: nextLevel
          };
        }
      }
      return {
        action: NetworkErrorAction.SendAlternateToPenaltyBox,
        flags: ErrorActionFlags.MoveAllAlternatesMatchingHost
      };
    };
    _proto.onErrorOut = function onErrorOut(event, data) {
      var _data$errorAction, _data$errorAction2;
      switch ((_data$errorAction = data.errorAction) == null ? void 0 : _data$errorAction.action) {
        case NetworkErrorAction.DoNothing:
          break;
        case NetworkErrorAction.SendAlternateToPenaltyBox:
          this.sendAlternateToPenaltyBox(data);
          if (!data.errorAction.resolved && data.details !== ErrorDetails.FRAG_GAP && data.details !== ErrorDetails.PLAYLIST_UNCHANGED_ERROR && !isAppendStateRelated(data)) {
            data.fatal = true;
          }
          break;
      }
      var flags = ((_data$errorAction2 = data.errorAction) == null ? void 0 : _data$errorAction2.flags) || 0;
      if (flags & ErrorActionFlags.ResetMediaSource) {
        this.hls.recoverMediaError();
      }
      if (data.fatal) {
        this.hls.stopLoad();
        return;
      }
    };
    _proto.sendAlternateToPenaltyBox = function sendAlternateToPenaltyBox(data) {
      var hls = this.hls;
      var errorAction = data.errorAction;
      if (!errorAction) {
        return;
      }
      var nextAutoLevel = errorAction.nextAutoLevel;
      if (errorAction.flags === ErrorActionFlags.None) {
        this.switchLevel(data, nextAutoLevel);
      } else if (errorAction.flags & ErrorActionFlags.SwitchToSDR) {
        var levels = this.hls.levels;
        var levelCountWithError = levels.length;
        for (var i = levelCountWithError; i--;) {
          if (levels[i].videoRange !== 'SDR') {
            levels[i].fragmentError++;
            levels[i].loadError++;
          } else if (nextAutoLevel === undefined) {
            nextAutoLevel = i;
          }
        }
      } else if (errorAction.flags & ErrorActionFlags.MoveAllAlternatesMatchingHDCP) {
        var levelIndex = this.getVariantLevelIndex(data.frag);
        var level = hls.levels[levelIndex];
        var restrictedHdcpLevel = level == null ? void 0 : level.attrs['HDCP-LEVEL'];
        errorAction.hdcpLevel = restrictedHdcpLevel;
        var restrictedOutputErrorWhileNoneDeclared = restrictedHdcpLevel === 'NONE';
        if (restrictedHdcpLevel && !restrictedOutputErrorWhileNoneDeclared) {
          hls.maxHdcpLevel = HdcpLevels[HdcpLevels.indexOf(restrictedHdcpLevel) - 1];
          errorAction.resolved = true;
        } else {
          if (restrictedOutputErrorWhileNoneDeclared) {
          }
          errorAction.flags |= ErrorActionFlags.MoveAllAlternatesMatchingKey;
        }
      }
      if (errorAction.flags & ErrorActionFlags.MoveAllAlternatesMatchingKey) {
        var levelKey = data.decryptdata;
        if (levelKey) {
          var _levels = this.hls.levels;
          var _levelCountWithError = _levels.length;
          for (var _i = _levelCountWithError; _i--;) {
            if (this.variantHasKey(_levels[_i], levelKey)) {
              var _levels$_i$audioGroup, _data$frag3;
              _levels[_i].fragmentError++;
              _levels[_i].loadError++;
              this.hls.removeLevel(_i);
            }
          }
          var frag = data.frag;
          if (this.hls.levels.length < _levelCountWithError) {
            errorAction.resolved = true;
          } else if (frag && frag.type !== PlaylistLevelType.MAIN) {
            var fragLevelKey = frag.decryptdata;
            if (fragLevelKey && !levelKey.matches(fragLevelKey)) {
              errorAction.resolved = true;
            }
          }
        }
      }

      if (!errorAction.resolved) {
        this.switchLevel(data, nextAutoLevel);
      }
    };
    _proto.switchLevel = function switchLevel(data, levelIndex) {
      if (levelIndex !== undefined && data.errorAction) {
        this.hls.nextAutoLevel = levelIndex;
        data.errorAction.resolved = true;
        this.hls.nextLoadLevel = this.hls.nextAutoLevel;
        if (data.details === ErrorDetails.BUFFER_ADD_CODEC_ERROR && data.mimeType && data.sourceBufferName !== 'audiovideo') {
          var codec = getCodecsForMimeType(data.mimeType);
          var levels = this.hls.levels;
          for (var i = levels.length; i--;) {
            if (levels[i][data.sourceBufferName + "Codec"] === codec) {
              this.hls.removeLevel(i);
            }
          }
        }
      }
    };
    return ErrorController;
  }(Logger);
  function isCodecRelated(data) {
    var errorDetails = data.details;
    if (errorDetails === ErrorDetails.BUFFER_ADD_CODEC_ERROR || errorDetails === ErrorDetails.MEDIA_SOURCE_REQUIRES_RESET) {
      return true;
    }
    if (errorDetails === ErrorDetails.BUFFER_APPEND_ERROR) {
      if (!isAppendStateRelated(data)) {
        return true;
      }
    }
    return false;
  }
  function isAppendStateRelated(data) {
    var errorDetails = data.details;
    return errorDetails === ErrorDetails.BUFFER_APPEND_ERROR && (data.error.name === 'QuotaExceededError' || data.error.name === 'InvalidStateError');
  }
  function createDoNothingErrorAction(resolved) {
    var errorAction = {
      action: NetworkErrorAction.DoNothing,
      flags: ErrorActionFlags.None
    };
    if (resolved) {
      errorAction.resolved = true;
    }
    return errorAction;
  }

  var DEFAULT_TARGET_DURATION = 10;

  var LevelDetails = function () {
    function LevelDetails(baseUrl) {
      this.PTSKnown = false;
      this.alignedSliding = false;
      this.averagetargetduration = void 0;
      this.endCC = 0;
      this.endSN = 0;
      this.fragments = void 0;
      this.fragmentHint = void 0;
      this.partList = null;
      this.dateRanges = void 0;
      this.dateRangeTagCount = 0;
      this.live = true;
      this.iframesOnly = false;
      this.requestScheduled = -1;
      this.ageHeader = 0;
      this.advancedDateTime = void 0;
      this.updated = true;
      this.advanced = true;
      this.misses = 0;
      this.startCC = 0;
      this.startSN = 0;
      this.startTimeOffset = null;
      this.targetduration = 0;
      this.totalduration = 0;
      this.type = null;
      this.url = void 0;
      this.m3u8 = '';
      this.version = null;
      this.canBlockReload = false;
      this.canSkipUntil = 0;
      this.canSkipDateRanges = false;
      this.skippedSegments = 0;
      this.recentlyRemovedDateranges = void 0;
      this.partHoldBack = 0;
      this.holdBack = 0;
      this.partTarget = 0;
      this.preloadHint = void 0;
      this.renditionReports = void 0;
      this.tuneInGoal = 0;
      this.deltaUpdateFailed = void 0;
      this.driftStartTime = 0;
      this.driftEndTime = 0;
      this.driftStart = 0;
      this.driftEnd = 0;
      this.encryptedFragments = void 0;
      this.playlistParsingError = null;
      this.variableList = null;
      this.hasVariableRefs = false;
      this.appliedTimelineOffset = void 0;
      this.fragments = [];
      this.encryptedFragments = [];
      this.dateRanges = {};
      this.url = baseUrl;
    }
    var _proto = LevelDetails.prototype;
    _proto.reloaded = function reloaded(previous) {
      if (!previous) {
        this.advanced = true;
        this.updated = true;
        return;
      }
      var partSnDiff = this.lastPartSn - previous.lastPartSn;
      var partIndexDiff = this.lastPartIndex - previous.lastPartIndex;
      this.updated = this.endSN !== previous.endSN || !!partIndexDiff || !!partSnDiff || !this.live;
      this.advanced = this.endSN > previous.endSN || partSnDiff > 0 || partSnDiff === 0 && partIndexDiff > 0;
      if (this.updated || this.advanced) {
        this.misses = 0;
      } else {
        this.misses = previous.misses + 1;
      }
    };
    _proto.hasKey = function hasKey(levelKey) {
      return this.encryptedFragments.some(function (frag) {
        var decryptdata = frag.decryptdata;
        if (!decryptdata) {
          frag.setKeyFormat(levelKey.keyFormat);
          decryptdata = frag.decryptdata;
        }
        return !!decryptdata && levelKey.matches(decryptdata);
      });
    };
    return _createClass(LevelDetails, [{
      key: "hasProgramDateTime",
      get: function get() {
        if (this.fragments.length) {
          return isFiniteNumber(this.fragments[this.fragments.length - 1].programDateTime);
        }
        return false;
      }
    }, {
      key: "levelTargetDuration",
      get: function get() {
        return this.averagetargetduration || this.targetduration || DEFAULT_TARGET_DURATION;
      }
    }, {
      key: "drift",
      get: function get() {
        var runTime = this.driftEndTime - this.driftStartTime;
        if (runTime > 0) {
          var runDuration = this.driftEnd - this.driftStart;
          return runDuration * 1000 / runTime;
        }
        return 1;
      }
    }, {
      key: "edge",
      get: function get() {
        return this.partEnd || this.fragmentEnd;
      }
    }, {
      key: "partEnd",
      get: function get() {
        var _this$partList;
        if ((_this$partList = this.partList) != null && _this$partList.length) {
          return this.partList[this.partList.length - 1].end;
        }
        return this.fragmentEnd;
      }
    }, {
      key: "fragmentEnd",
      get: function get() {
        if (this.fragments.length) {
          return this.fragments[this.fragments.length - 1].end;
        }
        return 0;
      }
    }, {
      key: "fragmentStart",
      get: function get() {
        if (this.fragments.length) {
          return this.fragments[0].start;
        }
        return 0;
      }
    }, {
      key: "age",
      get: function get() {
        if (this.advancedDateTime) {
          return Math.max(Date.now() - this.advancedDateTime, 0) / 1000;
        }
        return 0;
      }
    }, {
      key: "lastPartIndex",
      get: function get() {
        var _this$partList2;
        if ((_this$partList2 = this.partList) != null && _this$partList2.length) {
          return this.partList[this.partList.length - 1].index;
        }
        return -1;
      }
    }, {
      key: "maxPartIndex",
      get: function get() {
        var partList = this.partList;
        if (partList) {
          var lastIndex = this.lastPartIndex;
          if (lastIndex !== -1) {
            for (var i = partList.length; i--;) {
              if (partList[i].index > lastIndex) {
                return partList[i].index;
              }
            }
            return lastIndex;
          }
        }
        return 0;
      }
    }, {
      key: "lastPartSn",
      get: function get() {
        var _this$partList3;
        if ((_this$partList3 = this.partList) != null && _this$partList3.length) {
          return this.partList[this.partList.length - 1].fragment.sn;
        }
        return this.endSN;
      }
    }, {
      key: "expired",
      get: function get() {
        if (this.live && this.age && this.misses < 3) {
          var playlistWindowDuration = this.partEnd - this.fragmentStart;
          return this.age > Math.max(playlistWindowDuration, this.totalduration) + this.levelTargetDuration;
        }
        return false;
      }
    }]);
  }();

  var noopBuffered = {
    length: 0,
    start: function start() {
      return 0;
    },
    end: function end() {
      return 0;
    }
  };
  var BufferHelper = function () {
    function BufferHelper() {}
    BufferHelper.isBuffered = function isBuffered(media, position) {
      if (media) {
        var buffered = BufferHelper.getBuffered(media);
        for (var i = buffered.length; i--;) {
          if (position >= buffered.start(i) && position <= buffered.end(i)) {
            return true;
          }
        }
      }
      return false;
    };
    BufferHelper.bufferedRanges = function bufferedRanges(media) {
      if (media) {
        var timeRanges = BufferHelper.getBuffered(media);
        return BufferHelper.timeRangesToArray(timeRanges);
      }
      return [];
    };
    BufferHelper.timeRangesToArray = function timeRangesToArray(timeRanges) {
      var buffered = [];
      for (var i = 0; i < timeRanges.length; i++) {
        buffered.push({
          start: timeRanges.start(i),
          end: timeRanges.end(i)
        });
      }
      return buffered;
    };
    BufferHelper.bufferInfo = function bufferInfo(media, pos, maxHoleDuration) {
      if (media) {
        var buffered = BufferHelper.bufferedRanges(media);
        if (buffered.length) {
          return BufferHelper.bufferedInfo(buffered, pos, maxHoleDuration);
        }
      }
      return {
        len: 0,
        start: pos,
        end: pos,
        bufferedIndex: -1
      };
    };
    BufferHelper.bufferedInfo = function bufferedInfo(buffered, pos, maxHoleDuration) {
      pos = Math.max(0, pos);
      if (buffered.length > 1) {
        buffered.sort(function (a, b) {
          return a.start - b.start || b.end - a.end;
        });
      }
      var bufferedIndex = -1;
      var buffered2 = [];
      if (maxHoleDuration) {
        for (var i = 0; i < buffered.length; i++) {
          if (pos >= buffered[i].start && pos <= buffered[i].end) {
            bufferedIndex = i;
          }
          var buf2len = buffered2.length;
          if (buf2len) {
            var buf2end = buffered2[buf2len - 1].end;
            if (buffered[i].start - buf2end < maxHoleDuration) {
              if (buffered[i].end > buf2end) {
                buffered2[buf2len - 1].end = buffered[i].end;
              }
            } else {
              buffered2.push(buffered[i]);
            }
          } else {
            buffered2.push(buffered[i]);
          }
        }
      } else {
        buffered2 = buffered;
      }
      var bufferLen = 0;
      var nextStart;

      var bufferStart = pos;
      var bufferEnd = pos;
      for (var _i = 0; _i < buffered2.length; _i++) {
        var start = buffered2[_i].start;
        var end = buffered2[_i].end;
        if (bufferedIndex === -1 && pos >= start && pos <= end) {
          bufferedIndex = _i;
        }
        if (pos + maxHoleDuration >= start && pos < end) {
          bufferStart = start;
          bufferEnd = end;
          bufferLen = bufferEnd - pos;
        } else if (pos + maxHoleDuration < start) {
          nextStart = start;
          break;
        }
      }
      return {
        len: bufferLen,
        start: bufferStart || 0,
        end: bufferEnd || 0,
        nextStart: nextStart,
        buffered: buffered,
        bufferedIndex: bufferedIndex
      };
    }

;
    BufferHelper.getBuffered = function getBuffered(media) {
      try {
        return media.buffered || noopBuffered;
      } catch (e) {
        return noopBuffered;
      }
    };
    return BufferHelper;
  }();

  function addEventListener(el, type, listener) {
    removeEventListener(el, type, listener);
    el.addEventListener(type, listener);
  }
  function removeEventListener(el, type, listener) {
    el.removeEventListener(type, listener);
  }

  function timeRangesToString(r) {
    var log = '';
    var len = r.length;
    for (var i = 0; i < len; i++) {
      log += "[" + r.start(i).toFixed(3) + "-" + r.end(i).toFixed(3) + "]";
    }
    return log;
  }

  var VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\\.[^.,]+)+/;
  var TRACK_REMOVED_ERROR_NAME = 'HlsJsTrackRemovedError';
  var LOOP_FLUSH_SAFETY_MARGIN = 0.25;
  var MIN_BUFFERED_PROGRESS = 0.001;
  var HlsJsTrackRemovedError = function (_Error) {
    function HlsJsTrackRemovedError(message) {
      var _this;
      _this = _Error.call(this, message) || this;
      _this.name = TRACK_REMOVED_ERROR_NAME;
      return _this;
    }
    _inheritsLoose(HlsJsTrackRemovedError, _Error);
    return HlsJsTrackRemovedError;
  }( _wrapNativeSuper(Error));
  var BufferController = function (_Logger) {
    function BufferController(hls, fragmentTracker) {
      var _this2;
      _this2 = _Logger.call(this, 'buffer-controller', hls.logger) || this;
      _this2.hls = void 0;
      _this2.fragmentTracker = void 0;
      _this2.fragmentAppendProgress = Object.create(null);
      _this2.appendsWithoutProgress = Object.create(null);
      _this2.details = null;
      _this2._objectUrl = null;
      _this2.operationQueue = null;
      _this2.bufferCodecEventsTotal = 0;
      _this2.media = null;
      _this2.mediaSource = null;
      _this2.lastMpegAudioChunk = null;
      _this2.lastVideoAppendEnd = 0;
      _this2.appendSource = void 0;
      _this2.transferData = void 0;
      _this2.overrides = void 0;
      _this2.appendErrors = {
        audio: 0,
        video: 0,
        audiovideo: 0
      };
      _this2.appendError = void 0;
      _this2._quotaEvictionPending = {};
      _this2.tracks = {};
      _this2.sourceBuffers = [[null, null], [null, null]];
      _this2._onEndStreaming = function (event) {
        var _this2$mediaSource;
        if (!_this2.hls) {
          return;
        }
        if (((_this2$mediaSource = _this2.mediaSource) == null ? void 0 : _this2$mediaSource.readyState) !== 'open') {
          return;
        }
        if (!_this2.media || _this2.media.seeking) {
          return;
        }
        _this2.hls.pauseBuffering();
      };
      _this2._onStartStreaming = function (event) {
        if (!_this2.hls) {
          return;
        }
        _this2.hls.resumeBuffering();
      };
      _this2._onMediaSourceOpen = function (e) {
        var _this3 = _this2,
          media = _this3.media,
          mediaSource = _this3.mediaSource;
        if (e) {
        }
        if (!media || !mediaSource) {
          return;
        }
        removeEventListener(mediaSource, 'sourceopen', _this2._onMediaSourceOpen);
        removeEventListener(media, 'emptied', _this2._onMediaEmptied);
        _this2.updateDuration();
        _this2.hls.trigger(Events.MEDIA_ATTACHED, {
          media: media,
          mediaSource: mediaSource
        });
        if (_this2.mediaSource !== null) {
          _this2.checkPendingTracks();
        }
      };
      _this2._onMediaSourceClose = function () {
        if (_this2.media) {
          var mediaError = _this2.media.error;
          var _this4 = _this2,
            appendError = _this4.appendError,
            appendErrors = _this4.appendErrors;
          var fatal = false;
          if (!appendError && mediaError) {
            var appendErrorCount = Math.max(++appendErrors.audio, ++appendErrors.video, ++appendErrors.audiovideo);
            fatal = appendErrorCount >= _this2.hls.config.appendErrorMaxRetry;
          }
          var error = new Error("MediaSource closed while media attached" + mediaErrorRecoveryMessage(fatal, mediaError));
          _this2.hls.trigger(Events.ERROR, _extends({
            fatal: fatal
          }, appendError, {
            type: ErrorTypes.MEDIA_ERROR,
            details: ErrorDetails.MEDIA_SOURCE_REQUIRES_RESET,
            error: error
          }));
        }
      };
      _this2._onMediaSourceEnded = function () {
      };
      _this2._onMediaEmptied = function () {
        var _this5 = _this2,
          mediaSrc = _this5.mediaSrc,
          _objectUrl = _this5._objectUrl;
        if (mediaSrc !== _objectUrl) {
          _this2.error("Media element src was set while attaching MediaSource (" + _objectUrl + " > " + mediaSrc + ")");
        }
      };
      _this2._onMediaError = function () {
        var _this6 = _this2,
          media = _this6.media;
        if (media) {
          var _media$error, _media$error2;
        }
      };
      _this2.hls = hls;
      _this2.fragmentTracker = fragmentTracker;
      _this2.appendSource = isManagedMediaSource(getMediaSource(hls.config.preferManagedMediaSource));
      _this2.initTracks();
      _this2.registerListeners();
      return _this2;
    }
    _inheritsLoose(BufferController, _Logger);
    var _proto = BufferController.prototype;
    _proto.hasSourceTypes = function hasSourceTypes() {
      return Object.keys(this.tracks).length > 0;
    };
    _proto.destroy = function destroy() {
      this.unregisterListeners();
      this.details = null;
      this.lastMpegAudioChunk = null;
      this.transferData = this.overrides = undefined;
      if (this.operationQueue) {
        this.operationQueue.destroy();
        this.operationQueue = null;
      }
      this.hls = this.fragmentTracker = null;
      this._onMediaSourceOpen = this._onMediaSourceClose = null;
      this._onMediaSourceEnded = null;
      this._onStartStreaming = this._onEndStreaming = null;
      this._onMediaEmptied = this._onMediaError = null;
    };
    _proto.registerListeners = function registerListeners() {
      var hls = this.hls;
      hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
      hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
      hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
      hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
      hls.on(Events.BUFFER_RESET, this.onBufferReset, this);
      hls.on(Events.BUFFER_APPENDING, this.onBufferAppending, this);
      hls.on(Events.BUFFER_CODECS, this.onBufferCodecs, this);
      hls.on(Events.BUFFER_EOS, this.onBufferEos, this);
      hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
      hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
      hls.on(Events.FRAG_PARSED, this.onFragParsed, this);
      hls.on(Events.FRAG_CHANGED, this.onFragChanged, this);
      hls.on(Events.ERROR, this.onError, this);
    };
    _proto.unregisterListeners = function unregisterListeners() {
      var hls = this.hls;
      hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
      hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
      hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
      hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
      hls.off(Events.BUFFER_RESET, this.onBufferReset, this);
      hls.off(Events.BUFFER_APPENDING, this.onBufferAppending, this);
      hls.off(Events.BUFFER_CODECS, this.onBufferCodecs, this);
      hls.off(Events.BUFFER_EOS, this.onBufferEos, this);
      hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
      hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
      hls.off(Events.FRAG_PARSED, this.onFragParsed, this);
      hls.off(Events.FRAG_CHANGED, this.onFragChanged, this);
      hls.off(Events.ERROR, this.onError, this);
    };
    _proto.transferMedia = function transferMedia() {
      var _this7 = this;
      var media = this.media,
        mediaSource = this.mediaSource;
      if (!media) {
        return null;
      }
      var tracks = {};
      if (this.operationQueue) {
        var updating = this.isUpdating();
        if (!updating) {
          this.operationQueue.removeBlockers();
        }
        var queued = this.isQueued();
        if (updating || queued) {
        }
        this.operationQueue.destroy();
      }
      var transferData = this.transferData;
      if (transferData && !this.sourceBufferCount && transferData.mediaSource === mediaSource) {
        _extends(tracks, transferData.tracks);
      } else {
        this.sourceBuffers.forEach(function (tuple) {
          var type = tuple[0];
          if (type) {
            tracks[type] = _extends({}, _this7.tracks[type]);
            _this7.removeBuffer(type);
          }
          tuple[0] = tuple[1] = null;
        });
      }
      return {
        media: media,
        mediaSource: mediaSource,
        tracks: tracks
      };
    };
    _proto.initTracks = function initTracks() {
      var tracks = {};
      this.resetAppendProgress();
      this.sourceBuffers = [[null, null], [null, null]];
      this.tracks = tracks;
      this.resetQueue();
      this.lastMpegAudioChunk = null;
      this.lastVideoAppendEnd = 0;
    };
    _proto.onManifestLoading = function onManifestLoading() {
      this.bufferCodecEventsTotal = 0;
      this.details = null;
      this.resetAppendErrors();
      this.resetAppendProgress();
    };
    _proto.onManifestParsed = function onManifestParsed(event, data) {
      var _this$transferData;
      var codecEvents = 2;
      if (data.audio && !data.video || !data.altAudio) {
        codecEvents = 1;
      }
      this.bufferCodecEventsTotal = codecEvents;
      if ((_this$transferData = this.transferData) != null && _this$transferData.mediaSource && this.sourceBufferCount && codecEvents) {
        this.bufferCreated();
      }
    };
    _proto.onMediaAttaching = function onMediaAttaching(event, data) {
      var media = this.media = data.media;
      this.transferData = this.overrides = undefined;
      var MediaSource = getMediaSource(this.appendSource);
      if (MediaSource) {
        var transferringMedia = !!data.mediaSource;
        if (transferringMedia || data.overrides) {
          this.transferData = data;
          this.overrides = data.overrides;
        }
        var ms = this.mediaSource = data.mediaSource || new MediaSource();
        this.assignMediaSource(ms);
        if (transferringMedia) {
          this._objectUrl = media.src;
          this.attachTransferred();
        } else {
          var objectUrl = this._objectUrl = self.URL.createObjectURL(ms);
          if (this.appendSource) {
            try {
              media.removeAttribute('src');
              var MMS = self.ManagedMediaSource;
              media.disableRemotePlayback = media.disableRemotePlayback || MMS && ms instanceof MMS;
              removeSourceChildren(media);
              addSource(media, objectUrl);
              media.load();
            } catch (error) {
              media.src = objectUrl;
            }
          } else {
            media.src = objectUrl;
          }
        }
        addEventListener(media, 'emptied', this._onMediaEmptied);
        addEventListener(media, 'error', this._onMediaError);
        if (this.appendSource) {
          addEventListener(media, 'seeking', this._onStartStreaming);
        }
      }
    };
    _proto.assignMediaSource = function assignMediaSource(ms) {
      var _this$transferData2, _ms$constructor;
      addEventListener(ms, 'sourceopen', this._onMediaSourceOpen);
      addEventListener(ms, 'sourceended', this._onMediaSourceEnded);
      addEventListener(ms, 'sourceclose', this._onMediaSourceClose);
      if (this.appendSource) {
        addEventListener(ms, 'startstreaming', this._onStartStreaming);
        addEventListener(ms, 'endstreaming', this._onEndStreaming);
      }
    };
    _proto.attachTransferred = function attachTransferred() {
      var _this8 = this;
      var media = this.media;
      var data = this.transferData;
      if (!data || !media) {
        return;
      }
      var requiredTracks = this.tracks;
      var transferredTracks = data.tracks;
      var trackNames = transferredTracks ? Object.keys(transferredTracks) : null;
      var trackCount = trackNames ? trackNames.length : 0;
      var mediaSourceOpenCallback = function mediaSourceOpenCallback() {
        Promise.resolve().then(function () {
          if (_this8.media && _this8.mediaSourceOpenOrEnded) {
            _this8._onMediaSourceOpen();
          }
        });
      };
      if (transferredTracks && trackNames && trackCount) {
        if (!this.tracksReady) {
          this.hls.config.startFragPrefetch = true;
          return;
        }
        if (!isCompatibleTrackChange(transferredTracks, requiredTracks)) {
          data.mediaSource = null;
          data.tracks = undefined;
          var currentTime = media.currentTime;
          var details = this.details;
          var startTime = Math.max(currentTime, (details == null ? void 0 : details.fragments[0].start) || 0);
          if (startTime - currentTime > 1) {
            return;
          }
          this.onMediaDetaching(Events.MEDIA_DETACHING, {});
          this.onMediaAttaching(Events.MEDIA_ATTACHING, data);
          media.currentTime = startTime;
          return;
        }
        this.transferData = undefined;
        trackNames.forEach(function (trackName) {
          var type = trackName;
          var track = transferredTracks[type];
          if (track) {
            var sb = track.buffer;
            if (sb) {
              var fragmentTracker = _this8.fragmentTracker;
              var playlistType = track.id;
              if (fragmentTracker.hasFragments(playlistType) || fragmentTracker.hasParts(playlistType)) {
                var bufferedTimeRanges = BufferHelper.getBuffered(sb);
                fragmentTracker.detectEvictedFragments(type, bufferedTimeRanges, playlistType, null, null, true);
              }
              var sbIndex = sourceBufferNameToIndex(type);
              var sbTuple = [type, sb];
              _this8.sourceBuffers[sbIndex] = sbTuple;
              if (sb.updating && _this8.operationQueue) {
                _this8.operationQueue.prependBlocker(type);
              }
              _this8.trackSourceBuffer(type, track);
            }
          }
        });
        mediaSourceOpenCallback();
        this.bufferCreated();
      } else {
        mediaSourceOpenCallback();
      }
    };
    _proto.onMediaDetaching = function onMediaDetaching(event, data) {
      var _this9 = this;
      var transferringMedia = !!data.transferMedia;
      this.transferData = this.overrides = undefined;
      var media = this.media,
        mediaSource = this.mediaSource,
        _objectUrl = this._objectUrl;
      if (mediaSource) {
        if (transferringMedia) {
          this.sourceBuffers.forEach(function (_ref) {
            var type = _ref[0];
            if (type) {
              _this9.removeBuffer(type);
            }
          });
          this.resetQueue();
        } else {
          if (this.mediaSourceOpenOrEnded) {
            var open = mediaSource.readyState === 'open';
            try {
              var sourceBuffers = mediaSource.sourceBuffers;
              for (var i = sourceBuffers.length; i--;) {
                if (open) {
                  sourceBuffers[i].abort();
                }
                mediaSource.removeSourceBuffer(sourceBuffers[i]);
              }
              if (open) {
                mediaSource.endOfStream();
              }
            } catch (err) {
            }
          }
          if (this.sourceBufferCount) {
            this.onBufferReset();
          }
        }
        removeEventListener(mediaSource, 'sourceopen', this._onMediaSourceOpen);
        removeEventListener(mediaSource, 'sourceended', this._onMediaSourceEnded);
        removeEventListener(mediaSource, 'sourceclose', this._onMediaSourceClose);
        if (this.appendSource) {
          removeEventListener(mediaSource, 'startstreaming', this._onStartStreaming);
          removeEventListener(mediaSource, 'endstreaming', this._onEndStreaming);
        }
        this.mediaSource = null;
        this._objectUrl = null;
      }

      if (media) {
        removeEventListener(media, 'emptied', this._onMediaEmptied);
        removeEventListener(media, 'error', this._onMediaError);
        removeEventListener(media, 'seeking', this._onStartStreaming);
        if (!transferringMedia) {
          if (_objectUrl) {
            self.URL.revokeObjectURL(_objectUrl);
          }

          if (this.mediaSrc === _objectUrl) {
            media.removeAttribute('src');
            if (this.appendSource) {
              removeSourceChildren(media);
            }
            media.load();
          } else {
          }
        }
        this.media = null;
      }
    };
    _proto.onBufferReset = function onBufferReset() {
      var _this0 = this;
      this.sourceBuffers.forEach(function (_ref2) {
        var type = _ref2[0];
        if (type) {
          _this0.resetBuffer(type);
        }
      });
      this.initTracks();
    };
    _proto.resetBuffer = function resetBuffer(type) {
      var _this$tracks$type;
      var sb = (_this$tracks$type = this.tracks[type]) == null ? void 0 : _this$tracks$type.buffer;
      this.removeBuffer(type);
      if (sb) {
        try {
          var _this$mediaSource;
          if ((_this$mediaSource = this.mediaSource) != null && _this$mediaSource.sourceBuffers.length) {
            this.mediaSource.removeSourceBuffer(sb);
          }
        } catch (err) {
        }
      }
      delete this.tracks[type];
    };
    _proto.removeBuffer = function removeBuffer(type) {
      this.removeBufferListeners(type);
      this.sourceBuffers[sourceBufferNameToIndex(type)] = [null, null];
      var track = this.tracks[type];
      if (track) {
        this.clearBufferAppendTimeoutId(track);
        track.buffer = undefined;
      }
    };
    _proto.resetQueue = function resetQueue() {
      if (this.operationQueue) {
        this.operationQueue.destroy();
      }
      this.operationQueue = new BufferOperationQueue(this.tracks);
    };
    _proto.onBufferCodecs = function onBufferCodecs(event, data) {
      var _this1 = this,
        _data$audio;
      var tracks = this.tracks;
      var trackNames = Object.keys(data);
      var unmuxedToMuxed = 'audiovideo' in data && (tracks.audio || tracks.video) || tracks.audiovideo && ('audio' in data || 'video' in data);
      var muxedToUnmuxed = !unmuxedToMuxed && this.sourceBufferCount && this.media && trackNames.some(function (sbName) {
        return !tracks[sbName];
      });
      if (unmuxedToMuxed || muxedToUnmuxed) {
        return;
      }
      trackNames.forEach(function (trackName) {
        var _this1$transferData, _trackCodec;
        var parsedTrack = data[trackName];
        var id = parsedTrack.id,
          codec = parsedTrack.codec,
          levelCodec = parsedTrack.levelCodec,
          container = parsedTrack.container,
          metadata = parsedTrack.metadata,
          supplemental = parsedTrack.supplemental;
        var track = tracks[trackName];
        var transferredTrack = (_this1$transferData = _this1.transferData) == null || (_this1$transferData = _this1$transferData.tracks) == null ? void 0 : _this1$transferData[trackName];
        var sbTrack = transferredTrack != null && transferredTrack.buffer ? transferredTrack : track;
        var sbCodec = (sbTrack == null ? void 0 : sbTrack.pendingCodec) || (sbTrack == null ? void 0 : sbTrack.codec);
        var trackLevelCodec = sbTrack == null ? void 0 : sbTrack.levelCodec;
        if (!track) {
          track = tracks[trackName] = {
            buffer: undefined,
            listeners: [],
            codec: codec,
            supplemental: supplemental,
            container: container,
            levelCodec: levelCodec,
            metadata: metadata,
            id: id
          };
        }
        var currentCodecFull = pickMostCompleteCodecName(sbCodec, trackLevelCodec);
        var currentCodec = currentCodecFull == null ? void 0 : currentCodecFull.replace(VIDEO_CODEC_PROFILE_REPLACE, '$1');
        var trackCodec = pickMostCompleteCodecName(codec, levelCodec);
        var nextCodec = (_trackCodec = trackCodec) == null ? void 0 : _trackCodec.replace(VIDEO_CODEC_PROFILE_REPLACE, '$1');
        if (trackCodec && currentCodecFull && currentCodec !== nextCodec) {
          if (trackName.slice(0, 5) === 'audio') {
            trackCodec = getCodecCompatibleName(trackCodec, _this1.appendSource);
          }
          if (trackCodec !== (track.pendingCodec || track.codec)) {
            track.pendingCodec = trackCodec;
          }
          track.container = container;
          _this1.appendChangeType(trackName, container, trackCodec);
        }
      });
      if (this.tracksReady || this.sourceBufferCount) {
        data.tracks = this.sourceBufferTracks;
      }

      if (this.sourceBufferCount) {
        return;
      }
      if (this.bufferCodecEventsTotal > 1 && !this.tracks.video && !data.video && ((_data$audio = data.audio) == null ? void 0 : _data$audio.id) === 'main') {
        this.bufferCodecEventsTotal = 1;
      }
      if (this.mediaSourceOpenOrEnded) {
        this.checkPendingTracks();
      }
    };
    _proto.appendChangeType = function appendChangeType(type, container, codec) {
      var _this10 = this;
      var mimeType = container + ";codecs=" + codec;
      var operation = {
        label: "change-type=" + mimeType,
        execute: function execute() {
          var track = _this10.tracks[type];
          if (track) {
            var sb = track.buffer;
            if (sb != null && sb.changeType) {
              sb.changeType(mimeType);
              track.codec = codec;
              track.container = container;
            }
          }
          _this10.shiftAndExecuteNext(type);
        },
        onStart: function onStart() {},
        onComplete: function onComplete() {},
        onError: function onError(error) {
        }
      };
      this.append(operation, type, this.isPending(this.tracks[type]));
    };
    _proto.blockAudio = function blockAudio(partOrFrag) {
      var _this$fragmentTracker,
        _this11 = this;
      var pStart = partOrFrag.start;
      var pTime = pStart + partOrFrag.duration * 0.05;
      var atGap = ((_this$fragmentTracker = this.fragmentTracker.getAppendedFrag(pStart, PlaylistLevelType.MAIN)) == null ? void 0 : _this$fragmentTracker.gap) === true;
      if (atGap) {
        return;
      }
      var op = {
        label: 'block-audio',
        execute: function execute() {
          var _this11$tracks$video, _this11$fragmentTrack;
          var videoEnd = _this11.lastVideoAppendEnd;
          if (videoEnd > pTime || BufferHelper.isBuffered((_this11$tracks$video = _this11.tracks.video) == null ? void 0 : _this11$tracks$video.buffer, pTime) || ((_this11$fragmentTrack = _this11.fragmentTracker.getAppendedFrag(pTime, PlaylistLevelType.MAIN)) == null ? void 0 : _this11$fragmentTrack.gap) === true) {
            _this11.unblockAudio();
          }
        },
        onStart: function onStart() {},
        onComplete: function onComplete() {},
        onError: function onError(error) {
        }
      };
      this.append(op, 'audio', true);
    };
    _proto.unblockAudio = function unblockAudio() {
      if (this.operationQueue) {
        this.operationQueue.unblockAudio();
      }
    };
    _proto.onBufferAppending = function onBufferAppending(event, eventData) {
      var _this12 = this;
      var tracks = this.tracks;
      var data = eventData.data,
        type = eventData.type,
        parent = eventData.parent,
        frag = eventData.frag,
        part = eventData.part,
        chunkMeta = eventData.chunkMeta,
        offset = eventData.offset;
      var chunkStats = chunkMeta.buffering[type];
      var trackProgress = isMediaFragment(frag) && !part && !frag.gap && !chunkMeta.partial && !chunkMeta.iframe;
      var bufferedBefore = null;
      var fullyBufferedBefore = false;
      var sn = frag.sn,
        cc = frag.cc;
      var bufferAppendingStart = self.performance.now();
      chunkStats.start = bufferAppendingStart;
      var fragBuffering = frag.stats.buffering;
      var partBuffering = part ? part.stats.buffering : null;
      if (fragBuffering.start === 0) {
        fragBuffering.start = bufferAppendingStart;
      }
      if ((partBuffering == null ? void 0 : partBuffering.start) === 0) {
        partBuffering.start = bufferAppendingStart;
      }

      var audioTrack = tracks.audio;
      var checkTimestampOffset = false;
      if (type === 'audio' && (audioTrack == null ? void 0 : audioTrack.container) === 'audio/mpeg') {
        checkTimestampOffset = !this.lastMpegAudioChunk || chunkMeta.id === 1 || this.lastMpegAudioChunk.sn !== chunkMeta.sn;
        this.lastMpegAudioChunk = chunkMeta;
      }

      var videoTrack = tracks.video;
      var videoSb = videoTrack == null ? void 0 : videoTrack.buffer;
      if (videoSb && sn !== 'initSegment' && offset !== undefined) {
        var audioSb = audioTrack == null ? void 0 : audioTrack.buffer;
        var partOrFrag = part || frag;
        if (type === 'audio' && parent !== 'main' && !(videoTrack.ending || videoTrack.ended) && audioSb && BufferHelper.getBuffered(audioSb).length) {
          var pStart = partOrFrag.start;
          var pTime = pStart + partOrFrag.duration * 0.05;
          var vbuffered = BufferHelper.getBuffered(videoSb);
          var vappending = this.currentOp('video');
          if (!vbuffered.length && !vappending) {
            this.blockAudio(partOrFrag);
          } else if (!BufferHelper.isBuffered(videoSb, pTime) && this.lastVideoAppendEnd < pTime) {
            this.blockAudio(partOrFrag);
          }
        } else if (type === 'video') {
          var videoAppendEnd = partOrFrag.end;
          var diff = this.lastVideoAppendEnd - videoAppendEnd;
          this.lastVideoAppendEnd = videoAppendEnd;
          if (diff < 0 || diff > partOrFrag.duration) {
            this.unblockAudio();
          } else if (this.isAudioBlocked()) {
            this.executeNext('audio');
          }
        }
      }
      var fragStart = (part || frag).start;
      var operation = {
        label: "append-" + type,
        execute: function execute() {
          var _this12$tracks$type;
          chunkStats.executeStart = self.performance.now();
          var sb = (_this12$tracks$type = _this12.tracks[type]) == null ? void 0 : _this12$tracks$type.buffer;
          if (sb) {
            if (trackProgress) {
              bufferedBefore = BufferHelper.timeRangesToArray(BufferHelper.getBuffered(sb));
              fullyBufferedBefore = isFragmentFullyBuffered(fragmentBufferedCoverage(bufferedBefore, frag), frag);
            }
            if (checkTimestampOffset) {
              _this12.updateTimestampOffset(sb, fragStart, 0.1, type, sn, cc);
            } else if (offset !== undefined && isFiniteNumber(offset)) {
              _this12.updateTimestampOffset(sb, offset, 0.000001, type, sn, cc);
            }
          }
          _this12.appendExecutor(data, type);
        },
        onStart: function onStart() {
        },
        onComplete: function onComplete() {
          _this12.clearBufferAppendTimeoutId(_this12.tracks[type]);
          var end = self.performance.now();
          chunkStats.executeEnd = chunkStats.end = end;
          if (fragBuffering.first === 0) {
            fragBuffering.first = end;
          }
          if ((partBuffering == null ? void 0 : partBuffering.first) === 0) {
            partBuffering.first = end;
          }
          var timeRanges = {};
          _this12.sourceBuffers.forEach(function (_ref3) {
            var type = _ref3[0],
              sb = _ref3[1];
            if (type) {
              timeRanges[type] = BufferHelper.getBuffered(sb);
            }
          });
          if (trackProgress) {
            var progress = _this12.getFragmentAppendProgress(frag);
            var buffered = timeRanges[type];
            if (buffered && bufferedBefore) {
              var bufferedAfter = BufferHelper.timeRangesToArray(buffered);
              var coverageAfter = fragmentBufferedCoverage(bufferedAfter, frag);
              progress.progressed || (progress.progressed = coverageAfter - fragmentBufferedCoverage(bufferedBefore, frag) > MIN_BUFFERED_PROGRESS);
              var fullyBuffered = fullyBufferedBefore && isFragmentFullyBuffered(coverageAfter, frag);
              progress.fullyBuffered[type] = progress.fullyBuffered[type] === undefined ? fullyBuffered : progress.fullyBuffered[type] && fullyBuffered;
            }
          }
          _this12.hls.trigger(Events.BUFFER_APPENDED, {
            type: type,
            frag: frag,
            part: part,
            chunkMeta: chunkMeta,
            parent: parent,
            timeRanges: timeRanges
          });
        },
        onError: function onError(error) {
          var _this12$media;
          _this12.clearBufferAppendTimeoutId(_this12.tracks[type]);

          var isQuotaError = error.code === DOMException.QUOTA_EXCEEDED_ERR || error.name == 'QuotaExceededError' || "quota" in error;
          if (isQuotaError) {
            if (!_this12._quotaEvictionPending[type]) {
              var evictEnd = _this12.getBackBufferEvictionTarget(type, data.byteLength, frag.type);
              if (evictEnd > 0) {
                _this12._quotaEvictionPending[type] = true;
                var removeOp = _this12.getFlushOp(type, 0, evictEnd);
                var clearOp = _this12.getClearEvictionPendingOp(type);
                _this12.insertNext([removeOp, operation, clearOp], type);
                return;
              }
            }
          }

          if (trackProgress) {
            _this12.getFragmentAppendProgress(frag).errored = true;
          }
          var event = {
            type: ErrorTypes.MEDIA_ERROR,
            parent: parent,
            details: ErrorDetails.BUFFER_APPEND_ERROR,
            sourceBufferName: type,
            frag: frag,
            part: part,
            chunkMeta: chunkMeta,
            error: error,
            err: error,
            fatal: false
          };
          var mediaError = (_this12$media = _this12.media) == null ? void 0 : _this12$media.error;
          if (error.name === TRACK_REMOVED_ERROR_NAME && _this12.sourceBufferCount === 0 && (!_this12.media || _this12.pendingTrackCount === 0)) {
            event.errorAction = createDoNothingErrorAction(true);
          } else {
            var _this12$mediaSource;
            if (isQuotaError) {
              event.details = ErrorDetails.BUFFER_FULL_ERROR;
            }
            var appendErrorCount = ++_this12.appendErrors[type];
            var appendErrorMaxRetry = _this12.hls.config.appendErrorMaxRetry;
            if (appendErrorCount >= appendErrorMaxRetry) {
              event.fatal = !isQuotaError;
            }
            var readyState = (_this12$mediaSource = _this12.mediaSource) == null ? void 0 : _this12$mediaSource.readyState;
            if (readyState === 'ended' || readyState === 'closed' || !!mediaError) {
              event.details = ErrorDetails.MEDIA_SOURCE_REQUIRES_RESET;
            }
          }
          _this12.appendError = event;
          _this12.hls.trigger(Events.ERROR, event);
          if (isQuotaError && _this12.hls) {
            _this12.trimBuffers(frag.start, 1);
          }
        }
      };
      this.append(operation, type, this.isPending(this.tracks[type]));
    };
    _proto.getClearEvictionPendingOp = function getClearEvictionPendingOp(type) {
      var _this13 = this;
      return {
        label: 'clear',
        execute: function execute() {
          _this13._quotaEvictionPending[type] = false;
          _this13.shiftAndExecuteNext(type);
        },
        onStart: function onStart() {},
        onComplete: function onComplete() {},
        onError: function onError() {}
      };
    };
    _proto.getFlushOp = function getFlushOp(type, start, end) {
      var _this14 = this;
      return {
        label: 'remove',
        execute: function execute() {
          _this14.removeExecutor(type, start, end);
        },
        onStart: function onStart() {
        },
        onComplete: function onComplete() {
          var _this14$tracks$type;
          var sb = (_this14$tracks$type = _this14.tracks[type]) == null ? void 0 : _this14$tracks$type.buffer;
          _this14.hls.trigger(Events.BUFFER_FLUSHED, {
            type: type,
            start: start,
            end: end
          });
        },
        onError: function onError(error) {
          _this14.hls.trigger(Events.BUFFER_FLUSHED, {
            type: type,
            start: 0,
            end: 0,
            error: error
          });
        }
      };
    };
    _proto.onBufferFlushing = function onBufferFlushing(event, data) {
      var _this15 = this;
      var type = data.type,
        startOffset = data.startOffset,
        endOffset = data.endOffset;
      if (!type || type === 'audio') {
        this.unblockAudio();
      }
      if (type) {
        this.append(this.getFlushOp(type, startOffset, endOffset), type);
      } else {
        this.sourceBuffers.forEach(function (_ref4) {
          var type = _ref4[0];
          if (type) {
            _this15.append(_this15.getFlushOp(type, startOffset, endOffset), type);
          }
        });
      }
    };
    _proto.onFragParsed = function onFragParsed(event, data) {
      var _this16 = this;
      var frag = data.frag,
        part = data.part,
        chunkMeta = data.chunkMeta;
      var buffersAppendedTo = [];
      var elementaryStreams = part ? part.elementaryStreams : frag.elementaryStreams;
      if (elementaryStreams[ElementaryStreamTypes.AUDIOVIDEO]) {
        buffersAppendedTo.push('audiovideo');
      } else {
        if (elementaryStreams[ElementaryStreamTypes.AUDIO]) {
          buffersAppendedTo.push('audio');
        }
        if (elementaryStreams[ElementaryStreamTypes.VIDEO]) {
          buffersAppendedTo.push('video');
        }
      }
      var onUnblocked = function onUnblocked() {
        var now = self.performance.now();
        frag.stats.buffering.end = now;
        if (part) {
          part.stats.buffering.end = now;
        }
        var stats = part ? part.stats : frag.stats;
        _this16.checkAppendProgress(frag, part, chunkMeta, buffersAppendedTo);
        if (!_this16.hls) {
          return;
        }
        _this16.hls.trigger(Events.FRAG_BUFFERED, {
          frag: frag,
          part: part,
          stats: stats,
          id: frag.type,
          chunkMeta: chunkMeta
        });
        if (!part && frag.gap && stats.retry) {
        }
      };
      if (buffersAppendedTo.length === 0) {
      }
      this.blockBuffers(onUnblocked, buffersAppendedTo).catch(function (error) {
        _this16.stepOperationQueue(_this16.sourceBufferTypes);
      });
    };
    _proto.onFragChanged = function onFragChanged(event, data) {
      var _this$hls,
        _this$appendError,
        _this17 = this;
      var config = (_this$hls = this.hls) == null ? void 0 : _this$hls.config;
      if (!config) {
        return;
      }
      var backBufferLength = config.backBufferLength,
        frontBufferFlushThreshold = config.frontBufferFlushThreshold;
      this.trimBuffers(frontBufferFlushThreshold, backBufferLength, data.frag, data.previousFrag);

      var elementaryStreams = data.frag.elementaryStreams;
      var appendErrors = this.appendErrors;
      var appendErrorType = (_this$appendError = this.appendError) == null ? void 0 : _this$appendError.sourceBufferName;
      Object.keys(elementaryStreams).forEach(function (type) {
        if (!elementaryStreams[type]) {
          return;
        }
        appendErrors[type] = 0;
        if (type === appendErrorType) {
          _this17.appendError = undefined;
        }
        if (type === 'audio' || type === 'video') {
          appendErrors.audiovideo = 0;
          if (appendErrorType === 'audiovideo') {
            _this17.appendError = undefined;
          }
        } else {
          appendErrors.audio = 0;
          appendErrors.video = 0;
          if (appendErrorType !== 'audiovideo') {
            _this17.appendError = undefined;
          }
        }
      });
    };
    _proto.onBufferEos = function onBufferEos(event, data) {
      var _this18 = this,
        _this$overrides;
      this.sourceBuffers.forEach(function (_ref5) {
        var type = _ref5[0];
        if (type) {
          var track = _this18.tracks[type];
          if (!data.type || data.type === type) {
            track.ending = true;
            if (!track.ended) {
              track.ended = true;
            }
          }
        }
      });
      var allowEndOfStream = ((_this$overrides = this.overrides) == null ? void 0 : _this$overrides.endOfStream) !== false;
      var allTracksEnding = this.sourceBufferCount > 0 && !this.sourceBuffers.some(function (_ref6) {
        var _this18$tracks$type;
        var type = _ref6[0];
        return type && !((_this18$tracks$type = _this18.tracks[type]) != null && _this18$tracks$type.ended);
      });
      if (allTracksEnding) {
        this.unblockAudio();
        if (allowEndOfStream) {
          this.blockUntilOpen(function () {
            _this18.tracksEnded();
            var mediaSource = _this18.mediaSource;
            if ((mediaSource == null ? void 0 : mediaSource.readyState) !== 'open') {
              if (mediaSource) {
              }
              return;
            }
            mediaSource.endOfStream();
            _this18.hls.trigger(Events.BUFFERED_TO_END, undefined);
          });
        } else {
          this.tracksEnded();
          this.hls.trigger(Events.BUFFERED_TO_END, undefined);
        }
      } else if (data.type === 'video') {
        this.unblockAudio();
      }
    };
    _proto.tracksEnded = function tracksEnded() {
      var _this19 = this;
      this.sourceBuffers.forEach(function (_ref7) {
        var type = _ref7[0];
        if (type !== null) {
          var track = _this19.tracks[type];
          if (track) {
            track.ending = false;
          }
        }
      });
    };
    _proto.onLevelUpdated = function onLevelUpdated(event, _ref8) {
      var details = _ref8.details;
      if (!details.fragments.length) {
        return;
      }
      this.details = details;
      this.updateDuration();
    };
    _proto.updateDuration = function updateDuration() {
      var _this20 = this;
      this.blockUntilOpen(function () {
        var durationAndRange = _this20.getDurationAndRange();
        if (!durationAndRange) {
          return;
        }
        _this20.updateMediaSource(durationAndRange);
      });
    };
    _proto.onError = function onError(event, data) {
      if (data.details === ErrorDetails.BUFFER_APPEND_ERROR && data.frag) {
        var _data$errorAction;
        var nextAutoLevel = (_data$errorAction = data.errorAction) == null ? void 0 : _data$errorAction.nextAutoLevel;
        if (isFiniteNumber(nextAutoLevel) && nextAutoLevel !== data.frag.level) {
          this.resetAppendErrors();
        }
      }
    }

  ;
    _proto.getBackBufferEvictionTarget = function getBackBufferEvictionTarget(type, segmentBytes, playlistType) {
      var media = this.media;
      if (!media) {
        return 0;
      }
      return this.fragmentTracker.getBackBufferEvictionEnd(media.currentTime, playlistType, segmentBytes);
    }

  ;
    _proto.getFragmentAppendProgress = function getFragmentAppendProgress(frag) {
      var _progress;
      var key = appendProgressKey(frag);
      var progress = this.fragmentAppendProgress[key];
      if (((_progress = progress) == null ? void 0 : _progress.stats) !== frag.stats) {
        progress = {
          stats: frag.stats,
          progressed: false,
          errored: false,
          fullyBuffered: Object.create(null)
        };
        this.fragmentAppendProgress[key] = progress;
      }
      return progress;
    }

  ;
    _proto.checkAppendProgress = function checkAppendProgress(frag, part, chunkMeta, buffersAppendedTo) {
      if (part || !isMediaFragment(frag) || frag.gap || chunkMeta.iframe || chunkMeta.partial) {
        return;
      }
      var key = appendProgressKey(frag);
      var progress = this.fragmentAppendProgress[key];
      delete this.fragmentAppendProgress[key];
      var fragBuffering = frag.stats.buffering;
      if (fragBuffering.start && !fragBuffering.first) {
        return;
      }
      var cycle = (progress == null ? void 0 : progress.stats) === frag.stats ? progress : undefined;
      if (cycle != null && cycle.errored) {
        return;
      }
      var fullyBuffered = buffersAppendedTo.length > 0 && buffersAppendedTo.every(function (type) {
        return (cycle == null ? void 0 : cycle.fullyBuffered[type]) === true;
      });
      if (cycle != null && cycle.progressed || fullyBuffered) {
        delete this.appendsWithoutProgress[key];
        return;
      }
      if (!cycle && buffersAppendedTo.length === 0) {
        return;
      }
      var count = (this.appendsWithoutProgress[key] || 0) + 1;
      var hls = this.hls;
      var exhausted = count >= hls.config.appendErrorMaxRetry;
      if (exhausted) {
        delete this.appendsWithoutProgress[key];
      } else {
        this.appendsWithoutProgress[key] = count;
      }
      hls.trigger(Events.ERROR, {
        type: ErrorTypes.MEDIA_ERROR,
        details: ErrorDetails.BUFFER_APPEND_NO_PROGRESS,
        fatal: false,
        frag: frag,
        chunkMeta: chunkMeta,
        parent: frag.type,
        appendsWithoutProgress: count,
        error: new Error("Fragment append did not increase buffered coverage (" + count + ")")
      });
    };
    _proto.resetAppendProgress = function resetAppendProgress() {
      this.fragmentAppendProgress = Object.create(null);
      this.appendsWithoutProgress = Object.create(null);
    };
    _proto.resetAppendErrors = function resetAppendErrors() {
      this.appendErrors = {
        audio: 0,
        video: 0,
        audiovideo: 0
      };
      this.appendError = undefined;
    };
    _proto.trimBuffers = function trimBuffers(frontBufferFlushThreshold, backBufferLength, frag, previousFrag) {
      var hls = this.hls,
        details = this.details,
        media = this.media;
      if (!media || details === null) {
        return;
      }
      if (!this.sourceBufferCount) {
        return;
      }
      var config = hls.config;
      var currentTime = media.currentTime;
      var targetDuration = details.levelTargetDuration;

      backBufferLength = details.live && config.liveBackBufferLength !== null ? config.liveBackBufferLength : backBufferLength;
      var targetBackBufferPosition = -Infinity;
      if (isFiniteNumber(backBufferLength) && backBufferLength >= 0) {
        var maxBackBufferLength = Math.max(backBufferLength, targetDuration);
        targetBackBufferPosition = Math.floor(currentTime / targetDuration) * targetDuration - maxBackBufferLength;
      }

      if (frag) {
        var loopFlushEnd = this.getLoopBackBufferFlushEnd(frag, previousFrag != null ? previousFrag : null);
        if (loopFlushEnd > 0) {
          targetBackBufferPosition = Math.max(targetBackBufferPosition, loopFlushEnd);
        }
      }
      if (targetBackBufferPosition > 0) {
        this.flushBackBuffer(currentTime, targetDuration, targetBackBufferPosition);
      }
      if (isFiniteNumber(frontBufferFlushThreshold) && frontBufferFlushThreshold > 0) {
        var frontBufferLength = Math.max(config.maxBufferLength, frontBufferFlushThreshold);
        var maxFrontBufferLength = Math.max(frontBufferLength, targetDuration);
        var targetFrontBufferPosition = Math.floor(currentTime / targetDuration) * targetDuration + maxFrontBufferLength;
        this.flushFrontBuffer(currentTime, targetDuration, targetFrontBufferPosition);
      }
    }

;
    _proto.getLoopBackBufferFlushEnd = function getLoopBackBufferFlushEnd(frag, previousFrag) {
      var _this$hls2;
      var media = this.media;
      if (((_this$hls2 = this.hls) == null ? void 0 : _this$hls2.config.loopBackBufferFlush) === false || !(media != null && media.loop) || !previousFrag || frag.level <= previousFrag.level) {
        return 0;
      }
      var _frag$elementaryStrea = frag.elementaryStreams,
        video = _frag$elementaryStrea.video,
        audiovideo = _frag$elementaryStrea.audiovideo;
      if (video != null && video.partial || audiovideo != null && audiovideo.partial) {
        return 0;
      }
      var flushEnd = this.getEarliestElementaryStreamStart(frag) - LOOP_FLUSH_SAFETY_MARGIN;
      if (flushEnd <= 0) {
        return 0;
      }
      return flushEnd;
    };
    _proto.getEarliestElementaryStreamStart = function getEarliestElementaryStreamStart(frag) {
      var _frag$elementaryStrea2 = frag.elementaryStreams,
        audio = _frag$elementaryStrea2.audio,
        video = _frag$elementaryStrea2.video,
        audiovideo = _frag$elementaryStrea2.audiovideo;
      var earliest = frag.start;
      if (audiovideo) {
        earliest = Math.min(earliest, audiovideo.startDTS);
      } else {
        if (audio) {
          earliest = Math.min(earliest, audio.startDTS);
        }
        if (video) {
          earliest = Math.min(earliest, video.startDTS);
        }
      }
      return earliest;
    };
    _proto.flushBackBuffer = function flushBackBuffer(currentTime, targetDuration, targetBackBufferPosition) {
      var _this21 = this;
      this.sourceBuffers.forEach(function (_ref9) {
        var type = _ref9[0],
          sb = _ref9[1];
        if (sb) {
          var buffered = BufferHelper.getBuffered(sb);
          if (buffered.length > 0 && targetBackBufferPosition > buffered.start(0)) {
            var _this21$details, _this21$media, _this21$hls;
            _this21.hls.trigger(Events.BACK_BUFFER_REACHED, {
              bufferEnd: targetBackBufferPosition
            });

            var track = _this21.tracks[type];
            if ((_this21$details = _this21.details) != null && _this21$details.live) {
              _this21.hls.trigger(Events.LIVE_BACK_BUFFER_REACHED, {
                bufferEnd: targetBackBufferPosition
              });
            } else if (track != null && track.ended && !((_this21$media = _this21.media) != null && _this21$media.loop && ((_this21$hls = _this21.hls) == null ? void 0 : _this21$hls.config.loopBackBufferFlush) !== false)) {
              return;
            }
            _this21.hls.trigger(Events.BUFFER_FLUSHING, {
              startOffset: 0,
              endOffset: targetBackBufferPosition,
              type: type
            });
          }
        }
      });
    };
    _proto.flushFrontBuffer = function flushFrontBuffer(currentTime, targetDuration, targetFrontBufferPosition) {
      var _this22 = this;
      this.sourceBuffers.forEach(function (_ref0) {
        var type = _ref0[0],
          sb = _ref0[1];
        if (sb) {
          var buffered = BufferHelper.getBuffered(sb);
          var numBufferedRanges = buffered.length;
          if (numBufferedRanges < 2) {
            return;
          }
          var bufferStart = buffered.start(numBufferedRanges - 1);
          var bufferEnd = buffered.end(numBufferedRanges - 1);
          if (targetFrontBufferPosition > bufferStart || currentTime >= bufferStart && currentTime <= bufferEnd) {
            return;
          }
          _this22.hls.trigger(Events.BUFFER_FLUSHING, {
            startOffset: bufferStart,
            endOffset: Infinity,
            type: type
          });
        }
      });
    }

;
    _proto.getDurationAndRange = function getDurationAndRange() {
      var _this$overrides2;
      var details = this.details,
        mediaSource = this.mediaSource;
      if (!details || !this.media || (mediaSource == null ? void 0 : mediaSource.readyState) !== 'open') {
        return null;
      }
      var playlistEnd = details.edge;
      if (details.live && this.hls.config.liveDurationInfinity) {
        var len = details.fragments.length;
        if (len && !!mediaSource.setLiveSeekableRange) {
          var start = Math.max(0, details.fragmentStart);
          var end = Math.max(start, playlistEnd);
          return {
            duration: Infinity,
            start: start,
            end: end
          };
        }
        return {
          duration: Infinity
        };
      }
      var overrideDuration = (_this$overrides2 = this.overrides) == null ? void 0 : _this$overrides2.duration;
      if (overrideDuration) {
        if (!isFiniteNumber(overrideDuration)) {
          return null;
        }
        return {
          duration: overrideDuration
        };
      }
      var mediaDuration = this.media.duration;
      var msDuration = isFiniteNumber(mediaSource.duration) ? mediaSource.duration : 0;
      if (playlistEnd > msDuration && playlistEnd > mediaDuration || !isFiniteNumber(mediaDuration)) {
        return {
          duration: playlistEnd
        };
      }
      return null;
    };
    _proto.updateMediaSource = function updateMediaSource(_ref1) {
      var duration = _ref1.duration,
        start = _ref1.start,
        end = _ref1.end;
      var mediaSource = this.mediaSource;
      if (!mediaSource || !this.media || mediaSource.readyState !== 'open') {
        return;
      }
      if (mediaSource.duration !== duration) {
        if (isFiniteNumber(duration)) {
        }
        mediaSource.duration = duration;
      }
      if (start !== undefined && end !== undefined) {
        mediaSource.setLiveSeekableRange(start, end);
      }
    };
    _proto.checkPendingTracks = function checkPendingTracks() {
      var bufferCodecEventsTotal = this.bufferCodecEventsTotal,
        pendingTrackCount = this.pendingTrackCount,
        tracks = this.tracks;
      if (this.tracksReady) {
        var _this$transferData3;
        var transferredTracks = (_this$transferData3 = this.transferData) == null ? void 0 : _this$transferData3.tracks;
        if (transferredTracks && Object.keys(transferredTracks).length) {
          this.attachTransferred();
        } else {
          this.createSourceBuffers();
        }
      }
    };
    _proto.bufferCreated = function bufferCreated() {
      var _this23 = this;
      if (this.sourceBufferCount) {
        var tracks = {};
        this.sourceBuffers.forEach(function (_ref10) {
          var type = _ref10[0],
            buffer = _ref10[1];
          if (type) {
            var track = _this23.tracks[type];
            tracks[type] = {
              buffer: buffer,
              container: track.container,
              codec: track.codec,
              supplemental: track.supplemental,
              levelCodec: track.levelCodec,
              id: track.id,
              metadata: track.metadata
            };
          }
        });
        this.hls.trigger(Events.BUFFER_CREATED, {
          tracks: tracks
        });
        this.sourceBuffers.forEach(function (_ref11) {
          var type = _ref11[0];
          _this23.executeNext(type);
        });
      } else {
        var error = new Error('could not create source buffer for media codec(s)');
        this.hls.trigger(Events.ERROR, {
          type: ErrorTypes.MEDIA_ERROR,
          details: ErrorDetails.BUFFER_INCOMPATIBLE_CODECS_ERROR,
          fatal: true,
          error: error,
          reason: error.message
        });
      }
    };
    _proto.createSourceBuffers = function createSourceBuffers() {
      var tracks = this.tracks,
        sourceBuffers = this.sourceBuffers,
        mediaSource = this.mediaSource;
      if (!mediaSource) {
        throw new Error('createSourceBuffers called when mediaSource was null');
      }
      for (var trackName in tracks) {
        var type = trackName;
        var track = tracks[type];
        if (this.isPending(track)) {
          var codec = this.getTrackCodec(track, type);
          var mimeType = track.container + ";codecs=" + codec;
          track.codec = codec;
          try {
            var sb = mediaSource.addSourceBuffer(mimeType);
            var sbIndex = sourceBufferNameToIndex(type);
            var sbTuple = [type, sb];
            sourceBuffers[sbIndex] = sbTuple;
            track.buffer = sb;
          } catch (error) {
            var _this$operationQueue;
            this.error("error while trying to add sourceBuffer: " + error.message);
            this.shiftAndExecuteNext(type);
            (_this$operationQueue = this.operationQueue) == null || _this$operationQueue.removeBlockers();
            delete this.tracks[type];
            this.hls.trigger(Events.ERROR, {
              type: ErrorTypes.MEDIA_ERROR,
              details: ErrorDetails.BUFFER_ADD_CODEC_ERROR,
              fatal: false,
              error: error,
              sourceBufferName: type,
              mimeType: mimeType,
              parent: track.id
            });
            return;
          }
          this.trackSourceBuffer(type, track);
        }
      }
      this.bufferCreated();
    };
    _proto.clearBufferAppendTimeoutId = function clearBufferAppendTimeoutId(track) {
      if (!track) {
        return;
      }
      self.clearTimeout(track.bufferAppendTimeoutId);
      track.bufferAppendTimeoutId = undefined;
    };
    _proto.getTrackCodec = function getTrackCodec(track, trackName) {
      var supplementalCodec = track.supplemental;
      var trackCodec = track.codec;
      if (supplementalCodec && (trackName === 'video' || trackName === 'audiovideo') && areCodecsMediaSourceSupported(supplementalCodec, 'video')) {
        trackCodec = replaceVideoCodec(trackCodec, supplementalCodec);
      }
      var codec = pickMostCompleteCodecName(trackCodec, track.levelCodec);
      if (codec) {
        if (trackName.slice(0, 5) === 'audio') {
          return getCodecCompatibleName(codec, this.appendSource);
        }
        return codec;
      }
      return '';
    };
    _proto.trackSourceBuffer = function trackSourceBuffer(type, track) {
      var _this24 = this;
      var buffer = track.buffer;
      if (!buffer) {
        return;
      }
      var codec = this.getTrackCodec(track, type);
      this.tracks[type] = {
        buffer: buffer,
        codec: codec,
        container: track.container,
        levelCodec: track.levelCodec,
        supplemental: track.supplemental,
        metadata: track.metadata,
        id: track.id,
        listeners: []
      };
      this.removeBufferListeners(type);
      this.addBufferListener(type, 'updatestart', this.onSBUpdateStart);
      this.addBufferListener(type, 'updateend', this.onSBUpdateEnd);
      this.addBufferListener(type, 'error', this.onSBUpdateError);
      if (this.appendSource) {
        this.addBufferListener(type, 'bufferedchange', function (type, event) {
          var removedRanges = event.removedRanges;
          if (removedRanges != null && removedRanges.length) {
            _this24.hls.trigger(Events.BUFFER_FLUSHED, {
              type: type,
              start: removedRanges.start(0),
              end: removedRanges.end(removedRanges.length - 1)
            });
          }
        });
      }
    };
    _proto.onSBUpdateStart = function onSBUpdateStart(type) {
      var operation = this.currentOp(type);
      if (!operation) {
        return;
      }
      operation.onStart();
    };
    _proto.onSBUpdateEnd = function onSBUpdateEnd(type) {
      var _this$mediaSource2,
        _this$tracks$type2,
        _this25 = this;
      if (((_this$mediaSource2 = this.mediaSource) == null ? void 0 : _this$mediaSource2.readyState) === 'closed') {
        this.resetBuffer(type);
        return;
      }
      var operation = this.currentOp(type);
      if (!operation) {
        return;
      }
      operation.onComplete();
      var updating = (_this$tracks$type2 = this.tracks[type]) == null || (_this$tracks$type2 = _this$tracks$type2.buffer) == null ? void 0 : _this$tracks$type2.updating;
      if (updating) {
        this.blockUntilOpen(function () {
          _this25.shiftAndExecuteNext(type);
        });
        return;
      }
      this.shiftAndExecuteNext(type);
    };
    _proto.onSBUpdateError = function onSBUpdateError(type, event) {
      var _this$mediaSource3;
      var readyState = (_this$mediaSource3 = this.mediaSource) == null ? void 0 : _this$mediaSource3.readyState;
      var error = new Error(type + " SourceBuffer error. MediaSource readyState: " + readyState);
      this.error("" + error.message, event);
      this.hls.trigger(Events.ERROR, {
        type: ErrorTypes.MEDIA_ERROR,
        details: ErrorDetails.BUFFER_APPENDING_ERROR,
        sourceBufferName: type,
        error: error,
        fatal: false
      });
      var operation = this.currentOp(type);
      if (operation) {
        operation.onError(error);
      }
    };
    _proto.updateTimestampOffset = function updateTimestampOffset(sb, timestampOffset, tolerance, type, sn, cc) {
      var delta = timestampOffset - sb.timestampOffset;
      if (Math.abs(delta) >= tolerance) {
        sb.timestampOffset = timestampOffset;
      }
    }

  ;
    _proto.removeExecutor = function removeExecutor(type, startOffset, endOffset) {
      var media = this.media,
        mediaSource = this.mediaSource;
      var track = this.tracks[type];
      var sb = track == null ? void 0 : track.buffer;
      if (!media || !mediaSource || !sb) {
        throw new Error("Attempting to remove from the " + type + " SourceBuffer, but it does not exist");
      }
      var mediaDuration = isFiniteNumber(media.duration) ? media.duration : Infinity;
      var msDuration = isFiniteNumber(mediaSource.duration) ? mediaSource.duration : Infinity;
      var removeStart = Math.max(0, startOffset);
      if (removeStart >= msDuration) {
        var _this$currentOp;
        (_this$currentOp = this.currentOp(type)) == null || _this$currentOp.onComplete();
        this.shiftAndExecuteNext(type);
        return;
      }
      var removeEnd = Math.min(endOffset, mediaDuration, msDuration);
      if (removeEnd > removeStart && (!track.ending || track.ended)) {
        track.ended = false;
        sb.remove(removeStart, removeEnd);
      } else {
        throw new Error("Cannot remove " + (removeEnd <= removeStart ? "invalid range (" + removeStart + " >= " + removeEnd + ") " : '') + "from the " + type + " SourceBuffer" + (track.ending ? ' while track ending' : ''));
      }
    }

  ;
    _proto.appendExecutor = function appendExecutor(data, type) {
      var _this26 = this;
      var track = this.tracks[type];
      var sb = track == null ? void 0 : track.buffer;
      if (!sb) {
        throw new HlsJsTrackRemovedError("Attempting to append to the " + type + " SourceBuffer, but it does not exist");
      }
      track.ending = false;
      track.ended = false;
      if (this.hls.config.appendTimeout !== Infinity) {
        var appendTimeoutTime = this.calculateAppendTimeoutTime(sb);
        track.bufferAppendTimeoutId = self.setTimeout(function () {
          return _this26.appendTimeoutHandler(type, sb, appendTimeoutTime);
        }, appendTimeoutTime);
      }
      sb.appendBuffer(data);
    };
    _proto.appendTimeoutHandler = function appendTimeoutHandler(type, sb, appendTimeoutTime) {
      try {
        sb.abort();
      } catch (e) {
      }
      var operation = this.currentOp(type);
      if (operation) {
        operation.onError(new Error(type + "-append-timeout"));
      }
    };
    _proto.calculateAppendTimeoutTime = function calculateAppendTimeoutTime(sb) {
      var targetDuration = DEFAULT_TARGET_DURATION;
      if (this.details) {
        targetDuration = this.details.levelTargetDuration;
      }

      var desiredDefaultTimeoutValue = 2 * targetDuration * 1000;
      if (this.media === null) {
        return desiredDefaultTimeoutValue;
      }
      var activeBufferedRange = BufferHelper.bufferInfo(sb, this.media.currentTime, 0);
      if (!activeBufferedRange.len) {
        return desiredDefaultTimeoutValue;
      }
      desiredDefaultTimeoutValue = Math.max(activeBufferedRange.len * 1000, desiredDefaultTimeoutValue);
      return Math.max(this.hls.config.appendTimeout, desiredDefaultTimeoutValue);
    };
    _proto.blockUntilOpen = function blockUntilOpen(callback) {
      var _this27 = this;
      if (this.isUpdating() || this.isQueued()) {
        this.blockBuffers(callback).catch(function (error) {
          _this27.stepOperationQueue(_this27.sourceBufferTypes);
        });
      } else {
        try {
          callback();
        } catch (error) {
        }
      }
    };
    _proto.isUpdating = function isUpdating() {
      return this.sourceBuffers.some(function (_ref12) {
        var type = _ref12[0],
          sb = _ref12[1];
        return type && sb.updating;
      });
    };
    _proto.isQueued = function isQueued() {
      var _this28 = this;
      return this.sourceBuffers.some(function (_ref13) {
        var type = _ref13[0];
        return type && !!_this28.currentOp(type);
      });
    };
    _proto.isPending = function isPending(track) {
      return !!track && !track.buffer;
    };
    _proto.isAudioBlocked = function isAudioBlocked() {
      var _this$currentOp2;
      return ((_this$currentOp2 = this.currentOp('audio')) == null ? void 0 : _this$currentOp2.label) === 'block-audio';
    };
    _proto.isAudioBlocking = function isAudioBlocking() {
      if (this.operationQueue) {
        return this.operationQueue.audioBlocking();
      }
      return false;
    }

  ;
    _proto.blockBuffers = function blockBuffers(onUnblocked, bufferNames) {
      var _this29 = this;
      if (bufferNames === void 0) {
        bufferNames = this.sourceBufferTypes;
      }
      if (!bufferNames.length) {
        return Promise.resolve().then(onUnblocked);
      }
      var operationQueue = this.operationQueue;
      var audioAlreadyBlocked = bufferNames.length === 2 && this.isAudioBlocking();
      var blockingOperations = audioAlreadyBlocked ? [this.appendBlocker('video')] : bufferNames.map(function (type) {
        return _this29.appendBlocker(type);
      });
      return Promise.all(blockingOperations).then(function (result) {
        if (operationQueue !== _this29.operationQueue) {
          return;
        }
        onUnblocked();
        _this29.stepOperationQueue(_this29.sourceBufferTypes);
      });
    };
    _proto.stepOperationQueue = function stepOperationQueue(bufferNames) {
      var _this30 = this;
      bufferNames.forEach(function (type) {
        var _this30$tracks$type;
        var sb = (_this30$tracks$type = _this30.tracks[type]) == null ? void 0 : _this30$tracks$type.buffer;
        if (!sb || sb.updating || type === 'audio' && _this30.isAudioBlocked()) {
          return;
        }
        _this30.shiftAndExecuteNext(type);
      });
    };
    _proto.append = function append(operation, type, pending) {
      if (this.operationQueue) {
        this.operationQueue.append(operation, type, pending);
      }
    };
    _proto.insertNext = function insertNext(operations, type) {
      if (this.operationQueue) {
        this.operationQueue.insertNext(operations, type);
      }
    };
    _proto.appendBlocker = function appendBlocker(type) {
      if (this.operationQueue) {
        return this.operationQueue.appendBlocker(type);
      }
    };
    _proto.currentOp = function currentOp(type) {
      if (this.operationQueue) {
        return this.operationQueue.current(type);
      }
      return null;
    };
    _proto.executeNext = function executeNext(type) {
      if (type && this.operationQueue) {
        this.operationQueue.executeNext(type);
      }
    };
    _proto.shiftAndExecuteNext = function shiftAndExecuteNext(type) {
      if (this.operationQueue) {
        this.operationQueue.shiftAndExecuteNext(type);
      }
    };
    _proto.addBufferListener = function addBufferListener(type, event, fn) {
      var track = this.tracks[type];
      if (!track) {
        return;
      }
      var buffer = track.buffer;
      if (!buffer) {
        return;
      }
      var listener = fn.bind(this, type);
      track.listeners.push({
        event: event,
        listener: listener
      });
      addEventListener(buffer, event, listener);
    };
    _proto.removeBufferListeners = function removeBufferListeners(type) {
      var track = this.tracks[type];
      if (!track) {
        return;
      }
      var buffer = track.buffer;
      if (!buffer) {
        return;
      }
      track.listeners.forEach(function (l) {
        removeEventListener(buffer, l.event, l.listener);
      });
      track.listeners.length = 0;
    };
    return _createClass(BufferController, [{
      key: "mediaSourceOpenOrEnded",
      get: function get() {
        var _this$mediaSource4;
        var readyState = (_this$mediaSource4 = this.mediaSource) == null ? void 0 : _this$mediaSource4.readyState;
        return readyState === 'open' || readyState === 'ended';
      }
    }, {
      key: "sourceBufferTracks",
      get: function get() {
        var _this31 = this;
        return Object.keys(this.tracks).reduce(function (baseTracks, type) {
          var track = _this31.tracks[type];
          baseTracks[type] = {
            id: track.id,
            container: track.container,
            codec: track.codec,
            levelCodec: track.levelCodec
          };
          return baseTracks;
        }, {});
      }
    }, {
      key: "bufferedToEnd",
      get: function get() {
        var _this32 = this;
        return this.sourceBufferCount > 0 && !this.sourceBuffers.some(function (_ref14) {
          var type = _ref14[0];
          if (type) {
            var track = _this32.tracks[type];
            if (track) {
              return !track.ended || track.ending;
            }
          }
          return false;
        });
      }
    }, {
      key: "tracksReady",
      get: function get() {
        var pendingTrackCount = this.pendingTrackCount;
        return pendingTrackCount > 0 && (pendingTrackCount >= this.bufferCodecEventsTotal || this.isPending(this.tracks.audiovideo));
      }
    }, {
      key: "mediaSrc",
      get: function get() {
        var _this$media, _this$media$querySele;
        var media = ((_this$media = this.media) == null || (_this$media$querySele = _this$media.querySelector) == null ? void 0 : _this$media$querySele.call(_this$media, 'source')) || this.media;
        return media == null ? void 0 : media.src;
      }
    }, {
      key: "pendingTrackCount",
      get: function get() {
        var _this33 = this;
        return Object.keys(this.tracks).reduce(function (acc, type) {
          return acc + (_this33.isPending(_this33.tracks[type]) ? 1 : 0);
        }, 0);
      }
    }, {
      key: "sourceBufferCount",
      get: function get() {
        return this.sourceBuffers.reduce(function (acc, _ref15) {
          var type = _ref15[0];
          return acc + (type ? 1 : 0);
        }, 0);
      }
    }, {
      key: "sourceBufferTypes",
      get: function get() {
        return this.sourceBuffers.map(function (_ref16) {
          var type = _ref16[0];
          return type;
        }).filter(function (type) {
          return !!type;
        });
      }
    }]);
  }(Logger);
  function removeSourceChildren(node) {
    var sourceChildren = node.querySelectorAll('source');
    [].slice.call(sourceChildren).forEach(function (source) {
      node.removeChild(source);
    });
  }
  function addSource(media, url) {
    var source = self.document.createElement('source');
    source.type = 'video/mp4';
    source.src = url;
    media.appendChild(source);
  }
  function sourceBufferNameToIndex(type) {
    return type === 'audio' ? 1 : 0;
  }
  function mediaErrorRecoveryMessage(fatal, mediaError) {
    return "" + (fatal ? '' : ' - triggering recovery') + (mediaError ? ' with HTMLMediaElement Error: ' + mediaError.message : '');
  }
  function appendProgressKey(fragment) {
    return fragment.type + "_" + fragment.sn + "_" + fragment.level;
  }

  function fragmentBufferedCoverage(timeRanges, fragment) {
    var start = fragment.start,
      end = fragment.end;
    var coverage = 0;
    for (var i = 0; i < timeRanges.length; i++) {
      var timeRange = timeRanges[i];
      if (timeRange.start >= end) {
        break;
      }
      var rangeStart = Math.max(start, timeRange.start);
      var rangeEnd = Math.min(end, timeRange.end);
      if (rangeEnd > rangeStart) {
        coverage += rangeEnd - rangeStart;
      }
    }
    return coverage;
  }
  function isFragmentFullyBuffered(coverage, fragment) {
    return fragment.duration - coverage <= 0.05;
  }

  var DECIMAL_RESOLUTION_REGEX = /^(\\d+)x(\\d+)$/;
  var ATTR_LIST_REGEX = /(.+?)=(".*?"|.*?)(?:,|$)/g;

  var AttrList = function () {
    function AttrList(attrs, parsed) {
      if (typeof attrs === 'string') {
        attrs = AttrList.parseAttrList(attrs, parsed);
      }
      _extends(this, attrs);
    }
    var _proto = AttrList.prototype;
    _proto.decimalInteger = function decimalInteger(attrName) {
      var intValue = parseInt(this[attrName], 10);
      if (intValue > Number.MAX_SAFE_INTEGER) {
        return Infinity;
      }
      return intValue;
    };
    _proto.hexadecimalInteger = function hexadecimalInteger(attrName) {
      if (this[attrName]) {
        var stringValue = (this[attrName] || '0x').slice(2);
        stringValue = (stringValue.length & 1 ? '0' : '') + stringValue;
        var value = new Uint8Array(stringValue.length / 2);
        for (var i = 0; i < stringValue.length / 2; i++) {
          value[i] = parseInt(stringValue.slice(i * 2, i * 2 + 2), 16);
        }
        return value;
      }
      return null;
    };
    _proto.hexadecimalIntegerAsNumber = function hexadecimalIntegerAsNumber(attrName) {
      var intValue = parseInt(this[attrName], 16);
      if (intValue > Number.MAX_SAFE_INTEGER) {
        return Infinity;
      }
      return intValue;
    };
    _proto.decimalFloatingPoint = function decimalFloatingPoint(attrName) {
      return parseFloat(this[attrName]);
    };
    _proto.optionalFloat = function optionalFloat(attrName, defaultValue) {
      var value = this[attrName];
      return value ? parseFloat(value) : defaultValue;
    };
    _proto.enumeratedString = function enumeratedString(attrName) {
      return this[attrName];
    };
    _proto.enumeratedStringList = function enumeratedStringList(attrName, dict) {
      var attrValue = this[attrName];
      return (attrValue ? attrValue.split(/[ ,]+/) : []).reduce(function (result, identifier) {
        result[identifier.toLowerCase()] = true;
        return result;
      }, dict);
    };
    _proto.bool = function bool(attrName) {
      return this[attrName] === 'YES';
    };
    _proto.decimalResolution = function decimalResolution(attrName) {
      var res = DECIMAL_RESOLUTION_REGEX.exec(this[attrName]);
      if (res === null) {
        return undefined;
      }
      return {
        width: parseInt(res[1], 10),
        height: parseInt(res[2], 10)
      };
    };
    AttrList.parseAttrList = function parseAttrList(input, parsed) {
      var match;
      var attrs = {};
      var quote = '"';
      ATTR_LIST_REGEX.lastIndex = 0;
      while ((match = ATTR_LIST_REGEX.exec(input)) !== null) {
        var name = match[1].trim();
        var value = match[2];
        var quotedString = value.indexOf(quote) === 0 && value.lastIndexOf(quote) === value.length - 1;
        var hexadecimalSequence = false;
        if (quotedString) {
          value = value.slice(1, -1);
        } else {
          switch (name) {
            case 'IV':
            case 'SCTE35-CMD':
            case 'SCTE35-IN':
            case 'SCTE35-OUT':
              hexadecimalSequence = true;
          }
        }
        if (parsed && (quotedString || hexadecimalSequence)) ; else if (!hexadecimalSequence && !quotedString) {
          switch (name) {
            case 'CLOSED-CAPTIONS':
              if (value === 'NONE') {
                break;
              }
            case 'ALLOWED-CPC':
            case 'CLASS':
            case 'ASSOC-LANGUAGE':
            case 'AUDIO':
            case 'BYTERANGE':
            case 'CHANNELS':
            case 'CHARACTERISTICS':
            case 'CODECS':
            case 'DATA-ID':
            case 'END-DATE':
            case 'GROUP-ID':
            case 'ID':
            case 'IMPORT':
            case 'INSTREAM-ID':
            case 'KEYFORMAT':
            case 'KEYFORMATVERSIONS':
            case 'LANGUAGE':
            case 'NAME':
            case 'PATHWAY-ID':
            case 'QUERYPARAM':
            case 'RECENTLY-REMOVED-DATERANGES':
            case 'SERVER-URI':
            case 'STABLE-RENDITION-ID':
            case 'STABLE-VARIANT-ID':
            case 'START-DATE':
            case 'SUBTITLES':
            case 'SUPPLEMENTAL-CODECS':
            case 'URI':
            case 'VALUE':
            case 'VIDEO':
            case 'X-ASSET-LIST':
            case 'X-ASSET-URI':
          }
        }
        attrs[name] = value;
      }
      return attrs;
    };
    return _createClass(AttrList, [{
      key: "clientAttrs",
      get: function get() {
        return Object.keys(this).filter(function (attr) {
          return attr.substring(0, 2) === 'X-';
        });
      }
    }]);
  }();

  var CLASS_INTERSTITIAL = 'com.apple.hls.interstitial';
  function isDateRangeCueAttribute(attrName) {
    return attrName !== "ID" && attrName !== "CLASS" && attrName !== "CUE" && attrName !== "START-DATE" && attrName !== "DURATION" && attrName !== "END-DATE" && attrName !== "END-ON-NEXT";
  }
  function isSCTE35Attribute(attrName) {
    return attrName === "SCTE35-OUT" || attrName === "SCTE35-IN" || attrName === "SCTE35-CMD";
  }
  var DateRange = function () {
    function DateRange(dateRangeAttr, dateRangeWithSameId, tagCount) {
      var _dateRangeWithSameId$;
      if (tagCount === void 0) {
        tagCount = 0;
      }
      this.attr = void 0;
      this.tagAnchor = void 0;
      this.tagOrder = void 0;
      this._startDate = void 0;
      this._endDate = void 0;
      this._dateAtEnd = void 0;
      this._cue = void 0;
      this._badValueForSameId = void 0;
      this.tagAnchor = (dateRangeWithSameId == null ? void 0 : dateRangeWithSameId.tagAnchor) || null;
      this.tagOrder = (_dateRangeWithSameId$ = dateRangeWithSameId == null ? void 0 : dateRangeWithSameId.tagOrder) != null ? _dateRangeWithSameId$ : tagCount;
      if (dateRangeWithSameId) {
        var previousAttr = dateRangeWithSameId.attr;
        for (var key in previousAttr) {
          if (Object.prototype.hasOwnProperty.call(dateRangeAttr, key) && dateRangeAttr[key] !== previousAttr[key]) {
            this._badValueForSameId = key;
            break;
          }
        }
        dateRangeAttr = _extends(new AttrList({}), previousAttr, dateRangeAttr);
      }
      this.attr = dateRangeAttr;
      if (dateRangeWithSameId) {
        this._startDate = dateRangeWithSameId._startDate;
        this._cue = dateRangeWithSameId._cue;
        this._endDate = dateRangeWithSameId._endDate;
        this._dateAtEnd = dateRangeWithSameId._dateAtEnd;
      } else {
        this._startDate = new Date(dateRangeAttr["START-DATE"]);
      }
      if ("END-DATE" in dateRangeAttr) {
        var endDate = (dateRangeWithSameId == null ? void 0 : dateRangeWithSameId.endDate) || new Date(dateRangeAttr["END-DATE"]);
        if (isFiniteNumber(endDate.getTime())) {
          this._endDate = endDate;
        }
      }
    }
    return _createClass(DateRange, [{
      key: "id",
      get: function get() {
        return this.attr.ID;
      }
    }, {
      key: "class",
      get: function get() {
        return this.attr.CLASS;
      }
    }, {
      key: "cue",
      get: function get() {
        var _cue = this._cue;
        if (_cue === undefined) {
          return this._cue = this.attr.enumeratedStringList(this.attr.CUE ? 'CUE' : 'X-CUE', {
            pre: false,
            post: false,
            once: false
          });
        }
        return _cue;
      }
    }, {
      key: "startTime",
      get: function get() {
        var tagAnchor = this.tagAnchor;
        if (tagAnchor === null || tagAnchor.programDateTime === null) {
          return NaN;
        }
        return tagAnchor.start + (this.startDate.getTime() - tagAnchor.programDateTime) / 1000;
      }
    }, {
      key: "startDate",
      get: function get() {
        return this._startDate;
      }
    }, {
      key: "endDate",
      get: function get() {
        var dateAtEnd = this._endDate || this._dateAtEnd;
        if (dateAtEnd) {
          return dateAtEnd;
        }
        var duration = this.duration;
        if (duration !== null) {
          return this._dateAtEnd = new Date(this._startDate.getTime() + duration * 1000);
        }
        return null;
      }
    }, {
      key: "duration",
      get: function get() {
        if ("DURATION" in this.attr) {
          var duration = this.attr.decimalFloatingPoint("DURATION");
          if (isFiniteNumber(duration)) {
            return duration;
          }
        } else if (this._endDate) {
          return (this._endDate.getTime() - this._startDate.getTime()) / 1000;
        }
        return null;
      }
    }, {
      key: "plannedDuration",
      get: function get() {
        if ("PLANNED-DURATION" in this.attr) {
          return this.attr.decimalFloatingPoint("PLANNED-DURATION");
        }
        return null;
      }
    }, {
      key: "endOnNext",
      get: function get() {
        return this.attr.bool("END-ON-NEXT");
      }
    }, {
      key: "isInterstitial",
      get: function get() {
        return this.class === CLASS_INTERSTITIAL;
      }
    }, {
      key: "isValid",
      get: function get() {
        return !!this.id && !this._badValueForSameId && isFiniteNumber(this.startDate.getTime()) && (this.duration === null || this.duration >= 0) && (!this.endOnNext || !!this.class) && (!this.attr.CUE || !this.cue.pre && !this.cue.post || this.cue.pre !== this.cue.post) && (!this.isInterstitial || 'X-ASSET-URI' in this.attr || 'X-ASSET-LIST' in this.attr);
      }
    }, {
      key: "invalidReason",
      get: function get() {
        if (!this.id) {
          return 'Missing ID';
        }
        if (this._badValueForSameId) {
          return "Date range " + this._badValueForSameId + " mismatch";
        }
        if (!isFiniteNumber(this.startDate.getTime())) {
          return 'Date range invalid start date';
        }
        if (this.isInterstitial && !('X-ASSET-URI' in this.attr || 'X-ASSET-LIST' in this.attr)) {
          return 'Interstitial Date range missing X-ASSET-(LIST|URI)';
        }
        if (!this.isValid) {
          return 'Unknown (check DURATION(|END|NEXT), CLASS, and CUE)';
        }
        return null;
      }
    }]);
  }();

  function arrayValuesMatch(a, b) {
    if (a.length === b.length) {
      return !a.some(function (value, i) {
        return value !== b[i];
      });
    }
    return false;
  }
  function optionalArrayValuesMatch(a, b) {
    if (!a && !b) {
      return true;
    }
    if (!a || !b) {
      return false;
    }
    return arrayValuesMatch(a, b);
  }

  var DecrypterAesMode = {
    cbc: 0,
    ctr: 1
  };

  function isFullSegmentEncryption(method) {
    return method === 'AES-128' || method === 'AES-256' || method === 'AES-256-CTR';
  }
  function getAesModeFromFullSegmentMethod(method) {
    switch (method) {
      case 'AES-128':
      case 'AES-256':
        return DecrypterAesMode.cbc;
      case 'AES-256-CTR':
        return DecrypterAesMode.ctr;
      default:
        throw new Error("invalid full segment method " + method);
    }
  }

  var keyUriToKeyIdMap = {};
  var LevelKey = function () {
    function LevelKey(method, uri, format, formatversions, iv, keyId) {
      if (formatversions === void 0) {
        formatversions = [1];
      }
      if (iv === void 0) {
        iv = null;
      }
      this.uri = void 0;
      this.method = void 0;
      this.keyFormat = void 0;
      this.keyFormatVersions = void 0;
      this.encrypted = void 0;
      this.isCommonEncryption = void 0;
      this.iv = null;
      this.key = null;
      this.keyId = null;
      this.pssh = null;
      this.scheme = void 0;
      this.method = method;
      this.uri = uri;
      this.keyFormat = format;
      this.keyFormatVersions = formatversions;
      this.iv = iv;
      this.encrypted = method ? method !== 'NONE' : false;
      this.isCommonEncryption = this.encrypted && !isFullSegmentEncryption(method);
      if (keyId != null && keyId.startsWith('0x')) {
        this.keyId = new Uint8Array(hexToArrayBuffer(keyId));
      }
    }
    LevelKey.clearKeyUriToKeyIdMap = function clearKeyUriToKeyIdMap() {
      keyUriToKeyIdMap = {};
    };
    LevelKey.setKeyIdForUri = function setKeyIdForUri(uri, keyId) {
      keyUriToKeyIdMap[uri] = keyId;
    };
    LevelKey.addKeyIdForUri = function addKeyIdForUri(uri) {
      var val = Object.keys(keyUriToKeyIdMap).length % Number.MAX_SAFE_INTEGER;
      var keyId = new Uint8Array(16);
      var dv = new DataView(keyId.buffer, 12, 4);
      dv.setUint32(0, val);
      keyUriToKeyIdMap[uri] = keyId;
      return keyId;
    };
    var _proto = LevelKey.prototype;
    _proto.matches = function matches(key) {
      return key.uri === this.uri && key.method === this.method && key.encrypted === this.encrypted && key.keyFormat === this.keyFormat && arrayValuesMatch(key.keyFormatVersions, this.keyFormatVersions) && optionalArrayValuesMatch(key.iv, this.iv) && optionalArrayValuesMatch(key.keyId, this.keyId);
    };
    _proto.isSupported = function isSupported() {
      if (this.method) {
        if (isFullSegmentEncryption(this.method) || this.method === 'NONE') {
          return true;
        }
        if (this.keyFormat === 'identity') {
          return this.method === 'SAMPLE-AES';
        }
      }
      return false;
    };
    _proto.getDecryptData = function getDecryptData(sn, levelKeys) {
      if (!this.encrypted || !this.uri) {
        return null;
      }
      if (isFullSegmentEncryption(this.method)) {
        var iv = this.iv;
        if (!iv) {
          if (typeof sn !== 'number') {

            sn = 0;
          }
          iv = createInitializationVector(sn);
        }
        var decryptdata = new LevelKey(this.method, this.uri, 'identity', this.keyFormatVersions, iv);
        return decryptdata;
      }
      {
        return this;
      }
    };
    return LevelKey;
  }();
  function createInitializationVector(segmentNumber) {
    var uint8View = new Uint8Array(16);
    for (var i = 12; i < 16; i++) {
      uint8View[i] = segmentNumber >> 8 * (15 - i) & 0xff;
    }
    return uint8View;
  }

  var MASTER_PLAYLIST_REGEX = /#EXT-X-STREAM-INF:([^\\r\\n]*)(?:[\\r\\n](?:#[^\\r\\n]*)?)*([^\\r\\n]+)|#EXT-X-(I-FRAME-STREAM-INF|SESSION-DATA|SESSION-KEY|DEFINE|CONTENT-STEERING|START):([^\\r\\n]*)(?:[\\r\\n]+|$)/g;
  var MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
  var IS_MEDIA_PLAYLIST = /^#EXT(?:INF|-X-TARGETDURATION):/m;

  var LEVEL_PLAYLIST_REGEX_FAST = new RegExp([/#EXTINF:\\s*(\\d*(?:\\.\\d+)?)(?:,(.*)\\s+)?/.source,
  /(?!#) *(\\S[^\\r\\n]*)/.source,
  /#.*/.source
  ].join('|'), 'g');
  var LEVEL_PLAYLIST_REGEX_SLOW = new RegExp([/#EXT-X-(PROGRAM-DATE-TIME|BYTERANGE|DATERANGE|DEFINE|KEY|MAP|PART|PART-INF|PLAYLIST-TYPE|PRELOAD-HINT|RENDITION-REPORT|SERVER-CONTROL|SKIP|START):(.+)/.source, /#EXT-X-(BITRATE|DISCONTINUITY-SEQUENCE|MEDIA-SEQUENCE|TARGETDURATION|VERSION): *(\\d+)/.source, /#EXT-X-(DISCONTINUITY|ENDLIST|GAP|INDEPENDENT-SEGMENTS|I-FRAMES-ONLY)/.source, /(#)([^:]*):(.*)/.source, /(#)(.*)(?:.*)\\r?\\n?/.source].join('|'));
  var M3U8Parser = function () {
    function M3U8Parser() {}
    M3U8Parser.findGroup = function findGroup(groups, mediaGroupId) {
      for (var i = 0; i < groups.length; i++) {
        var group = groups[i];
        if (group.id === mediaGroupId) {
          return group;
        }
      }
    };
    M3U8Parser.resolve = function resolve(url, baseUrl) {
      return urlToolkitExports.buildAbsoluteURL(baseUrl, url, {
        alwaysNormalize: true
      });
    };
    M3U8Parser.isMediaPlaylist = function isMediaPlaylist(str) {
      return IS_MEDIA_PLAYLIST.test(str);
    };
    M3U8Parser.parseMasterPlaylist = function parseMasterPlaylist(string, baseurl) {
      var hasVariableRefs = false;
      var parsed = {
        contentSteering: null,
        levels: [],
        iframeVariants: [],
        playlistParsingError: null,
        sessionData: null,
        sessionKeys: null,
        startTimeOffset: null,
        variableList: null,
        hasVariableRefs: hasVariableRefs
      };
      var levelsWithKnownCodecs = [];
      MASTER_PLAYLIST_REGEX.lastIndex = 0;
      if (!string.startsWith('#EXTM3U')) {
        parsed.playlistParsingError = new Error('no EXTM3U delimiter');
        return parsed;
      }
      var result;
      while ((result = MASTER_PLAYLIST_REGEX.exec(string)) != null) {
        if (result[1]) {
          var attrs = new AttrList(result[1], parsed);
          var level = createVariant(attrs, result[2], baseurl, parsed, levelsWithKnownCodecs);
          if (level) {
            parsed.levels.push(level);
          }
        } else if (result[3]) {
          var tag = result[3];
          var attributes = result[4];
          switch (tag) {
            case 'I-FRAME-STREAM-INF':
              {
                var _attrs = new AttrList(attributes, parsed);
                var iframeVariant = createVariant(_attrs, _attrs.URI, baseurl, parsed, levelsWithKnownCodecs, true);
                if (iframeVariant) {
                  parsed.iframeVariants.push(iframeVariant);
                }
              }
              break;
            case 'SESSION-DATA':
              {
                var sessionAttrs = new AttrList(attributes, parsed);
                var dataId = sessionAttrs['DATA-ID'];
                if (dataId) {
                  if (parsed.sessionData === null) {
                    parsed.sessionData = {};
                  }
                  parsed.sessionData[dataId] = sessionAttrs;
                }
                break;
              }
            case 'SESSION-KEY':
              {
                var sessionKey = parseKey(attributes, baseurl, parsed);
                if (sessionKey.encrypted && sessionKey.isSupported()) {
                  if (parsed.sessionKeys === null) {
                    parsed.sessionKeys = [];
                  }
                  parsed.sessionKeys.push(sessionKey);
                } else {
                }
                break;
              }
            case 'DEFINE':
              {
                break;
              }
            case 'CONTENT-STEERING':
              {
                var contentSteeringAttributes = new AttrList(attributes, parsed);
                parsed.contentSteering = {
                  uri: M3U8Parser.resolve(contentSteeringAttributes['SERVER-URI'], baseurl),
                  pathwayId: contentSteeringAttributes['PATHWAY-ID'] || '.'
                };
                break;
              }
            case 'START':
              {
                parsed.startTimeOffset = parseStartTimeOffset(attributes);
                break;
              }
          }
        }
      }
      var stripUnknownCodecLevels = levelsWithKnownCodecs.length > 0 && levelsWithKnownCodecs.length < parsed.levels.length;
      parsed.levels = stripUnknownCodecLevels ? levelsWithKnownCodecs : parsed.levels;
      if (parsed.levels.length === 0) {
        parsed.playlistParsingError = new Error('no levels found in manifest');
      }
      return parsed;
    };
    M3U8Parser.parseMasterPlaylistMedia = function parseMasterPlaylistMedia(string, baseurl, parsed) {
      var result;
      var results = {};
      var levels = parsed.levels;
      var groupsByType = {
        AUDIO: levels.map(function (level) {
          return {
            id: level.attrs.AUDIO,
            audioCodec: level.audioCodec
          };
        }),
        SUBTITLES: levels.map(function (level) {
          return {
            id: level.attrs.SUBTITLES,
            textCodec: level.textCodec
          };
        }),
        'CLOSED-CAPTIONS': []
      };
      var id = 0;
      MASTER_PLAYLIST_MEDIA_REGEX.lastIndex = 0;
      while ((result = MASTER_PLAYLIST_MEDIA_REGEX.exec(string)) !== null) {
        var attrs = new AttrList(result[1], parsed);
        var type = attrs.TYPE;
        if (type) {
          var groups = groupsByType[type];
          var medias = results[type] || [];
          results[type] = medias;
          var lang = attrs.LANGUAGE;
          var assocLang = attrs['ASSOC-LANGUAGE'];
          var channels = attrs.CHANNELS;
          var characteristics = attrs.CHARACTERISTICS;
          var instreamId = attrs['INSTREAM-ID'];
          var media = {
            attrs: attrs,
            bitrate: 0,
            id: id++,
            groupId: attrs['GROUP-ID'] || '',
            name: attrs.NAME || lang || '',
            type: type,
            default: attrs.bool('DEFAULT'),
            autoselect: attrs.bool('AUTOSELECT'),
            forced: attrs.bool('FORCED'),
            lang: lang,
            url: attrs.URI ? M3U8Parser.resolve(attrs.URI, baseurl) : ''
          };
          if (assocLang) {
            media.assocLang = assocLang;
          }
          if (channels) {
            media.channels = channels;
          }
          if (characteristics) {
            media.characteristics = characteristics;
          }
          if (instreamId) {
            media.instreamId = instreamId;
          }
          if (groups != null && groups.length) {
            var groupCodec = M3U8Parser.findGroup(groups, media.groupId) || groups[0];
            assignCodec(media, groupCodec, 'audioCodec');
            assignCodec(media, groupCodec, 'textCodec');
          }
          medias.push(media);
        }
      }
      return results;
    };
    M3U8Parser.parseLevelPlaylist = function parseLevelPlaylist(string, baseurl, id, type, levelUrlId, multivariantVariableList) {
      var _LEVEL_PLAYLIST_REGEX;
      var base = {
        url: baseurl
      };
      var level = new LevelDetails(baseurl);
      var fragments = level.fragments;
      var programDateTimes = [];
      var currentInitSegment = null;
      var currentSN = 0;
      var currentPart = 0;
      var totalduration = 0;
      var discontinuityCounter = 0;
      var currentBitrate = 0;
      var prevFrag = null;
      var frag = new Fragment(type, base);
      var result;
      var i;
      var levelkeys;
      var firstPdtIndex = -1;
      var createNextFrag = false;
      var nextByteRange = null;
      var serverControlAttrs;
      LEVEL_PLAYLIST_REGEX_FAST.lastIndex = 0;
      level.m3u8 = string;
      level.hasVariableRefs = false;
      if (((_LEVEL_PLAYLIST_REGEX = LEVEL_PLAYLIST_REGEX_FAST.exec(string)) == null ? void 0 : _LEVEL_PLAYLIST_REGEX[0]) !== '#EXTM3U') {
        level.playlistParsingError = new Error('Missing format identifier #EXTM3U');
        return level;
      }
      while ((result = LEVEL_PLAYLIST_REGEX_FAST.exec(string)) !== null) {
        if (createNextFrag) {
          createNextFrag = false;
          frag = new Fragment(type, base);
          frag.playlistOffset = totalduration;
          frag.setStart(totalduration);
          frag.sn = currentSN;
          frag.cc = discontinuityCounter;
          if (currentBitrate) {
            frag.bitrate = currentBitrate;
          }
          frag.level = id;
          if (currentInitSegment) {
            frag.initSegment = currentInitSegment;
            if (currentInitSegment.rawProgramDateTime) {
              frag.rawProgramDateTime = currentInitSegment.rawProgramDateTime;
              currentInitSegment.rawProgramDateTime = null;
            }
            if (nextByteRange) {
              frag.setByteRange(nextByteRange);
              nextByteRange = null;
            }
          }
        }
        var duration = result[1];
        if (duration) {
          frag.duration = parseFloat(duration);
          var title = (' ' + result[2]).slice(1);
          frag.title = title || null;
          frag.tagList.push(title ? ['INF', duration, title] : ['INF', duration]);
        } else if (result[3]) {
          if (isFiniteNumber(frag.duration)) {
            var _currentInitSegment;
            frag.playlistOffset = totalduration;
            frag.setStart(totalduration);
            if (levelkeys) {
              setFragLevelKeys(frag, levelkeys, level);
            }
            frag.sn = currentSN;
            frag.level = id;
            frag.cc = discontinuityCounter;
            var uri = (' ' + result[3]).slice(1);
            frag.relurl = uri;
            assignProgramDateTime(frag, prevFrag, programDateTimes);
            prevFrag = frag;
            totalduration += frag.duration;
            currentSN++;
            currentPart = 0;
            var byteRange = frag.byteRange;
            if (byteRange.length === 2) {
              frag.bitrate = (byteRange[1] - byteRange[0]) * 8 / frag.duration | 0;
            }

            if (level.iframesOnly && byteRange[0] && ((_currentInitSegment = currentInitSegment) == null ? void 0 : _currentInitSegment.cc) !== discontinuityCounter) {
              var init = new Fragment(type, base);
              init.relurl = frag.relurl;
              init.setByteRange(Math.min(byteRange[0], 7 * 188) + "@0");
              init.level = id;
              init.sn = 'initSegment';
              if (levelkeys) {
                init.levelkeys = levelkeys;
                if (levelkeys.identity) {
                  init._decryptdata = levelkeys.identity.getDecryptData(0);
                }
              }
              currentInitSegment = init;
              frag.initSegment = currentInitSegment;
            }
            fragments.push(frag);
            createNextFrag = true;
          }
        } else {
          result = result[0].match(LEVEL_PLAYLIST_REGEX_SLOW);
          if (!result) {
            continue;
          }
          for (i = 1; i < result.length; i++) {
            if (result[i] !== undefined) {
              break;
            }
          }

          var tag = (' ' + result[i]).slice(1);
          var value1 = (' ' + result[i + 1]).slice(1);
          var value2 = result[i + 2] ? (' ' + result[i + 2]).slice(1) : null;
          switch (tag) {
            case 'BYTERANGE':
              if (prevFrag) {
                frag.setByteRange(value1, prevFrag);
              } else {
                frag.setByteRange(value1);
              }
              break;
            case 'PROGRAM-DATE-TIME':
              frag.rawProgramDateTime = value1;
              frag.tagList.push(['PROGRAM-DATE-TIME', value1]);
              if (firstPdtIndex === -1) {
                firstPdtIndex = fragments.length;
              }
              break;
            case 'PLAYLIST-TYPE':
              if (level.type) {
                assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
              }
              level.type = value1.toUpperCase();
              break;
            case 'MEDIA-SEQUENCE':
              if (level.startSN !== 0) {
                assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
              } else if (prevFrag !== null) {
                assignMustAppearBeforeSegmentsError(level, tag, result);
              }
              level.startSN = parseInt(value1);
              currentSN = level.startSN + level.skippedSegments;
              break;
            case 'SKIP':
              {
                if (level.skippedSegments) {
                  assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
                }
                var skipAttrs = new AttrList(value1, level);
                var skippedSegments = skipAttrs.decimalInteger('SKIPPED-SEGMENTS');
                if (isFiniteNumber(skippedSegments)) {
                  level.skippedSegments += skippedSegments;
                  for (var _i = skippedSegments; _i--;) {
                    fragments.push(null);
                  }
                  currentSN += skippedSegments;
                }
                var recentlyRemovedDateranges = skipAttrs.enumeratedString('RECENTLY-REMOVED-DATERANGES');
                if (recentlyRemovedDateranges) {
                  level.recentlyRemovedDateranges = (level.recentlyRemovedDateranges || []).concat(recentlyRemovedDateranges.split('\\t'));
                }
                break;
              }
            case 'TARGETDURATION':
              if (level.targetduration !== 0) {
                assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
              }
              level.targetduration = Math.max(parseInt(value1), 1);
              break;
            case 'VERSION':
              if (level.version !== null) {
                assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
              }
              level.version = parseInt(value1);
              break;
            case 'INDEPENDENT-SEGMENTS':
              break;
            case 'I-FRAMES-ONLY':
              level.iframesOnly = true;
              break;
            case 'ENDLIST':
              if (!level.live) {
                assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
              }
              level.live = false;
              break;
            case '#':
              if (value1 || value2) {
                frag.tagList.push(value2 ? [value1, value2] : [value1]);
              }
              break;
            case 'DISCONTINUITY':
              discontinuityCounter++;
              frag.tagList.push(['DIS']);
              break;
            case 'GAP':
              frag.gap = true;
              frag.tagList.push([tag]);
              break;
            case 'BITRATE':
              frag.tagList.push([tag, value1]);
              currentBitrate = parseInt(value1) * 1000;
              if (isFiniteNumber(currentBitrate)) {
                frag.bitrate = currentBitrate;
              } else {
                currentBitrate = 0;
              }
              break;
            case 'DATERANGE':
              {
                var dateRangeAttr = new AttrList(value1, level);
                var dateRange = new DateRange(dateRangeAttr, level.dateRanges[dateRangeAttr.ID], level.dateRangeTagCount);
                level.dateRangeTagCount++;
                if (dateRange.isValid || level.skippedSegments) {
                  level.dateRanges[dateRange.id] = dateRange;
                } else {
                }
                frag.tagList.push(['EXT-X-DATERANGE', value1]);
                break;
              }
            case 'DEFINE':
              {
                break;
              }
            case 'DISCONTINUITY-SEQUENCE':
              if (level.startCC !== 0) {
                assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
              }
              level.startCC = discontinuityCounter = parseInt(value1);
              fragments.forEach(function (frag) {
                return frag && (frag.cc = discontinuityCounter);
              });
              break;
            case 'KEY':
              {
                var levelKey = parseKey(value1, baseurl, level);
                if (levelKey.isSupported()) {
                  if (levelKey.method === 'NONE') {
                    levelkeys = undefined;
                    break;
                  }
                  if (!levelkeys) {
                    levelkeys = {};
                  }
                  var currentKey = levelkeys[levelKey.keyFormat];
                  if (!(currentKey != null && currentKey.matches(levelKey))) {
                    if (currentKey) {
                      levelkeys = _extends({}, levelkeys);
                    }
                    levelkeys[levelKey.keyFormat] = levelKey;
                  }
                } else {
                }
                break;
              }
            case 'START':
              level.startTimeOffset = parseStartTimeOffset(value1);
              break;
            case 'MAP':
              {
                var mapAttrs = new AttrList(value1, level);
                if (frag.duration) {
                  var _init = new Fragment(type, base);
                  setInitSegment(_init, mapAttrs, id, levelkeys);
                  currentInitSegment = _init;
                  frag.initSegment = currentInitSegment;
                  if (currentInitSegment.rawProgramDateTime && !frag.rawProgramDateTime) {
                    frag.rawProgramDateTime = currentInitSegment.rawProgramDateTime;
                  }
                } else {
                  var end = frag.byteRangeEndOffset;
                  if (end) {
                    var start = frag.byteRangeStartOffset;
                    nextByteRange = end - start + "@" + start;
                  } else {
                    nextByteRange = null;
                  }
                  setInitSegment(frag, mapAttrs, id, levelkeys);
                  currentInitSegment = frag;
                  createNextFrag = true;
                }
                currentInitSegment.cc = discontinuityCounter;
                break;
              }
            case 'SERVER-CONTROL':
              {
                if (serverControlAttrs) {
                  assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
                }
                serverControlAttrs = new AttrList(value1);
                level.canBlockReload = serverControlAttrs.bool('CAN-BLOCK-RELOAD');
                level.canSkipUntil = serverControlAttrs.optionalFloat('CAN-SKIP-UNTIL', 0);
                level.canSkipDateRanges = level.canSkipUntil > 0 && serverControlAttrs.bool('CAN-SKIP-DATERANGES');
                level.partHoldBack = serverControlAttrs.optionalFloat('PART-HOLD-BACK', 0);
                level.holdBack = serverControlAttrs.optionalFloat('HOLD-BACK', 0);
                break;
              }
            case 'PART-INF':
              {
                if (level.partTarget) {
                  assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
                }
                var partInfAttrs = new AttrList(value1);
                level.partTarget = partInfAttrs.decimalFloatingPoint('PART-TARGET');
                break;
              }
            case 'PART':
              {
                var partList = level.partList;
                if (!partList) {
                  partList = level.partList = [];
                }
                var previousPart = currentPart > 0 ? partList[partList.length - 1] : undefined;
                var index = currentPart++;
                var partAttrs = new AttrList(value1, level);
                var part = new Part(partAttrs, frag, base, index, previousPart);
                partList.push(part);
                frag.duration += part.duration;
                break;
              }
            case 'PRELOAD-HINT':
              {
                var preloadHintAttrs = new AttrList(value1, level);
                level.preloadHint = preloadHintAttrs;
                break;
              }
            case 'RENDITION-REPORT':
              {
                var renditionReportAttrs = new AttrList(value1, level);
                level.renditionReports = level.renditionReports || [];
                level.renditionReports.push(renditionReportAttrs);
                break;
              }
            default:
              break;
          }
        }
      }
      if (prevFrag && !prevFrag.relurl) {
        fragments.pop();
        totalduration -= prevFrag.duration;
        if (level.partList) {
          level.fragmentHint = prevFrag;
        }
      } else if (level.partList) {
        assignProgramDateTime(frag, prevFrag, programDateTimes);
        frag.cc = discontinuityCounter;
        level.fragmentHint = frag;
        if (levelkeys) {
          setFragLevelKeys(frag, levelkeys, level);
        }
      }
      if (!level.targetduration) {
        level.playlistParsingError = new Error("Missing Target Duration");
      }
      var fragmentLength = fragments.length;
      var firstFragment = fragments[0];
      var lastFragment = fragments[fragmentLength - 1];
      totalduration += level.skippedSegments * level.targetduration;
      if (totalduration > 0 && fragmentLength && lastFragment) {
        level.averagetargetduration = totalduration / fragmentLength;
        var lastSn = lastFragment.sn;
        level.endSN = lastSn !== 'initSegment' ? lastSn : 0;
        if (!level.live) {
          lastFragment.endList = true;
        }
        if (firstPdtIndex > 0) {
          backfillProgramDateTimes(fragments, firstPdtIndex, firstPdtIndex > fragmentLength - 1 ? frag : null);
          if (firstFragment) {
            programDateTimes.unshift(firstFragment);
          }
        }
      }
      if (level.fragmentHint) {
        totalduration += level.fragmentHint.duration;
      }
      level.totalduration = totalduration;
      if (programDateTimes.length && level.dateRangeTagCount && firstFragment) {
        mapDateRanges(programDateTimes, level);
      }
      level.endCC = discontinuityCounter;
      return level;
    };
    return M3U8Parser;
  }();
  function createVariant(attrs, tUri, baseurl, parsed, levelsWithKnownCodecs, iframes) {
    var _level$unknownCodecs;
    if (tUri === undefined) {
      return null;
    }
    var uri = tUri;
    var level = {
      attrs: attrs,
      bitrate: attrs.decimalInteger('BANDWIDTH') || attrs.decimalInteger('AVERAGE-BANDWIDTH'),
      name: attrs.NAME,
      url: M3U8Parser.resolve(uri, baseurl)
    };
    var resolution = attrs.decimalResolution('RESOLUTION');
    if (resolution) {
      level.width = resolution.width;
      level.height = resolution.height;
    }
    setCodecs(attrs.CODECS, level);
    var supplementalCodecs = attrs['SUPPLEMENTAL-CODECS'];
    if (supplementalCodecs) {
      level.supplemental = {};
      setCodecs(supplementalCodecs, level.supplemental);
    }
    if (!((_level$unknownCodecs = level.unknownCodecs) != null && _level$unknownCodecs.length)) {
      levelsWithKnownCodecs.push(level);
    }
    if (iframes) {
      level.iframes = true;
    }
    return level;
  }
  function mapDateRanges(programDateTimes, details) {
    var programDateTimeCount = programDateTimes.length;
    if (!programDateTimeCount) {
      if (details.hasProgramDateTime) {
        var lastFragment = details.fragments[details.fragments.length - 1];
        programDateTimes.push(lastFragment);
        programDateTimeCount++;
      } else {
        return;
      }
    }
    var lastProgramDateTime = programDateTimes[programDateTimeCount - 1];
    var playlistEnd = details.live ? Infinity : details.totalduration;
    var dateRangeIds = Object.keys(details.dateRanges);
    for (var i = dateRangeIds.length; i--;) {
      var dateRange = details.dateRanges[dateRangeIds[i]];
      var startDateTime = dateRange.startDate.getTime();
      dateRange.tagAnchor = lastProgramDateTime.ref;
      for (var j = programDateTimeCount; j--;) {
        var _programDateTimes$j;
        if (((_programDateTimes$j = programDateTimes[j]) == null ? void 0 : _programDateTimes$j.sn) < details.startSN) {
          break;
        }
        var fragment = findFragmentWithStartDate(details, startDateTime, programDateTimes, j, playlistEnd);
        if (fragment) {
          dateRange.tagAnchor = fragment.ref;
          break;
        }
      }
    }
  }
  function findFragmentWithStartDate(details, startDateTime, programDateTimes, index, endTime) {
    var pdtFragment = programDateTimes[index];
    if (pdtFragment) {
      var pdtStart = pdtFragment.programDateTime;
      if (startDateTime >= pdtStart || index === 0) {
        var _programDateTimes;
        var durationBetweenPdt = (((_programDateTimes = programDateTimes[index + 1]) == null ? void 0 : _programDateTimes.start) || endTime) - pdtFragment.start;
        if (startDateTime <= pdtStart + durationBetweenPdt * 1000) {
          var startIndex = pdtFragment.sn - details.startSN;
          var fragments = details.fragments;
          if (startIndex >= 0 && startIndex < fragments.length) {
            if (fragments.length > programDateTimes.length) {
              var endSegment = programDateTimes[index + 1] || fragments[fragments.length - 1];
              var endIndex = Math.min(endSegment.sn - details.startSN, fragments.length - 1);
              for (var i = endIndex; i > startIndex; i--) {
                var fragStartDateTime = fragments[i].programDateTime;
                if (startDateTime >= fragStartDateTime && startDateTime < fragStartDateTime + fragments[i].duration * 1000) {
                  return fragments[i];
                }
              }
            }
            return fragments[startIndex];
          }
        }
      }
    }
    return null;
  }
  function parseKey(keyTagAttributes, baseurl, parsed) {
    var _keyAttrs$METHOD, _keyAttrs$KEYFORMAT;
    var keyAttrs = new AttrList(keyTagAttributes, parsed);
    var decryptmethod = (_keyAttrs$METHOD = keyAttrs.METHOD) != null ? _keyAttrs$METHOD : '';
    var decrypturi = keyAttrs.URI;
    var decryptiv = keyAttrs.hexadecimalInteger('IV');
    var decryptkeyformatversions = keyAttrs.KEYFORMATVERSIONS;
    var decryptkeyformat = (_keyAttrs$KEYFORMAT = keyAttrs.KEYFORMAT) != null ? _keyAttrs$KEYFORMAT : 'identity';
    if (decrypturi && keyAttrs.IV && !decryptiv) {
      logger.error("Invalid IV: " + keyAttrs.IV);
    }
    var resolvedUri = decrypturi ? M3U8Parser.resolve(decrypturi, baseurl) : '';
    var keyFormatVersions = (decryptkeyformatversions ? decryptkeyformatversions : '1').split('/').map(Number).filter(Number.isFinite);
    return new LevelKey(decryptmethod, resolvedUri, decryptkeyformat, keyFormatVersions, decryptiv, keyAttrs.KEYID);
  }
  function parseStartTimeOffset(startAttributes) {
    var startAttrs = new AttrList(startAttributes);
    var startTimeOffset = startAttrs.decimalFloatingPoint('TIME-OFFSET');
    if (isFiniteNumber(startTimeOffset)) {
      return startTimeOffset;
    }
    return null;
  }
  function setCodecs(codecsAttributeValue, level) {
    var codecs = (codecsAttributeValue || '').split(/[ ,]+/).filter(function (c) {
      return c;
    });
    ['video', 'audio', 'text', 'image'].forEach(function (type) {
      var filtered = codecs.filter(function (codec) {
        return isCodecType(codec, type);
      });
      if (filtered.length) {
        level[type + "Codec"] = filtered.map(function (c) {
          return c.split('/')[0];
        }).join(',');
        codecs = codecs.filter(function (codec) {
          return filtered.indexOf(codec) === -1;
        });
      }
    });
    level.unknownCodecs = codecs;
  }
  function assignCodec(media, groupItem, codecProperty) {
    var codecValue = groupItem[codecProperty];
    if (codecValue) {
      media[codecProperty] = codecValue;
    }
  }
  function backfillProgramDateTimes(fragments, firstPdtIndex, fragPrev) {
    fragPrev || (fragPrev = fragments[firstPdtIndex]);
    if (!fragPrev) {
      return;
    }
    for (var i = firstPdtIndex; i--;) {
      var frag = fragments[i];
      if (!frag) {
        return;
      }
      frag.programDateTime = fragPrev.programDateTime - frag.duration * 1000;
      fragPrev = frag;
    }
  }
  function assignProgramDateTime(frag, prevFrag, programDateTimes) {
    if (frag.rawProgramDateTime) {
      programDateTimes.push(frag);
    } else if (prevFrag != null && prevFrag.programDateTime) {
      frag.programDateTime = prevFrag.endProgramDateTime;
    }
  }
  function setInitSegment(frag, mapAttrs, id, levelkeys) {
    frag.relurl = mapAttrs.URI;
    if (mapAttrs.BYTERANGE) {
      frag.setByteRange(mapAttrs.BYTERANGE);
    }
    frag.level = id;
    frag.sn = 'initSegment';
    if (levelkeys) {
      frag.levelkeys = levelkeys;
    }
    frag.initSegment = null;
  }
  function setFragLevelKeys(frag, levelkeys, level) {
    frag.levelkeys = levelkeys;
    var encryptedFragments = level.encryptedFragments;
    if ((!encryptedFragments.length || encryptedFragments[encryptedFragments.length - 1].levelkeys !== levelkeys) && Object.keys(levelkeys).some(function (format) {
      return levelkeys[format].isCommonEncryption;
    })) {
      encryptedFragments.push(frag);
    }
  }
  function assignMultipleMediaPlaylistTagOccuranceError(level, tag, result) {
    level.playlistParsingError = new Error("#EXT-X-" + tag + " must not appear more than once (" + result[0] + ")");
  }
  function assignMustAppearBeforeSegmentsError(level, tag, result) {
    level.playlistParsingError = new Error("#EXT-X-" + tag + " must appear before the first Media Segment (" + result[0] + ")");
  }

  function updateFromToPTS(fragFrom, fragTo) {
    var fragToPTS = fragTo.startPTS;
    if (isFiniteNumber(fragToPTS)) {
      var duration = 0;
      var frag;
      if (fragTo.sn > fragFrom.sn) {
        duration = fragToPTS - fragFrom.start;
        frag = fragFrom;
      } else {
        duration = fragFrom.start - fragToPTS;
        frag = fragTo;
      }
      if (frag.duration !== duration) {
        frag.setDuration(duration);
      }
    } else if (fragTo.sn > fragFrom.sn) {
      var contiguous = fragFrom.cc === fragTo.cc;
      if (contiguous && fragFrom.minEndPTS) {
        fragTo.setStart(fragFrom.start + (fragFrom.minEndPTS - fragFrom.start));
      } else {
        fragTo.setStart(fragFrom.start + fragFrom.duration);
      }
    } else {
      fragTo.setStart(Math.max(fragFrom.start - fragTo.duration, 0));
    }
  }
  function updateFragPTSDTS(details, frag, startPTS, endPTS, startDTS, endDTS, iframesOnly, logger) {
    var parsedMediaDuration = endPTS - startPTS;
    if (parsedMediaDuration <= 0) {
      endPTS = startPTS + frag.duration;
      endDTS = startDTS + frag.duration;
    }
    var maxStartPTS = startPTS;
    var minEndPTS = endPTS;
    var fragStartPts = frag.startPTS;
    var fragEndPts = frag.endPTS;
    if (isFiniteNumber(fragStartPts)) {
      var deltaPTS = Math.abs(fragStartPts - startPTS);
      if (details && deltaPTS > details.totalduration) {
      } else if (!isFiniteNumber(frag.deltaPTS)) {
        frag.deltaPTS = deltaPTS;
      } else {
        frag.deltaPTS = Math.max(deltaPTS, frag.deltaPTS);
      }
      maxStartPTS = Math.max(startPTS, fragStartPts);
      startPTS = Math.min(startPTS, fragStartPts);
      startDTS = frag.startDTS !== undefined ? Math.min(startDTS, frag.startDTS) : startDTS;
      minEndPTS = Math.min(endPTS, fragEndPts);
      endPTS = Math.max(endPTS, fragEndPts);
      endDTS = frag.endDTS !== undefined ? Math.max(endDTS, frag.endDTS) : endDTS;
    }
    var drift = startPTS - frag.start;
    if (frag.start !== 0) {
      frag.setStart(startPTS);
    }
    frag.setDuration(endPTS - frag.start);
    frag.startPTS = startPTS;
    frag.maxStartPTS = maxStartPTS;
    frag.startDTS = startDTS;
    frag.endPTS = endPTS;
    frag.minEndPTS = minEndPTS;
    frag.endDTS = endDTS;
    var sn = frag.sn;
    if (!details || sn < details.startSN || sn > details.endSN) {
      return 0;
    }
    var i;
    var fragIdx = sn - details.startSN;
    var fragments = details.fragments;
    fragments[fragIdx] = frag;
    for (i = fragIdx; i > 0; i--) {
      updateFromToPTS(fragments[i], fragments[i - 1]);
    }

    for (i = fragIdx; i < fragments.length - 1; i++) {
      updateFromToPTS(fragments[i], fragments[i + 1]);
    }
    if (details.fragmentHint) {
      updateFromToPTS(fragments[fragments.length - 1], details.fragmentHint);
    }
    details.PTSKnown = details.alignedSliding = true;
    return drift;
  }
  function mergeDetails(oldDetails, newDetails, logger) {
    if (oldDetails === newDetails) {
      return;
    }
    var currentInitSegment = null;
    var oldFragments = oldDetails.fragments;
    for (var i = oldFragments.length - 1; i >= 0; i--) {
      var oldInit = oldFragments[i].initSegment;
      if (oldInit) {
        currentInitSegment = oldInit;
        break;
      }
    }
    if (oldDetails.fragmentHint) {
      delete oldDetails.fragmentHint.endPTS;
    }
    var PTSFrag;
    mapFragmentIntersection(oldDetails, newDetails, function (oldFrag, newFrag, newFragIndex, newFragments) {
      if ((!newDetails.startCC || newDetails.skippedSegments) && newFrag.cc !== oldFrag.cc) {
        var ccOffset = oldFrag.cc - newFrag.cc;
        for (var _i = newFragIndex; _i < newFragments.length; _i++) {
          newFragments[_i].cc += ccOffset;
        }
        newDetails.endCC = newFragments[newFragments.length - 1].cc;
      }
      if (isFiniteNumber(oldFrag.startPTS) && isFiniteNumber(oldFrag.endPTS)) {
        newFrag.setStart(newFrag.startPTS = oldFrag.startPTS);
        newFrag.startDTS = oldFrag.startDTS;
        newFrag.maxStartPTS = oldFrag.maxStartPTS;
        newFrag.endPTS = oldFrag.endPTS;
        newFrag.endDTS = oldFrag.endDTS;
        newFrag.minEndPTS = oldFrag.minEndPTS;
        newFrag.setDuration(oldFrag.endPTS - oldFrag.startPTS);
        if (newFrag.duration) {
          PTSFrag = newFrag;
        }

        newDetails.PTSKnown = newDetails.alignedSliding = true;
      }
      if (oldFrag.hasStreams) {
        newFrag.elementaryStreams = oldFrag.elementaryStreams;
      }
      newFrag.loader = oldFrag.loader;
      if (oldFrag.hasStats) {
        newFrag.stats = oldFrag.stats;
      }
      if (oldFrag.initSegment) {
        newFrag.initSegment = oldFrag.initSegment;
        currentInitSegment = oldFrag.initSegment;
      }
    });
    var newFragments = newDetails.fragments;
    var fragmentsToCheck = newDetails.fragmentHint ? newFragments.concat(newDetails.fragmentHint) : newFragments;
    if (currentInitSegment) {
      fragmentsToCheck.forEach(function (frag) {
        var _currentInitSegment;
        if (frag && (!frag.initSegment || frag.initSegment.relurl === ((_currentInitSegment = currentInitSegment) == null ? void 0 : _currentInitSegment.relurl))) {
          frag.initSegment = currentInitSegment;
        }
      });
    }
    if (newDetails.skippedSegments) {
      newDetails.deltaUpdateFailed = newFragments.some(function (frag) {
        return !frag;
      });
      if (newDetails.deltaUpdateFailed) {
        for (var _i2 = newDetails.skippedSegments; _i2--;) {
          newFragments.shift();
        }
        newDetails.startSN = newFragments[0].sn;
      } else {
        if (newDetails.canSkipDateRanges) {
          newDetails.dateRanges = mergeDateRanges(oldDetails.dateRanges, newDetails, logger);
        }
        var programDateTimes = oldDetails.fragments.filter(function (frag) {
          return frag.rawProgramDateTime;
        });
        if (oldDetails.hasProgramDateTime && !newDetails.hasProgramDateTime) {
          for (var _i3 = 1; _i3 < fragmentsToCheck.length; _i3++) {
            if (fragmentsToCheck[_i3].programDateTime === null) {
              assignProgramDateTime(fragmentsToCheck[_i3], fragmentsToCheck[_i3 - 1], programDateTimes);
            }
          }
        }
        mapDateRanges(programDateTimes, newDetails);
      }
      newDetails.endCC = newFragments[newFragments.length - 1].cc;
    }
    if (!newDetails.startCC) {
      var _fragPriorToNewStart$;
      var fragPriorToNewStart = getFragmentWithSN(oldDetails, newDetails.startSN - 1);
      newDetails.startCC = (_fragPriorToNewStart$ = fragPriorToNewStart == null ? void 0 : fragPriorToNewStart.cc) != null ? _fragPriorToNewStart$ : newFragments[0].cc;
    }

    mapPartIntersection(oldDetails.partList, newDetails.partList, function (oldPart, newPart) {
      newPart.elementaryStreams = oldPart.elementaryStreams;
      newPart.stats = oldPart.stats;
      newPart.gap = oldPart.gap || newPart.gap;
    });

    if (PTSFrag) {
      var iframesOnly = newDetails.iframesOnly && PTSFrag.type === PlaylistLevelType.MAIN;
      updateFragPTSDTS(newDetails, PTSFrag, PTSFrag.startPTS, PTSFrag.endPTS, PTSFrag.startDTS, PTSFrag.endDTS, iframesOnly, logger);
    } else {
      adjustSliding(oldDetails, newDetails);
    }
    if (newFragments.length) {
      newDetails.totalduration = newDetails.edge - newFragments[0].start;
    }
    newDetails.driftStartTime = oldDetails.driftStartTime;
    newDetails.driftStart = oldDetails.driftStart;
    var advancedDateTime = newDetails.advancedDateTime;
    if (newDetails.advanced && advancedDateTime) {
      var edge = newDetails.edge;
      if (!newDetails.driftStart) {
        newDetails.driftStartTime = advancedDateTime;
        newDetails.driftStart = edge;
      }
      newDetails.driftEndTime = advancedDateTime;
      newDetails.driftEnd = edge;
    } else {
      newDetails.driftEndTime = oldDetails.driftEndTime;
      newDetails.driftEnd = oldDetails.driftEnd;
      newDetails.advancedDateTime = oldDetails.advancedDateTime;
    }
    if (newDetails.requestScheduled === -1) {
      newDetails.requestScheduled = oldDetails.requestScheduled;
    }
  }
  function mergeDateRanges(oldDateRanges, newDetails, logger) {
    var deltaDateRanges = newDetails.dateRanges,
      recentlyRemovedDateranges = newDetails.recentlyRemovedDateranges;
    var dateRanges = _extends({}, oldDateRanges);
    if (recentlyRemovedDateranges) {
      recentlyRemovedDateranges.forEach(function (id) {
        delete dateRanges[id];
      });
    }
    var mergeIds = Object.keys(dateRanges);
    var mergeCount = mergeIds.length;
    if (!mergeCount) {
      return deltaDateRanges;
    }
    Object.keys(deltaDateRanges).forEach(function (id) {
      var mergedDateRange = dateRanges[id];
      var dateRange = new DateRange(deltaDateRanges[id].attr, mergedDateRange);
      if (dateRange.isValid) {
        dateRanges[id] = dateRange;
        if (!mergedDateRange) {
          dateRange.tagOrder += mergeCount;
        }
      } else {
      }
    });
    return dateRanges;
  }
  function mapPartIntersection(oldParts, newParts, intersectionFn) {
    if (oldParts && newParts) {
      var delta = 0;
      for (var i = 0, len = oldParts.length; i < len; i++) {
        var _oldPart = oldParts[i];
        var _newPart = newParts[i + delta];
        if (_oldPart && _newPart && _oldPart.index === _newPart.index && _oldPart.fragment.sn === _newPart.fragment.sn) {
          intersectionFn(_oldPart, _newPart);
        } else {
          delta--;
        }
      }
    }
  }
  function mapFragmentIntersection(oldDetails, newDetails, intersectionFn) {
    var _oldDetails$fragmentH;
    var skippedSegments = newDetails.skippedSegments;
    var start = Math.max(oldDetails.startSN, newDetails.startSN) - newDetails.startSN;
    var end = (oldDetails.fragmentHint ? 1 : 0) + (skippedSegments ? newDetails.endSN : Math.min(oldDetails.endSN, newDetails.endSN)) - newDetails.startSN;
    var delta = newDetails.startSN - oldDetails.startSN;
    var newFrags = newDetails.fragmentHint ? newDetails.fragments.concat(newDetails.fragmentHint) : newDetails.fragments;
    var oldFrags = (_oldDetails$fragmentH = oldDetails.fragmentHint) != null && _oldDetails$fragmentH.duration ? oldDetails.fragments.concat(oldDetails.fragmentHint) : oldDetails.fragments;
    for (var i = start; i <= end; i++) {
      var _oldFrag = oldFrags[delta + i];
      var _newFrag = newFrags[i];
      if (skippedSegments && !_newFrag && _oldFrag) {
        _newFrag = newDetails.fragments[i] = _oldFrag;
      }
      if (_oldFrag && _newFrag) {
        intersectionFn(_oldFrag, _newFrag, i, newFrags);
        var uriBefore = _oldFrag.relurl;
        var uriAfter = _newFrag.relurl;
        if (uriBefore && notEqualAfterStrippingQueries(uriBefore, uriAfter)) {
          newDetails.playlistParsingError = getSequenceError("media sequence mismatch " + _newFrag.sn + ":", oldDetails, newDetails, _oldFrag, _newFrag);
          return;
        } else if (_oldFrag.cc !== _newFrag.cc) {
          newDetails.playlistParsingError = getSequenceError("discontinuity sequence mismatch (" + _oldFrag.cc + "!=" + _newFrag.cc + ")", oldDetails, newDetails, _oldFrag, _newFrag);
          return;
        }
      }
    }
  }
  function getSequenceError(message, oldDetails, newDetails, oldFrag, newFrag) {
    return new Error(message + " " + newFrag.url + "\\nPlaylist starting @" + oldDetails.startSN + "\\n" + oldDetails.m3u8 + "\\n\\nPlaylist starting @" + newDetails.startSN + "\\n" + newDetails.m3u8);
  }
  function adjustSliding(oldDetails, newDetails, matchingStableVariantOrRendition, logger) {
    if (matchingStableVariantOrRendition === void 0) {
      matchingStableVariantOrRendition = true;
    }
    var delta = newDetails.startSN + newDetails.skippedSegments - oldDetails.startSN;
    var oldFragments = oldDetails.fragments;
    var advancedOrStable = delta >= 0;
    var sliding = 0;
    if (advancedOrStable && delta < oldFragments.length) {
      sliding = oldFragments[delta].start;
      logger == null || logger.log("Aligning playlists based on SN " + newDetails.startSN + " (diff: " + sliding + ")");
    } else if (advancedOrStable && newDetails.startSN === oldDetails.endSN + 1) {
      sliding = oldDetails.fragmentEnd;
      logger == null || logger.log("Aligning playlists based on first/last SN " + newDetails.startSN + " (diff: " + sliding + ")");
    } else if (advancedOrStable && matchingStableVariantOrRendition) {
      sliding = oldDetails.fragmentStart + delta * newDetails.levelTargetDuration;
    } else if (!newDetails.skippedSegments && newDetails.fragmentStart === 0) {
      sliding = oldDetails.fragmentStart;
      logger == null || logger.log("Aligning playlists based on first SN " + newDetails.startSN + " (diff: " + sliding + ")");
    } else {
      return sliding;
    }
    addSliding(newDetails, sliding);
    return sliding;
  }
  function addSliding(details, sliding) {
    if (sliding) {
      var fragments = details.fragments;
      for (var i = details.skippedSegments; i < fragments.length; i++) {
        fragments[i].addStart(sliding);
      }
      if (details.fragmentHint) {
        details.fragmentHint.addStart(sliding);
      }
    }
  }
  function computeReloadInterval(newDetails, distanceToLiveEdgeMs) {
    if (distanceToLiveEdgeMs === void 0) {
      distanceToLiveEdgeMs = Infinity;
    }
    var reloadInterval = 1000 * newDetails.targetduration;
    if (newDetails.updated) {
      var fragments = newDetails.fragments;
      var liveEdgeMaxTargetDurations = 4;
      if (fragments.length && reloadInterval * liveEdgeMaxTargetDurations > distanceToLiveEdgeMs) {
        var lastSegmentDuration = fragments[fragments.length - 1].duration * 1000;
        if (lastSegmentDuration > 0 && lastSegmentDuration < reloadInterval) {
          reloadInterval = lastSegmentDuration;
        }
      }
    } else {
      reloadInterval /= 2;
    }
    return Math.round(reloadInterval);
  }
  function getFragmentWithSN(details, sn, fragCurrent) {
    var _fragment;
    if (!details) {
      return null;
    }
    var fragment = details.fragments[sn - details.startSN];
    if (fragment) {
      return fragment;
    }
    fragment = details.fragmentHint;
    if (((_fragment = fragment) == null ? void 0 : _fragment.sn) === sn) {
      return fragment;
    }
    if (fragCurrent && sn < details.startSN && fragCurrent.sn === sn) {
      return fragCurrent;
    }
    return null;
  }
  function getPartWith(details, sn, partIndex) {
    if (!details) {
      return null;
    }
    return findPart(details.partList, sn, partIndex);
  }
  function findPart(partList, sn, partIndex) {
    if (partList) {
      for (var i = partList.length; i--;) {
        var part = partList[i];
        if (part.index === partIndex && part.fragment.sn === sn) {
          return part;
        }
      }
    }
    return null;
  }
  function reassignFragmentLevelIndexes(levels) {
    levels.forEach(function (level, index) {
      var _level$details;
      (_level$details = level.details) == null || _level$details.fragments.forEach(function (fragment) {
        fragment.level = index;
        if (fragment.initSegment) {
          fragment.initSegment.level = index;
        }
      });
    });
  }
  function notEqualAfterStrippingQueries(uriBefore, uriAfter) {
    if (uriBefore !== uriAfter && uriAfter) {
      return stripQuery(uriBefore) !== stripQuery(uriAfter);
    }
    return false;
  }
  function stripQuery(uri) {
    return uri.replace(/\\?[^?]*$/, '');
  }

  var PATHWAY_PENALTY_DURATION_MS = 300000;
  function performUriReplacement(uri, stableId, perOptionKey, uriReplacement) {
    if (stableId) {
      var perOptionUris = uriReplacement[perOptionKey];
      if (perOptionUris) {
        var perVariantUri = perOptionUris[stableId];
        if (perVariantUri) {
          return perVariantUri;
        }
      }
    }
    var host = uriReplacement.HOST,
      params = uriReplacement.PARAMS;
    var url = new self.URL(uri);
    if (host) {
      url.hostname = host;
    }
    if (params) {
      Object.keys(params).sort().forEach(function (key) {
        if (key) {
          setQueryParam(url, key, params[key]);
        }
      });
    }
    return url.href;
  }

  var FragmentState = {
    NOT_LOADED: "NOT_LOADED",
    APPENDING: "APPENDING",
    PARTIAL: "PARTIAL",
    OK: "OK"
  };
  var FragmentTracker = function () {
    function FragmentTracker(hls) {
      this.activePartLists = Object.create(null);
      this.endListFragments = Object.create(null);
      this.fragments = Object.create(null);
      this.timeRanges = Object.create(null);
      this.bufferPadding = 0.2;
      this.hls = void 0;
      this.hasGaps = false;
      this.hls = hls;
      this._registerListeners();
    }
    var _proto = FragmentTracker.prototype;
    _proto._registerListeners = function _registerListeners() {
      var hls = this.hls;
      if (hls) {
        hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
        hls.on(Events.BUFFER_APPENDED, this.onBufferAppended, this);
        hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
        hls.on(Events.FRAG_LOADED, this.onFragLoaded, this);
      }
    };
    _proto._unregisterListeners = function _unregisterListeners() {
      var hls = this.hls;
      if (hls) {
        hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
        hls.off(Events.BUFFER_APPENDED, this.onBufferAppended, this);
        hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
        hls.off(Events.FRAG_LOADED, this.onFragLoaded, this);
      }
    };
    _proto.destroy = function destroy() {
      this._unregisterListeners();
      this.hls =
      this.fragments =
      this.activePartLists =
      this.endListFragments = this.timeRanges = null;
    }

;
    _proto.getAppendedFrag = function getAppendedFrag(position, levelType) {
      var activeParts = this.activePartLists[levelType];
      if (activeParts) {
        for (var i = activeParts.length; i--;) {
          var activePart = activeParts[i];
          if (!activePart) {
            break;
          }
          if (activePart.start <= position && position <= activePart.end && activePart.loaded) {
            return activePart;
          }
        }
      }
      return this.getBufferedFrag(position, levelType);
    }

;
    _proto.getBufferedFrag = function getBufferedFrag(position, levelType) {
      return this.getFragAtPos(position, levelType, true);
    };
    _proto.getFragAtPos = function getFragAtPos(position, levelType, buffered) {
      var fragments = this.fragments;
      var keys = Object.keys(fragments);
      for (var i = keys.length; i--;) {
        var fragmentEntity = fragments[keys[i]];
        if ((fragmentEntity == null ? void 0 : fragmentEntity.body.type) === levelType && (!buffered || fragmentEntity.buffered)) {
          var frag = fragmentEntity.body;
          if (frag.start <= position && position <= frag.end) {
            return frag;
          }
        }
      }
      return null;
    }

;
    _proto.detectEvictedFragments = function detectEvictedFragments(elementaryStream, timeRange, playlistType, appendedFrag, appendedPart, removeAppending) {
      var _this = this;
      if (this.timeRanges) {
        this.timeRanges[elementaryStream] = timeRange;
      }
      var appendedPartSn = (appendedPart == null ? void 0 : appendedPart.fragment.sn) || -1;
      Object.keys(this.fragments).forEach(function (key) {
        var fragmentEntity = _this.fragments[key];
        if (!fragmentEntity || !_this.hls) {
          return;
        }
        var frag = fragmentEntity.body;
        if (appendedPartSn >= frag.sn) {
          return;
        }
        if (!fragmentEntity.buffered && (!fragmentEntity.loaded || removeAppending)) {
          if (frag.type === playlistType) {
            _this.removeFragment(frag);
          }
          return;
        }
        var esData = fragmentEntity.range[elementaryStream];
        if (!esData) {
          return;
        }
        if (esData.time.length === 0) {
          _this.removeFragment(frag);
          return;
        }
        esData.time.some(function (time) {
          var isNotBuffered = !_this.isTimeBuffered(time.startPTS, time.endPTS, timeRange);
          if (isNotBuffered) {
            _this.removeFragment(frag);
          }
          return isNotBuffered;
        });
        if (appendedFrag && appendedFrag !== frag && userAgentChromeVersion()) {
          var endPTS = appendedFrag.endPTS;
          var otherStartPTS = frag.startPTS;
          if (endPTS && otherStartPTS && fragmentEntity.range.video) {
            var diff = otherStartPTS - endPTS;
            if (diff < 0 && diff > -0.1) {
              _this.removeFragment(frag);
              _this.hls.trigger(Events.BUFFER_FLUSHING, {
                startOffset: endPTS + 0.004,
                endOffset: Infinity,
                type: 'video'
              });
            }
          }
        }
      });
    }

;
    _proto.detectPartialFragments = function detectPartialFragments(data) {
      var _this2 = this;
      var timeRanges = this.timeRanges;
      var frag = data.frag,
        part = data.part,
        playlistType = data.id;
      if (!timeRanges || !isMediaFragment(frag)) {
        return;
      }
      var fragKey = getFragmentKey(frag);
      var fragmentEntity = this.fragments[fragKey];
      if (!fragmentEntity || fragmentEntity.buffered && frag.gap) {
        return;
      }
      var isFragHint = !frag.relurl;
      Object.keys(timeRanges).forEach(function (elementaryStream) {
        var streamInfo = frag.elementaryStreams[elementaryStream];
        if (!streamInfo) {
          return;
        }
        var timeRange = timeRanges[elementaryStream];
        var partial = isFragHint || streamInfo.partial === true;
        fragmentEntity.range[elementaryStream] = _this2.getBufferedTimes(frag, part, partial, timeRange);
        _this2.detectEvictedFragments(elementaryStream, timeRange, playlistType, frag, part);
      });
      fragmentEntity.loaded = null;
      var trackNames = Object.keys(fragmentEntity.range);
      if (trackNames.length) {
        this.bufferedEnd(fragmentEntity, frag);
        if (!isPartial(fragmentEntity)) {
          this.removeParts(frag.sn - 1, frag.type);
        }
        if (!part) {
          var minPartialAppend = Math.min(0.004, frag.duration);
          trackNames.some(function (elementaryStream) {
            var _fragmentEntity$range;
            var times = (_fragmentEntity$range = fragmentEntity.range[elementaryStream]) == null ? void 0 : _fragmentEntity$range.time;
            var bufferedGap = times.length === 0 || times.length === 1 && times[0].endPTS - times[0].startPTS < minPartialAppend;
            if (bufferedGap) {
              _this2.addAsGap(frag);
            }
            return bufferedGap;
          });
        }
      } else {
        this.removeFragment(frag);
      }
    };
    _proto.addAsGap = function addAsGap(frag) {
      frag.gap = true;
      this.removeFragment(frag);
      this.fragBuffered(frag, true);
    };
    _proto.bufferedEnd = function bufferedEnd(fragmentEntity, frag) {
      fragmentEntity.buffered = true;
      var endList = fragmentEntity.body.endList = frag.endList || fragmentEntity.body.endList;
      if (endList && !frag.gap) {
        this.endListFragments[fragmentEntity.body.type] = fragmentEntity;
      }
    };
    _proto.removeParts = function removeParts(snToKeep, levelType) {
      var activeParts = this.activePartLists[levelType];
      if (!activeParts) {
        return;
      }
      this.activePartLists[levelType] = filterParts(activeParts, function (part) {
        return part.fragment.sn >= snToKeep;
      });
    };
    _proto.fragBuffered = function fragBuffered(frag, force) {
      var fragKey = getFragmentKey(frag);
      var fragmentEntity = this.fragments[fragKey];
      if (!fragmentEntity && force) {
        fragmentEntity = this.fragments[fragKey] = {
          body: frag,
          appendedPTS: null,
          loaded: null,
          buffered: false,
          range: Object.create(null)
        };
        if (frag.gap) {
          this.hasGaps = true;
        }
      }
      if (fragmentEntity) {
        fragmentEntity.loaded = null;
        this.bufferedEnd(fragmentEntity, frag);
      }
      return fragmentEntity;
    };
    _proto.getBufferedTimes = function getBufferedTimes(fragment, part, partial, timeRange) {
      var buffered = {
        time: [],
        partial: partial
      };
      var startPTS = fragment.start;
      var endPTS = fragment.end;
      var minEndPTS = fragment.minEndPTS || endPTS;
      var maxStartPTS = fragment.maxStartPTS || startPTS;
      for (var i = 0; i < timeRange.length; i++) {
        var startTime = timeRange.start(i) - this.bufferPadding;
        var endTime = timeRange.end(i) + this.bufferPadding;
        if (maxStartPTS >= startTime && minEndPTS <= endTime) {
          buffered.time.push({
            startPTS: Math.max(startPTS, timeRange.start(i)),
            endPTS: Math.min(endPTS, timeRange.end(i))
          });
          break;
        } else if (startPTS < endTime && endPTS > startTime) {
          var start = Math.max(startPTS, timeRange.start(i));
          var end = Math.min(endPTS, timeRange.end(i));
          if (end > start) {
            buffered.partial = true;
            buffered.time.push({
              startPTS: start,
              endPTS: end
            });
          }
        } else if (endPTS <= startTime) {
          break;
        }
      }
      return buffered;
    }

;
    _proto.getPartialFragment = function getPartialFragment(time) {
      var bestFragment = null;
      var timePadding;
      var startTime;
      var endTime;
      var bestOverlap = 0;
      var bufferPadding = this.bufferPadding,
        fragments = this.fragments;
      Object.keys(fragments).forEach(function (key) {
        var fragmentEntity = fragments[key];
        if (!fragmentEntity) {
          return;
        }
        if (isPartial(fragmentEntity)) {
          startTime = fragmentEntity.body.start - bufferPadding;
          endTime = fragmentEntity.body.end + bufferPadding;
          if (time >= startTime && time <= endTime) {
            timePadding = Math.min(time - startTime, endTime - time);
            if (bestOverlap <= timePadding) {
              bestFragment = fragmentEntity.body;
              bestOverlap = timePadding;
            }
          }
        }
      });
      return bestFragment;
    };
    _proto.isEndListAppended = function isEndListAppended(type) {
      var lastFragmentEntity = this.endListFragments[type];
      return lastFragmentEntity !== undefined && (lastFragmentEntity.buffered || isPartial(lastFragmentEntity));
    };
    _proto.getState = function getState(fragment) {
      var fragKey = getFragmentKey(fragment);
      var fragmentEntity = this.fragments[fragKey];
      if (fragmentEntity) {
        if (!fragmentEntity.buffered) {
          return FragmentState.APPENDING;
        } else if (isPartial(fragmentEntity)) {
          return FragmentState.PARTIAL;
        } else {
          return FragmentState.OK;
        }
      }
      return FragmentState.NOT_LOADED;
    };
    _proto.isTimeBuffered = function isTimeBuffered(startPTS, endPTS, timeRange) {
      var startTime;
      var endTime;
      for (var i = 0; i < timeRange.length; i++) {
        startTime = timeRange.start(i) - this.bufferPadding;
        endTime = timeRange.end(i) + this.bufferPadding;
        if (startPTS >= startTime && endPTS <= endTime) {
          return true;
        }
        if (endPTS <= startTime) {
          return false;
        }
      }
      return false;
    };
    _proto.onManifestLoading = function onManifestLoading() {
      this.removeAllFragments();
    };
    _proto.onFragLoaded = function onFragLoaded(event, data) {
      if (!isMediaFragment(data.frag) || data.frag.bitrateTest) {
        return;
      }
      var frag = data.frag;
      var loaded = data.part ? null : data;
      var fragKey = getFragmentKey(frag);
      this.fragments[fragKey] = {
        body: frag,
        appendedPTS: null,
        loaded: loaded,
        buffered: false,
        range: Object.create(null)
      };
    };
    _proto.onBufferAppended = function onBufferAppended(event, data) {
      var frag = data.frag,
        part = data.part,
        timeRanges = data.timeRanges;
      if (!isMediaFragment(frag)) {
        return;
      }
      var playlistType = frag.type;
      if (part) {
        var activeParts = this.activePartLists[playlistType];
        if (!activeParts) {
          this.activePartLists[playlistType] = activeParts = [];
        }
        activeParts.push(part);
      }
      this.timeRanges = timeRanges;
    };
    _proto.onFragBuffered = function onFragBuffered(event, data) {
      this.detectPartialFragments(data);
    };
    _proto.hasFragment = function hasFragment(fragment) {
      var fragKey = getFragmentKey(fragment);
      return !!this.fragments[fragKey];
    };
    _proto.hasFragments = function hasFragments(type) {
      var fragments = this.fragments;
      var keys = Object.keys(fragments);
      if (!type) {
        return keys.length > 0;
      }
      for (var i = keys.length; i--;) {
        var fragmentEntity = fragments[keys[i]];
        if ((fragmentEntity == null ? void 0 : fragmentEntity.body.type) === type) {
          return true;
        }
      }
      return false;
    };
    _proto.hasParts = function hasParts(type) {
      var _this$activePartLists;
      return !!((_this$activePartLists = this.activePartLists[type]) != null && _this$activePartLists.length);
    }

;
    _proto.getBackBufferEvictionEnd = function getBackBufferEvictionEnd(beforePosition, levelType, bytesNeeded) {
      var fragments = this.fragments;

      var bytesFreed = 0;
      var evictEnd = 0;
      var keys = Object.keys(fragments);
      for (var i = 0; i < keys.length; i++) {
        var entity = fragments[keys[i]];
        if (!entity || !entity.buffered || entity.body.type !== levelType) {
          continue;
        }
        var frag = entity.body;
        if (frag.gap) {
          return 0;
        }
        var bytes = frag.hasStats && frag.stats.loaded || frag.byteLength || frag.bitrate && frag.bitrate * 8 * frag.duration;
        if (frag.end <= beforePosition && bytes) {
          bytesFreed += bytes;
          evictEnd = Math.max(evictEnd, frag.end);
          if (bytesFreed >= bytesNeeded) {
            return evictEnd;
          }
        }
      }

      return evictEnd > 0 ? evictEnd : 0;
    };
    _proto.removeFragmentsInRange = function removeFragmentsInRange(start, end, playlistType, withGapOnly, unbufferedOnly) {
      var _this3 = this;
      if (withGapOnly && !this.hasGaps) {
        return;
      }
      Object.keys(this.fragments).forEach(function (key) {
        var fragmentEntity = _this3.fragments[key];
        if (!fragmentEntity) {
          return;
        }
        var frag = fragmentEntity.body;
        if (frag.type !== playlistType || withGapOnly && !frag.gap) {
          return;
        }
        if (frag.start < end && frag.end > start && (fragmentEntity.buffered || unbufferedOnly)) {
          _this3.removeFragment(frag);
        }
      });
    };
    _proto.removeFragment = function removeFragment(fragment) {
      var fragKey = getFragmentKey(fragment);
      fragment.clearElementaryStreamInfo();
      var activeParts = this.activePartLists[fragment.type];
      if (activeParts) {
        var snToRemove = fragment.sn;
        this.activePartLists[fragment.type] = filterParts(activeParts, function (part) {
          return part.fragment.sn !== snToRemove;
        });
      }
      delete this.fragments[fragKey];
      if (fragment.endList) {
        delete this.endListFragments[fragment.type];
      }
    };
    _proto.removeAllFragments = function removeAllFragments() {
      var _this$hls;
      this.fragments = Object.create(null);
      this.endListFragments = Object.create(null);
      this.activePartLists = Object.create(null);
      this.hasGaps = false;
      var partlist = (_this$hls = this.hls) == null || (_this$hls = _this$hls.latestLevelDetails) == null ? void 0 : _this$hls.partList;
      if (partlist) {
        partlist.forEach(function (part) {
          return part.clearElementaryStreamInfo();
        });
      }
    };
    return FragmentTracker;
  }();
  function isPartial(fragmentEntity) {
    var _fragmentEntity$range2, _fragmentEntity$range3, _fragmentEntity$range4, _fragmentEntity$range5;
    return fragmentEntity.buffered && !!(fragmentEntity.body.gap || (_fragmentEntity$range2 = fragmentEntity.range.video) != null && _fragmentEntity$range2.partial || (_fragmentEntity$range3 = fragmentEntity.range.audio) != null && _fragmentEntity$range3.partial || (_fragmentEntity$range4 = fragmentEntity.range.audiovideo) != null && _fragmentEntity$range4.partial || (_fragmentEntity$range5 = fragmentEntity.range.subs) != null && _fragmentEntity$range5.partial);
  }
  function getFragmentKey(fragment) {
    return fragment.type + "_" + fragment.level + "_" + fragment.sn;
  }
  function filterParts(partList, predicate) {
    return partList.filter(function (part) {
      var keep = predicate(part);
      if (!keep) {
        part.clearElementaryStreamInfo();
      }
      return keep;
    });
  }

  var AESCrypto = function () {
    function AESCrypto(subtle, iv, aesMode) {
      this.subtle = void 0;
      this.aesIV = void 0;
      this.aesMode = void 0;
      this.subtle = subtle;
      this.aesIV = iv;
      this.aesMode = aesMode;
    }
    var _proto = AESCrypto.prototype;
    _proto.decrypt = function decrypt(data, key) {
      switch (this.aesMode) {
        case DecrypterAesMode.cbc:
          return this.subtle.decrypt({
            name: 'AES-CBC',
            iv: this.aesIV
          }, key, data);
        case DecrypterAesMode.ctr:
          return this.subtle.decrypt({
            name: 'AES-CTR',
            counter: this.aesIV,
            length: 64
          },
          key, data);
        default:
          throw new Error("[AESCrypto] invalid aes mode " + this.aesMode);
      }
    };
    return AESCrypto;
  }();

  function removePadding(array) {
    var outputBytes = array.byteLength;
    var paddingBytes = outputBytes && new DataView(array.buffer).getUint8(outputBytes - 1);
    if (paddingBytes) {
      return array.slice(0, outputBytes - paddingBytes);
    }
    return array;
  }
  var AESDecryptor = function () {
    function AESDecryptor() {
      this.rcon = [0x0, 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36];
      this.subMix = [new Uint32Array(256), new Uint32Array(256), new Uint32Array(256), new Uint32Array(256)];
      this.invSubMix = [new Uint32Array(256), new Uint32Array(256), new Uint32Array(256), new Uint32Array(256)];
      this.sBox = new Uint32Array(256);
      this.invSBox = new Uint32Array(256);
      this.key = new Uint32Array(0);
      this.ksRows = 0;
      this.keySize = 0;
      this.keySchedule = void 0;
      this.invKeySchedule = void 0;
      this.initTable();
    }

    var _proto = AESDecryptor.prototype;
    _proto.uint8ArrayToUint32Array_ = function uint8ArrayToUint32Array_(arrayBuffer) {
      var view = new DataView(arrayBuffer);
      var newArray = new Uint32Array(4);
      for (var i = 0; i < 4; i++) {
        newArray[i] = view.getUint32(i * 4);
      }
      return newArray;
    };
    _proto.initTable = function initTable() {
      var sBox = this.sBox;
      var invSBox = this.invSBox;
      var subMix = this.subMix;
      var subMix0 = subMix[0];
      var subMix1 = subMix[1];
      var subMix2 = subMix[2];
      var subMix3 = subMix[3];
      var invSubMix = this.invSubMix;
      var invSubMix0 = invSubMix[0];
      var invSubMix1 = invSubMix[1];
      var invSubMix2 = invSubMix[2];
      var invSubMix3 = invSubMix[3];
      var d = new Uint32Array(256);
      var x = 0;
      var xi = 0;
      var i = 0;
      for (i = 0; i < 256; i++) {
        if (i < 128) {
          d[i] = i << 1;
        } else {
          d[i] = i << 1 ^ 0x11b;
        }
      }
      for (i = 0; i < 256; i++) {
        var sx = xi ^ xi << 1 ^ xi << 2 ^ xi << 3 ^ xi << 4;
        sx = sx >>> 8 ^ sx & 0xff ^ 0x63;
        sBox[x] = sx;
        invSBox[sx] = x;

        var x2 = d[x];
        var x4 = d[x2];
        var x8 = d[x4];

        var t = d[sx] * 0x101 ^ sx * 0x1010100;
        subMix0[x] = t << 24 | t >>> 8;
        subMix1[x] = t << 16 | t >>> 16;
        subMix2[x] = t << 8 | t >>> 24;
        subMix3[x] = t;

        t = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
        invSubMix0[sx] = t << 24 | t >>> 8;
        invSubMix1[sx] = t << 16 | t >>> 16;
        invSubMix2[sx] = t << 8 | t >>> 24;
        invSubMix3[sx] = t;

        if (!x) {
          x = xi = 1;
        } else {
          x = x2 ^ d[d[d[x8 ^ x2]]];
          xi ^= d[d[xi]];
        }
      }
    };
    _proto.expandKey = function expandKey(keyBuffer) {
      var key = this.uint8ArrayToUint32Array_(keyBuffer);
      var sameKey = true;
      var offset = 0;
      while (offset < key.length && sameKey) {
        sameKey = key[offset] === this.key[offset];
        offset++;
      }
      if (sameKey) {
        return;
      }
      this.key = key;
      var keySize = this.keySize = key.length;
      if (keySize !== 4 && keySize !== 6 && keySize !== 8) {
        throw new Error('Invalid aes key size=' + keySize);
      }
      var ksRows = this.ksRows = (keySize + 6 + 1) * 4;
      var ksRow;
      var invKsRow;
      var keySchedule = this.keySchedule = new Uint32Array(ksRows);
      var invKeySchedule = this.invKeySchedule = new Uint32Array(ksRows);
      var sbox = this.sBox;
      var rcon = this.rcon;
      var invSubMix = this.invSubMix;
      var invSubMix0 = invSubMix[0];
      var invSubMix1 = invSubMix[1];
      var invSubMix2 = invSubMix[2];
      var invSubMix3 = invSubMix[3];
      var prev;
      var t;
      for (ksRow = 0; ksRow < ksRows; ksRow++) {
        if (ksRow < keySize) {
          prev = keySchedule[ksRow] = key[ksRow];
          continue;
        }
        t = prev;
        if (ksRow % keySize === 0) {
          t = t << 8 | t >>> 24;

          t = sbox[t >>> 24] << 24 | sbox[t >>> 16 & 0xff] << 16 | sbox[t >>> 8 & 0xff] << 8 | sbox[t & 0xff];

          t ^= rcon[ksRow / keySize | 0] << 24;
        } else if (keySize > 6 && ksRow % keySize === 4) {
          t = sbox[t >>> 24] << 24 | sbox[t >>> 16 & 0xff] << 16 | sbox[t >>> 8 & 0xff] << 8 | sbox[t & 0xff];
        }
        keySchedule[ksRow] = prev = (keySchedule[ksRow - keySize] ^ t) >>> 0;
      }
      for (invKsRow = 0; invKsRow < ksRows; invKsRow++) {
        ksRow = ksRows - invKsRow;
        if (invKsRow & 3) {
          t = keySchedule[ksRow];
        } else {
          t = keySchedule[ksRow - 4];
        }
        if (invKsRow < 4 || ksRow <= 4) {
          invKeySchedule[invKsRow] = t;
        } else {
          invKeySchedule[invKsRow] = invSubMix0[sbox[t >>> 24]] ^ invSubMix1[sbox[t >>> 16 & 0xff]] ^ invSubMix2[sbox[t >>> 8 & 0xff]] ^ invSubMix3[sbox[t & 0xff]];
        }
        invKeySchedule[invKsRow] = invKeySchedule[invKsRow] >>> 0;
      }
    }

  ;
    _proto.networkToHostOrderSwap = function networkToHostOrderSwap(word) {
      return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24;
    };
    _proto.decrypt = function decrypt(inputArrayBuffer, offset, aesIV) {
      var nRounds = this.keySize + 6;
      var invKeySchedule = this.invKeySchedule;
      var invSBOX = this.invSBox;
      var invSubMix = this.invSubMix;
      var invSubMix0 = invSubMix[0];
      var invSubMix1 = invSubMix[1];
      var invSubMix2 = invSubMix[2];
      var invSubMix3 = invSubMix[3];
      var initVector = this.uint8ArrayToUint32Array_(aesIV);
      var initVector0 = initVector[0];
      var initVector1 = initVector[1];
      var initVector2 = initVector[2];
      var initVector3 = initVector[3];
      var inputInt32 = new Int32Array(inputArrayBuffer);
      var outputInt32 = new Int32Array(inputInt32.length);
      var t0, t1, t2, t3;
      var s0, s1, s2, s3;
      var inputWords0, inputWords1, inputWords2, inputWords3;
      var ksRow, i;
      var swapWord = this.networkToHostOrderSwap;
      while (offset < inputInt32.length) {
        inputWords0 = swapWord(inputInt32[offset]);
        inputWords1 = swapWord(inputInt32[offset + 1]);
        inputWords2 = swapWord(inputInt32[offset + 2]);
        inputWords3 = swapWord(inputInt32[offset + 3]);
        s0 = inputWords0 ^ invKeySchedule[0];
        s1 = inputWords3 ^ invKeySchedule[1];
        s2 = inputWords2 ^ invKeySchedule[2];
        s3 = inputWords1 ^ invKeySchedule[3];
        ksRow = 4;

        for (i = 1; i < nRounds; i++) {
          t0 = invSubMix0[s0 >>> 24] ^ invSubMix1[s1 >> 16 & 0xff] ^ invSubMix2[s2 >> 8 & 0xff] ^ invSubMix3[s3 & 0xff] ^ invKeySchedule[ksRow];
          t1 = invSubMix0[s1 >>> 24] ^ invSubMix1[s2 >> 16 & 0xff] ^ invSubMix2[s3 >> 8 & 0xff] ^ invSubMix3[s0 & 0xff] ^ invKeySchedule[ksRow + 1];
          t2 = invSubMix0[s2 >>> 24] ^ invSubMix1[s3 >> 16 & 0xff] ^ invSubMix2[s0 >> 8 & 0xff] ^ invSubMix3[s1 & 0xff] ^ invKeySchedule[ksRow + 2];
          t3 = invSubMix0[s3 >>> 24] ^ invSubMix1[s0 >> 16 & 0xff] ^ invSubMix2[s1 >> 8 & 0xff] ^ invSubMix3[s2 & 0xff] ^ invKeySchedule[ksRow + 3];
          s0 = t0;
          s1 = t1;
          s2 = t2;
          s3 = t3;
          ksRow = ksRow + 4;
        }

        t0 = invSBOX[s0 >>> 24] << 24 ^ invSBOX[s1 >> 16 & 0xff] << 16 ^ invSBOX[s2 >> 8 & 0xff] << 8 ^ invSBOX[s3 & 0xff] ^ invKeySchedule[ksRow];
        t1 = invSBOX[s1 >>> 24] << 24 ^ invSBOX[s2 >> 16 & 0xff] << 16 ^ invSBOX[s3 >> 8 & 0xff] << 8 ^ invSBOX[s0 & 0xff] ^ invKeySchedule[ksRow + 1];
        t2 = invSBOX[s2 >>> 24] << 24 ^ invSBOX[s3 >> 16 & 0xff] << 16 ^ invSBOX[s0 >> 8 & 0xff] << 8 ^ invSBOX[s1 & 0xff] ^ invKeySchedule[ksRow + 2];
        t3 = invSBOX[s3 >>> 24] << 24 ^ invSBOX[s0 >> 16 & 0xff] << 16 ^ invSBOX[s1 >> 8 & 0xff] << 8 ^ invSBOX[s2 & 0xff] ^ invKeySchedule[ksRow + 3];

        outputInt32[offset] = swapWord(t0 ^ initVector0);
        outputInt32[offset + 1] = swapWord(t3 ^ initVector1);
        outputInt32[offset + 2] = swapWord(t2 ^ initVector2);
        outputInt32[offset + 3] = swapWord(t1 ^ initVector3);

        initVector0 = inputWords0;
        initVector1 = inputWords1;
        initVector2 = inputWords2;
        initVector3 = inputWords3;
        offset = offset + 4;
      }
      return outputInt32.buffer;
    };
    return AESDecryptor;
  }();

  var FastAESKey = function () {
    function FastAESKey(subtle, key, aesMode) {
      this.subtle = void 0;
      this.key = void 0;
      this.aesMode = void 0;
      this.subtle = subtle;
      this.key = key;
      this.aesMode = aesMode;
    }
    var _proto = FastAESKey.prototype;
    _proto.expandKey = function expandKey() {
      var subtleAlgoName = getSubtleAlgoName(this.aesMode);
      return this.subtle.importKey('raw', this.key, {
        name: subtleAlgoName
      }, false, ['encrypt', 'decrypt']);
    };
    return FastAESKey;
  }();
  function getSubtleAlgoName(aesMode) {
    switch (aesMode) {
      case DecrypterAesMode.cbc:
        return 'AES-CBC';
      case DecrypterAesMode.ctr:
        return 'AES-CTR';
      default:
        throw new Error("[FastAESKey] invalid aes mode " + aesMode);
    }
  }

  var CHUNK_SIZE = 16;
  var Decrypter = function () {
    function Decrypter(config, useSoftware) {
      this.logEnabled = true;
      this.decryptRange = void 0;
      this.subtle = null;
      this.softwareDecrypter = null;
      this.key = null;
      this.fastAesKey = null;
      this.remainderData = null;
      this.currentIV = null;
      this.currentResult = null;
      this.useSoftware = void 0;
      this.enableSoftwareAES = void 0;
      this.enableSoftwareAES = config.enableSoftwareAES;
      if (useSoftware !== true) {
        try {
          var browserCrypto = self.crypto;
          if (browserCrypto) {
            this.subtle = browserCrypto.subtle || browserCrypto.webkitSubtle;
          }
        } catch (e) {
        }
      }
      this.useSoftware = !this.subtle;
    }
    var _proto = Decrypter.prototype;
    _proto.destroy = function destroy() {
      this.subtle = null;
      this.softwareDecrypter = null;
      this.key = null;
      this.fastAesKey = null;
      this.remainderData = null;
      this.currentIV = null;
      this.currentResult = null;
    };
    _proto.isSync = function isSync() {
      return this.useSoftware;
    };
    _proto.flush = function flush() {
      var currentResult = this.currentResult,
        remainderData = this.remainderData;
      if (!currentResult || remainderData) {
        this.reset();
        return null;
      }
      var data = new Uint8Array(currentResult);
      this.reset();
      var decryptRange = this.decryptRange;
      if (decryptRange) {
        if (decryptRange.start === 0 && decryptRange.end >= data.length) {
          return data;
        }
        return data.slice(decryptRange.start, decryptRange.end);
      }
      return removePadding(data);
    };
    _proto.reset = function reset() {
      this.currentResult = null;
      this.currentIV = null;
      this.remainderData = null;
      if (this.softwareDecrypter) {
        this.softwareDecrypter = null;
      }
    };
    _proto.decrypt = function decrypt(data, key, iv, aesMode, decryptRange) {
      var _this = this;
      this.decryptRange = decryptRange;
      if (this.useSoftware || decryptRange) {
        return new Promise(function (resolve, reject) {
          var dataView = ArrayBuffer.isView(data) ? data : new Uint8Array(data);
          _this.softwareDecrypt(dataView, key, iv, aesMode);
          var decryptResult = _this.flush();
          if (decryptResult) {
            resolve(decryptResult.buffer);
          } else {
            reject(new Error('[softwareDecrypt] Failed to decrypt data'));
          }
        });
      }
      return this.webCryptoDecrypt(new Uint8Array(data), key, iv, aesMode);
    }

  ;
    _proto.softwareDecrypt = function softwareDecrypt(data, key, iv, aesMode) {
      var currentIV = this.currentIV,
        currentResult = this.currentResult,
        remainderData = this.remainderData;
      if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
        return null;
      }
      this.logOnce('JS AES decrypt');

      if (remainderData) {
        data = appendUint8Array(remainderData, data);
        this.remainderData = null;
      }

      var currentChunk = this.getValidChunk(data);
      if (!currentChunk.length) {
        return null;
      }
      if (currentIV) {
        iv = currentIV;
      }
      var softwareDecrypter = this.softwareDecrypter;
      if (!softwareDecrypter) {
        softwareDecrypter = this.softwareDecrypter = new AESDecryptor();
      }
      softwareDecrypter.expandKey(key);
      var result = currentResult;
      this.currentResult = softwareDecrypter.decrypt(currentChunk.buffer, 0, iv);
      this.currentIV = currentChunk.slice(-16).buffer;
      if (!result) {
        return null;
      }
      return result;
    };
    _proto.webCryptoDecrypt = function webCryptoDecrypt(data, key, iv, aesMode) {
      var _this2 = this;
      if (this.key !== key || !this.fastAesKey) {
        if (!this.subtle) {
          return Promise.resolve(this.onWebCryptoError(data, key, iv, aesMode));
        }
        this.key = key;
        this.fastAesKey = new FastAESKey(this.subtle, key, aesMode);
      }
      return this.fastAesKey.expandKey().then(function (aesKey) {
        if (!_this2.subtle) {
          return Promise.reject(new Error('web crypto not initialized'));
        }
        _this2.logOnce('WebCrypto AES decrypt');
        var crypto = new AESCrypto(_this2.subtle, new Uint8Array(iv), aesMode);
        return crypto.decrypt(data.buffer, aesKey);
      }).catch(function (err) {
        return _this2.onWebCryptoError(data, key, iv, aesMode);
      });
    };
    _proto.onWebCryptoError = function onWebCryptoError(data, key, iv, aesMode) {
      var enableSoftwareAES = this.enableSoftwareAES;
      if (enableSoftwareAES) {
        this.useSoftware = true;
        this.logEnabled = true;
        this.softwareDecrypt(data, key, iv, aesMode);
        var decryptResult = this.flush();
        if (decryptResult) {
          return decryptResult.buffer;
        }
      }
      throw new Error('WebCrypto' + (enableSoftwareAES ? ' and softwareDecrypt' : '') + ': failed to decrypt data');
    };
    _proto.getValidChunk = function getValidChunk(data) {
      var currentChunk = data;
      var splitPoint = data.length - data.length % CHUNK_SIZE;
      if (splitPoint !== data.length) {
        currentChunk = data.slice(0, splitPoint);
        this.remainderData = data.slice(splitPoint);
      }
      return currentChunk;
    };
    _proto.logOnce = function logOnce(msg) {
      if (!this.logEnabled) {
        return;
      }
      this.logEnabled = false;
    };
    return Decrypter;
  }();

  var MIN_CHUNK_SIZE = Math.pow(2, 17);
  var FragmentLoader = function () {
    function FragmentLoader(config) {
      this.config = void 0;
      this.loader = null;
      this.partLoadTimeout = -1;
      this.config = config;
    }
    var _proto = FragmentLoader.prototype;
    _proto.destroy = function destroy() {
      if (this.loader) {
        this.loader.destroy();
        this.loader = null;
      }
    };
    _proto.abort = function abort() {
      if (this.loader) {
        this.loader.abort();
      }
    };
    _proto.load = function load(frag, isIFrame, onProgress, progressGate) {
      var _this = this;
      var url = frag.url;
      if (!url) {
        return Promise.reject(new LoadError({
          type: ErrorTypes.NETWORK_ERROR,
          details: ErrorDetails.FRAG_LOAD_ERROR,
          fatal: false,
          frag: frag,
          error: new Error("Fragment does not have a " + (url ? 'part list' : 'url')),
          networkDetails: null
        }));
      }
      this.abort();
      var config = this.config;
      var FragmentILoader = config.fLoader;
      var DefaultILoader = config.loader;
      return new Promise(function (resolve, reject) {
        if (_this.loader) {
          _this.loader.destroy();
        }
        if (frag.gap) {
          if (frag.tagList.some(function (tags) {
            return tags[0] === 'GAP';
          })) {
            reject(createGapLoadError(frag));
            return;
          } else {
            frag.gap = false;
          }
        }
        var loader = _this.loader = FragmentILoader ? new FragmentILoader(config) : new DefaultILoader(config);
        var loaderContext = createLoaderContext(frag, null, isIFrame);
        frag.loader = loader;
        var loadPolicy = getLoaderConfigWithoutReties(config.fragLoadPolicy.default);
        var loaderConfig = {
          loadPolicy: loadPolicy,
          timeout: loadPolicy.maxLoadTimeMs,
          maxRetry: 0,
          retryDelay: 0,
          maxRetryDelay: 0,
          highWaterMark: frag.sn === 'initSegment' ? Infinity : MIN_CHUNK_SIZE
        };
        loader.stats.retry = frag.stats.retry;
        frag.stats = loader.stats;
        var callbacks = {
          onSuccess: function onSuccess(response, stats, context, networkDetails) {
            _this.resetLoader(frag, loader);
            var payload = response.data;
            if (context.resetIV && frag.decryptdata) {
              frag.decryptdata.iv = new Uint8Array(payload.slice(0, 16));
              payload = payload.slice(16);
            }
            resolve({
              frag: frag,
              part: null,
              payload: payload,
              networkDetails: networkDetails
            });
          },
          onError: function onError(response, context, networkDetails, stats) {
            _this.resetLoader(frag, loader);
            reject(new LoadError({
              type: ErrorTypes.NETWORK_ERROR,
              details: ErrorDetails.FRAG_LOAD_ERROR,
              fatal: false,
              frag: frag,
              response: _objectSpread2({
                url: url,
                data: undefined
              }, response),
              error: new Error("HTTP Error " + response.code + " " + response.text),
              networkDetails: networkDetails,
              stats: stats
            }));
          },
          onAbort: function onAbort(stats, context, networkDetails) {
            _this.resetLoader(frag, loader);
            reject(new LoadError({
              type: ErrorTypes.NETWORK_ERROR,
              details: ErrorDetails.INTERNAL_ABORTED,
              fatal: false,
              frag: frag,
              error: new Error('Aborted'),
              networkDetails: networkDetails,
              stats: stats
            }));
          },
          onTimeout: function onTimeout(stats, context, networkDetails) {
            _this.resetLoader(frag, loader);
            reject(new LoadError({
              type: ErrorTypes.NETWORK_ERROR,
              details: ErrorDetails.FRAG_LOAD_TIMEOUT,
              fatal: false,
              frag: frag,
              error: new Error("Timeout after " + loaderConfig.timeout + "ms"),
              networkDetails: networkDetails,
              stats: stats
            }));
          }
        };
        if (onProgress) {
          var onProgressGated = _this.gateProgress(onProgress, progressGate);
          callbacks.onProgress = function (stats, context, data, networkDetails) {
            return onProgressGated({
              frag: frag,
              part: null,
              payload: data,
              networkDetails: networkDetails
            });
          };
        }
        loader.load(loaderContext, loaderConfig, callbacks);
      });
    }

;
    _proto.gateProgress = function gateProgress(onProgress, progressGate) {
      var handleChunk = onProgress;
      if (progressGate) {
        var pendingChunks = [];
        var gateDone = false;
        progressGate.then(function () {
          gateDone = true;
          pendingChunks.forEach(function (chunk) {
            return onProgress(chunk);
          });
          pendingChunks.length = 0;
        }).catch(function () {});
        handleChunk = function handleChunk(data) {
          if (gateDone) {
            onProgress(data);
          } else {
            pendingChunks.push(data);
          }
        };
      }
      return handleChunk;
    };
    _proto.loadPart = function loadPart(frag, part, onProgress) {
      var _this2 = this;
      this.abort();
      var config = this.config;
      var FragmentILoader = config.fLoader;
      var DefaultILoader = config.loader;
      return new Promise(function (resolve, reject) {
        if (_this2.loader) {
          _this2.loader.destroy();
        }
        if (frag.gap || part.gap) {
          reject(createGapLoadError(frag, part));
          return;
        }
        var loader = _this2.loader = FragmentILoader ? new FragmentILoader(config) : new DefaultILoader(config);
        var loaderContext = createLoaderContext(frag, part);
        frag.loader = loader;
        var loadPolicy = getLoaderConfigWithoutReties(config.fragLoadPolicy.default);
        var loaderConfig = {
          loadPolicy: loadPolicy,
          timeout: loadPolicy.maxLoadTimeMs,
          maxRetry: 0,
          retryDelay: 0,
          maxRetryDelay: 0,
          highWaterMark: MIN_CHUNK_SIZE
        };
        part.stats = loader.stats;
        loader.load(loaderContext, loaderConfig, {
          onSuccess: function onSuccess(response, stats, context, networkDetails) {
            _this2.resetLoader(frag, loader);
            _this2.updateStatsFromPart(frag, part);
            var partLoadedData = {
              frag: frag,
              part: part,
              payload: response.data,
              networkDetails: networkDetails
            };
            onProgress(partLoadedData);
            resolve(partLoadedData);
          },
          onError: function onError(response, context, networkDetails, stats) {
            _this2.resetLoader(frag, loader);
            reject(new LoadError({
              type: ErrorTypes.NETWORK_ERROR,
              details: ErrorDetails.FRAG_LOAD_ERROR,
              fatal: false,
              frag: frag,
              part: part,
              response: _objectSpread2({
                url: loaderContext.url,
                data: undefined
              }, response),
              error: new Error("HTTP Error " + response.code + " " + response.text),
              networkDetails: networkDetails,
              stats: stats
            }));
          },
          onAbort: function onAbort(stats, context, networkDetails) {
            frag.stats.aborted = part.stats.aborted;
            _this2.resetLoader(frag, loader);
            reject(new LoadError({
              type: ErrorTypes.NETWORK_ERROR,
              details: ErrorDetails.INTERNAL_ABORTED,
              fatal: false,
              frag: frag,
              part: part,
              error: new Error('Aborted'),
              networkDetails: networkDetails,
              stats: stats
            }));
          },
          onTimeout: function onTimeout(stats, context, networkDetails) {
            _this2.resetLoader(frag, loader);
            reject(new LoadError({
              type: ErrorTypes.NETWORK_ERROR,
              details: ErrorDetails.FRAG_LOAD_TIMEOUT,
              fatal: false,
              frag: frag,
              part: part,
              error: new Error("Timeout after " + loaderConfig.timeout + "ms"),
              networkDetails: networkDetails,
              stats: stats
            }));
          }
        });
      });
    };
    _proto.updateStatsFromPart = function updateStatsFromPart(frag, part) {
      var fragStats = frag.stats;
      var partStats = part.stats;
      var partTotal = partStats.total;
      fragStats.loaded += partStats.loaded;
      if (partTotal) {
        var estTotalParts = Math.round(frag.duration / part.duration);
        var estLoadedParts = Math.min(Math.round(fragStats.loaded / partTotal), estTotalParts);
        var estRemainingParts = estTotalParts - estLoadedParts;
        var estRemainingBytes = estRemainingParts * Math.round(fragStats.loaded / estLoadedParts);
        fragStats.total = fragStats.loaded + estRemainingBytes;
      } else {
        fragStats.total = Math.max(fragStats.loaded, fragStats.total);
      }
      var fragLoading = fragStats.loading;
      var partLoading = partStats.loading;
      if (fragLoading.start) {
        fragLoading.first += partLoading.first - partLoading.start;
      } else {
        fragLoading.start = partLoading.start;
        fragLoading.first = partLoading.first;
      }
      fragLoading.end = partLoading.end;
    };
    _proto.resetLoader = function resetLoader(frag, loader) {
      frag.loader = null;
      frag.url = null;
      if (this.loader === loader) {
        self.clearTimeout(this.partLoadTimeout);
        this.loader = null;
      }
      loader.destroy();
    };
    return FragmentLoader;
  }();
  function createLoaderContext(frag, part, isIFrame) {
    if (part === void 0) {
      part = null;
    }
    var segment = part || frag;
    var loaderContext = {
      type: LoaderContextType.MEDIA_FRAGMENT,
      frag: frag,
      part: part,
      responseType: 'arraybuffer',
      url: segment.url,
      headers: {},
      rangeStart: 0,
      rangeEnd: 0
    };
    var aesAdjustments = getAESAdjustments(frag, part, isIFrame);
    if (aesAdjustments.resetIV) {
      loaderContext.resetIV = true;
    }
    if (aesAdjustments.byteRange) {
      loaderContext.rangeStart = aesAdjustments.byteRange.start;
      loaderContext.rangeEnd = aesAdjustments.byteRange.end;
    }
    return loaderContext;
  }
  function getAESAdjustments(frag, part, isIFrame) {
    if (part === void 0) {
      part = null;
    }
    var result = {};
    var segment = part || frag;
    var start = segment.byteRangeStartOffset;
    var end = segment.byteRangeEndOffset;
    if (isFiniteNumber(start) && isFiniteNumber(end)) {
      var _frag$decryptdata;
      if ((frag.sn === 'initSegment' || isIFrame) && isMethodFullSegmentAesCbc((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method)) {
        var byteRangeStart = Math.floor(start / 16) * 16;
        var byteRangeEnd = Math.ceil(end / 16) * 16;
        result.decryptRange = {
          start: start - byteRangeStart,
          end: end - byteRangeStart
        };
        if (byteRangeStart >= 16) {
          result.resetIV = true;
          byteRangeStart = byteRangeStart - 16;
        }
        result.byteRange = {
          start: byteRangeStart,
          end: byteRangeEnd
        };
      } else {
        result.byteRange = {
          start: start,
          end: end
        };
      }
    }
    return result;
  }
  function createGapLoadError(frag, part) {
    var error = new Error("GAP " + (frag.gap ? 'tag' : 'attribute') + " found");
    var errorData = {
      type: ErrorTypes.MEDIA_ERROR,
      details: ErrorDetails.FRAG_GAP,
      fatal: false,
      frag: frag,
      error: error,
      networkDetails: null
    };
    if (part) {
      errorData.part = part;
    }
    (part ? part : frag).stats.aborted = true;
    return new LoadError(errorData);
  }
  function isMethodFullSegmentAesCbc(method) {
    return method === 'AES-128' || method === 'AES-256';
  }
  var LoadError = function (_Error) {
    function LoadError(data) {
      var _this3;
      _this3 = _Error.call(this, data.error.message) || this;
      _this3.data = void 0;
      _this3.data = data;
      return _this3;
    }
    _inheritsLoose(LoadError, _Error);
    return LoadError;
  }( _wrapNativeSuper(Error));

  var TaskLoop = function (_Logger) {
    function TaskLoop(label, logger) {
      var _this;
      _this = _Logger.call(this, label, logger) || this;
      _this._boundTick = void 0;
      _this._tickTimer = null;
      _this._tickInterval = null;
      _this._tickCallCount = 0;
      _this._boundTick = _this.tick.bind(_this);
      return _this;
    }
    _inheritsLoose(TaskLoop, _Logger);
    var _proto = TaskLoop.prototype;
    _proto.destroy = function destroy() {
      this.onHandlerDestroying();
      this.onHandlerDestroyed();
    };
    _proto.onHandlerDestroying = function onHandlerDestroying() {
      this.clearNextTick();
      this.clearInterval();
    };
    _proto.onHandlerDestroyed = function onHandlerDestroyed() {};
    _proto.hasInterval = function hasInterval() {
      return !!this._tickInterval;
    };
    _proto.hasNextTick = function hasNextTick() {
      return !!this._tickTimer;
    }

;
    _proto.setInterval = function setInterval(millis) {
      if (!this._tickInterval) {
        this._tickCallCount = 0;
        this._tickInterval = self.setInterval(this._boundTick, millis);
        return true;
      }
      return false;
    }

;
    _proto.clearInterval = function clearInterval() {
      if (this._tickInterval) {
        self.clearInterval(this._tickInterval);
        this._tickInterval = null;
        return true;
      }
      return false;
    }

;
    _proto.clearNextTick = function clearNextTick() {
      if (this._tickTimer) {
        self.clearTimeout(this._tickTimer);
        this._tickTimer = null;
        return true;
      }
      return false;
    }

;
    _proto.tick = function tick() {
      this._tickCallCount++;
      if (this._tickCallCount === 1) {
        this.doTick();
        if (this._tickCallCount > 1) {
          this.tickImmediate();
        }
        this._tickCallCount = 0;
      } else {
        this._tickCallCount = 0;
      }
    };
    _proto.tickImmediate = function tickImmediate() {
      this.clearNextTick();
      this._tickTimer = self.setTimeout(this._boundTick, 0);
    }

;
    _proto.doTick = function doTick() {};
    return TaskLoop;
  }(Logger);

  var ChunkMetadata = function ChunkMetadata(level, sn, id, size, part, partial, duration, iframe, decryptRange) {
    if (size === void 0) {
      size = 0;
    }
    if (part === void 0) {
      part = -1;
    }
    if (partial === void 0) {
      partial = false;
    }
    this.level = void 0;
    this.sn = void 0;
    this.part = void 0;
    this.id = void 0;
    this.size = void 0;
    this.partial = void 0;
    this.iframe = void 0;
    this.duration = void 0;
    this.decryptRange = void 0;
    this.transmuxing = getNewPerformanceTiming();
    this.buffering = {
      audio: getNewPerformanceTiming(),
      video: getNewPerformanceTiming(),
      audiovideo: getNewPerformanceTiming()
    };
    this.level = level;
    this.sn = sn;
    this.id = id;
    this.size = size;
    this.part = part;
    this.partial = partial;
    this.duration = duration || 0;
    this.decryptRange = decryptRange;
    this.iframe = iframe || false;
  };
  function getNewPerformanceTiming() {
    return {
      start: 0,
      executeStart: 0,
      executeEnd: 0,
      end: 0
    };
  }

  function findFirstFragWithCC(fragments, cc) {
    for (var i = 0, len = fragments.length; i < len; i++) {
      var _fragments$i;
      if (((_fragments$i = fragments[i]) == null ? void 0 : _fragments$i.cc) === cc) {
        return fragments[i];
      }
    }
    return null;
  }
  function shouldAlignOnDiscontinuities(refDetails, details) {
    if (refDetails) {
      if (details.startCC < refDetails.endCC && details.endCC > refDetails.startCC) {
        return true;
      }
    }
    return false;
  }
  function adjustFragmentStart(frag, sliding) {
    var start = frag.start + sliding;
    frag.setStart(start);
  }
  function adjustSlidingStart(sliding, details) {
    var fragments = details.fragments;
    for (var i = 0, len = fragments.length; i < len; i++) {
      adjustFragmentStart(fragments[i], sliding);
    }
    if (details.fragmentHint) {
      adjustFragmentStart(details.fragmentHint, sliding);
    }
    details.alignedSliding = true;
  }

  function alignStream(switchDetails, details, logger) {
    if (!switchDetails) {
      return;
    }
    alignDiscontinuities(details, switchDetails, logger);
    if (!details.alignedSliding) {
      alignMediaPlaylistByPDT(details, switchDetails, logger);
    }
    if (!details.alignedSliding && !details.skippedSegments) {
      adjustSliding(switchDetails, details, false, logger);
    }
  }

  function alignDiscontinuities(details, refDetails, logger) {
    if (!shouldAlignOnDiscontinuities(refDetails, details)) {
      return;
    }
    var targetCC = Math.min(refDetails.endCC, details.endCC);
    var refFrag = findFirstFragWithCC(refDetails.fragments, targetCC);
    var frag = findFirstFragWithCC(details.fragments, targetCC);
    if (!refFrag || !frag) {
      return;
    }
    var delta = refFrag.start - frag.start;
    adjustSlidingStart(delta, details);
  }

  function alignMediaPlaylistByPDT(details, refDetails, logger) {
    if (!details.hasProgramDateTime || !refDetails.hasProgramDateTime) {
      return;
    }
    var fragments = details.fragments;
    var refFragments = refDetails.fragments;
    if (!fragments.length || !refFragments.length) {
      return;
    }

    var refFrag;
    var frag;
    var targetCC = Math.min(refDetails.endCC, details.endCC);
    if (refDetails.startCC < targetCC && details.startCC < targetCC) {
      refFrag = findFirstFragWithCC(refFragments, targetCC);
      frag = findFirstFragWithCC(fragments, targetCC);
    }
    if (!refFrag || !frag) {
      refFrag = refFragments[Math.floor(refFragments.length / 2)] || refFragments.filter(function (f) {
        return !!f;
      })[0];
      frag = findFirstFragWithCC(fragments, refFrag.cc) || fragments[Math.floor(fragments.length / 2)];
    }
    var refPDT = refFrag.programDateTime;
    var targetPDT = frag.programDateTime;
    if (!refPDT || !targetPDT) {
      return;
    }
    var dateDifference = (targetPDT - refPDT) / 1000;
    if (Math.abs(dateDifference) > Math.max(60, details.totalduration)) {
      return;
    }
    var delta = dateDifference - (frag.start - refFrag.start);
    adjustSlidingStart(delta, details);
  }

  var State = {
    STOPPED: 'STOPPED',
    IDLE: 'IDLE',
    KEY_LOADING: 'KEY_LOADING',
    FRAG_LOADING: 'FRAG_LOADING',
    FRAG_LOADING_WAITING_RETRY: 'FRAG_LOADING_WAITING_RETRY',
    PARSING: 'PARSING',
    PARSED: 'PARSED',
    ENDED: 'ENDED',
    ERROR: 'ERROR',
    WAITING_LEVEL: 'WAITING_LEVEL'
  };
  var BaseStreamController = function (_TaskLoop) {
    function BaseStreamController(hls, fragmentTracker, keyLoader, logPrefix, playlistType) {
      var _this;
      _this = _TaskLoop.call(this, logPrefix, hls.logger) || this;
      _this.hls = void 0;
      _this.fragPrevious = null;
      _this.fragCurrent = null;
      _this.fragPlaying = null;
      _this.fragmentTracker = void 0;
      _this.transmuxer = null;
      _this._state = State.STOPPED;
      _this.playlistType = void 0;
      _this.media = null;
      _this.mediaBuffer = null;
      _this.config = void 0;
      _this.bitrateTest = false;
      _this.lastCurrentTime = 0;
      _this.nextLoadPosition = 0;
      _this.startPosition = -1;
      _this.startTimeOffset = null;
      _this.retryDate = 0;
      _this.levels = null;
      _this.fragmentLoader = void 0;
      _this.initFragmentLoader = void 0;
      _this.keyLoader = void 0;
      _this.levelLastLoaded = null;
      _this.startFragRequested = false;
      _this.decrypter = void 0;
      _this.initPTS = [];
      _this.buffering = true;
      _this.loadingParts = false;
      _this.loopSn = void 0;
      _this.onMediaSeeking = function () {
        var _this2 = _this,
          config = _this2.config,
          fragCurrent = _this2.fragCurrent,
          media = _this2.media,
          mediaBuffer = _this2.mediaBuffer,
          state = _this2.state;
        var currentTime = media ? media.currentTime : 0;
        var bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
        var fowardBuffer = bufferInfo.len;
        if (_this.state === State.ENDED) {
          _this.resetLoadingState();
        } else if (fragCurrent) {
          var tolerance = config.maxFragLookUpTolerance;
          var fragStartOffset = fragCurrent.start - tolerance;
          var fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
          if (!fowardBuffer || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
            var beforeFragment = currentTime < fragStartOffset;
            var pastFragment = currentTime > fragEndOffset;
            if (beforeFragment || pastFragment) {
              if (fragCurrent.loader && (pastFragment || !_this.isFragmentNearlyDownloaded(fragCurrent))) {
                fragCurrent.abortRequests();
                _this.resetLoadingState();
              } else {
                _this.fragPrevious = null;
              }
            }
          }
        }
        if (media) {
          _this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, _this.playlistType, true);

          var lastCurrentTime = _this.lastCurrentTime;
          if (currentTime > lastCurrentTime) {
            _this.lastCurrentTime = currentTime;
          }
          if (!_this.loadingParts) {
            var bufferEnd = Math.max(bufferInfo.end, currentTime);
            var shouldLoadParts = _this.shouldLoadParts(_this.getLevelDetails(), bufferEnd);
            if (shouldLoadParts) {
              _this.loadingParts = shouldLoadParts;
            }
          }
        }

        var bufferEmpty = !BufferHelper.isBuffered(media, currentTime);
        if (!_this.hls.hasEnoughToStart || bufferEmpty) {
          _this.nextLoadPosition = currentTime;
          if (bufferEmpty) {
            _this.startPosition = currentTime;
          }
        }
        if (fowardBuffer < 1 && _this.state === State.IDLE) {
          _this.tickImmediate();
        }
      };
      _this.onMediaEnded = function () {
        _this.startPosition = _this.lastCurrentTime = 0;
      };
      _this.playlistType = playlistType;
      _this.hls = hls;
      _this.fragmentLoader = new FragmentLoader(hls.config);
      _this.initFragmentLoader = new FragmentLoader(hls.config);
      _this.keyLoader = keyLoader;
      _this.fragmentTracker = fragmentTracker;
      _this.config = hls.config;
      _this.decrypter = new Decrypter(hls.config);
      return _this;
    }
    _inheritsLoose(BaseStreamController, _TaskLoop);
    var _proto = BaseStreamController.prototype;
    _proto.registerListeners = function registerListeners() {
      var hls = this.hls;
      hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
      hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
      hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
      hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
      hls.on(Events.ERROR, this.onError, this);
    };
    _proto.unregisterListeners = function unregisterListeners() {
      var hls = this.hls;
      hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
      hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
      hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
      hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
      hls.off(Events.ERROR, this.onError, this);
    };
    _proto.doTick = function doTick() {
      this.onTickEnd();
    };
    _proto.onTickEnd = function onTickEnd() {};
    _proto.startLoad = function startLoad(startPosition) {};
    _proto.stopLoad = function stopLoad() {
      if (this.state === State.STOPPED) {
        return;
      }
      this.fragmentLoader.abort();
      this.initFragmentLoader.abort();
      this.keyLoader.abort(this.playlistType);
      var frag = this.fragCurrent;
      if (frag != null && frag.loader) {
        frag.abortRequests();
        this.fragmentTracker.removeFragment(frag);
      }
      this.resetTransmuxer();
      this.fragCurrent = null;
      this.fragPrevious = null;
      this.clearInterval();
      this.clearNextTick();
      this.state = State.STOPPED;
    };
    _proto.pauseBuffering = function pauseBuffering() {
      this.buffering = false;
    };
    _proto.resumeBuffering = function resumeBuffering() {
      this.buffering = true;
    };
    _proto._streamEnded = function _streamEnded(bufferInfo, levelDetails) {
      if (levelDetails.live || !this.media) {
        return false;
      }
      var bufferEnd = bufferInfo.end || 0;
      var timelineStart = this.config.timelineOffset || 0;
      if (bufferEnd <= timelineStart) {
        return false;
      }
      var bufferedRanges = bufferInfo.buffered;
      if (this.config.maxBufferHole && bufferedRanges && bufferedRanges.length > 1) {
        bufferInfo = BufferHelper.bufferedInfo(bufferedRanges, bufferInfo.start, 0);
      }
      var nextStart = bufferInfo.nextStart;
      var hasSecondBufferedRange = nextStart && nextStart > timelineStart && nextStart < levelDetails.edge;
      if (hasSecondBufferedRange) {
        return false;
      }
      if (this.media.currentTime < bufferInfo.start) {
        return false;
      }
      var partList = levelDetails.partList;
      if (partList != null && partList.length) {
        var lastPart = partList[partList.length - 1];

        var lastPartBuffered = BufferHelper.isBuffered(this.media, lastPart.start + lastPart.duration / 2);
        return lastPartBuffered;
      }
      var lastFragment = levelDetails.fragments[levelDetails.fragments.length - 1];
      return this.fragmentTracker.isEndListAppended(lastFragment.type);
    };
    _proto.getLevelDetails = function getLevelDetails() {
      if (this.levels && this.levelLastLoaded !== null) {
        return this.levelLastLoaded.details;
      }
    };
    _proto.onMediaAttached = function onMediaAttached(event, data) {
      var media = this.media = this.mediaBuffer = data.media;
      addEventListener(media, 'seeking', this.onMediaSeeking);
      addEventListener(media, 'ended', this.onMediaEnded);
      var config = this.config;
      if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
        this.startLoad(config.startPosition);
      }
    };
    _proto.onMediaDetaching = function onMediaDetaching(event, data) {
      this.fragPlaying = null;
      var transferringMedia = !!data.transferMedia;
      var media = this.media;
      if (media === null) {
        return;
      }
      if (media.ended) {
        this.startPosition = this.lastCurrentTime = 0;
      }

      removeEventListener(media, 'seeking', this.onMediaSeeking);
      removeEventListener(media, 'ended', this.onMediaEnded);
      this.media = this.mediaBuffer = null;
      this.loopSn = undefined;
      if (transferringMedia) {
        this.resetLoadingState();
        this.resetTransmuxer();
        return;
      }
      this.loadingParts = false;
      this.fragmentTracker.removeAllFragments();
      this.stopLoad();
    };
    _proto.onManifestLoading = function onManifestLoading() {
      this.initPTS = [];
      this.fragPlaying = this.levels = this.levelLastLoaded = this.fragCurrent = null;
      this.lastCurrentTime = 0;
      this.startPosition = -1;
      this.startFragRequested = false;
    };
    _proto.onError = function onError(event, data) {};
    _proto.onManifestLoaded = function onManifestLoaded(event, data) {
      this.startTimeOffset = data.startTimeOffset;
    };
    _proto.onHandlerDestroying = function onHandlerDestroying() {
      this.stopLoad();
      if (this.transmuxer) {
        this.transmuxer.destroy();
        this.transmuxer = null;
      }
      _TaskLoop.prototype.onHandlerDestroying.call(this);
      this.hls = this.onMediaSeeking = this.onMediaEnded = null;
    };
    _proto.onHandlerDestroyed = function onHandlerDestroyed() {
      this.state = State.STOPPED;
      if (this.fragmentLoader) {
        this.fragmentLoader.destroy();
      }
      if (this.initFragmentLoader) {
        this.initFragmentLoader.destroy();
      }
      if (this.keyLoader) {
        this.keyLoader.destroy();
      }
      if (this.decrypter) {
        this.decrypter.destroy();
      }

      this.hls = this.decrypter = this.keyLoader = null;
      this.fragmentLoader = this.initFragmentLoader = this.fragmentTracker = null;
      _TaskLoop.prototype.onHandlerDestroyed.call(this);
    };
    _proto.loadFragment = function loadFragment(frag, level, targetBufferTime) {
      this.startFragRequested = true;
      this._loadFragForPlayback(frag, level, targetBufferTime);
    };
    _proto._loadFragForPlayback = function _loadFragForPlayback(fragment, level, targetBufferTime) {
      var _this3 = this;
      var progressCallback = function progressCallback(data) {
        var frag = data.frag;
        if (_this3.fragContextChanged(frag)) {
          _this3.fragmentTracker.removeFragment(frag);
          return;
        }
        var chunkCount = ++frag.stats.chunkCount;
        _this3._handleFragmentLoadProgress(data);
      };
      this._doFragLoad(fragment, level, targetBufferTime, progressCallback).then(function (data) {
        if (!data) {
          return;
        }
        var state = _this3.state;
        var frag = data.frag;
        if (_this3.fragContextChanged(frag)) {
          if (state === State.FRAG_LOADING || !_this3.fragCurrent && state === State.PARSING) {
            _this3.fragmentTracker.removeFragment(frag);
            _this3.state = State.IDLE;
          }
          return;
        }
        if ('payload' in data) {
          _this3.hls.trigger(Events.FRAG_LOADED, data);
        }

        _this3._handleFragmentLoadComplete(data);
      }).catch(function (reason) {
        if (_this3.state === State.STOPPED || _this3.state === State.ERROR) {
          return;
        }
        _this3.resetFragmentLoading(fragment);
      });
    };
    _proto.clearTrackerIfNeeded = function clearTrackerIfNeeded(frag) {
      var _this$mediaBuffer;
      var fragmentTracker = this.fragmentTracker;
      var fragState = fragmentTracker.getState(frag);
      if (fragState === FragmentState.APPENDING) {
        var playlistType = frag.type;
        var bufferedInfo = this.getFwdBufferInfo(this.mediaBuffer, playlistType);
        var minForwardBufferLength = Math.max(frag.duration, bufferedInfo ? bufferedInfo.len : this.config.maxBufferLength);
        var backtrackFragment = this.backtrackFragment;
        var backtracked = backtrackFragment ? frag.sn - backtrackFragment.sn : 0;
        if (backtracked === 1 || this.reduceMaxBufferLength(minForwardBufferLength, frag.duration)) {
          fragmentTracker.removeFragment(frag);
        }
      } else if (((_this$mediaBuffer = this.mediaBuffer) == null ? void 0 : _this$mediaBuffer.buffered.length) === 0) {
        fragmentTracker.removeAllFragments();
      } else if (fragmentTracker.hasParts(frag.type)) {
        fragmentTracker.detectPartialFragments({
          frag: frag,
          part: null,
          stats: frag.stats,
          id: frag.type
        });
        if (fragmentTracker.getState(frag) === FragmentState.PARTIAL) {
          fragmentTracker.removeFragment(frag);
        }
      }
    };
    _proto.checkLiveUpdate = function checkLiveUpdate(details) {
      if (details.updated && !details.live) {
        var fragmentTracker = this.fragmentTracker;
        var lastFragment = details.fragments[details.fragments.length - 1];
        if (lastFragment.endList && !fragmentTracker.isEndListAppended(this.playlistType)) {
          var fragmentAtEnd = fragmentTracker.getPartialFragment(lastFragment.end);
          if (fragmentAtEnd) {
            fragmentTracker.removeFragment(fragmentAtEnd);
            fragmentTracker.fragBuffered(lastFragment, true);
          }
        }
        fragmentTracker.detectPartialFragments({
          frag: lastFragment,
          part: null,
          stats: lastFragment.stats,
          id: lastFragment.type
        });
      }
      if (!details.fragments[0]) {
        details.deltaUpdateFailed = true;
      }
    };
    _proto.waitForLive = function waitForLive(levelInfo) {
      var details = levelInfo.details;
      return (details == null ? void 0 : details.live) && details.type !== 'EVENT' && (this.levelLastLoaded !== levelInfo || details.expired);
    };
    _proto.flushMainBuffer = function flushMainBuffer(startOffset, endOffset, type) {
      if (type === void 0) {
        type = null;
      }
      if (!(startOffset - endOffset)) {
        return;
      }
      var flushScope = {
        startOffset: startOffset,
        endOffset: endOffset,
        type: type
      };
      this.hls.trigger(Events.BUFFER_FLUSHING, flushScope);
    };
    _proto._loadInitSegment = function _loadInitSegment(initFrag) {
      var _this4 = this;
      var hls = this.hls;
      this.initFragmentLoader.abort();
      hls.trigger(Events.FRAG_LOADING, {
        frag: initFrag,
        targetBufferTime: 0
      });
      return this.initFragmentLoader.load(initFrag).then(function (data) {
        var _this4$fragCurrent;
        var frag = data.frag;
        if (!_this4.levels || !fragmentsAreEqual(frag, (_this4$fragCurrent = _this4.fragCurrent) == null ? void 0 : _this4$fragCurrent.initSegment)) {
          throw new LoadError({
            type: ErrorTypes.NETWORK_ERROR,
            details: ErrorDetails.INTERNAL_ABORTED,
            error: new Error('init load aborted'),
            fatal: false,
            frag: frag,
            networkDetails: null
          });
        }
        return data;
      }).then(function (data) {
        var frag = data.frag,
          payload = data.payload;
        var decryptData = frag.decryptdata;

        if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
          var startTime = self.performance.now();
          var _getAESAdjustments = getAESAdjustments(frag, null, _this4.iframesOnly),
            decryptRange = _getAESAdjustments.decryptRange;
          return _this4.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method), decryptRange).catch(function (err) {
            hls.trigger(Events.ERROR, {
              type: ErrorTypes.MEDIA_ERROR,
              details: ErrorDetails.FRAG_DECRYPT_ERROR,
              fatal: false,
              error: err,
              reason: err.message,
              frag: frag
            });
            _this4.fragmentLoader.abort();
            throw err;
          }).then(function (decryptedData) {
            var endTime = self.performance.now();
            hls.trigger(Events.FRAG_DECRYPTED, {
              frag: frag,
              part: null,
              payload: decryptedData,
              stats: {
                tstart: startTime,
                tdecrypt: endTime
              }
            });
            data.payload = decryptedData;
            return _this4.completeInitSegmentLoad(data);
          });
        }
        return _this4.completeInitSegmentLoad(data);
      });
    };
    _proto.completeInitSegmentLoad = function completeInitSegmentLoad(data) {
      var stats = data.frag.stats;
      data.frag.data = new Uint8Array(data.payload);
      stats.parsing.start = stats.buffering.start = self.performance.now();
      stats.parsing.end = stats.buffering.end = self.performance.now();
    };
    _proto.loadInitSegmentIfNeeded = function loadInitSegmentIfNeeded(mediaFrag) {
      var _this5 = this;
      var initSegment = mediaFrag.initSegment;
      if (!this.bitrateTest && isMediaFragment(mediaFrag) && initSegment && !initSegment.data) {
        var _initSegment$decryptd;
        var initPromise = initSegment.encrypted && !((_initSegment$decryptd = initSegment.decryptdata) != null && _initSegment$decryptd.key) ? this.keyLoader.load(initSegment).then(function () {
          return _this5._loadInitSegment(initSegment);
        }) : this._loadInitSegment(initSegment);
        return initPromise.catch(function (reason) {
          if (_this5.state === State.STOPPED || _this5.state === State.ERROR) {
            throw reason;
          }
          if ('data' in reason) {
            reason.data.frag = mediaFrag;
            _this5.fragmentLoader.abort();
            _this5.handleFragLoadError(reason);
          }
          _this5.resetFragmentLoading(mediaFrag);
          throw reason;
        });
      }
    };
    _proto.unhandledEncryptionError = function unhandledEncryptionError(initSegment, frag) {
      var _tracks$audio, _tracks$video;
      var tracks = initSegment.tracks;
      if (tracks && !frag.encrypted && ((_tracks$audio = tracks.audio) != null && _tracks$audio.encrypted || (_tracks$video = tracks.video) != null && _tracks$video.encrypted) && (!this.config.emeEnabled || !this.keyLoader.emeController)) {
        var media = this.media;
        var error = new Error('EME not supported (light build)');
        if (!media || media.mediaKeys) {
          return false;
        }
        this.hls.trigger(Events.ERROR, {
          type: ErrorTypes.KEY_SYSTEM_ERROR,
          details: ErrorDetails.KEY_SYSTEM_NO_KEYS,
          fatal: true,
          error: error,
          frag: frag
        });
        this.resetTransmuxer();
        return true;
      }
      return false;
    };
    _proto.fragContextChanged = function fragContextChanged(frag) {
      var fragCurrent = this.fragCurrent;
      return !frag || !fragmentsAreEqual(frag, fragCurrent);
    };
    _proto.fragBufferedComplete = function fragBufferedComplete(frag, part) {
      var media = this.mediaBuffer ? this.mediaBuffer : this.media;
      this.log("Buffered " + frag.type + " sn: " + frag.sn + (part ? ' part: ' + part.index : '') + " of " + this.fragInfo(frag, false, part) + " > buffer:" + (media ? timeRangesToString(BufferHelper.getBuffered(media)) : '(detached)') + ")");
      if (isMediaFragment(frag)) {
        var _this$levels;
        if (frag.type !== PlaylistLevelType.SUBTITLE) {
          var el = frag.elementaryStreams;
          if (!Object.keys(el).some(function (type) {
            return !!el[type];
          })) {
            this.state = State.IDLE;
            return;
          }
        }
        var level = (_this$levels = this.levels) == null ? void 0 : _this$levels[frag.level];
        if (level != null && level.fragmentError && (part || this.fragmentTracker.getState(frag) !== FragmentState.PARTIAL)) {
          level.fragmentError = 0;
          frag.stats.retry = 0;
        }
      }
      this.state = State.IDLE;
    };
    _proto._handleFragmentLoadComplete = function _handleFragmentLoadComplete(fragLoadedEndData) {
      var transmuxer = this.transmuxer;
      if (!transmuxer) {
        return;
      }
      var frag = fragLoadedEndData.frag;
      var part = fragLoadedEndData.part,
        partsLoaded = fragLoadedEndData.partsLoaded;
      var complete = !partsLoaded || partsLoaded.length === 0 || partsLoaded.some(function (fragLoaded) {
        return !fragLoaded;
      });
      var _getAESAdjustments2 = getAESAdjustments(frag, part),
        decryptRange = _getAESAdjustments2.decryptRange;
      var chunkMeta = new ChunkMetadata(frag.level, frag.sn, frag.stats.chunkCount + 1, 0, part ? part.index : -1, !complete, frag.duration, this.iframesOnly, decryptRange);
      transmuxer.flush(chunkMeta);
    };
    _proto._handleFragmentLoadProgress = function _handleFragmentLoadProgress(frag) {};
    _proto.loadKeyFor = function loadKeyFor(frag, details, initDataPromise) {
      var _frag$decryptdata,
        _this6 = this;
      var keyLoadingPromise = null;
      if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) {
        this.state = State.KEY_LOADING;
        keyLoadingPromise = this.keyLoader.load(frag, initDataPromise).then(function (keyLoadedData) {
          if (!_this6.fragContextChanged(keyLoadedData.frag)) {
            _this6.hls.trigger(Events.KEY_LOADED, keyLoadedData);
            if (_this6.state === State.KEY_LOADING) {
              _this6.state = State.IDLE;
            }
            return keyLoadedData;
          }
        });
        this.hls.trigger(Events.KEY_LOADING, {
          frag: frag
        });
      } else if (!frag.encrypted) {
        keyLoadingPromise = this.keyLoader.loadClear(frag, details.encryptedFragments, this.startFragRequested);
        if (keyLoadingPromise) {
        }
      }
      return keyLoadingPromise;
    };
    _proto._doFragLoad = function _doFragLoad(frag, level, targetBufferTime, progressCallback) {
      var _this7 = this;
      if (targetBufferTime === void 0) {
        targetBufferTime = null;
      }
      this.fragCurrent = frag;
      var details = level.details;
      if (!this.levels || !details) {
        throw new Error("frag load aborted, missing level" + (details ? '' : ' detail') + "s");
      }
      var fragPrevious = this.fragPrevious;
      if (!mediaFragmentsAreEqual(frag, fragPrevious)) {
        var shouldLoadParts = this.shouldLoadParts(level.details, frag.end);
        if (shouldLoadParts !== this.loadingParts) {
          this.loadingParts = shouldLoadParts;
        }
      }
      targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
      if (this.loadingParts) {
        var partList = details.partList;
        if (partList && progressCallback) {
          if (targetBufferTime > details.fragmentEnd && details.fragmentHint) {
            frag = details.fragmentHint;
          }
          var partIndex = this.getNextPart(partList, frag, targetBufferTime);
          if (partIndex > -1) {
            var part = partList[partIndex];
            frag = this.fragCurrent = part.fragment;
            if (!mediaFragmentsAreEqual(frag, fragPrevious) && !this.shouldLoadParts(level.details, frag.end)) {
              this.loadingParts = false;
              return Promise.resolve(null);
            }
            if (!this.filterReplacedPrimary(part, level.details)) {
              return Promise.resolve(null);
            }
            var _keyLoadingPromise = this.loadKeyFor(frag, details);
            if (this.fragContextChanged(frag)) {
              return Promise.resolve(null);
            }
            this.nextLoadPosition = part.start + part.duration;
            this.state = State.FRAG_LOADING;
            var _result;
            var _initDataPromise = this.loadInitSegmentIfNeeded(frag);
            if (_keyLoadingPromise || _initDataPromise) {
              var useKeyLoader = !!_keyLoadingPromise;
              _result = Promise.all([_keyLoadingPromise, _initDataPromise]).then(function (_ref) {
                var keyLoadedData = _ref[0];
                if (useKeyLoader && (!keyLoadedData || _this7.fragContextChanged(keyLoadedData.frag))) {
                  return null;
                }
                return _this7.doFragPartsLoad(frag, part, level, progressCallback);
              }).catch(function (error) {
                return _this7.handleFragLoadError(error);
              });
            } else {
              _result = this.doFragPartsLoad(frag, part, level, progressCallback).catch(function (error) {
                return _this7.handleFragLoadError(error);
              });
            }
            this.hls.trigger(Events.FRAG_LOADING, {
              frag: frag,
              part: part,
              targetBufferTime: targetBufferTime
            });
            if (this.fragCurrent === null) {
              return Promise.reject(new Error("frag load aborted, context changed in FRAG_LOADING parts"));
            }
            return _result;
          } else if (!frag.url || this.loadedEndOfParts(partList, targetBufferTime)) {
            return Promise.resolve(null);
          }
        }
      }
      if (this.loadingParts) {
        var _details$partList;
        this.loadingParts = false;
      } else if (!frag.url) {
        return Promise.resolve(null);
      }
      if (!this.filterReplacedPrimary(frag, level.details)) {
        return Promise.resolve(null);
      }
      var initDataPromise = this.loadInitSegmentIfNeeded(frag);
      var keyLoadingPromise = this.loadKeyFor(frag, details, initDataPromise);
      if (this.fragContextChanged(frag)) {
        var _this$fragCurrent;
        initDataPromise == null || initDataPromise.catch(function () {
          return null;
        });
        keyLoadingPromise == null || keyLoadingPromise.catch(function () {
          return null;
        });
        return Promise.resolve(null);
      }
      if (isFiniteNumber(frag.sn) && !this.bitrateTest) {
        this.nextLoadPosition = frag.start + frag.duration;
      }
      this.state = State.FRAG_LOADING;

      var dataOnProgress = this.config.progressive && frag.type !== PlaylistLevelType.SUBTITLE;
      var result;
      if (dataOnProgress && keyLoadingPromise) {
        result = keyLoadingPromise.then(function (keyLoadedData) {
          if (!keyLoadedData || _this7.fragContextChanged(keyLoadedData.frag)) {
            return null;
          }
          return _this7.fragmentLoader.load(frag, _this7.iframesOnly, progressCallback, initDataPromise);
        }).catch(function (error) {
          return _this7.handleFragLoadError(error);
        });
      } else {
        result = Promise.all([this.fragmentLoader.load(frag, this.iframesOnly, dataOnProgress ? progressCallback : undefined, dataOnProgress ? initDataPromise : undefined), keyLoadingPromise, initDataPromise]).then(function (_ref2) {
          var fragLoadedData = _ref2[0];
          if (!dataOnProgress && progressCallback) {
            progressCallback(fragLoadedData);
          }
          return fragLoadedData;
        }).catch(function (error) {
          return _this7.handleFragLoadError(error);
        });
      }
      this.hls.trigger(Events.FRAG_LOADING, {
        frag: frag,
        targetBufferTime: targetBufferTime
      });
      if (this.fragCurrent === null) {
        return Promise.reject(new Error("frag load aborted, context changed in FRAG_LOADING"));
      }
      return Promise.all([result, initDataPromise]).then(function (_ref3) {
        var fragData = _ref3[0];
        return fragData;
      });
    };
    _proto.doFragPartsLoad = function doFragPartsLoad(frag, fromPart, level, progressCallback) {
      var _this8 = this;
      return new Promise(function (resolve, reject) {
        var _level$details;
        var partsLoaded = [];
        var initialPartList = (_level$details = level.details) == null ? void 0 : _level$details.partList;
        var _loadPart = function loadPart(part) {
          _this8.fragmentLoader.loadPart(frag, part, progressCallback).then(function (partLoadedData) {
            partsLoaded[part.index] = partLoadedData;
            var loadedPart = partLoadedData.part;
            _this8.hls.trigger(Events.FRAG_LOADED, partLoadedData);
            var nextPart = getPartWith(level.details, frag.sn, part.index + 1) || findPart(initialPartList, frag.sn, part.index + 1);
            if (nextPart) {
              _loadPart(nextPart);
            } else {
              return resolve({
                frag: frag,
                part: loadedPart,
                partsLoaded: partsLoaded
              });
            }
          }).catch(reject);
        };
        _loadPart(fromPart);
      });
    };
    _proto.handleFragLoadError = function handleFragLoadError(error) {
      if ('data' in error) {
        var data = error.data;
        if (data.frag && data.details === ErrorDetails.INTERNAL_ABORTED) {
          this.handleFragLoadAborted(data.frag, data.part);
        } else if (data.frag && data.type === ErrorTypes.KEY_SYSTEM_ERROR && !data.fatal) {
          data.frag.abortRequests();
          this.resetStartWhenNotLoaded();
          this.resetFragmentLoading(data.frag);
        }
        this.hls.trigger(Events.ERROR, data);
      } else {
        this.hls.trigger(Events.ERROR, {
          type: ErrorTypes.OTHER_ERROR,
          details: ErrorDetails.INTERNAL_EXCEPTION,
          err: error,
          error: error,
          fatal: true
        });
      }
      return null;
    };
    _proto._handleTransmuxerFlush = function _handleTransmuxerFlush(chunkMeta) {
      var context = this.getCurrentContext(chunkMeta);
      if (!context || this.state !== State.PARSING) {
        if (!this.fragCurrent && this.state !== State.STOPPED && this.state !== State.ERROR) {
          this.state = State.IDLE;
        }
        return;
      }
      var frag = context.frag,
        part = context.part,
        level = context.level;
      var now = self.performance.now();
      frag.stats.parsing.end = now;
      if (part) {
        part.stats.parsing.end = now;
      }
      var levelDetails = this.getLevelDetails();
      var loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
      var shouldLoadParts = loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
      if (shouldLoadParts !== this.loadingParts) {
        this.loadingParts = shouldLoadParts;
      }
      this.updateLevelTiming(frag, part, level, chunkMeta);
    };
    _proto.shouldLoadParts = function shouldLoadParts(details, bufferEnd) {
      if (this.config.lowLatencyMode) {
        if (!details) {
          return this.loadingParts;
        }
        if (details.partList && !this.iframesOnly) {
          var _details$fragmentHint;
          var firstPart = details.partList[0];
          var safePartStart = firstPart.end + (((_details$fragmentHint = details.fragmentHint) == null ? void 0 : _details$fragmentHint.duration) || 0);
          if (bufferEnd >= safePartStart) {
            var playhead = this.playhead;
            if (playhead > firstPart.start - firstPart.fragment.duration) {
              return true;
            }
          }
        }
      }
      return false;
    };
    _proto.getCurrentContext = function getCurrentContext(chunkMeta) {
      var levels = this.levels,
        fragCurrent = this.fragCurrent;
      var levelIndex = chunkMeta.level,
        sn = chunkMeta.sn,
        partIndex = chunkMeta.part;
      if (!(levels != null && levels[levelIndex])) {
        return null;
      }
      var level = levels[levelIndex];
      var levelDetails = level.details;
      var part = partIndex > -1 ? getPartWith(levelDetails, sn, partIndex) : null;
      var frag = part ? part.fragment : getFragmentWithSN(levelDetails, sn, fragCurrent);
      if (!frag) {
        return null;
      }
      if (fragCurrent && fragCurrent !== frag) {
        frag.stats = fragCurrent.stats;
      }
      return {
        frag: frag,
        part: part,
        level: level
      };
    };
    _proto.bufferFragmentData = function bufferFragmentData(data, frag, part, chunkMeta, noBacktracking) {
      if (this.state !== State.PARSING) {
        return;
      }
      if (isMediaFragment(frag) && !this.fragContextChanged(frag)) {
        this.fragPrevious = frag;
      }
      var data1 = data.data1,
        data2 = data.data2;
      var buffer = data1;
      if (data2) {
        buffer = appendUint8Array(data1, data2);
      }
      if (!buffer.length) {
        return;
      }
      var offsetTimestamp = this.initPTS[frag.cc];
      var offset = offsetTimestamp ? -offsetTimestamp.baseTime / offsetTimestamp.timescale : undefined;
      var segment = {
        type: data.type,
        frag: frag,
        part: part,
        chunkMeta: chunkMeta,
        offset: offset,
        parent: frag.type,
        data: buffer
      };
      this.hls.trigger(Events.BUFFER_APPENDING, segment);
      if (data.dropped && data.independent && !part) {
        if (noBacktracking) {
          return;
        }
        this.flushBufferGap(frag);
      }
    };
    _proto.flushBufferGap = function flushBufferGap(frag) {
      var media = this.media;
      if (!media) {
        return;
      }
      if (!BufferHelper.isBuffered(media, media.currentTime)) {
        this.flushMainBuffer(0, frag.start);
        return;
      }
      var currentTime = media.currentTime;
      var bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0);
      var fragDuration = frag.duration;
      var segmentFraction = Math.min(this.config.maxFragLookUpTolerance * 2, fragDuration * 0.25);
      var start = Math.max(Math.min(frag.start - segmentFraction, bufferInfo.end - segmentFraction), currentTime + segmentFraction);
      if (frag.start - start > segmentFraction) {
        this.flushMainBuffer(start, frag.start);
      }
    };
    _proto.getFwdBufferInfo = function getFwdBufferInfo(bufferable, type) {
      var _this$media;
      var pos = this.playhead;
      var backwardSeek = this.lastCurrentTime > pos;
      var maxBufferHole = backwardSeek || (_this$media = this.media) != null && _this$media.paused ? 0 : this.config.maxBufferHole;
      return this.getFwdBufferInfoAtPos(bufferable, pos, type, maxBufferHole);
    };
    _proto.getFwdBufferInfoAtPos = function getFwdBufferInfoAtPos(bufferable, pos, type, maxBufferHole) {
      var bufferInfo = BufferHelper.bufferInfo(bufferable, pos, maxBufferHole);
      if (bufferInfo.len === 0 && bufferInfo.nextStart !== undefined) {
        var bufferedFragAtPos = this.fragmentTracker.getBufferedFrag(pos, type);
        if (bufferedFragAtPos && (bufferInfo.nextStart <= bufferedFragAtPos.end || bufferedFragAtPos.gap)) {
          var gapDuration = Math.max(Math.min(bufferInfo.nextStart, bufferedFragAtPos.end) - pos, maxBufferHole);
          return BufferHelper.bufferInfo(bufferable, pos, gapDuration);
        }
      }
      return bufferInfo;
    };
    _proto.getMaxBufferLength = function getMaxBufferLength(levelBitrate) {
      var config = this.config;
      var maxBufLen;
      if (levelBitrate) {
        maxBufLen = Math.max(8 * config.maxBufferSize / levelBitrate, config.maxBufferLength);
      } else {
        maxBufLen = config.maxBufferLength;
      }
      return Math.min(maxBufLen, config.maxMaxBufferLength);
    };
    _proto.exceedsMaxBuffer = function exceedsMaxBuffer(bufferInfo, maxBufLen, selected) {
      var nextStart = bufferInfo.nextStart;
      if (nextStart && selected.start > nextStart) {
        var bufferedRanges = bufferInfo.buffered;
        if (bufferedRanges) {
          var fullBufferLength = bufferInfo.len;
          var bufferedIndex = bufferInfo.bufferedIndex;
          for (var i = bufferedRanges.length - 1; i > bufferedIndex; i--) {
            if (bufferedRanges[i].start < selected.start) {
              fullBufferLength += bufferedRanges[i].end - bufferedRanges[i].start;
            }
          }
          return fullBufferLength >= maxBufLen;
        }
      }
      return false;
    };
    _proto.reduceMaxBufferLength = function reduceMaxBufferLength(threshold, fragDuration) {
      var config = this.config;
      var minLength = Math.max(Math.min(threshold - fragDuration, config.maxBufferLength), fragDuration / 2);
      var reducedLength = Math.max(threshold - fragDuration * 3, config.maxMaxBufferLength / 2, minLength);
      if (reducedLength >= minLength) {
        config.maxMaxBufferLength = reducedLength;
        return true;
      }
      return false;
    };
    _proto.getAppendedFrag = function getAppendedFrag(position) {
      var fragOrPart = this.fragmentTracker ? this.fragmentTracker.getAppendedFrag(position, this.playlistType) : null;
      if (fragOrPart && 'fragment' in fragOrPart) {
        return fragOrPart.fragment;
      }
      return fragOrPart;
    };
    _proto.getNextFragment = function getNextFragment(pos, levelDetails) {
      var fragments = levelDetails.fragments;
      var fragLen = fragments.length;
      if (!fragLen) {
        return null;
      }

      var config = this.config;
      var playlistStart = levelDetails.fragmentStart;
      var canLoadParts = config.lowLatencyMode && !!levelDetails.partList;
      var frag = null;
      if (levelDetails.live) {
        var initialLiveManifestSize = config.initialLiveManifestSize;
        if (fragLen < initialLiveManifestSize) {
          return null;
        }
        if (!this.startFragRequested && canLoadParts && !this.loadingParts) {
          this.loadingParts = true;
        }
        if (!levelDetails.PTSKnown && !this.startFragRequested && this.startPosition === -1 || pos < playlistStart) {
          var _frag;
          frag = this.getInitialLiveFragment(levelDetails);
          var configValue = this.config.startPosition;
          var mainStart = this.hls.startPosition;
          var liveSyncPosition = this.hls.liveSyncPosition;
          var fragStart = ((_frag = frag) == null ? void 0 : _frag.start) || 0;
          var startPosition;
          var reason;
          if (mainStart !== -1 && mainStart >= playlistStart) {
            startPosition = mainStart;
            reason = mainStart === configValue ? 'config' : 'next load start';
          } else if (liveSyncPosition) {
            startPosition = liveSyncPosition;
            reason = 'live edge';
          } else {
            startPosition = pos;
            reason = 'buffer pos';
          }
          if (startPosition < fragStart) {
            startPosition = fragStart;
            reason = 'live frag start';
          }
          if (startPosition < playlistStart) {
            startPosition = playlistStart;
            reason = 'playlist start';
          }
          if (this.startPosition != startPosition || this.nextLoadPosition != startPosition) {
            var liveDetached = levelDetails.live && !this.filterReplacedPrimary(frag, levelDetails);
            var tmpStartPosition = liveDetached ? -1 : startPosition;
            this.startPosition = tmpStartPosition;
            this.nextLoadPosition = startPosition;
          }
        }
      } else if (pos <= playlistStart) {
        frag = fragments[0];
      }

      if (!frag) {
        var end = this.loadingParts ? levelDetails.partEnd : levelDetails.fragmentEnd;
        frag = this.getFragmentAtPosition(pos, end, levelDetails);
      }
      var programFrag = this.filterReplacedPrimary(frag, levelDetails);
      if (!programFrag && frag) {
        programFrag = getNextFrag(levelDetails, frag.sn);
        if (programFrag) {
          this.nextLoadPosition = programFrag.start;
        }
      }
      return programFrag;
    };
    _proto.isLoopLoading = function isLoopLoading(frag, targetBufferTime) {
      if (this.nextLoadPosition <= targetBufferTime) {
        return false;
      }
      var trackerState = this.fragmentTracker.getState(frag);
      if (trackerState === FragmentState.OK) {
        return true;
      }
      if (trackerState === FragmentState.PARTIAL) {
        if (frag.gap) {
          return true;
        }
        if (frag.stats.retry > 1) {
          return true;
        }
      }
      return false;
    };
    _proto.getNextFragmentLoopLoading = function getNextFragmentLoopLoading(frag, levelDetails, bufferInfo, playlistType, maxBufLen) {
      var nextFragment = null;
      if (frag.gap) {
        nextFragment = this.getNextFragment(this.nextLoadPosition, levelDetails);
        if (nextFragment && !nextFragment.gap && bufferInfo.nextStart) {
          var nextbufferInfo = this.getFwdBufferInfoAtPos(this.mediaBuffer ? this.mediaBuffer : this.media, bufferInfo.nextStart, playlistType, 0);
          if (nextbufferInfo !== null && bufferInfo.len + nextbufferInfo.len >= maxBufLen) {
            var sn = nextFragment.sn;
            if (this.loopSn !== sn) {
              this.loopSn = sn;
            }
            return null;
          }
        }
      }
      this.loopSn = undefined;
      return nextFragment;
    };
    _proto.filterReplacedPrimary = function filterReplacedPrimary(frag, details) {
      if (!frag) {
        return frag;
      }
      if (interstitialsEnabled(this.config)) ;
      return frag;
    };
    _proto.getNextPart = function getNextPart(partList, frag, targetBufferTime) {
      var nextPart = -1;
      var contiguous = false;
      var independentAttrOmitted = true;
      for (var i = 0, len = partList.length; i < len; i++) {
        var _part = partList[i];
        independentAttrOmitted = independentAttrOmitted && !_part.independent;
        if (nextPart > -1 && targetBufferTime < _part.start) {
          break;
        }
        var loaded = _part.loaded || _part.gap;
        if (loaded) {
          nextPart = -1;
        } else if (contiguous || (_part.independent || independentAttrOmitted) && _part.fragment === frag) {
          nextPart = i;
        }
        contiguous = loaded && !_part.gap;
      }
      var part = partList[nextPart];
      if (part && part.fragment !== frag) {
      }
      return nextPart;
    };
    _proto.loadedEndOfParts = function loadedEndOfParts(partList, targetBufferTime) {
      var part;
      for (var i = partList.length; i--;) {
        part = partList[i];
        if (!part.loaded) {
          return false;
        }
        if (targetBufferTime > part.start) {
          return true;
        }
      }
      return false;
    }

;
    _proto.getInitialLiveFragment = function getInitialLiveFragment(levelDetails) {
      var fragments = levelDetails.fragments;
      var fragPrevious = this.fragPrevious;
      var frag = null;
      if (fragPrevious) {
        if (levelDetails.hasProgramDateTime) {
          frag = findFragmentByPDT(fragments, fragPrevious.endProgramDateTime, this.config.maxFragLookUpTolerance);
          if (frag) {
          }
        }
        if (!frag) {
          var targetSN = fragPrevious.sn + 1;
          if (targetSN >= levelDetails.startSN && targetSN <= levelDetails.endSN) {
            var fragNext = fragments[targetSN - levelDetails.startSN];
            if (fragPrevious.cc === fragNext.cc) {
              frag = fragNext;
            }
          }
          if (!frag) {
            frag = findNearestWithCC(levelDetails, fragPrevious.cc, fragPrevious.end);
            if (frag) {
            }
          }
        }
      } else {
        var liveStart = this.hls.liveSyncPosition;
        if (liveStart !== null) {
          frag = this.getFragmentAtPosition(liveStart, this.bitrateTest ? levelDetails.fragmentEnd : levelDetails.edge, levelDetails);
        }
      }
      return frag;
    }

;
    _proto.getFragmentAtPosition = function getFragmentAtPosition(bufferEnd, end, levelDetails) {
      var config = this.config;
      var fragPrevious = this.fragPrevious;
      var fragments = levelDetails.fragments,
        endSN = levelDetails.endSN;
      var fragmentHint = levelDetails.fragmentHint;
      var maxFragLookUpTolerance = config.maxFragLookUpTolerance;
      var partList = levelDetails.partList;
      var loadingParts = !!(this.loadingParts && partList != null && partList.length && fragmentHint);
      if (loadingParts && !this.bitrateTest && partList[partList.length - 1].fragment.sn === fragmentHint.sn) {
        fragments = fragments.concat(fragmentHint);
        endSN = fragmentHint.sn;
      }

      if (fragPrevious && this.fragmentTracker.getState(fragPrevious) === FragmentState.NOT_LOADED) {
        fragPrevious = null;
      }
      var frag;
      if (bufferEnd < end) {
        var _this$media2;
        var backwardSeek = bufferEnd < this.lastCurrentTime;
        var lookupTolerance = backwardSeek || bufferEnd > end - maxFragLookUpTolerance || (_this$media2 = this.media) != null && _this$media2.paused || !this.startFragRequested ? 0 : maxFragLookUpTolerance;
        frag = findFragmentByPTS(fragPrevious, fragments, bufferEnd, lookupTolerance);
      } else {
        frag = fragments[fragments.length - 1];
      }
      if (frag) {
        var curSNIdx = frag.sn - levelDetails.startSN;
        var fragState = this.fragmentTracker.getState(frag);
        if (fragState === FragmentState.OK || fragState === FragmentState.PARTIAL && frag.gap) {
          fragPrevious = frag;
        }
        if (mediaFragmentsAreEqual(frag, fragPrevious) && (!loadingParts || partList[0].fragment.sn > frag.sn || !levelDetails.live)) {
          var nextFrag = fragments[curSNIdx + 1];
          if (frag.sn < endSN && this.fragmentTracker.getState(nextFrag) !== FragmentState.OK) {
            frag = nextFrag;
          } else {
            frag = null;
          }
        }
      }
      return frag;
    };
    _proto.alignPlaylists = function alignPlaylists(details, previousDetails, switchDetails) {
      var length = details.fragments.length;
      if (!length) {
        return 0;
      }
      var slidingStart = details.fragmentStart;
      var firstLevelLoad = !previousDetails;
      var aligned = details.alignedSliding && isFiniteNumber(slidingStart);
      if (firstLevelLoad || !aligned && !slidingStart) {
        alignStream(switchDetails, details, this);
        var alignedSlidingStart = details.fragmentStart;
        return alignedSlidingStart;
      }
      return slidingStart;
    };
    _proto.waitForCdnTuneIn = function waitForCdnTuneIn(details) {
      var advancePartLimit = 3;
      return details.live && details.canBlockReload && details.partTarget && details.tuneInGoal > Math.max(details.partHoldBack, details.partTarget * advancePartLimit);
    };
    _proto.setStartPosition = function setStartPosition(details, sliding) {
      var startPosition = this.startPosition;
      if (startPosition < sliding) {
        startPosition = -1;
      }
      var timelineOffset = this.timelineOffset;
      if (startPosition === -1) {
        var offsetInMultivariantPlaylist = this.startTimeOffset !== null;
        var startTimeOffset = offsetInMultivariantPlaylist ? this.startTimeOffset : details.startTimeOffset;
        if (startTimeOffset !== null && isFiniteNumber(startTimeOffset)) {
          startPosition = sliding + startTimeOffset;
          if (startTimeOffset < 0) {
            startPosition += details.edge;
          }
          startPosition = Math.min(Math.max(sliding, startPosition), sliding + details.totalduration);
          this.startPosition = startPosition;
        } else if (details.live) {
          var liveSyncPosition = this.hls.liveSyncPosition;
          startPosition = liveSyncPosition || sliding;
          this.startPosition = -1;
        } else {
          this.startPosition = startPosition = 0;
        }
        this.lastCurrentTime = startPosition + timelineOffset;
      }
      this.nextLoadPosition = startPosition + timelineOffset;
    };
    _proto.getLoadPosition = function getLoadPosition() {
      var _this$hls;
      var media = this.media;
      var pos = 0;
      if ((_this$hls = this.hls) != null && _this$hls.hasEnoughToStart && media && !this.iframesOnly) {
        pos = media.currentTime;
      } else if (this.nextLoadPosition >= 0) {
        pos = this.nextLoadPosition;
      }
      return pos;
    };
    _proto.handleFragLoadAborted = function handleFragLoadAborted(frag, part) {
      if (this.transmuxer && frag.type === this.playlistType && isMediaFragment(frag) && frag.stats.aborted) {
        this.resetFragmentLoading(frag);
      }
    };
    _proto.resetFragmentLoading = function resetFragmentLoading(frag) {
      if (!this.fragCurrent || !this.fragContextChanged(frag) && this.state !== State.FRAG_LOADING_WAITING_RETRY) {
        this.state = State.IDLE;
      }
    };
    _proto.onFragmentOrKeyLoadError = function onFragmentOrKeyLoadError(filterType, data) {
      if (data.chunkMeta && !data.frag) {
        var context = this.getCurrentContext(data.chunkMeta);
        if (context) {
          data.frag = context.frag;
        }
      }
      var frag = data.frag,
        part = data.part;
      if (!frag || !this.levels || frag.type !== filterType) {
        return;
      }
      if (this.fragContextChanged(frag)) {
        var _this$fragCurrent2;
        return;
      }
      var gapTagEncountered = data.details === ErrorDetails.FRAG_GAP;
      if (gapTagEncountered) {
        this.fragmentTracker.addAsGap(frag);
      }
      var errorAction = data.errorAction;
      if (!errorAction) {
        this.state = State.ERROR;
        return;
      }
      var action = errorAction.action,
        flags = errorAction.flags,
        _errorAction$retryCou = errorAction.retryCount,
        retryCount = _errorAction$retryCou === void 0 ? 0 : _errorAction$retryCou,
        retryConfig = errorAction.retryConfig;
      var couldRetry = !!retryConfig;
      var retry = couldRetry && action === NetworkErrorAction.RetryRequest;
      var noAlternate = couldRetry && !errorAction.resolved && flags & ErrorActionFlags.MoveAllAlternatesMatchingHost;
      var levelDetails = this.hls.latestLevelDetails;
      var live = levelDetails == null ? void 0 : levelDetails.live;
      if (!retry && noAlternate && isMediaFragment(frag) && live && frag.sn < levelDetails.endSN && !isUnusableKeyError(data)) {
        this.resetFragmentErrors(filterType);
        if (part) {
          part.gap = true;
        } else {
          this.treatAsGap(frag);
        }
        errorAction.resolved = true;
      } else if ((retry || noAlternate) && retryCount < retryConfig.maxNumRetry) {
        var _data$response;
        var offlineStatus = offlineHttpStatus((_data$response = data.response) == null ? void 0 : _data$response.code);
        var delay = getRetryDelay(retryConfig, retryCount);
        this.resetStartWhenNotLoaded();
        this.retryDate = self.performance.now() + delay;
        this.state = State.FRAG_LOADING_WAITING_RETRY;
        errorAction.resolved = true;
        if (offlineStatus) {
          this.retryDate = Infinity;
          data.reason = 'offline';
          return;
        }
      } else if (retryConfig) {
        this.resetFragmentErrors(filterType);
        if (retryCount < retryConfig.maxNumRetry) {
          if (!gapTagEncountered && action !== NetworkErrorAction.RemoveAlternatePermanently) {
            errorAction.resolved = true;
          }
        } else {
          return;
        }
      } else if (action === NetworkErrorAction.SendAlternateToPenaltyBox) {
        this.state = State.WAITING_LEVEL;
      } else {
        this.state = State.ERROR;
      }
      this.tickImmediate();
    };
    _proto.checkRetryDate = function checkRetryDate() {
      var now = self.performance.now();
      var retryDate = this.retryDate;
      var waitingForConnection = retryDate === Infinity;
      if (!retryDate || now >= retryDate || waitingForConnection && !offlineHttpStatus(0)) {
        if (waitingForConnection) {
        }
        this.resetStartWhenNotLoaded();
        this.state = State.IDLE;
      }
    };
    _proto.reduceLengthAndFlushBuffer = function reduceLengthAndFlushBuffer(data) {
      if (this.state === State.PARSING || this.state === State.PARSED) {
        var frag = data.frag;
        var playlistType = data.parent;
        var bufferedInfo = this.getFwdBufferInfo(this.mediaBuffer, playlistType);
        var buffered = bufferedInfo && bufferedInfo.len > 0.5;
        if (buffered) {
          this.reduceMaxBufferLength(bufferedInfo.len, (frag == null ? void 0 : frag.duration) || 10);
        }
        var flushBuffer = !buffered;
        if (flushBuffer) {
        } else if (bufferedInfo.nextStart && frag && bufferedInfo.nextStart > frag.start) {
          this.flushMainBuffer(bufferedInfo.nextStart, Number.POSITIVE_INFINITY, playlistType === PlaylistLevelType.AUDIO ? playlistType : undefined);
        }
        if (frag) {
          this.fragmentTracker.removeFragment(frag);
          this.nextLoadPosition = frag.start;
        }
        this.resetLoadingState();
        return flushBuffer;
      }
      return false;
    };
    _proto.resetFragmentErrors = function resetFragmentErrors(filterType) {
      if (filterType === PlaylistLevelType.AUDIO) {
        this.fragCurrent = null;
      }
      if (!this.hls.hasEnoughToStart) {
        this.startFragRequested = false;
      }
      if (this.state !== State.STOPPED) {
        this.state = State.IDLE;
      }
    };
    _proto.afterBufferFlushed = function afterBufferFlushed(media, bufferType) {
      if (!media) {
        return;
      }
      var bufferedTimeRanges = BufferHelper.getBuffered(media);
      this.fragmentTracker.detectEvictedFragments(bufferType, bufferedTimeRanges, this.playlistType);
      if (this.state === State.ENDED) {
        this.resetLoadingState();
      }
    };
    _proto.resetLoadingState = function resetLoadingState() {
      this.fragCurrent = null;
      this.fragPrevious = null;
      if (this.state !== State.STOPPED) {
        this.state = State.IDLE;
      }
    };
    _proto.resetStartWhenNotLoaded = function resetStartWhenNotLoaded() {
      if (!this.hls.hasEnoughToStart) {
        this.startFragRequested = false;
        var details = this.getLevelDetails();
        if (details != null && details.live) {
          this.startPosition = -1;
          this.setStartPosition(details, details.fragmentStart);
          this.resetLoadingState();
        } else {
          this.nextLoadPosition = this.startPosition;
        }
      }
    };
    _proto.resetWhenMissingContext = function resetWhenMissingContext(chunkMeta) {
      this.removeUnbufferedFrags();
      this.resetStartWhenNotLoaded();
      this.resetLoadingState();
    };
    _proto.removeUnbufferedFrags = function removeUnbufferedFrags(start) {
      if (start === void 0) {
        start = 0;
      }
      this.fragmentTracker.removeFragmentsInRange(start, Infinity, this.playlistType, false, true);
    };
    _proto.updateLevelTiming = function updateLevelTiming(frag, part, level, chunkMeta) {
      var _this9 = this;
      var details = level.details;
      if (!details) {
        return;
      }
      var parsed = Object.keys(frag.elementaryStreams).reduce(function (result, type) {
        var info = frag.elementaryStreams[type];
        if (info) {
          var parsedDuration = info.endPTS - info.startPTS;
          if (parsedDuration <= 0) {
            return result || false;
          }
          var drift = chunkMeta.partial ? 0 : updateFragPTSDTS(details, frag, info.startPTS, info.endPTS, info.startDTS, info.endDTS, _this9.iframesOnly, _this9);
          _this9.hls.trigger(Events.LEVEL_PTS_UPDATED, {
            details: details,
            level: level,
            drift: drift,
            type: type,
            frag: frag,
            start: info.startPTS,
            end: info.endPTS
          });
          return true;
        }
        return result;
      }, false);
      if (!parsed) {
        var _this$transmuxer, _this$transmuxer2, _this$levels$length, _this$levels2;
        var mediaNotFound = ((_this$transmuxer = this.transmuxer) == null ? void 0 : _this$transmuxer.error) === null;
        var transmuxerError = ((_this$transmuxer2 = this.transmuxer) == null ? void 0 : _this$transmuxer2.error) != null;
        var hasAlternateLevels = ((_this$levels$length = (_this$levels2 = this.levels) == null ? void 0 : _this$levels2.length) != null ? _this$levels$length : 0) > 1;
        if (level.fragmentError === 0 || mediaNotFound && (level.fragmentError < 2 || frag.endList) || transmuxerError && hasAlternateLevels) {
          this.treatAsGap(frag, level);
        }
        if (mediaNotFound) {
          var error = new Error("Found no media in " + this.playlistLabel() + " " + frag.level + " " + (part ? "part: " + part.index + " of " : '') + "sn: " + frag.sn + " at playlist time: " + frag.start + ". Resetting transmuxer to fallback to playlist timing");
          this.hls.trigger(Events.ERROR, {
            type: ErrorTypes.MEDIA_ERROR,
            details: ErrorDetails.FRAG_PARSING_ERROR,
            fatal: false,
            error: error,
            frag: frag,
            reason: "Found no media in msn " + frag.sn + " of " + this.playlistLabel() + " \\"" + level.url + "\\""
          });
          if (!this.hls) {
            return;
          }
          this.resetTransmuxer();
        }
      }
      this.state = State.PARSED;
      this.hls.trigger(Events.FRAG_PARSED, {
        frag: frag,
        part: part,
        chunkMeta: chunkMeta
      });
    };
    _proto.playlistLabel = function playlistLabel() {
      return this.playlistType + " playlist";
    };
    _proto.fragInfo = function fragInfo(frag, pts, part) {
      var _ref4, _ref5;
      if (pts === void 0) {
        pts = true;
      }
      return this.playlistLabel() + " " + frag.level + " (" + (part ? 'part' : 'frag') + ":[" + ((_ref4 = pts && !part ? frag.startPTS : (part || frag).start) != null ? _ref4 : NaN) + "-" + ((_ref5 = pts && !part ? frag.endPTS : (part || frag).end) != null ? _ref5 : NaN) + "]" + (part && frag.type === 'main' ? 'INDEPENDENT=' + (part.independent ? 'YES' : 'NO') : '') + ")";
    };
    _proto.treatAsGap = function treatAsGap(frag, level) {
      if (level) {
        level.fragmentError++;
      }
      this.fragmentTracker.addAsGap(frag);
    };
    _proto.resetTransmuxer = function resetTransmuxer() {
      var _this$transmuxer3;
      (_this$transmuxer3 = this.transmuxer) == null || _this$transmuxer3.reset();
    };
    _proto.isFragmentNearlyDownloaded = function isFragmentNearlyDownloaded(fragment) {
      var _fragment$loader;
      var stats = (_fragment$loader = fragment.loader) == null ? void 0 : _fragment$loader.stats;
      if (!stats) {
        return false;
      }
      var hasFirstByte = stats.loading.first > 0;
      var bitsRemaining = stats.total - stats.loaded;
      var timeToCompleteFragDownload = bitsRemaining / (this.hls.bandwidthEstimate || this.hls.config.abrEwmaDefaultEstimate);

      return hasFirstByte && timeToCompleteFragDownload <= 0.15;
    };
    _proto.recoverWorkerError = function recoverWorkerError(data) {
      if (data.event === 'demuxerWorker') {
        this.fragmentTracker.removeAllFragments();
        if (this.transmuxer) {
          this.transmuxer.destroy();
          this.transmuxer = null;
        }
        this.resetStartWhenNotLoaded();
        this.resetLoadingState();
      }
    };
    _proto.calculateOptimalSwitchPoint = function calculateOptimalSwitchPoint(nextLevel, bufferInfo) {
      var fetchdelay = 0;
      var hls = this.hls,
        media = this.media,
        config = this.config,
        levels = this.levels,
        playlistType = this.playlistType;
      var levelDetails = this.getLevelDetails();
      if (media && !media.paused && levels) {
        var _this$fragCurrent3;
        var maxBitrate = playlistType === PlaylistLevelType.AUDIO ? emptyExports.estimatedAudioBitrate(nextLevel.audioCodec, 128000) : nextLevel.maxBitrate;
        var ttfbSec = 1 + hls.ttfbEstimate / 1000;
        var bandwidth = hls.bandwidthEstimate * config.abrBandWidthUpFactor;
        var fragDuration = levelDetails && (this.loadingParts ? levelDetails.partTarget : levelDetails.averagetargetduration) || ((_this$fragCurrent3 = this.fragCurrent) == null ? void 0 : _this$fragCurrent3.duration) || 6;
        fetchdelay = ttfbSec + maxBitrate * fragDuration / bandwidth;
        if (!nextLevel.details) {
          fetchdelay += ttfbSec;
        }
      }
      var currentTime = this.playhead;
      var okToFlushForwardBuffer = !(levelDetails != null && levelDetails.live) || bufferInfo.end - currentTime > fetchdelay * 1.5;
      return {
        fetchdelay: fetchdelay,
        okToFlushForwardBuffer: okToFlushForwardBuffer
      };
    }

;
    _proto.scheduleTrackSwitch = function scheduleTrackSwitch(bufferInfo, fetchdelay, okToFlushForwardBuffer) {
      var media = this.media,
        playlistType = this.playlistType;
      if (!media || !bufferInfo) {
        return;
      }

      var bufferedFrag = okToFlushForwardBuffer ? this.getBufferedFrag(this.playhead + fetchdelay) : null;
      if (bufferedFrag) {
        var nextBufferedFrag = this.followingBufferedFrag(bufferedFrag);
        if (nextBufferedFrag) {
          var _this$getLevelDetails;
          this.abortCurrentFrag();
          var maxStart = nextBufferedFrag.maxStartPTS ? nextBufferedFrag.maxStartPTS : nextBufferedFrag.start;
          var fragDuration = nextBufferedFrag.duration;
          var startOffset = Math.max(bufferedFrag.end, maxStart + Math.min(Math.max(fragDuration - this.config.maxFragLookUpTolerance, fragDuration * (this.couldBacktrack ? 0.5 : 0.125)), fragDuration * (this.couldBacktrack ? 0.75 : 0.25)));

          var liveWindowStart = ((_this$getLevelDetails = this.getLevelDetails()) == null ? void 0 : _this$getLevelDetails.fragmentStart) || 0;
          if (liveWindowStart > startOffset) {
            return;
          }
          var bufferType = playlistType === PlaylistLevelType.MAIN ? null : 'audio';
          this.flushMainBuffer(startOffset, Number.POSITIVE_INFINITY, bufferType);

          this.cleanupBackBuffer();
        }
      }
    }

;
    _proto.cleanupBackBuffer = function cleanupBackBuffer() {
      var media = this.media,
        playlistType = this.playlistType;
      if (!media) {
        return;
      }

      var fragPlayingCurrent = this.getAppendedFrag(this.playhead);
      if (fragPlayingCurrent && fragPlayingCurrent.start > 1) {
        var isAudio = playlistType === PlaylistLevelType.AUDIO;
        this.flushMainBuffer(0, fragPlayingCurrent.start - (isAudio ? 0 : 1), isAudio ? 'audio' : null);
      }
    }

;
    _proto.getBufferedFrag = function getBufferedFrag(position) {
      return this.fragmentTracker.getBufferedFrag(position, this.playlistType);
    }

;
    _proto.followingBufferedFrag = function followingBufferedFrag(frag) {
      if (frag) {
        return this.getBufferedFrag(frag.end + 0.5);
      }
      return null;
    }

;
    _proto.abortCurrentFrag = function abortCurrentFrag() {
      var fragCurrent = this.fragCurrent;
      this.fragCurrent = null;
      if (fragCurrent) {
        fragCurrent.abortRequests();
        this.fragmentTracker.removeFragment(fragCurrent);
      }
      switch (this.state) {
        case State.KEY_LOADING:
        case State.FRAG_LOADING:
        case State.FRAG_LOADING_WAITING_RETRY:
        case State.PARSING:
        case State.PARSED:
          this.state = State.IDLE;
          break;
      }
      this.nextLoadPosition = this.playhead;
    };
    _proto.checkFragPlaying = function checkFragPlaying() {
      var video = this.media;
      var fragPlayingCurrent = null;
      if (video && video.readyState > 1 && video.seeking === false) {
        var currentTime = video.currentTime;

        if (BufferHelper.isBuffered(video, currentTime)) {
          fragPlayingCurrent = this.getAppendedFrag(currentTime);
        } else if (BufferHelper.isBuffered(video, currentTime + 0.1)) {
          fragPlayingCurrent = this.getAppendedFrag(currentTime + 0.1);
        }
        if (fragPlayingCurrent) {
          this.backtrackFragment = undefined;
          if (!mediaFragmentsAreEqual(fragPlayingCurrent, this.fragPlaying)) {
            this.fragPlaying = fragPlayingCurrent;
            return true;
          }
        }
      }
      return false;
    };
    _proto.getBufferOutput = function getBufferOutput() {
      return null;
    }

;
    _proto.nextLevelSwitch = function nextLevelSwitch() {
      var levels = this.levels,
        media = this.media,
        hls = this.hls,
        config = this.config,
        playlistType = this.playlistType;
      if (media != null && media.readyState && levels && hls && config) {
        var bufferOutput = this.getBufferOutput();
        var bufferInfo = this.getFwdBufferInfo(bufferOutput, playlistType);
        if (!bufferInfo) {
          return;
        }
        var nextLevelId = playlistType === PlaylistLevelType.AUDIO ? hls.nextAudioTrack : hls.nextLoadLevel;
        var nextLevel = levels[nextLevelId];
        var _this$calculateOptima = this.calculateOptimalSwitchPoint(nextLevel, bufferInfo),
          fetchdelay = _this$calculateOptima.fetchdelay,
          okToFlushForwardBuffer = _this$calculateOptima.okToFlushForwardBuffer;
        this.scheduleTrackSwitch(bufferInfo, fetchdelay, okToFlushForwardBuffer);
      }
      this.tickImmediate();
    };
    return _createClass(BaseStreamController, [{
      key: "startPositionValue",
      get: function get() {
        var nextLoadPosition = this.nextLoadPosition,
          startPosition = this.startPosition;
        if (startPosition === -1 && nextLoadPosition) {
          var details = this.getLevelDetails();
          if (details != null && details.live) {
            var liveSyncPosition = this.hls.liveSyncPosition;
            var liveStart = liveSyncPosition || details.fragmentStart;
            return liveStart + this.timelineOffset;
          }
          return nextLoadPosition;
        }
        return startPosition;
      }
    }, {
      key: "bufferingEnabled",
      get: function get() {
        return this.buffering;
      }

    }, {
      key: "backtrackFragment",
      get: function get() {
        return undefined;
      }

,
      set: function set(_value) {}

    }, {
      key: "couldBacktrack",
      get: function get() {
        return false;
      }

,
      set: function set(_value) {}
    }, {
      key: "inFlightFrag",
      get: function get() {
        return {
          frag: this.fragCurrent,
          state: this.state
        };
      }
    }, {
      key: "timelineOffset",
      get: function get() {
        var configuredTimelineOffset = this.config.timelineOffset;
        if (configuredTimelineOffset) {
          var _this$getLevelDetails2;
          return ((_this$getLevelDetails2 = this.getLevelDetails()) == null ? void 0 : _this$getLevelDetails2.appliedTimelineOffset) || configuredTimelineOffset;
        }
        return 0;
      }
    }, {
      key: "primaryPrefetch",
      get: function get() {
        if (interstitialsEnabled(this.config)) ;
        return false;
      }
    }, {
      key: "playhead",
      get: function get() {
        var _this$hls2;
        if ((_this$hls2 = this.hls) != null && _this$hls2.hasEnoughToStart) {
          var _this$media3;
          var currentTime = (_this$media3 = this.media) == null ? void 0 : _this$media3.currentTime;
          if (isFiniteNumber(currentTime)) {
            return currentTime;
          }
          return this.lastCurrentTime;
        }
        return this.getLoadPosition();
      }
    }, {
      key: "iframesOnly",
      get: function get() {
        var _this$levelLastLoaded;
        if (this.playlistType !== PlaylistLevelType.MAIN) {
          return false;
        }
        var details = this.getLevelDetails();
        if (details) {
          return details.iframesOnly;
        }
        return (_this$levelLastLoaded = this.levelLastLoaded) == null ? void 0 : _this$levelLastLoaded.iframes;
      }
    }, {
      key: "state",
      get: function get() {
        return this._state;
      },
      set: function set(nextState) {
        var previousState = this._state;
        if (previousState !== nextState) {
          this._state = nextState;
        }
      }
    }]);
  }(TaskLoop);
  function interstitialsEnabled(config) {
    return false;
  }

  var MAX_START_GAP_JUMP = 2.0;
  var TICK_INTERVAL$1 = 100;
  var GapController = function (_TaskLoop) {
    function GapController(hls, fragmentTracker) {
      var _this;
      _this = _TaskLoop.call(this, 'gap-controller', hls.logger) || this;
      _this.hls = void 0;
      _this.fragmentTracker = void 0;
      _this.media = null;
      _this.mediaSource = void 0;
      _this.nudgeRetry = 0;
      _this.skipRetry = 0;
      _this.stallReported = false;
      _this.stalled = null;
      _this.moved = false;
      _this.seeking = false;
      _this.buffered = {};
      _this.lastCurrentTime = void 0;
      _this.ended = 0;
      _this.waiting = 0;
      _this.onMediaPlaying = function () {
        _this.ended = 0;
        _this.waiting = 0;
      };
      _this.onMediaWaiting = function () {
        var _this$media;
        if ((_this$media = _this.media) != null && _this$media.seeking) {
          return;
        }
        _this.waiting = self.performance.now();
        _this.tick();
      };
      _this.onMediaEnded = function () {
        if (_this.hls) {
          var _this$media2;
          _this.ended = ((_this$media2 = _this.media) == null ? void 0 : _this$media2.currentTime) || 1;
          _this.hls.trigger(Events.MEDIA_ENDED, {
            stalled: false
          });
        }
      };
      _this.hls = hls;
      _this.fragmentTracker = fragmentTracker;
      _this.lastCurrentTime = _this.getCurrentTime();
      _this.registerListeners();
      return _this;
    }
    _inheritsLoose(GapController, _TaskLoop);
    var _proto = GapController.prototype;
    _proto.registerListeners = function registerListeners() {
      var hls = this.hls;
      if (hls) {
        hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
        hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
        hls.on(Events.BUFFER_APPENDED, this.onBufferAppended, this);
      }
    };
    _proto.unregisterListeners = function unregisterListeners() {
      var hls = this.hls;
      if (hls) {
        hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
        hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
        hls.off(Events.BUFFER_APPENDED, this.onBufferAppended, this);
      }
    };
    _proto.destroy = function destroy() {
      _TaskLoop.prototype.destroy.call(this);
      this.unregisterListeners();
      this.media = this.hls = this.fragmentTracker = null;
      this.mediaSource = undefined;
    };
    _proto.onMediaAttached = function onMediaAttached(event, data) {
      this.setInterval(TICK_INTERVAL$1);
      this.mediaSource = data.mediaSource;
      var media = this.media = data.media;
      addEventListener(media, 'playing', this.onMediaPlaying);
      addEventListener(media, 'waiting', this.onMediaWaiting);
      addEventListener(media, 'ended', this.onMediaEnded);
    };
    _proto.onMediaDetaching = function onMediaDetaching(event, data) {
      this.clearInterval();
      var media = this.media;
      if (media) {
        removeEventListener(media, 'playing', this.onMediaPlaying);
        removeEventListener(media, 'waiting', this.onMediaWaiting);
        removeEventListener(media, 'ended', this.onMediaEnded);
        this.media = null;
      }
      this.mediaSource = undefined;
    };
    _proto.onBufferAppended = function onBufferAppended(event, data) {
      this.buffered = data.timeRanges;
    };
    _proto.getCurrentTime = function getCurrentTime() {
      var _this$hls;
      var timelineOffset = ((_this$hls = this.hls) == null ? void 0 : _this$hls.config.timelineOffset) || 0;
      if (this.media) {
        return Math.max(this.media.currentTime, timelineOffset);
      }
      return timelineOffset;
    };
    _proto.tick = function tick() {
      var _this$media3;
      if (!((_this$media3 = this.media) != null && _this$media3.readyState) || !this.hasBuffered) {
        return;
      }
      var currentTime = this.getCurrentTime();
      if (this.media.currentTime < currentTime) {
        return;
      }
      this.poll(currentTime, this.lastCurrentTime);
      this.lastCurrentTime = currentTime;
    }

;
    _proto.poll = function poll(currentTime, lastCurrentTime) {
      var _this$hls2, _this$hls3;
      var config = (_this$hls2 = this.hls) == null ? void 0 : _this$hls2.config;
      if (!config) {
        return;
      }
      var media = this.media;
      if (!media) {
        return;
      }
      var seeking = media.seeking;
      var seeked = this.seeking && !seeking;
      var beginSeek = !this.seeking && seeking;
      var pausedEndedOrHalted = media.paused && !seeking || media.ended || media.playbackRate === 0;
      this.seeking = seeking;

      if (currentTime !== lastCurrentTime) {
        if (lastCurrentTime) {
          this.ended = 0;
        }
        this.moved = true;
        if (!seeking) {
          this.skipRetry = this.nudgeRetry = 0;
          if (config.nudgeOnVideoHole && !pausedEndedOrHalted && currentTime > lastCurrentTime) {
            this.nudgeOnVideoHole(currentTime, lastCurrentTime);
          }
        }
        if (this.waiting === 0) {
          this.stallResolved(currentTime);
        }
        return;
      }

      if (beginSeek || seeked) {
        if (seeked) {
          this.stallResolved(currentTime);
        }
        return;
      }

      if (pausedEndedOrHalted) {
        this.skipRetry = this.nudgeRetry = 0;
        this.stallResolved(currentTime);
        if (!this.ended && media.ended && this.hls) {
          this.ended = currentTime || 1;
          this.hls.trigger(Events.MEDIA_ENDED, {
            stalled: false
          });
        }
        return;
      }
      if (!BufferHelper.getBuffered(media).length) {
        this.skipRetry = this.nudgeRetry = 0;
        return;
      }

      var bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0);
      var fragmentTracker = this.fragmentTracker;
      if (fragmentTracker && this.hls) {
        withInterstitialBoundary(bufferInfo, currentTime, fragmentTracker, this.hls);
      }
      var nextStart = bufferInfo.nextStart || 0;
      if (seeking && fragmentTracker && this.hls) {
        var inFlightDependency = getInFlightDependency(this.hls.inFlightFragments, currentTime);

        var hasEnoughBuffer = bufferInfo.len > MAX_START_GAP_JUMP;
        var noBufferHole = !nextStart || inFlightDependency || nextStart - currentTime > MAX_START_GAP_JUMP && !fragmentTracker.getPartialFragment(currentTime);
        if (hasEnoughBuffer || noBufferHole) {
          return;
        }
        this.moved = false;
      }

      var levelDetails = (_this$hls3 = this.hls) == null ? void 0 : _this$hls3.latestLevelDetails;
      if (!this.moved && this.stalled !== null && fragmentTracker) {
        var isBuffered = bufferInfo.len > 0;
        if (!isBuffered && !nextStart) {
          return;
        }
        var startJump = Math.max(nextStart, bufferInfo.start || 0) - currentTime;

        var isLive = !!(levelDetails != null && levelDetails.live);
        var maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
        var appended = appendedFragAtPosition(currentTime, fragmentTracker);
        if (startJump > 0 && (startJump <= maxStartGapJump || appended)) {
          if (!media.paused) {
            this._trySkipBufferHole(appended);
          }
          return;
        }
      }

      var detectStallWithCurrentTimeMs = config.detectStallWithCurrentTimeMs;
      var tnow = self.performance.now();
      var tWaiting = this.waiting;
      var stalled = this.stalled;
      if (stalled === null) {
        if (tWaiting > 0 && tnow - tWaiting < detectStallWithCurrentTimeMs) {
          stalled = this.stalled = tWaiting;
        } else {
          this.stalled = tnow;
          return;
        }
      }
      var stalledDuration = tnow - stalled;
      if (
      !seeking && (
      stalledDuration >= detectStallWithCurrentTimeMs || tWaiting && this.moved) &&
      this.hls) {
        var _this$mediaSource;
        if (((_this$mediaSource = this.mediaSource) == null ? void 0 : _this$mediaSource.readyState) === 'ended' && !(levelDetails != null && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) {
          if (this.ended) {
            return;
          }
          this.ended = currentTime || 1;
          this.hls.trigger(Events.MEDIA_ENDED, {
            stalled: true
          });
          return;
        }
        this._reportStall(bufferInfo);
        if (!this.media || !this.hls) {
          return;
        }
        if (media.currentTime !== currentTime) {
          var currentTimeBeforeStallEvent = currentTime;
          currentTime = media.currentTime;
          this.moved = true;
          return;
        }
      }
      var bufferedWithHoles = BufferHelper.bufferInfo(media, currentTime, config.maxBufferHole);
      this._tryFixBufferStall(bufferedWithHoles, stalledDuration, currentTime);
    };
    _proto.stallResolved = function stallResolved(currentTime) {
      var stalled = this.stalled;
      if (stalled && this.hls) {
        this.stalled = null;
        if (this.stallReported) {
          var stalledDuration = self.performance.now() - stalled;
          this.stallReported = false;
          this.waiting = 0;
          this.hls.trigger(Events.STALL_RESOLVED, {});
        }
      }
    };
    _proto.nudgeOnVideoHole = function nudgeOnVideoHole(currentTime, lastCurrentTime) {
      var _this$buffered$audio;
      var videoSourceBuffered = this.buffered.video;
      if (this.hls && this.media && this.fragmentTracker && (_this$buffered$audio = this.buffered.audio) != null && _this$buffered$audio.length && videoSourceBuffered && videoSourceBuffered.length > 1 && currentTime > videoSourceBuffered.end(0)) {
        var audioBufferInfo = BufferHelper.bufferedInfo(BufferHelper.timeRangesToArray(this.buffered.audio), currentTime, 0);
        if (audioBufferInfo.len > 1 && lastCurrentTime >= audioBufferInfo.start) {
          var videoTimes = BufferHelper.timeRangesToArray(videoSourceBuffered);
          var lastBufferedIndex = BufferHelper.bufferedInfo(videoTimes, lastCurrentTime, 0).bufferedIndex;
          if (lastBufferedIndex > -1 && lastBufferedIndex < videoTimes.length - 1) {
            var bufferedIndex = BufferHelper.bufferedInfo(videoTimes, currentTime, 0).bufferedIndex;
            var holeStart = videoTimes[lastBufferedIndex].end;
            var holeEnd = videoTimes[lastBufferedIndex + 1].start;
            if ((bufferedIndex === -1 || bufferedIndex > lastBufferedIndex) && holeEnd - holeStart < 1 &&
            currentTime - holeStart < 2) {
              var error = new Error("nudging playhead to flush pipeline after video hole. currentTime: " + currentTime + " hole: " + holeStart + " -> " + holeEnd + " buffered index: " + bufferedIndex);
              this.media.currentTime += 0.000001;
              var frag = appendedFragAtPosition(currentTime, this.fragmentTracker);
              if (frag && 'fragment' in frag) {
                frag = frag.fragment;
              } else if (!frag) {
                frag = undefined;
              }
              var bufferInfo = BufferHelper.bufferInfo(this.media, currentTime, 0);
              this.hls.trigger(Events.ERROR, {
                type: ErrorTypes.MEDIA_ERROR,
                details: ErrorDetails.BUFFER_SEEK_OVER_HOLE,
                fatal: false,
                error: error,
                reason: error.message,
                frag: frag,
                buffer: bufferInfo.len,
                bufferInfo: bufferInfo
              });
            }
          }
        }
      }
    }

;
    _proto._tryFixBufferStall = function _tryFixBufferStall(bufferInfo, stalledDurationMs, currentTime) {
      var _this$hls4, _this$hls5;
      var fragmentTracker = this.fragmentTracker,
        media = this.media;
      var config = (_this$hls4 = this.hls) == null ? void 0 : _this$hls4.config;
      if (!media || !fragmentTracker || !config) {
        return;
      }
      var levelDetails = (_this$hls5 = this.hls) == null ? void 0 : _this$hls5.latestLevelDetails;
      var appended = appendedFragAtPosition(currentTime, fragmentTracker);
      if (appended || levelDetails != null && levelDetails.live && currentTime < levelDetails.fragmentStart) {
        var targetTime = this._trySkipBufferHole(appended);
        if (targetTime || !this.media) {
          return;
        }
      }

      var bufferedRanges = bufferInfo.buffered;
      var adjacentTraversal = this.adjacentTraversal(bufferInfo, currentTime);
      if ((bufferedRanges && bufferedRanges.length > 1 && bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && (bufferInfo.nextStart - currentTime < config.maxBufferHole || adjacentTraversal)) && (stalledDurationMs > config.highBufferWatchdogPeriod * 1000 || this.waiting)) {
        this._tryNudgeBuffer(bufferInfo);
      }
    };
    _proto.adjacentTraversal = function adjacentTraversal(bufferInfo, currentTime) {
      var fragmentTracker = this.fragmentTracker;
      var nextStart = bufferInfo.nextStart;
      if (fragmentTracker && nextStart) {
        var current = fragmentTracker.getFragAtPos(currentTime, PlaylistLevelType.MAIN);
        var next = fragmentTracker.getFragAtPos(nextStart, PlaylistLevelType.MAIN);
        if (current && next) {
          return next.sn - current.sn < 2;
        }
      }
      return false;
    }

;
    _proto._reportStall = function _reportStall(bufferInfo) {
      var hls = this.hls,
        media = this.media,
        stallReported = this.stallReported,
        stalled = this.stalled;
      if (!stallReported && stalled !== null && media && hls) {
        this.stallReported = true;
        var error = new Error("Playback stalling at @" + media.currentTime + " due to low buffer (" + stringify(bufferInfo) + ")");
        hls.trigger(Events.ERROR, {
          type: ErrorTypes.MEDIA_ERROR,
          details: ErrorDetails.BUFFER_STALLED_ERROR,
          fatal: false,
          error: error,
          buffer: bufferInfo.len,
          bufferInfo: bufferInfo,
          stalled: {
            start: stalled
          }
        });
      }
    }

;
    _proto._trySkipBufferHole = function _trySkipBufferHole(appended) {
      var fragmentTracker = this.fragmentTracker,
        media = this.media,
        hls = this.hls;
      var config = hls == null ? void 0 : hls.config;
      if (!media || !fragmentTracker || !config) {
        return 0;
      }

      var currentTime = media.currentTime;
      var bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0);
      withInterstitialBoundary(bufferInfo, currentTime, fragmentTracker, hls);
      var startTime = currentTime < bufferInfo.start ? bufferInfo.start : bufferInfo.nextStart;
      if (startTime) {
        var bufferStarved = bufferInfo.len <= config.maxBufferHole;
        var waiting = bufferInfo.len > 0 && bufferInfo.len < 1 && media.readyState < 3;
        var gapLength = startTime - currentTime;
        if (gapLength > 0 && (bufferStarved || waiting)) {
          if (gapLength > config.maxBufferHole) {
            var startGap = false;
            if (currentTime === 0) {
              var startFrag = fragmentTracker.getAppendedFrag(0, PlaylistLevelType.MAIN);
              if (startFrag && startTime < startFrag.end) {
                startGap = true;
              }
            }
            if (!startGap && appended) {
              var _hls$loadLevelObj;
              if (!((_hls$loadLevelObj = hls.loadLevelObj) != null && _hls$loadLevelObj.details)) {
                return 0;
              }
              var inFlightDependency = getInFlightDependency(hls.inFlightFragments, startTime);
              if (inFlightDependency) {
                return 0;
              }
              var moreToLoad = false;
              var pos = appended.end;
              while (pos < startTime) {
                var provisioned = appendedFragAtPosition(pos, fragmentTracker);
                var duration = provisioned ? provisioned.duration : 0;
                if (duration > 0) {
                  pos += duration;
                } else {
                  moreToLoad = true;
                  break;
                }
              }
              if (moreToLoad) {
                return 0;
              }
            }
          }
          var nudgeMaxRetry = config.nudgeMaxRetry,
            skipBufferHolePadding = config.skipBufferHolePadding;
          var fatal = ++this.skipRetry > nudgeMaxRetry;
          var targetTime = Math.max(startTime, currentTime) + skipBufferHolePadding;
          if (!fatal) {
            this.moved = true;
            media.currentTime = targetTime;
          }
          if (!(appended != null && appended.gap) || fatal) {
            var error = new Error(fatal ? "Playhead still not moving after seeking over buffer hole from " + currentTime + " to " + targetTime + " after " + config.nudgeMaxRetry + " attempts." : "fragment loaded with buffer holes, seeking from " + currentTime + " to " + targetTime);
            var errorData = {
              type: ErrorTypes.MEDIA_ERROR,
              details: ErrorDetails.BUFFER_SEEK_OVER_HOLE,
              fatal: fatal,
              error: error,
              reason: error.message,
              buffer: bufferInfo.len,
              bufferInfo: bufferInfo
            };
            if (appended) {
              if ('fragment' in appended) {
                errorData.part = appended;
              } else {
                errorData.frag = appended;
              }
            }
            hls.trigger(Events.ERROR, errorData);
          }
          return targetTime;
        }
      }
      return 0;
    }

;
    _proto._tryNudgeBuffer = function _tryNudgeBuffer(bufferInfo) {
      var hls = this.hls,
        media = this.media,
        nudgeRetry = this.nudgeRetry;
      var config = hls == null ? void 0 : hls.config;
      if (!media || !config) {
        return 0;
      }
      var currentTime = media.currentTime;
      this.nudgeRetry++;
      if (nudgeRetry < config.nudgeMaxRetry) {
        var targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
        var error = new Error("Nudging 'currentTime' from " + currentTime + " to " + targetTime);
        media.currentTime = targetTime;
        hls.trigger(Events.ERROR, {
          type: ErrorTypes.MEDIA_ERROR,
          details: ErrorDetails.BUFFER_NUDGE_ON_STALL,
          error: error,
          fatal: false,
          buffer: bufferInfo.len,
          bufferInfo: bufferInfo
        });
      } else {
        var _error = new Error("Playhead still not moving while enough data buffered @" + currentTime + " after " + config.nudgeMaxRetry + " nudges");
        this.error(_error.message);
        hls.trigger(Events.ERROR, {
          type: ErrorTypes.MEDIA_ERROR,
          details: ErrorDetails.BUFFER_STALLED_ERROR,
          error: _error,
          fatal: true,
          buffer: bufferInfo.len,
          bufferInfo: bufferInfo
        });
      }
    };
    return _createClass(GapController, [{
      key: "hasBuffered",
      get: function get() {
        return Object.keys(this.buffered).length > 0;
      }
    }]);
  }(TaskLoop);
  function getInFlightDependency(inFlightFragments, currentTime) {
    var main = inFlight(inFlightFragments.main);
    if (main && main.start <= currentTime) {
      return main;
    }
    var audio = inFlight(inFlightFragments.audio);
    if (audio && audio.start <= currentTime) {
      return audio;
    }
    return null;
  }
  function inFlight(inFlightData) {
    if (!inFlightData) {
      return null;
    }
    switch (inFlightData.state) {
      case State.IDLE:
      case State.STOPPED:
      case State.ENDED:
      case State.ERROR:
        return null;
    }
    return inFlightData.frag;
  }
  function appendedFragAtPosition(pos, fragmentTracker) {
    return fragmentTracker.getAppendedFrag(pos, PlaylistLevelType.MAIN) || fragmentTracker.getPartialFragment(pos);
  }
  function withInterstitialBoundary(bufferInfo, currentTime, fragmentTracker, hls) {
    hls.latestLevelDetails;
    appendedFragAtPosition(currentTime, fragmentTracker);
    if (interstitialsEnabled(hls.config)) ;
  }

  var MetadataSchema = function (MetadataSchema) {
    MetadataSchema["audioId3"] = "org.id3";
    MetadataSchema["dateRange"] = "com.apple.quicktime.HLS";
    MetadataSchema["emsg"] = "https://aomedia.org/emsg/ID3";
    MetadataSchema["misbklv"] = "urn:misb:KLV:bin:1910.1";
    return MetadataSchema;
  }({});

  function createTrackNode(videoEl, kind, label, lang, mode) {
    var el = videoEl.ownerDocument.createElement('track');
    el.kind = kind;
    el.label = label;
    el.src = 'data:,WEBVTT';
    el.track.mode = mode;
    videoEl.appendChild(el);
    return el;
  }

  function getFirstCueIndexFromTime(cues, time) {
    if (time <= cues[0].startTime) {
      return 0;
    }
    var len = cues.length - 1;
    if (time > cues[len].endTime) {
      return -1;
    }
    var left = 0;
    var right = len;
    var mid;
    while (left <= right) {
      mid = Math.floor((right + left) / 2);
      if (time < cues[mid].startTime) {
        right = mid - 1;
      } else if (time > cues[mid].startTime && left < len) {
        left = mid + 1;
      } else {
        return mid;
      }
    }
    return cues[left].startTime - time < time - cues[right].startTime ? left : right;
  }
  function getCuesInRange(cues, start, end) {
    var cuesFound = [];
    var firstCueInRange = getFirstCueIndexFromTime(cues, start);
    if (firstCueInRange > -1) {
      for (var i = firstCueInRange, len = cues.length; i < len; i++) {
        var cue = cues[i];
        if (cue.startTime >= start && cue.endTime <= end) {
          cuesFound.push(cue);
        } else if (cue.startTime > end) {
          return cuesFound;
        }
      }
    }
    return cuesFound;
  }

  var MIN_CUE_DURATION = 0.25;
  function getCueClass() {
    if (typeof self === 'undefined') return undefined;
    return self.VTTCue || self.TextTrackCue;
  }

  var MAX_CUE_ENDTIME = function () {
    var Cue = getCueClass();
    try {
      Cue && new Cue(0, Number.POSITIVE_INFINITY, '');
    } catch (e) {
      return Number.MAX_VALUE;
    }
    return Number.POSITIVE_INFINITY;
  }();
  var LatencyController = function () {
    function LatencyController(hls) {
      var _this = this;
      this.hls = void 0;
      this.media = null;
      this.currentTime = 0;
      this.stallCount = 0;
      this._latency = null;
      this._targetLatencyUpdated = false;
      this.onTimeupdate = function () {
        var media = _this.media;
        var levelDetails = _this.levelDetails;
        if (!media || !levelDetails || !_this.hls) {
          return;
        }
        _this.currentTime = media.currentTime;
        var config = _this.hls.config;
        var latency = _this.computeLatency();
        if (latency === null) {
          return;
        }
        _this._latency = latency;

        var lowLatencyMode = config.lowLatencyMode,
          maxLiveSyncPlaybackRate = config.maxLiveSyncPlaybackRate;
        if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
          return;
        }
        var targetLatency = _this.targetLatency;
        if (targetLatency === null) {
          return;
        }
        var distanceFromTarget = latency - targetLatency;
        var liveMinLatencyDuration = Math.min(_this.maxLatency, targetLatency + levelDetails.targetduration);
        var inLiveRange = distanceFromTarget < liveMinLatencyDuration;
        if (inLiveRange && distanceFromTarget > 0.05 && _this.forwardBufferLength > 1 && true) {
          var max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
          var rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - _this.edgeStalled)) * 20) / 20;
          var playbackRate = Math.min(max, Math.max(1, rate));
          _this.changeMediaPlaybackRate(media, playbackRate);
        } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
          _this.changeMediaPlaybackRate(media, 1);
        }
      };
      this.hls = hls;
      this.registerListeners();
    }
    var _proto = LatencyController.prototype;
    _proto.destroy = function destroy() {
      this.unregisterListeners();
      this.onMediaDetaching();
      this.hls = null;
    };
    _proto.registerListeners = function registerListeners() {
      var hls = this.hls;
      if (!hls) {
        return;
      }
      hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
      hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
      hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
      hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
      hls.on(Events.ERROR, this.onError, this);
      hls.on(Events.INTERSTITIAL_ASSET_STARTED, this.onAssetStarted, this);
    };
    _proto.unregisterListeners = function unregisterListeners() {
      var hls = this.hls;
      if (!hls) {
        return;
      }
      hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
      hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
      hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
      hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
      hls.off(Events.ERROR, this.onError, this);
      hls.off(Events.INTERSTITIAL_ASSET_STARTED, this.onAssetStarted, this);
    };
    _proto.onMediaAttached = function onMediaAttached(event, data) {
      this.media = data.media;
      this.media.addEventListener('timeupdate', this.onTimeupdate);
    };
    _proto.onMediaDetaching = function onMediaDetaching() {
      if (this.media) {
        this.media.removeEventListener('timeupdate', this.onTimeupdate);
        this.media = null;
      }
    };
    _proto.onManifestLoading = function onManifestLoading() {
      this._latency = null;
      this.stallCount = 0;
    };
    _proto.onLevelUpdated = function onLevelUpdated(event, _ref) {
      var details = _ref.details;
      if (details.advanced) {
        this.onTimeupdate();
      }
      if (!details.live && this.media) {
        this.media.removeEventListener('timeupdate', this.onTimeupdate);
      }
    };
    _proto.onAssetStarted = function onAssetStarted(event, data) {
      var _hls$interstitialsMan;
      var hls = this.hls;
      var media = this.media || data.event.appendInPlace && (hls == null || (_hls$interstitialsMan = hls.interstitialsManager) == null ? void 0 : _hls$interstitialsMan.playerQueue.reduce(function (found, player) {
        return found || player.media;
      }, null));
      if (hls && media && media.playbackRate > 1 && media.playbackRate <= hls.config.maxLiveSyncPlaybackRate) {
        this.changeMediaPlaybackRate(media, 1);
      }
    };
    _proto.onError = function onError(event, data) {
      var _this$levelDetails;
      if (data.details !== ErrorDetails.BUFFER_STALLED_ERROR) {
        return;
      }
      this.stallCount++;
      if (this.hls && (_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) {
        this.hls.logger.warn('[latency-controller]: Stall detected, adjusting target latency');
      }
    };
    _proto.changeMediaPlaybackRate = function changeMediaPlaybackRate(media, playbackRate) {
      var _this$hls, _this$targetLatency;
      if (media.playbackRate === playbackRate) {
        return;
      }
      (_this$hls = this.hls) == null || _this$hls.logger.debug("[latency-controller]: latency=" + this.latency.toFixed(3) + ", targetLatency=" + ((_this$targetLatency = this.targetLatency) == null ? void 0 : _this$targetLatency.toFixed(3)) + ", forwardBufferLength=" + this.forwardBufferLength.toFixed(3) + ": adjusting playback rate from " + media.playbackRate + " to " + playbackRate);
      media.playbackRate = playbackRate;
    };
    _proto.estimateLiveEdge = function estimateLiveEdge() {
      var levelDetails = this.levelDetails;
      if (levelDetails === null) {
        return null;
      }
      return levelDetails.edge + levelDetails.age;
    };
    _proto.computeLatency = function computeLatency() {
      var liveEdge = this.estimateLiveEdge();
      if (liveEdge === null) {
        return null;
      }
      return liveEdge - this.currentTime;
    };
    return _createClass(LatencyController, [{
      key: "levelDetails",
      get: function get() {
        var _this$hls2;
        return ((_this$hls2 = this.hls) == null ? void 0 : _this$hls2.latestLevelDetails) || null;
      }
    }, {
      key: "latency",
      get: function get() {
        return this._latency || 0;
      }
    }, {
      key: "maxLatency",
      get: function get() {
        var _this$hls3;
        var config = (_this$hls3 = this.hls) == null ? void 0 : _this$hls3.config;
        if (!config) {
          return 0;
        }
        if (config.liveMaxLatencyDuration !== undefined) {
          return config.liveMaxLatencyDuration;
        }
        var levelDetails = this.levelDetails;
        return levelDetails ? config.liveMaxLatencyDurationCount * levelDetails.targetduration : 0;
      }
    }, {
      key: "targetLatency",
      get: function get() {
        var levelDetails = this.levelDetails;
        if (levelDetails === null || !this.hls) {
          return null;
        }
        var config = this.hls.config;
        var holdBack = levelDetails.holdBack,
          partHoldBack = levelDetails.partHoldBack,
          targetduration = levelDetails.targetduration;
        var liveSyncDuration = config.liveSyncDuration,
          liveSyncDurationCount = config.liveSyncDurationCount,
          lowLatencyMode = config.lowLatencyMode;
        var userConfig = this.hls.userConfig;
        var targetLatency = lowLatencyMode ? partHoldBack || holdBack : holdBack;
        if (this._targetLatencyUpdated || userConfig.liveSyncDuration || userConfig.liveSyncDurationCount || targetLatency === 0) {
          targetLatency = liveSyncDuration !== undefined ? liveSyncDuration : liveSyncDurationCount * targetduration;
        }
        var maxLiveSyncOnStallIncrease = targetduration;
        return targetLatency + Math.min(this.stallCount * config.liveSyncOnStallIncrease, maxLiveSyncOnStallIncrease);
      },
      set: function set(latency) {
        if (!this.hls) {
          return;
        }
        this.stallCount = 0;
        this.hls.config.liveSyncDuration = latency;
        this._targetLatencyUpdated = true;
      }
    }, {
      key: "liveSyncPosition",
      get: function get() {
        var liveEdge = this.estimateLiveEdge();
        var targetLatency = this.targetLatency;
        if (liveEdge === null || targetLatency === null || !this.hls) {
          return null;
        }
        var levelDetails = this.levelDetails;
        if (levelDetails === null) {
          return null;
        }
        var edge = levelDetails.edge;
        var syncPosition = liveEdge - targetLatency - this.edgeStalled;
        var min = edge - levelDetails.totalduration;
        var max = edge - (this.hls.config.lowLatencyMode && levelDetails.partTarget || levelDetails.targetduration);
        return Math.min(Math.max(min, syncPosition), max);
      }
    }, {
      key: "drift",
      get: function get() {
        var levelDetails = this.levelDetails;
        if (levelDetails === null) {
          return 1;
        }
        return levelDetails.drift;
      }
    }, {
      key: "edgeStalled",
      get: function get() {
        var levelDetails = this.levelDetails;
        if (levelDetails === null || !this.hls) {
          return 0;
        }
        var maxLevelUpdateAge = (this.hls.config.lowLatencyMode && levelDetails.partTarget || levelDetails.targetduration) * 3;
        return Math.max(levelDetails.age - maxLevelUpdateAge, 0);
      }
    }, {
      key: "forwardBufferLength",
      get: function get() {
        var media = this.media;
        var levelDetails = this.levelDetails;
        if (!media || !levelDetails) {
          return 0;
        }
        var bufferedRanges = media.buffered.length;
        return (bufferedRanges ? media.buffered.end(bufferedRanges - 1) : levelDetails.edge) - this.currentTime;
      }
    }]);
  }();

  function getAudioConfig(observer, data, offset, manifestCodec) {
    var adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
    var byte2 = data[offset + 2];
    var adtsSamplingIndex = byte2 >> 2 & 0xf;
    if (adtsSamplingIndex > 12) {
      var error = new Error("invalid ADTS sampling index:" + adtsSamplingIndex);
      observer.emit(Events.ERROR, Events.ERROR, {
        type: ErrorTypes.MEDIA_ERROR,
        details: ErrorDetails.FRAG_PARSING_ERROR,
        fatal: true,
        error: error,
        reason: error.message
      });
      return;
    }
    var adtsObjectType = (byte2 >> 6 & 0x3) + 1;
    var channelCount = data[offset + 3] >> 6 & 0x3 | (byte2 & 1) << 2;
    var samplerate = adtsSamplingRates[adtsSamplingIndex];
    var aacSampleIndex = adtsSamplingIndex;
    if (adtsObjectType === 5 || adtsObjectType === 29) {
      aacSampleIndex -= 3;
    }
    var config;
    var samplingIndexHigh = (aacSampleIndex & 0x0e) >> 1;
    var samplingIndexLowAndChannels = (aacSampleIndex & 0x01) << 7 | channelCount << 3;
    var adtsOverride = 0;
    if (adtsObjectType === 1 && adtsSamplingIndex >= 6) {
      adtsOverride = 5;
      var adtsExtensionSamplingIndex = adtsSamplingIndex - 3;
      config = [adtsOverride << 3 | samplingIndexHigh, samplingIndexLowAndChannels | (adtsExtensionSamplingIndex & 0x0e) >> 1, (adtsExtensionSamplingIndex & 0x01) << 7 | 2 << 2, 0];
    } else {
      config = [adtsObjectType << 3 | samplingIndexHigh, samplingIndexLowAndChannels];
    }
    var codec = 'mp4a.40.' + (adtsOverride || adtsObjectType);
    return {
      config: config,
      samplerate: samplerate,
      channelCount: channelCount,
      codec: codec,
      parsedCodec: codec,
      manifestCodec: manifestCodec
    };
  }
  function isHeaderPattern$1(data, offset) {
    return data[offset] === 0xff && (data[offset + 1] & 0xf6) === 0xf0;
  }
  function getHeaderLength(data, offset) {
    return data[offset + 1] & 0x01 ? 7 : 9;
  }
  function getFullFrameLength(data, offset) {
    return (data[offset + 3] & 0x03) << 11 | data[offset + 4] << 3 | (data[offset + 5] & 0xe0) >>> 5;
  }
  function canGetFrameLength(data, offset) {
    return offset + 5 < data.length;
  }
  function isHeader$1(data, offset) {
    return offset + 1 < data.length && isHeaderPattern$1(data, offset);
  }
  function canParse$1(data, offset) {
    return canGetFrameLength(data, offset) && isHeaderPattern$1(data, offset) && getFullFrameLength(data, offset) <= data.length - offset;
  }
  function probe$1(data, offset) {
    if (isHeader$1(data, offset)) {
      var headerLength = getHeaderLength(data, offset);
      if (offset + headerLength >= data.length) {
        return false;
      }
      var frameLength = getFullFrameLength(data, offset);
      if (frameLength <= headerLength) {
        return false;
      }
      var newOffset = offset + frameLength;
      return newOffset === data.length || isHeader$1(data, newOffset);
    }
    return false;
  }
  function initTrackConfig(track, observer, data, offset, audioCodec) {
    if (!track.samplerate) {
      var config = getAudioConfig(observer, data, offset, audioCodec);
      if (!config) {
        return;
      }
      _extends(track, config);
    }
  }
  function getFrameDuration(samplerate) {
    return 1024 * 90000 / samplerate;
  }
  function parseFrameHeader(data, offset) {
    var headerLength = getHeaderLength(data, offset);
    if (offset + headerLength <= data.length) {
      var frameLength = getFullFrameLength(data, offset) - headerLength;
      if (frameLength > 0) {
        return {
          headerLength: headerLength,
          frameLength: frameLength
        };
      }
    }
  }
  function appendFrame$1(track, data, offset, pts, frameIndex) {
    var frameDuration = getFrameDuration(track.samplerate);
    var stamp = pts + frameIndex * frameDuration;
    var header = parseFrameHeader(data, offset);
    var unit;
    if (header) {
      var frameLength = header.frameLength,
        headerLength = header.headerLength;
      var _length = headerLength + frameLength;
      var missing = Math.max(0, offset + _length - data.length);
      if (missing) {
        unit = new Uint8Array(_length - headerLength);
        unit.set(data.subarray(offset + headerLength, data.length), 0);
      } else {
        unit = data.subarray(offset + headerLength, offset + _length);
      }
      var _sample = {
        unit: unit,
        pts: stamp
      };
      if (!missing) {
        track.samples.push(_sample);
      }
      return {
        sample: _sample,
        length: _length,
        missing: missing
      };
    }
    var length = data.length - offset;
    unit = new Uint8Array(length);
    unit.set(data.subarray(offset, data.length), 0);
    var sample = {
      unit: unit,
      pts: stamp
    };
    return {
      sample: sample,
      length: length,
      missing: -1
    };
  }

  function dummyTrack(type, inputTimeScale) {
    if (type === void 0) {
      type = '';
    }
    if (inputTimeScale === void 0) {
      inputTimeScale = 90000;
    }
    return {
      type: type,
      id: -1,
      pid: -1,
      inputTimeScale: inputTimeScale,
      sequenceNumber: -1,
      samples: [],
      dropped: 0
    };
  }

  var initPTSFn = function initPTSFn(timestamp, timeOffset, initPTS) {
    if (isFiniteNumber(timestamp)) {
      return timestamp * 90;
    }
    var init90kHz = initPTS ? initPTS.baseTime * 90000 / initPTS.timescale : 0;
    return timeOffset * 90000 + init90kHz;
  };

  var BitratesMap = [32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160];
  var SamplingRateMap = [44100, 48000, 32000, 22050, 24000, 16000, 11025, 12000, 8000];
  var SamplesCoefficients = [
  [0,
  72,
  144,
  12
  ],
  [0,
  0,
  0,
  0
  ],
  [0,
  72,
  144,
  12
  ],
  [0,
  144,
  144,
  12
  ]];
  var BytesInSlot = [0,
  1,
  1,
  4
  ];
  function appendFrame(track, data, offset, pts, frameIndex) {
    if (offset + 24 > data.length) {
      return;
    }
    var header = parseHeader(data, offset);
    if (header && offset + header.frameLength <= data.length) {
      var frameDuration = header.samplesPerFrame * 90000 / header.sampleRate;
      var stamp = pts + frameIndex * frameDuration;
      var sample = {
        unit: data.subarray(offset, offset + header.frameLength),
        pts: stamp,
        dts: stamp
      };
      track.config = [];
      track.channelCount = header.channelCount;
      track.samplerate = header.sampleRate;
      track.samples.push(sample);
      return {
        sample: sample,
        length: header.frameLength,
        missing: 0
      };
    }
  }
  function parseHeader(data, offset) {
    var mpegVersion = data[offset + 1] >> 3 & 3;
    var mpegLayer = data[offset + 1] >> 1 & 3;
    var bitRateIndex = data[offset + 2] >> 4 & 15;
    var sampleRateIndex = data[offset + 2] >> 2 & 3;
    if (mpegVersion !== 1 && bitRateIndex !== 0 && bitRateIndex !== 15 && sampleRateIndex !== 3) {
      var paddingBit = data[offset + 2] >> 1 & 1;
      var channelMode = data[offset + 3] >> 6;
      var columnInBitrates = mpegVersion === 3 ? 3 - mpegLayer : mpegLayer === 3 ? 3 : 4;
      var bitRate = BitratesMap[columnInBitrates * 14 + bitRateIndex - 1] * 1000;
      var columnInSampleRates = mpegVersion === 3 ? 0 : mpegVersion === 2 ? 1 : 2;
      var sampleRate = SamplingRateMap[columnInSampleRates * 3 + sampleRateIndex];
      var channelCount = channelMode === 3 ? 1 : 2;
      var sampleCoefficient = SamplesCoefficients[mpegVersion][mpegLayer];
      var bytesInSlot = BytesInSlot[mpegLayer];
      var samplesPerFrame = sampleCoefficient * 8 * bytesInSlot;
      var frameLength = Math.floor(sampleCoefficient * bitRate / sampleRate + paddingBit) * bytesInSlot;
      var chromeVersion = userAgentChromeVersion();
      var needChromeFix = !!chromeVersion && chromeVersion <= 87;
      if (needChromeFix && mpegLayer === 2 && bitRate >= 224000 && channelMode === 0) {
        data[offset + 3] = data[offset + 3] | 0x80;
      }
      return {
        sampleRate: sampleRate,
        channelCount: channelCount,
        frameLength: frameLength,
        samplesPerFrame: samplesPerFrame
      };
    }
  }
  function isHeaderPattern(data, offset) {
    return data[offset] === 0xff && (data[offset + 1] & 0xe0) === 0xe0 && (data[offset + 1] & 0x06) !== 0x00;
  }
  function isHeader(data, offset) {
    return offset + 1 < data.length && isHeaderPattern(data, offset);
  }
  function canParse(data, offset) {
    var headerSize = 4;
    return isHeaderPattern(data, offset) && headerSize <= data.length - offset;
  }
  function probe(data, offset) {
    if (offset + 1 < data.length && isHeaderPattern(data, offset)) {
      var headerLength = 4;
      var header = parseHeader(data, offset);
      var frameLength = headerLength;
      if (header != null && header.frameLength) {
        frameLength = header.frameLength;
      }
      var newOffset = offset + frameLength;
      return newOffset === data.length || isHeader(data, newOffset);
    }
    return false;
  }

  var SampleAesDecrypter = function () {
    function SampleAesDecrypter(observer, config, keyData) {
      this.keyData = void 0;
      this.decrypter = void 0;
      this.keyData = keyData;
      this.decrypter = new Decrypter(config, true);
    }
    var _proto = SampleAesDecrypter.prototype;
    _proto.decryptBuffer = function decryptBuffer(encryptedData) {
      return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer, DecrypterAesMode.cbc,
      {
        start: 0,
        end: encryptedData.byteLength
      });
    }

  ;
    _proto.decryptAacSample = function decryptAacSample(samples, sampleIndex, callback) {
      var _this = this;
      var curUnit = samples[sampleIndex].unit;
      if (curUnit.length <= 16) {
        return;
      }
      var encryptedData = curUnit.subarray(16, curUnit.length - curUnit.length % 16);
      var encryptedBuffer = encryptedData.buffer.slice(encryptedData.byteOffset, encryptedData.byteOffset + encryptedData.length);
      this.decryptBuffer(encryptedBuffer).then(function (decryptedBuffer) {
        var decryptedData = new Uint8Array(decryptedBuffer);
        curUnit.set(decryptedData, 16);
        if (!_this.decrypter.isSync()) {
          _this.decryptAacSamples(samples, sampleIndex + 1, callback);
        }
      }).catch(callback);
    };
    _proto.decryptAacSamples = function decryptAacSamples(samples, sampleIndex, callback) {
      for (;; sampleIndex++) {
        if (sampleIndex >= samples.length) {
          callback();
          return;
        }
        if (samples[sampleIndex].unit.length < 32) {
          continue;
        }
        this.decryptAacSample(samples, sampleIndex, callback);
        if (!this.decrypter.isSync()) {
          return;
        }
      }
    }

  ;
    _proto.getAvcEncryptedData = function getAvcEncryptedData(decodedData) {
      var encryptedDataLen = Math.floor((decodedData.length - 48) / 160) * 16 + 16;
      var encryptedData = new Int8Array(encryptedDataLen);
      var outputPos = 0;
      for (var inputPos = 32; inputPos < decodedData.length - 16; inputPos += 160, outputPos += 16) {
        encryptedData.set(decodedData.subarray(inputPos, inputPos + 16), outputPos);
      }
      return encryptedData;
    };
    _proto.getAvcDecryptedUnit = function getAvcDecryptedUnit(decodedData, decryptedData) {
      var uint8DecryptedData = new Uint8Array(decryptedData);
      var inputPos = 0;
      for (var outputPos = 32; outputPos < decodedData.length - 16; outputPos += 160, inputPos += 16) {
        decodedData.set(uint8DecryptedData.subarray(inputPos, inputPos + 16), outputPos);
      }
      return decodedData;
    };
    _proto.decryptAvcSample = function decryptAvcSample(samples, sampleIndex, unitIndex, callback, curUnit) {
      var _this2 = this;
      var decodedData = discardEPB(curUnit.data);
      var encryptedData = this.getAvcEncryptedData(decodedData);
      this.decryptBuffer(encryptedData.buffer).then(function (decryptedBuffer) {
        curUnit.data = _this2.getAvcDecryptedUnit(decodedData, decryptedBuffer);
        if (!_this2.decrypter.isSync()) {
          _this2.decryptAvcSamples(samples, sampleIndex, unitIndex + 1, callback);
        }
      }).catch(callback);
    };
    _proto.decryptAvcSamples = function decryptAvcSamples(samples, sampleIndex, unitIndex, callback) {
      if (samples instanceof Uint8Array) {
        throw new Error('Cannot decrypt samples of type Uint8Array');
      }
      for (;; sampleIndex++, unitIndex = 0) {
        if (sampleIndex >= samples.length) {
          callback();
          return;
        }
        var curUnits = samples[sampleIndex].units;
        for (;; unitIndex++) {
          if (unitIndex >= curUnits.length) {
            break;
          }
          var curUnit = curUnits[unitIndex];
          if (curUnit.data.length <= 48 || curUnit.type !== 1 && curUnit.type !== 5) {
            continue;
          }
          this.decryptAvcSample(samples, sampleIndex, unitIndex, callback, curUnit);
          if (!this.decrypter.isSync()) {
            return;
          }
        }
      }
    };
    return SampleAesDecrypter;
  }();

  var getAudioBSID = function getAudioBSID(data, offset) {
    var bsid = 0;
    var numBits = 5;
    offset += numBits;
    var temp = new Uint32Array(1);
    var mask = new Uint32Array(1);
    var byte = new Uint8Array(1);
    while (numBits > 0) {
      byte[0] = data[offset];
      var bits = Math.min(numBits, 8);
      var shift = 8 - bits;
      mask[0] = 0xff000000 >>> 24 + shift << shift;
      temp[0] = (byte[0] & mask[0]) >> shift;
      bsid = !bsid ? temp[0] : bsid << bits | temp[0];
      offset += 1;
      numBits -= bits;
    }
    return bsid;
  };

  var emsgSchemePattern = /\\/emsg[-/]ID3/i;
  var MP4Demuxer = function () {
    function MP4Demuxer(observer, config, typeSupported, logger$1) {
      if (logger$1 === void 0) {
        logger$1 = logger;
      }
      this.remainderData = null;
      this.timeOffset = 0;
      this.config = void 0;
      this.videoTrack = void 0;
      this.audioTrack = void 0;
      this.id3Track = void 0;
      this.txtTrack = void 0;
      this.initData = void 0;
      this.logger = void 0;
      this.config = config;
      this.logger = logger$1;
    }
    var _proto = MP4Demuxer.prototype;
    _proto.resetTimeStamp = function resetTimeStamp() {};
    _proto.resetInitSegment = function resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) {
      var videoTrack = this.videoTrack = dummyTrack('video', 1);
      var audioTrack = this.audioTrack = dummyTrack('audio', 1);
      var captionTrack = this.txtTrack = dummyTrack('text', 1);
      this.id3Track = dummyTrack('id3', 1);
      this.initData = undefined;
      this.timeOffset = 0;
      if (!(initSegment != null && initSegment.byteLength)) {
        return;
      }
      var initData = this.initData = parseInitSegment(initSegment);
      if (initData.video) {
        var _initData$video = initData.video,
          id = _initData$video.id,
          timescale = _initData$video.timescale,
          codec = _initData$video.codec,
          supplemental = _initData$video.supplemental;
        videoTrack.id = id;
        videoTrack.timescale = captionTrack.timescale = timescale;
        videoTrack.codec = codec;
        videoTrack.supplemental = supplemental;
      }
      if (initData.audio) {
        var _initData$audio = initData.audio,
          _id = _initData$audio.id,
          _timescale = _initData$audio.timescale,
          _codec = _initData$audio.codec;
        audioTrack.id = _id;
        audioTrack.timescale = _timescale;
        audioTrack.codec = _codec;
      }
      captionTrack.id = RemuxerTrackIdConfig.text;
      videoTrack.sampleDuration = 0;
      videoTrack.duration = audioTrack.duration = trackDuration;
    };
    _proto.resetContiguity = function resetContiguity() {
      this.remainderData = null;
    };
    MP4Demuxer.probe = function probe(data) {
      return hasBoxData(data, 'moof');
    };
    _proto.demux = function demux(data, timeOffset, chunkMeta) {
      this.timeOffset = timeOffset;
      var videoSamples = data;
      var videoTrack = this.videoTrack;
      var textTrack = this.txtTrack;
      if (this.config.progressive) {
        if (this.remainderData) {
          videoSamples = appendUint8Array(this.remainderData, data);
        }
        var segmentedData = segmentValidRange(videoSamples);
        this.remainderData = segmentedData.remainder;
        videoTrack.samples = segmentedData.valid || new Uint8Array();
      } else {
        videoTrack.samples = videoSamples;
      }
      var id3Track = this.extractID3Track(videoTrack, timeOffset);
      var sampleData = this.parseFragment(videoTrack, textTrack, timeOffset, chunkMeta);
      return {
        videoTrack: videoTrack,
        audioTrack: this.audioTrack,
        id3Track: id3Track,
        textTrack: this.txtTrack,
        initData: this.initData,
        sampleData: sampleData
      };
    };
    _proto.flush = function flush(_timeOffset, chunkMeta) {
      var timeOffset = this.timeOffset;
      var videoTrack = this.videoTrack;
      var textTrack = this.txtTrack;
      videoTrack.samples = this.remainderData || new Uint8Array();
      this.remainderData = null;
      var id3Track = this.extractID3Track(videoTrack, this.timeOffset);
      var sampleData = this.parseFragment(videoTrack, textTrack, timeOffset, chunkMeta);
      return {
        videoTrack: videoTrack,
        audioTrack: dummyTrack(),
        id3Track: id3Track,
        textTrack: dummyTrack(),
        initData: this.initData,
        sampleData: sampleData
      };
    };
    _proto.parseFragment = function parseFragment(videoTrack, textTrack, timeOffset, chunkMeta) {
      var initData = this.initData;
      if (initData && chunkMeta) {
        var _parseSamplesAndTimin = parseSamplesAndTiming(timeOffset, videoTrack, initData, this.logger, false),
          samples = _parseSamplesAndTimin.samples,
          sampleData = _parseSamplesAndTimin.sampleData;
        textTrack.samples = samples;
        return sampleData;
      }
      textTrack.samples = parseSamples(timeOffset, videoTrack);
    };
    _proto.extractID3Track = function extractID3Track(videoTrack, timeOffset) {
      var _this = this;
      var id3Track = this.id3Track;
      if (videoTrack.samples.length) {
        var emsgs = findBox(videoTrack.samples, ['emsg']);
        if (emsgs) {
          emsgs.forEach(function (data) {
            var emsgInfo = parseEmsg(data);
            if (emsgSchemePattern.test(emsgInfo.schemeIdUri)) {
              var pts = getEmsgStartTime(emsgInfo, timeOffset);
              var duration = emsgInfo.eventDuration === 0xffffffff ? Number.POSITIVE_INFINITY : emsgInfo.eventDuration / emsgInfo.timeScale;
              if (duration <= 0.001) {
                duration = Number.POSITIVE_INFINITY;
              }
              var payload = emsgInfo.payload;
              id3Track.samples.push({
                data: payload,
                len: payload.byteLength,
                dts: pts,
                pts: pts,
                type: MetadataSchema.emsg,
                duration: duration
              });
            } else if (_this.config.enableEmsgKLVMetadata) {
              var klvSchemaUri = _this.config.emsgKLVSchemaUri || MetadataSchema.misbklv;
              if (emsgInfo.schemeIdUri.startsWith(klvSchemaUri)) {
                var _pts = getEmsgStartTime(emsgInfo, timeOffset);
                id3Track.samples.push({
                  data: emsgInfo.payload,
                  len: emsgInfo.payload.byteLength,
                  dts: _pts,
                  pts: _pts,
                  type: MetadataSchema.misbklv,
                  duration: Number.POSITIVE_INFINITY
                });
              }
            }
          });
        }
      }
      return id3Track;
    };
    _proto.demuxSampleAes = function demuxSampleAes(data, keyData, timeOffset) {
      return Promise.reject(new Error('The MP4 demuxer does not support SAMPLE-AES decryption'));
    };
    _proto.destroy = function destroy() {
      this.config = null;
      this.remainderData = null;
      this.videoTrack = this.audioTrack = this.id3Track = this.txtTrack = undefined;
      this.initData = undefined;
    };
    return MP4Demuxer;
  }();
  function getEmsgStartTime(emsgInfo, timeOffset) {
    return isFiniteNumber(emsgInfo.presentationTime) ? emsgInfo.presentationTime / emsgInfo.timeScale : timeOffset + emsgInfo.presentationTimeDelta / emsgInfo.timeScale;
  }

  var ANNEX_B_START_CODE_MARKER = 0x01;
  var MIN_START_CODE_ZERO_RUN = 2;
  var NAL_UNIT_TYPE_PENDING = -1;
  var BaseVideoParser = function () {
    function BaseVideoParser() {
      this.VideoSample = null;
    }
    var _proto = BaseVideoParser.prototype;
    _proto.createVideoSample = function createVideoSample(key, pts, dts) {
      return {
        key: key,
        frame: false,
        pts: pts,
        dts: dts,
        units: [],
        length: 0
      };
    };
    _proto.getLastNalUnit = function getLastNalUnit(samples) {
      var _VideoSample;
      var VideoSample = this.VideoSample;
      var lastUnit;
      if (!VideoSample || VideoSample.units.length === 0) {
        VideoSample = samples[samples.length - 1];
      }
      if ((_VideoSample = VideoSample) != null && _VideoSample.units) {
        var units = VideoSample.units;
        lastUnit = units[units.length - 1];
      }
      return lastUnit;
    };
    _proto.pushAccessUnit = function pushAccessUnit(VideoSample, videoTrack) {
      if (VideoSample.units.length && VideoSample.frame) {
        if (VideoSample.pts === undefined) {
          var samples = videoTrack.samples;
          var nbSamples = samples.length;
          if (nbSamples) {
            var lastSample = samples[nbSamples - 1];
            VideoSample.pts = lastSample.pts;
            VideoSample.dts = lastSample.dts;
          } else {
            videoTrack.dropped++;
            return;
          }
        }
        videoTrack.samples.push(VideoSample);
      }
    };
    _proto.parseNALu = function parseNALu(track, array, _endOfSegment) {
      var len = array.byteLength;
      if (len === 0) {
        return [];
      }
      var previousState = track.naluState || 0;
      var previousZeroRun = previousState === NAL_UNIT_TYPE_PENDING ? 0 : previousState;
      var units = [];
      var nextState = 0;
      var searchFrom = 0;
      var unitStart = -1;
      var unitType = 0;
      if (previousState === NAL_UNIT_TYPE_PENDING) {
        unitStart = 0;
        unitType = this.getNALuType(array, 0);
        searchFrom = 1;
      }
      while (searchFrom < len) {
        var markerIndex = array.indexOf(ANNEX_B_START_CODE_MARKER, searchFrom);
        if (markerIndex === -1) {
          break;
        }
        searchFrom = markerIndex + 1;
        var nalEnd = markerIndex;
        while (nalEnd > 0 && array[nalEnd - 1] === 0) {
          nalEnd--;
        }

        var zeroRun = markerIndex - nalEnd + (nalEnd === 0 ? previousZeroRun : 0);
        if (zeroRun < MIN_START_CODE_ZERO_RUN) {
          continue;
        }
        if (unitStart >= 0) {
          units.push({
            data: array.subarray(unitStart, nalEnd),
            type: unitType
          });
        } else {
          var previousUnit = this.getLastNalUnit(track.samples);
          if (previousUnit) {
            if (nalEnd > 0) {
              previousUnit.data = appendUint8Array(previousUnit.data, array.subarray(0, nalEnd));
            } else if (previousZeroRun > 0) {
              previousUnit.data = previousUnit.data.subarray(0, previousUnit.data.byteLength - previousZeroRun);
            }
            previousUnit.state = 0;
          }
        }
        if (searchFrom < len) {
          unitStart = searchFrom;
          unitType = this.getNALuType(array, searchFrom);
        } else {
          nextState = NAL_UNIT_TYPE_PENDING;
          unitStart = -1;
          break;
        }
      }
      if (nextState !== NAL_UNIT_TYPE_PENDING) {
        var trailingZeroStart = len;
        while (trailingZeroStart > 0 && array[trailingZeroStart - 1] === 0) {
          trailingZeroStart--;
        }
        nextState = len - trailingZeroStart + (trailingZeroStart === 0 ? previousZeroRun : 0);
      }
      if (unitStart >= 0) {
        units.push({
          data: array.subarray(unitStart),
          type: unitType,
          state: nextState
        });
      } else if (units.length === 0 && nextState !== NAL_UNIT_TYPE_PENDING) {
        var _previousUnit = this.getLastNalUnit(track.samples);
        if (_previousUnit) {
          _previousUnit.data = appendUint8Array(_previousUnit.data, array);
          _previousUnit.state = nextState;
        }
      }
      track.naluState = nextState;
      return units;
    };
    return BaseVideoParser;
  }();

  var ExpGolomb = function () {
    function ExpGolomb(data) {
      this.data = void 0;
      this.bytesAvailable = void 0;
      this.word = void 0;
      this.bitsAvailable = void 0;
      this.data = data;
      this.bytesAvailable = data.byteLength;
      this.word = 0;
      this.bitsAvailable = 0;
    }

    var _proto = ExpGolomb.prototype;
    _proto.loadWord = function loadWord() {
      var data = this.data;
      var bytesAvailable = this.bytesAvailable;
      var position = data.byteLength - bytesAvailable;
      var workingBytes = new Uint8Array(4);
      var availableBytes = Math.min(4, bytesAvailable);
      if (availableBytes === 0) {
        throw new Error('no bytes available');
      }
      workingBytes.set(data.subarray(position, position + availableBytes));
      this.word = new DataView(workingBytes.buffer).getUint32(0);
      this.bitsAvailable = availableBytes * 8;
      this.bytesAvailable -= availableBytes;
    }

  ;
    _proto.skipBits = function skipBits(count) {
      var skipBytes;
      count = Math.min(count, this.bytesAvailable * 8 + this.bitsAvailable);
      if (this.bitsAvailable > count) {
        this.word <<= count;
        this.bitsAvailable -= count;
      } else {
        count -= this.bitsAvailable;
        skipBytes = count >> 3;
        count -= skipBytes << 3;
        this.bytesAvailable -= skipBytes;
        this.loadWord();
        this.word <<= count;
        this.bitsAvailable -= count;
      }
    }

  ;
    _proto.readBits = function readBits(size) {
      var bits = Math.min(this.bitsAvailable, size);
      var valu = this.word >>> 32 - bits;
      if (size > 32) {
        logger.error('Cannot read more than 32 bits at a time');
      }
      this.bitsAvailable -= bits;
      if (this.bitsAvailable > 0) {
        this.word <<= bits;
      } else if (this.bytesAvailable > 0) {
        this.loadWord();
      } else {
        throw new Error('no bits available');
      }
      bits = size - bits;
      if (bits > 0 && this.bitsAvailable) {
        return valu << bits | this.readBits(bits);
      } else {
        return valu;
      }
    }

  ;
    _proto.skipLZ = function skipLZ() {
      var leadingZeroCount;
      for (leadingZeroCount = 0; leadingZeroCount < this.bitsAvailable; ++leadingZeroCount) {
        if ((this.word & 0x80000000 >>> leadingZeroCount) !== 0) {
          this.word <<= leadingZeroCount;
          this.bitsAvailable -= leadingZeroCount;
          return leadingZeroCount;
        }
      }
      this.loadWord();
      return leadingZeroCount + this.skipLZ();
    }

  ;
    _proto.skipUEG = function skipUEG() {
      this.skipBits(1 + this.skipLZ());
    }

  ;
    _proto.skipEG = function skipEG() {
      this.skipBits(1 + this.skipLZ());
    }

  ;
    _proto.readUEG = function readUEG() {
      var clz = this.skipLZ();
      return this.readBits(clz + 1) - 1;
    }

  ;
    _proto.readEG = function readEG() {
      var valu = this.readUEG();
      if (0x01 & valu) {
        return 1 + valu >>> 1;
      } else {
        return -1 * (valu >>> 1);
      }
    }

  ;
    _proto.readBoolean = function readBoolean() {
      return this.readBits(1) === 1;
    }

  ;
    _proto.readUByte = function readUByte() {
      return this.readBits(8);
    };
    return ExpGolomb;
  }();

  var AvcVideoParser = function (_BaseVideoParser) {
    function AvcVideoParser() {
      return _BaseVideoParser.apply(this, arguments) || this;
    }
    _inheritsLoose(AvcVideoParser, _BaseVideoParser);
    var _proto = AvcVideoParser.prototype;
    _proto.parsePES = function parsePES(track, textTrack, pes, endOfSegment, chunkMeta) {
      var _this = this;
      var iframesOnly = chunkMeta.iframe;
      var units = this.parseNALu(track, pes.data, endOfSegment);
      var VideoSample = this.VideoSample;
      var push;
      var spsfound = false;
      pes.data = null;

      if (VideoSample && units.length && !track.audFound) {
        this.pushAccessUnit(VideoSample, track);
        VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts);
      }
      units.forEach(function (unit) {
        var _VideoSample2, _VideoSample3;
        switch (unit.type) {
          case 1:
            {
              if (iframesOnly) {
                break;
              }
              var iskey = false;
              push = true;
              var data = unit.data;
              if (spsfound && data.length > 4) {
                var sliceType = _this.readSliceType(data);
                if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) {
                  iskey = true;
                }
              }
              if (iskey) {
                var _VideoSample;
                if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
                  _this.pushAccessUnit(VideoSample, track);
                  VideoSample = _this.VideoSample = null;
                }
              }
              if (!VideoSample) {
                VideoSample = _this.VideoSample = _this.createVideoSample(true, pes.pts, pes.dts);
              }
              VideoSample.frame = true;
              VideoSample.key = iskey;
              break;
            }
          case 5:
            push = true;
            if ((_VideoSample2 = VideoSample) != null && _VideoSample2.frame && !VideoSample.key) {
              _this.pushAccessUnit(VideoSample, track);
              VideoSample = _this.VideoSample = null;
            }
            if (!VideoSample) {
              VideoSample = _this.VideoSample = _this.createVideoSample(true, pes.pts, pes.dts);
            }
            VideoSample.key = true;
            VideoSample.frame = true;
            break;
          case 6:
            {
              if (iframesOnly) {
                break;
              }
              push = true;
              parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples);
              break;
            }
          case 7:
            {
              var _track$pixelRatio, _track$pixelRatio2;
              push = true;
              spsfound = true;
              var sps = unit.data;
              var config = _this.readSPS(sps);
              if (!track.sps || track.width !== config.width || track.height !== config.height || ((_track$pixelRatio = track.pixelRatio) == null ? void 0 : _track$pixelRatio[0]) !== config.pixelRatio[0] || ((_track$pixelRatio2 = track.pixelRatio) == null ? void 0 : _track$pixelRatio2[1]) !== config.pixelRatio[1]) {
                track.width = config.width;
                track.height = config.height;
                track.pixelRatio = config.pixelRatio;
                track.sps = [sps];
                var codecarray = sps.subarray(1, 4);
                var codecstring = 'avc1.';
                for (var i = 0; i < 3; i++) {
                  var h = codecarray[i].toString(16);
                  if (h.length < 2) {
                    h = '0' + h;
                  }
                  codecstring += h;
                }
                track.codec = codecstring;
              }
              break;
            }
          case 8:
            push = true;
            track.pps = [unit.data];
            break;
          case 9:
            push = true;
            track.audFound = true;
            if ((_VideoSample3 = VideoSample) != null && _VideoSample3.frame) {
              _this.pushAccessUnit(VideoSample, track);
              VideoSample = null;
            }
            if (!VideoSample) {
              VideoSample = _this.VideoSample = _this.createVideoSample(false, pes.pts, pes.dts);
            }
            break;
          case 12:
            push = true;
            break;
          default:
            push = false;
            break;
        }
        if (VideoSample && push) {
          var _units = VideoSample.units;
          _units.push(unit);
        }
      });
      if (endOfSegment && VideoSample) {
        this.pushAccessUnit(VideoSample, track);
        this.VideoSample = null;
      }
    };
    _proto.getNALuType = function getNALuType(data, offset) {
      return data[offset] & 0x1f;
    };
    _proto.readSliceType = function readSliceType(data) {
      var eg = new ExpGolomb(data);
      eg.readUByte();
      eg.readUEG();
      return eg.readUEG();
    }

;
    _proto.skipScalingList = function skipScalingList(count, reader) {
      var lastScale = 8;
      var nextScale = 8;
      var deltaScale;
      for (var j = 0; j < count; j++) {
        if (nextScale !== 0) {
          deltaScale = reader.readEG();
          nextScale = (lastScale + deltaScale + 256) % 256;
        }
        lastScale = nextScale === 0 ? lastScale : nextScale;
      }
    }

;
    _proto.readSPS = function readSPS(sps) {
      var eg = new ExpGolomb(sps);
      var frameCropLeftOffset = 0;
      var frameCropRightOffset = 0;
      var frameCropTopOffset = 0;
      var frameCropBottomOffset = 0;
      var numRefFramesInPicOrderCntCycle;
      var scalingListCount;
      var i;
      var readUByte = eg.readUByte.bind(eg);
      var readBits = eg.readBits.bind(eg);
      var readUEG = eg.readUEG.bind(eg);
      var readBoolean = eg.readBoolean.bind(eg);
      var skipBits = eg.skipBits.bind(eg);
      var skipEG = eg.skipEG.bind(eg);
      var skipUEG = eg.skipUEG.bind(eg);
      var skipScalingList = this.skipScalingList.bind(this);
      readUByte();
      var profileIdc = readUByte();
      readBits(5);
      skipBits(3);
      readUByte();
      skipUEG();
      if (profileIdc === 100 || profileIdc === 110 || profileIdc === 122 || profileIdc === 244 || profileIdc === 44 || profileIdc === 83 || profileIdc === 86 || profileIdc === 118 || profileIdc === 128) {
        var chromaFormatIdc = readUEG();
        if (chromaFormatIdc === 3) {
          skipBits(1);
        }

        skipUEG();
        skipUEG();
        skipBits(1);
        if (readBoolean()) {
          scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
          for (i = 0; i < scalingListCount; i++) {
            if (readBoolean()) {
              if (i < 6) {
                skipScalingList(16, eg);
              } else {
                skipScalingList(64, eg);
              }
            }
          }
        }
      }
      skipUEG();
      var picOrderCntType = readUEG();
      if (picOrderCntType === 0) {
        readUEG();
      } else if (picOrderCntType === 1) {
        skipBits(1);
        skipEG();
        skipEG();
        numRefFramesInPicOrderCntCycle = readUEG();
        for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
          skipEG();
        }
      }
      skipUEG();
      skipBits(1);
      var picWidthInMbsMinus1 = readUEG();
      var picHeightInMapUnitsMinus1 = readUEG();
      var frameMbsOnlyFlag = readBits(1);
      if (frameMbsOnlyFlag === 0) {
        skipBits(1);
      }

      skipBits(1);
      if (readBoolean()) {
        frameCropLeftOffset = readUEG();
        frameCropRightOffset = readUEG();
        frameCropTopOffset = readUEG();
        frameCropBottomOffset = readUEG();
      }
      var pixelRatio = [1, 1];
      if (readBoolean()) {
        if (readBoolean()) {
          var aspectRatioIdc = readUByte();
          switch (aspectRatioIdc) {
            case 1:
              pixelRatio = [1, 1];
              break;
            case 2:
              pixelRatio = [12, 11];
              break;
            case 3:
              pixelRatio = [10, 11];
              break;
            case 4:
              pixelRatio = [16, 11];
              break;
            case 5:
              pixelRatio = [40, 33];
              break;
            case 6:
              pixelRatio = [24, 11];
              break;
            case 7:
              pixelRatio = [20, 11];
              break;
            case 8:
              pixelRatio = [32, 11];
              break;
            case 9:
              pixelRatio = [80, 33];
              break;
            case 10:
              pixelRatio = [18, 11];
              break;
            case 11:
              pixelRatio = [15, 11];
              break;
            case 12:
              pixelRatio = [64, 33];
              break;
            case 13:
              pixelRatio = [160, 99];
              break;
            case 14:
              pixelRatio = [4, 3];
              break;
            case 15:
              pixelRatio = [3, 2];
              break;
            case 16:
              pixelRatio = [2, 1];
              break;
            case 255:
              {
                pixelRatio = [readUByte() << 8 | readUByte(), readUByte() << 8 | readUByte()];
                break;
              }
          }
        }
      }
      return {
        width: Math.ceil((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2),
        height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - (frameMbsOnlyFlag ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset),
        pixelRatio: pixelRatio
      };
    };
    return AvcVideoParser;
  }(BaseVideoParser);

  var PACKET_LENGTH = 188;
  var TSDemuxer = function () {
    function TSDemuxer(observer, config, typeSupported, logger) {
      this.logger = void 0;
      this.observer = void 0;
      this.config = void 0;
      this.typeSupported = void 0;
      this.sampleAes = null;
      this.pmtParsed = false;
      this.audioCodec = void 0;
      this.videoCodec = void 0;
      this._pmtId = -1;
      this._videoTrack = void 0;
      this._audioTrack = void 0;
      this._id3Track = void 0;
      this._txtTrack = void 0;
      this._klvPid = -1;
      this.aacOverFlow = null;
      this.remainderData = null;
      this.videoParser = void 0;
      this.videoIntegrityChecker = null;
      this.observer = observer;
      this.config = config;
      this.typeSupported = typeSupported;
      this.logger = logger;
      this.videoParser = null;
    }
    TSDemuxer.probe = function probe(data, logger) {
      var syncOffset = TSDemuxer.syncOffset(data);
      if (syncOffset > 0) {
      }
      return syncOffset !== -1;
    };
    TSDemuxer.syncOffset = function syncOffset(data) {
      var length = data.length;
      var scanwindow = Math.min(PACKET_LENGTH * 5, length - PACKET_LENGTH) + 1;
      var i = 0;
      while (i < scanwindow) {
        var foundPat = false;
        var packetStart = -1;
        var tsPackets = 0;
        for (var j = i; j < length; j += PACKET_LENGTH) {
          if (data[j] === 0x47 && (length - j === PACKET_LENGTH || data[j + PACKET_LENGTH] === 0x47)) {
            tsPackets++;
            if (packetStart === -1) {
              packetStart = j;
              if (packetStart !== 0) {
                scanwindow = Math.min(packetStart + PACKET_LENGTH * 99, data.length - PACKET_LENGTH) + 1;
              }
            }
            if (!foundPat) {
              foundPat = parsePID(data, j) === 0;
            }
            if (foundPat && tsPackets > 1 && (packetStart === 0 && tsPackets > 2 || j + PACKET_LENGTH > scanwindow)) {
              return packetStart;
            }
          } else if (tsPackets) {
            return -1;
          } else {
            break;
          }
        }
        i++;
      }
      return -1;
    }

;
    TSDemuxer.createTrack = function createTrack(type, duration) {
      return {
        container: type === 'video' || type === 'audio' ? 'video/mp2t' : undefined,
        type: type,
        id: RemuxerTrackIdConfig[type],
        pid: -1,
        inputTimeScale: 90000,
        sequenceNumber: 0,
        samples: [],
        dropped: 0,
        duration: type === 'audio' ? duration : undefined
      };
    }

;
    var _proto = TSDemuxer.prototype;
    _proto.resetInitSegment = function resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration, decryptdata, chunkMeta) {
      this.pmtParsed = false;
      this._pmtId = -1;
      this._videoTrack = TSDemuxer.createTrack('video');
      this._videoTrack.duration = trackDuration;
      this.videoIntegrityChecker = this.config.handleMpegTsVideoIntegrityErrors === 'skip' ? new PacketsIntegrityChecker(this.logger) : null;
      this._audioTrack = TSDemuxer.createTrack('audio', trackDuration);
      this._id3Track = TSDemuxer.createTrack('id3');
      this._txtTrack = TSDemuxer.createTrack('text');
      this._audioTrack.segmentCodec = 'aac';

      this.videoParser = null;
      this.aacOverFlow = null;
      this.remainderData = null;
      this.audioCodec = audioCodec;
      this.videoCodec = videoCodec;
      if (initSegment) {
        this.demux(initSegment, 0, chunkMeta, (decryptdata == null ? void 0 : decryptdata.method) === 'SAMPLE-AES');
      }
    };
    _proto.resetTimeStamp = function resetTimeStamp() {};
    _proto.resetContiguity = function resetContiguity() {
      var _audioTrack = this._audioTrack,
        _videoTrack = this._videoTrack,
        _id3Track = this._id3Track;
      if (_audioTrack) {
        _audioTrack.pesData = null;
      }
      if (_videoTrack) {
        _videoTrack.pesData = null;
      }
      if (_id3Track) {
        _id3Track.pesData = null;
      }
      this.aacOverFlow = null;
      this.remainderData = null;
    };
    _proto.demux = function demux(data, timeOffset, chunkMeta, isSampleAes, flush) {
      if (isSampleAes === void 0) {
        isSampleAes = false;
      }
      if (flush === void 0) {
        flush = false;
      }
      if (!isSampleAes) {
        this.sampleAes = null;
      }
      var pes;
      var videoTrack = this._videoTrack;
      var videoIntegrityChecker = this.videoIntegrityChecker;
      var audioTrack = this._audioTrack;
      var id3Track = this._id3Track;
      var textTrack = this._txtTrack;
      var iframe = chunkMeta.iframe;
      var videoPid = videoTrack.pid;
      var videoData = videoTrack.pesData;
      var audioPid = audioTrack.pid;
      var id3Pid = id3Track.pid;
      var klvPid = this._klvPid;
      var audioData = audioTrack.pesData;
      var id3Data = id3Track.pesData;
      var klvData = null;
      var unknownPID = null;
      var pmtParsed = this.pmtParsed;
      var pmtId = this._pmtId;
      var len = data.length;
      if (this.remainderData) {
        data = appendUint8Array(this.remainderData, data);
        len = data.length;
        this.remainderData = null;
      }
      if (len < PACKET_LENGTH && !flush) {
        this.remainderData = data;
        return {
          audioTrack: audioTrack,
          videoTrack: videoTrack,
          id3Track: id3Track,
          textTrack: textTrack
        };
      }
      var syncOffset = Math.max(0, TSDemuxer.syncOffset(data));
      len -= (len - syncOffset) % PACKET_LENGTH;
      if (len < data.byteLength && !flush) {
        this.remainderData = new Uint8Array(data.buffer, len, data.buffer.byteLength - len);
      }

      var tsPacketErrors = 0;
      for (var start = syncOffset; start < len; start += PACKET_LENGTH) {
        if (data[start] === 0x47) {
          var stt = !!(data[start + 1] & 0x40);
          var pid = parsePID(data, start);
          var atf = (data[start + 3] & 0x30) >> 4;

          var offset = void 0;
          if (atf > 1) {
            offset = start + 5 + data[start + 4];
            if (offset === start + PACKET_LENGTH) {
              continue;
            }
          } else {
            offset = start + 4;
          }
          switch (pid) {
            case videoPid:
              if (stt) {
                if (videoData && !(videoIntegrityChecker != null && videoIntegrityChecker.isCorrupted) && (pes = parsePES(videoData, this.logger))) {
                  this.readyVideoParser(videoTrack.segmentCodec);
                  if (this.videoParser !== null) {
                    this.videoParser.parsePES(videoTrack, textTrack, pes, false, chunkMeta);
                  }
                }
                videoData = {
                  data: [],
                  size: 0
                };
                videoIntegrityChecker == null || videoIntegrityChecker.reset(videoPid);
              }
              videoIntegrityChecker == null || videoIntegrityChecker.handlePacket(data.subarray(start));
              if (videoData) {
                videoData.data.push(data.subarray(offset, start + PACKET_LENGTH));
                videoData.size += start + PACKET_LENGTH - offset;
              }
              break;
            case audioPid:
              if (iframe) {
                break;
              }
              if (stt) {
                if (audioData && (pes = parsePES(audioData, this.logger))) {
                  switch (audioTrack.segmentCodec) {
                    case 'aac':
                      this.parseAACPES(audioTrack, pes, chunkMeta);
                      break;
                    case 'mp3':
                      this.parseMPEGPES(audioTrack, pes);
                      break;
                  }
                }
                audioData = {
                  data: [],
                  size: 0
                };
              }
              if (audioData) {
                audioData.data.push(data.subarray(offset, start + PACKET_LENGTH));
                audioData.size += start + PACKET_LENGTH - offset;
              }
              break;
            case id3Pid:
              if (iframe) {
                break;
              }
              if (stt) {
                if (id3Data && (pes = parsePES(id3Data, this.logger))) {
                  this.parseID3PES(id3Track, pes);
                }
                id3Data = {
                  data: [],
                  size: 0
                };
              }
              if (id3Data) {
                id3Data.data.push(data.subarray(offset, start + PACKET_LENGTH));
                id3Data.size += start + PACKET_LENGTH - offset;
              }
              break;
            case klvPid:
              if (iframe) {
                break;
              }
              if (stt) {
                if (klvData && (pes = parsePES(klvData, this.logger))) {
                  this.parseKlvPES(id3Track, pes);
                }
                klvData = {
                  data: [],
                  size: 0
                };
              }
              if (klvData) {
                klvData.data.push(data.subarray(offset, start + PACKET_LENGTH));
                klvData.size += start + PACKET_LENGTH - offset;
              }
              break;
            case 0:
              if (stt) {
                offset += data[offset] + 1;
              }
              pmtId = this._pmtId = parsePAT(data, offset);
              break;
            case pmtId:
              {
                if (stt) {
                  offset += data[offset] + 1;
                }
                var parsedPIDs = parsePMT(data, offset, this.typeSupported, isSampleAes, this.observer, this.logger, this.config, chunkMeta);

                videoPid = parsedPIDs.videoPid;
                if (videoPid > 0) {
                  videoTrack.pid = videoPid;
                  videoTrack.segmentCodec = parsedPIDs.segmentVideoCodec;
                }
                audioPid = parsedPIDs.audioPid;
                if (audioPid > 0) {
                  audioTrack.pid = audioPid;
                  audioTrack.segmentCodec = parsedPIDs.segmentAudioCodec;
                }
                id3Pid = parsedPIDs.id3Pid;
                if (id3Pid > 0) {
                  id3Track.pid = id3Pid;
                }
                klvPid = parsedPIDs.klvPid;
                if (klvPid > 0) {
                  this._klvPid = klvPid;
                }
                if (unknownPID !== null && !pmtParsed) {
                  unknownPID = null;
                  start = syncOffset - 188;
                }
                pmtParsed = this.pmtParsed = true;
                break;
              }
            case 0x11:
            case 0x1fff:
              break;
            default:
              unknownPID = pid;
              break;
          }
        } else {
          tsPacketErrors++;
        }
      }
      if (tsPacketErrors > 0) {
        emitParsingError(this.observer, new Error("Found " + tsPacketErrors + " TS packet/s that do not start with 0x47"), chunkMeta, undefined, this.logger);
      }
      videoTrack.pesData = videoData;
      audioTrack.pesData = audioData;
      id3Track.pesData = id3Data;
      var demuxResult = {
        audioTrack: audioTrack,
        videoTrack: videoTrack,
        id3Track: id3Track,
        textTrack: textTrack
      };
      if (flush) {
        this.extractRemainingSamples(demuxResult, chunkMeta);
      }
      return demuxResult;
    };
    _proto.flush = function flush(timeOffset, chunkMeta) {
      var remainderData = this.remainderData;
      this.remainderData = null;
      var result;
      if (remainderData) {
        result = this.demux(remainderData, 0, chunkMeta, false, true);
      } else {
        result = {
          videoTrack: this._videoTrack,
          audioTrack: this._audioTrack,
          id3Track: this._id3Track,
          textTrack: this._txtTrack
        };
      }
      this.extractRemainingSamples(result, chunkMeta);
      if (this.sampleAes) {
        return this.decrypt(result, this.sampleAes);
      }
      return result;
    };
    _proto.extractRemainingSamples = function extractRemainingSamples(demuxResult, chunkMeta) {
      var audioTrack = demuxResult.audioTrack,
        videoTrack = demuxResult.videoTrack,
        id3Track = demuxResult.id3Track,
        textTrack = demuxResult.textTrack;
      var videoData = videoTrack.pesData;
      var audioData = audioTrack.pesData;
      var id3Data = id3Track.pesData;
      var videoIntegrityChecker = this.videoIntegrityChecker;
      var pes;
      if (videoData && !(videoIntegrityChecker != null && videoIntegrityChecker.isCorrupted) && (pes = parsePES(videoData, this.logger))) {
        this.readyVideoParser(videoTrack.segmentCodec);
        if (this.videoParser !== null) {
          this.videoParser.parsePES(videoTrack, textTrack, pes, true, chunkMeta);
          videoTrack.pesData = null;
        }
      } else {
        videoTrack.pesData = videoData;
      }
      if (audioData && (pes = parsePES(audioData, this.logger))) {
        switch (audioTrack.segmentCodec) {
          case 'aac':
            this.parseAACPES(audioTrack, pes, chunkMeta);
            break;
          case 'mp3':
            this.parseMPEGPES(audioTrack, pes);
            break;
        }
        audioTrack.pesData = null;
      } else {
        if (audioData != null && audioData.size) {
        }

        audioTrack.pesData = audioData;
      }
      if (id3Data && (pes = parsePES(id3Data, this.logger))) {
        this.parseID3PES(id3Track, pes);
        id3Track.pesData = null;
      } else {
        id3Track.pesData = id3Data;
      }
    };
    _proto.demuxSampleAes = function demuxSampleAes(data, keyData, timeOffset, chunkMeta) {
      var demuxResult = this.demux(data, timeOffset, chunkMeta, true, !this.config.progressive);
      var sampleAes = this.sampleAes = new SampleAesDecrypter(this.observer, this.config, keyData);
      return this.decrypt(demuxResult, sampleAes);
    };
    _proto.readyVideoParser = function readyVideoParser(codec) {
      if (this.videoParser === null) {
        if (codec === 'avc') {
          this.videoParser = new AvcVideoParser();
        }
      }
    };
    _proto.decrypt = function decrypt(demuxResult, sampleAes) {
      return new Promise(function (resolve) {
        var audioTrack = demuxResult.audioTrack,
          videoTrack = demuxResult.videoTrack;
        if (audioTrack.samples && audioTrack.segmentCodec === 'aac') {
          sampleAes.decryptAacSamples(audioTrack.samples, 0, function () {
            if (videoTrack.samples) {
              sampleAes.decryptAvcSamples(videoTrack.samples, 0, 0, function () {
                resolve(demuxResult);
              });
            } else {
              resolve(demuxResult);
            }
          });
        } else if (videoTrack.samples) {
          sampleAes.decryptAvcSamples(videoTrack.samples, 0, 0, function () {
            resolve(demuxResult);
          });
        }
      });
    };
    _proto.destroy = function destroy() {
      if (this.observer) {
        this.observer.removeAllListeners();
      }
      this.config = this.logger = this.observer = null;
      this.aacOverFlow = this.videoParser = this.remainderData = this.sampleAes = null;
      this._videoTrack = this._audioTrack = this._id3Track = this._txtTrack = undefined;
      this.videoIntegrityChecker = null;
    };
    _proto.parseAACPES = function parseAACPES(track, pes, chunkMeta) {
      var startOffset = 0;
      var aacOverFlow = this.aacOverFlow;
      var data = pes.data;
      if (aacOverFlow) {
        this.aacOverFlow = null;
        var frameMissingBytes = aacOverFlow.missing;
        var sampleLength = aacOverFlow.sample.unit.byteLength;
        if (frameMissingBytes === -1) {
          data = appendUint8Array(aacOverFlow.sample.unit, data);
        } else {
          var frameOverflowBytes = sampleLength - frameMissingBytes;
          aacOverFlow.sample.unit.set(data.subarray(0, frameMissingBytes), frameOverflowBytes);
          track.samples.push(aacOverFlow.sample);
          startOffset = aacOverFlow.missing;
        }
      }
      var offset;
      var len;
      for (offset = startOffset, len = data.length; offset < len - 1; offset++) {
        if (isHeader$1(data, offset)) {
          break;
        }
      }
      if (offset !== startOffset) {
        var reason;
        var recoverable = offset < len - 1;
        if (recoverable) {
          reason = "AAC PES did not start with ADTS header,offset:" + offset;
        } else {
          reason = 'No ADTS header found in AAC PES';
        }
        emitParsingError(this.observer, new Error(reason), chunkMeta, recoverable, this.logger);
        if (!recoverable) {
          return;
        }
      }
      initTrackConfig(track, this.observer, data, offset, this.audioCodec);
      var pts;
      if (pes.pts !== undefined) {
        pts = pes.pts;
      } else if (aacOverFlow) {
        var frameDuration = getFrameDuration(track.samplerate);
        pts = aacOverFlow.sample.pts + frameDuration;
      } else {
        return;
      }

      var frameIndex = 0;
      var frame;
      while (offset < len) {
        frame = appendFrame$1(track, data, offset, pts, frameIndex);
        offset += frame.length;
        if (!frame.missing) {
          frameIndex++;
          for (; offset < len - 1; offset++) {
            if (isHeader$1(data, offset)) {
              break;
            }
          }
        } else {
          this.aacOverFlow = frame;
          break;
        }
      }
    };
    _proto.parseMPEGPES = function parseMPEGPES(track, pes) {
      var data = pes.data;
      var length = data.length;
      var frameIndex = 0;
      var offset = 0;
      var pts = pes.pts;
      if (pts === undefined) {
        return;
      }
      while (offset < length) {
        if (isHeader(data, offset)) {
          var frame = appendFrame(track, data, offset, pts, frameIndex);
          if (frame) {
            offset += frame.length;
            frameIndex++;
          } else {
            break;
          }
        } else {
          offset++;
        }
      }
    };
    _proto.parseAC3PES = function parseAC3PES(track, pes) {
    };
    _proto.parseID3PES = function parseID3PES(id3Track, pes) {
      if (pes.pts === undefined) {
        return;
      }
      var id3Sample = _extends({}, pes, {
        type: this._videoTrack ? MetadataSchema.emsg : MetadataSchema.audioId3,
        duration: Number.POSITIVE_INFINITY
      });
      id3Track.samples.push(id3Sample);
    };
    _proto.parseKlvPES = function parseKlvPES(id3Track, pes) {
      var _pes$pts;
      var pts = (_pes$pts = pes.pts) != null ? _pes$pts : pes.dts;
      if (pts === undefined) {
        return;
      }
      var klvData = pes.data;
      if (klvData.length < 16) {
        return;
      }

      var offset = 0;
      while (offset < klvData.length) {
        var _pes$dts;
        if (offset + 16 > klvData.length) {
          break;
        }
        var klvStart = offset;

        offset += 16;
        if (offset >= klvData.length) {
          break;
        }

        var firstByte = klvData[offset];
        offset += 1;
        var length = 0;
        if (firstByte & 0x80) {
          var lengthBytes = firstByte & 0x7f;
          if (lengthBytes === 0 || lengthBytes > 4 || offset + lengthBytes > klvData.length) {
            break;
          }
          for (var i = 0; i < lengthBytes; i++) {
            length = length << 8 | klvData[offset + i];
          }
          offset += lengthBytes;
        } else {
          length = firstByte;
        }
        if (offset + length > klvData.length) {
          break;
        }

        var klvEnd = offset + length;
        var klvPacket = klvData.subarray(klvStart, klvEnd);
        var klvSample = {
          data: klvPacket,
          len: klvPacket.byteLength,
          pts: pts,
          dts: (_pes$dts = pes.dts) != null ? _pes$dts : pts,
          type: MetadataSchema.misbklv,
          duration: Number.POSITIVE_INFINITY
        };
        id3Track.samples.push(klvSample);
        offset = klvEnd;
      }
    };
    return TSDemuxer;
  }();
  function parsePID(data, offset) {
    return ((data[offset + 1] & 0x1f) << 8) + data[offset + 2];
  }
  function parsePAT(data, offset) {
    return (data[offset + 10] & 0x1f) << 8 | data[offset + 11];
  }
  function parsePMT(data, offset, typeSupported, isSampleAes, observer, logger, config, chunkMeta) {
    var result = {
      audioPid: -1,
      videoPid: -1,
      id3Pid: -1,
      klvPid: -1,
      segmentVideoCodec: 'avc',
      segmentAudioCodec: 'aac'
    };
    var sectionLength = (data[offset + 1] & 0x0f) << 8 | data[offset + 2];
    var tableEnd = offset + 3 + sectionLength - 4;
    var programInfoLength = (data[offset + 10] & 0x0f) << 8 | data[offset + 11];
    offset += 12 + programInfoLength;
    while (offset < tableEnd) {
      var pid = parsePID(data, offset);
      var esInfoLength = (data[offset + 3] & 0x0f) << 8 | data[offset + 4];
      switch (data[offset]) {
        case 0xcf:
          if (!isSampleAes) {
            logEncryptedSamplesFoundInUnencryptedStream('ADTS AAC', logger);
            break;
          }
        case 0x0f:
          if (result.audioPid === -1) {
            result.audioPid = pid;
          }
          break;

        case 0x15:
          if (result.id3Pid === -1) {
            result.id3Pid = pid;
          }
          break;
        case 0xdb:
          if (!isSampleAes) {
            logEncryptedSamplesFoundInUnencryptedStream('H.264', logger);
            break;
          }
        case 0x1b:
          if (result.videoPid === -1) {
            result.videoPid = pid;
          }
          break;

        case 0x03:
        case 0x04:
          if (!typeSupported.mpeg && !typeSupported.mp3) {
          } else if (result.audioPid === -1) {
            result.audioPid = pid;
            result.segmentAudioCodec = 'mp3';
          }
          break;
        case 0xc1:
          if (!isSampleAes) {
            logEncryptedSamplesFoundInUnencryptedStream('AC-3', logger);
            break;
          }
        case 0x81:
          {
          }
          break;
        case 0x06:
          if (esInfoLength > 0) {
            var parsePos = offset + 5;
            var remaining = esInfoLength;
            while (remaining > 2) {
              var descriptorId = data[parsePos];
              switch (descriptorId) {
                case 0x6a:
                  if (result.audioPid === -1) {
                    {
                    }
                  }
                  break;
                case 0x05:
                  if (result.klvPid === -1 && config.enableEmsgKLVMetadata) {
                    result.klvPid = pid;
                  }
                  break;
              }
              var descriptorLen = data[parsePos + 1] + 2;
              parsePos += descriptorLen;
              remaining -= descriptorLen;
            }
          }
          break;
        case 0xc2:
        case 0x87:
          emitParsingError(observer, new Error('Unsupported EC-3 in M2TS found'), chunkMeta, undefined, logger);
          return result;
        case 0x24:
          {
            emitParsingError(observer, new Error('Unsupported HEVC in M2TS found'), chunkMeta, undefined, logger);
            return result;
          }
      }
      offset += esInfoLength + 5;
    }
    return result;
  }
  function emitParsingError(observer, error, chunkMeta, levelRetry, logger) {
    observer.emit(Events.ERROR, Events.ERROR, {
      type: ErrorTypes.MEDIA_ERROR,
      details: ErrorDetails.FRAG_PARSING_ERROR,
      fatal: false,
      levelRetry: levelRetry,
      chunkMeta: chunkMeta,
      error: error,
      reason: error.message
    });
  }
  function logEncryptedSamplesFoundInUnencryptedStream(type, logger) {
  }
  function parsePES(stream, logger) {
    var i = 0;
    var frag;
    var pesLen;
    var pesHdrLen;
    var pesPts;
    var pesDts;
    var data = stream.data;
    if (!stream || stream.size === 0) {
      return null;
    }

    while (data[0].length < 19 && data.length > 1) {
      data[0] = appendUint8Array(data[0], data[1]);
      data.splice(1, 1);
    }
    frag = data[0];
    var pesPrefix = (frag[0] << 16) + (frag[1] << 8) + frag[2];
    if (pesPrefix === 1) {
      pesLen = (frag[4] << 8) + frag[5];
      if (pesLen && pesLen > stream.size - 6) {
        return null;
      }
      var pesFlags = frag[7];
      if (pesFlags & 0xc0) {
        pesPts = (frag[9] & 0x0e) * 536870912 +
        (frag[10] & 0xff) * 4194304 +
        (frag[11] & 0xfe) * 16384 +
        (frag[12] & 0xff) * 128 +
        (frag[13] & 0xfe) / 2;
        if (pesFlags & 0x40) {
          pesDts = (frag[14] & 0x0e) * 536870912 +
          (frag[15] & 0xff) * 4194304 +
          (frag[16] & 0xfe) * 16384 +
          (frag[17] & 0xff) * 128 +
          (frag[18] & 0xfe) / 2;
          if (pesPts - pesDts > 60 * 90000) {
            pesPts = pesDts;
          }
        } else {
          pesDts = pesPts;
        }
      }
      pesHdrLen = frag[8];
      var payloadStartOffset = pesHdrLen + 9;
      if (stream.size <= payloadStartOffset) {
        return null;
      }
      stream.size -= payloadStartOffset;
      var pesData = new Uint8Array(stream.size);
      for (var j = 0, dataLen = data.length; j < dataLen; j++) {
        frag = data[j];
        var len = frag.byteLength;
        if (payloadStartOffset) {
          if (payloadStartOffset > len) {
            payloadStartOffset -= len;
            continue;
          } else {
            frag = frag.subarray(payloadStartOffset);
            len -= payloadStartOffset;
            payloadStartOffset = 0;
          }
        }
        pesData.set(frag, i);
        i += len;
      }
      if (pesLen) {
        pesLen -= pesHdrLen + 3;
      }
      return {
        data: pesData,
        pts: pesPts,
        dts: pesDts,
        len: pesLen
      };
    }
    return null;
  }

  var PacketsIntegrityChecker = function () {
    function PacketsIntegrityChecker(logger) {
      this.logger = void 0;
      this.pid = 0;
      this.lastContinuityCounter = -1;
      this.integrityState = 'ok';
      this.logger = logger;
    }
    var _proto2 = PacketsIntegrityChecker.prototype;
    _proto2.reset = function reset(pid) {
      this.pid = pid;
      this.lastContinuityCounter = -1;
      this.integrityState = 'ok';
    };
    _proto2.handlePacket = function handlePacket(data) {
      if (data.byteLength < 4) {
        return;
      }
      var pid = parsePID(data, 0);
      if (pid !== this.pid) {
        return;
      }
      var adaptationFieldControl = (data[3] & 0x30) >> 4;
      if (adaptationFieldControl === 0) {
        return;
      }
      var continuityCounter = data[3] & 0xf;
      var lastContinuityCounter = this.lastContinuityCounter;
      this.lastContinuityCounter = continuityCounter;
      var hasPayload = (adaptationFieldControl & 1) != 0;
      var hasAdaptation = (adaptationFieldControl & 2) != 0;
      var isDiscontinuity = hasAdaptation && data[4] != 0 && (data[5] & 0x80) != 0;
      if (isDiscontinuity) {
        return;
      }
      if (lastContinuityCounter < 0) {
        return;
      }
      var expectedContinuityCounter = hasPayload ? lastContinuityCounter + 1 & 0x0f : lastContinuityCounter;
      if (continuityCounter !== expectedContinuityCounter) {
        this.integrityState = 'cc-failed';
        return;
      }
      if ((data[1] & 0x80) !== 0) {
        this.integrityState = 'tei-bit';
        return;
      }
    };
    return _createClass(PacketsIntegrityChecker, [{
      key: "isCorrupted",
      get: function get() {
        return this.integrityState !== 'ok';
      }
    }]);
  }();

  var AAC = function () {
    function AAC() {}
    AAC.getSilentFrame = function getSilentFrame(codec, channelCount) {
      switch (codec) {
        case 'mp4a.40.2':
          if (channelCount === 1) {
            return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x23, 0x80]);
          } else if (channelCount === 2) {
            return new Uint8Array([0x21, 0x00, 0x49, 0x90, 0x02, 0x19, 0x00, 0x23, 0x80]);
          } else if (channelCount === 3) {
            return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x8e]);
          } else if (channelCount === 4) {
            return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x80, 0x2c, 0x80, 0x08, 0x02, 0x38]);
          } else if (channelCount === 5) {
            return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x82, 0x30, 0x04, 0x99, 0x00, 0x21, 0x90, 0x02, 0x38]);
          } else if (channelCount === 6) {
            return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x82, 0x30, 0x04, 0x99, 0x00, 0x21, 0x90, 0x02, 0x00, 0xb2, 0x00, 0x20, 0x08, 0xe0]);
          }
          break;
        default:
          if (channelCount === 1) {
            return new Uint8Array([0x1, 0x40, 0x22, 0x80, 0xa3, 0x4e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0, 0x0, 0x1c, 0x6, 0xf1, 0xc1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5e]);
          } else if (channelCount === 2) {
            return new Uint8Array([0x1, 0x40, 0x22, 0x80, 0xa3, 0x5e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0, 0x0, 0x0, 0x95, 0x0, 0x6, 0xf1, 0xa1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5e]);
          } else if (channelCount === 3) {
            return new Uint8Array([0x1, 0x40, 0x22, 0x80, 0xa3, 0x5e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0, 0x0, 0x0, 0x95, 0x0, 0x6, 0xf1, 0xa1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5e]);
          }
          break;
      }
      return undefined;
    };
    return AAC;
  }();

  var _MP;
  var HDLR_TYPES = {
    video: new Uint8Array([0x00,
    0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x76, 0x69, 0x64, 0x65,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00
    ]),
    audio: new Uint8Array([0x00,
    0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x73, 0x6f, 0x75, 0x6e,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00
    ])
  };
  var STCO = new Uint8Array([0x00,
  0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00
  ]);
  var STSZ = new Uint8Array([0x00,
  0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00
  ]);
  var VMHD = new Uint8Array([0x00,
  0x00, 0x00, 0x01,
  0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00
  ]);
  var SMHD = new Uint8Array([0x00,
  0x00, 0x00, 0x00,
  0x00, 0x00,
  0x00, 0x00
  ]);
  var STSD = new Uint8Array([0x00,
  0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x01]);

  var majorBrand = new Uint8Array([105, 115, 111, 109]);
  var avc1Brand = new Uint8Array([97, 118, 99, 49]);
  var minorVersion = new Uint8Array([0, 0, 0, 1]);
  var MP4 = function () {
    function MP4() {}
    MP4.box = function box(type) {
      var size = 8;
      for (var _len = arguments.length, payload = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
        payload[_key - 1] = arguments[_key];
      }
      var i = payload.length;
      var len = i;
      while (i--) {
        size += payload[i].byteLength;
      }
      var result = new Uint8Array(size);
      writeUint32(result, 0, size);
      writeUint32(result, 4, type);
      for (i = 0, size = 8; i < len; i++) {
        result.set(payload[i], size);
        size += payload[i].byteLength;
      }
      return result;
    };
    MP4.hdlr = function hdlr(type) {
      return MP4.box(types.hdlr, HDLR_TYPES[type]);
    };
    MP4.mdat = function mdat(data) {
      return MP4.box(types.mdat, data);
    };
    MP4.mdhd = function mdhd(timescale, duration) {
      duration *= timescale;
      var upperWordDuration = Math.floor(duration / (UINT32_MAX + 1));
      var lowerWordDuration = Math.floor(duration % (UINT32_MAX + 1));
      return MP4.box(types.mdhd, new Uint8Array([0x01,
      0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
      timescale >> 24 & 0xff, timescale >> 16 & 0xff, timescale >> 8 & 0xff, timescale & 0xff,
      upperWordDuration >> 24, upperWordDuration >> 16 & 0xff, upperWordDuration >> 8 & 0xff, upperWordDuration & 0xff, lowerWordDuration >> 24, lowerWordDuration >> 16 & 0xff, lowerWordDuration >> 8 & 0xff, lowerWordDuration & 0xff, 0x55, 0xc4,
      0x00, 0x00]));
    };
    MP4.mdia = function mdia(track) {
      return MP4.box(types.mdia, MP4.mdhd(track.timescale || 0, track.duration || 0), MP4.hdlr(track.type), MP4.minf(track));
    };
    MP4.mfhd = function mfhd(sequenceNumber) {
      return MP4.box(types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00,
      sequenceNumber >> 24, sequenceNumber >> 16 & 0xff, sequenceNumber >> 8 & 0xff, sequenceNumber & 0xff
      ]));
    };
    MP4.minf = function minf(track) {
      if (track.type === 'audio') {
        return MP4.box(types.minf, MP4.box(types.smhd, SMHD), MP4.DINF, MP4.stbl(track));
      } else {
        return MP4.box(types.minf, MP4.box(types.vmhd, VMHD), MP4.DINF, MP4.stbl(track));
      }
    };
    MP4.moof = function moof(sn, baseMediaDecodeTime, track) {
      return MP4.box(types.moof, MP4.mfhd(sn), MP4.traf(track, baseMediaDecodeTime));
    };
    MP4.moov = function moov(tracks) {
      var i = tracks.length;
      var boxes = [];
      while (i--) {
        boxes[i] = MP4.trak(tracks[i]);
      }
      return MP4.box.apply(null, [types.moov, MP4.mvhd(tracks[0].timescale || 0, tracks[0].duration || 0)].concat(boxes).concat(MP4.mvex(tracks)));
    };
    MP4.mvex = function mvex(tracks) {
      var i = tracks.length;
      var boxes = [];
      while (i--) {
        boxes[i] = MP4.trex(tracks[i]);
      }
      return MP4.box.apply(null, [types.mvex].concat(boxes));
    };
    MP4.mvhd = function mvhd(timescale, duration) {
      duration *= timescale;
      var upperWordDuration = Math.floor(duration / (UINT32_MAX + 1));
      var lowerWordDuration = Math.floor(duration % (UINT32_MAX + 1));
      var bytes = new Uint8Array([0x01,
      0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
      timescale >> 24 & 0xff, timescale >> 16 & 0xff, timescale >> 8 & 0xff, timescale & 0xff,
      upperWordDuration >> 24, upperWordDuration >> 16 & 0xff, upperWordDuration >> 8 & 0xff, upperWordDuration & 0xff, lowerWordDuration >> 24, lowerWordDuration >> 16 & 0xff, lowerWordDuration >> 8 & 0xff, lowerWordDuration & 0xff, 0x00, 0x01, 0x00, 0x00,
      0x01, 0x00,
      0x00, 0x00,
      0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00,
      0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0xff, 0xff, 0xff, 0xff
      ]);
      return MP4.box(types.mvhd, bytes);
    };
    MP4.sdtp = function sdtp(track) {
      var samples = track.samples || [];
      var bytes = new Uint8Array(4 + samples.length);
      var i;
      var flags;
      for (i = 0; i < samples.length; i++) {
        flags = samples[i].flags;
        bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy;
      }
      return MP4.box(types.sdtp, bytes);
    };
    MP4.stbl = function stbl(track) {
      return MP4.box(types.stbl, MP4.stsd(track), MP4.box(types.stts, STCO), MP4.box(types.stsc, STCO), MP4.box(types.stsz, STSZ), MP4.box(types.stco, STCO));
    };
    MP4.avc1 = function avc1(track) {
      var sps = [];
      var pps = [];
      var i;
      var data;
      var len;

      for (i = 0; i < track.sps.length; i++) {
        data = track.sps[i];
        len = data.byteLength;
        sps.push(len >>> 8 & 0xff);
        sps.push(len & 0xff);

        sps = sps.concat(Array.prototype.slice.call(data));
      }

      for (i = 0; i < track.pps.length; i++) {
        data = track.pps[i];
        len = data.byteLength;
        pps.push(len >>> 8 & 0xff);
        pps.push(len & 0xff);
        pps = pps.concat(Array.prototype.slice.call(data));
      }
      var avcc = MP4.box(types.avcC, new Uint8Array([0x01,
      sps[3],
      sps[4],
      sps[5],
      0xfc | 3,
      0xe0 | track.sps.length
      ].concat(sps).concat([track.pps.length
      ]).concat(pps)));
      var width = track.width;
      var height = track.height;
      var hSpacing = track.pixelRatio[0];
      var vSpacing = track.pixelRatio[1];
      return MP4.box(types.avc1, new Uint8Array([0x00, 0x00, 0x00,
      0x00, 0x00, 0x00,
      0x00, 0x01,
      0x00, 0x00,
      0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      width >> 8 & 0xff, width & 0xff,
      height >> 8 & 0xff, height & 0xff,
      0x00, 0x48, 0x00, 0x00,
      0x00, 0x48, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00,
      0x00, 0x01,
      0x12, 0x64, 0x61, 0x69, 0x6c,
      0x79, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x68, 0x6c, 0x73, 0x2e, 0x6a, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x18,
      0x11, 0x11]),
      avcc, MP4.box(types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
      0x00, 0x2d, 0xc6, 0xc0,
      0x00, 0x2d, 0xc6, 0xc0])),
      MP4.box(types.pasp, new Uint8Array([hSpacing >> 24,
      hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24,
      vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff])));
    };
    MP4.esds = function esds(track) {
      var config = track.config;
      var len = config.length;
      return new Uint8Array([0x00,
      0x00, 0x00, 0x00,

      0x03,
      0x17 + len,

      0x00, 0x01,

      0x00,

      0x04,
      0x0f + len,
      0x40,
      0x15,
      0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00,

      0x05,
      len].concat(config, [0x06, 0x01, 0x02
      ]));
    };
    MP4.audioStsd = function audioStsd(track) {
      var samplerate = track.samplerate || 0;
      return new Uint8Array([0x00, 0x00, 0x00,
      0x00, 0x00, 0x00,
      0x00, 0x01,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, track.channelCount || 0,
      0x00, 0x10,
      0x00, 0x00, 0x00, 0x00,
      samplerate >> 8 & 0xff, samplerate & 0xff,
      0x00, 0x00]);
    };
    MP4.mp4a = function mp4a(track) {
      return MP4.box(types.mp4a, MP4.audioStsd(track), MP4.box(types.esds, MP4.esds(track)));
    };
    MP4.mp3 = function mp3(track) {
      return MP4.box(types['.mp3'], MP4.audioStsd(track));
    };
    MP4.ac3 = function ac3(track) {
      return MP4.box(types['ac-3'], MP4.audioStsd(track), MP4.box(types.dac3, track.config));
    };
    MP4.stsd = function stsd(track) {
      var segmentCodec = track.segmentCodec;
      if (track.type === 'audio') {
        if (segmentCodec === 'aac') {
          return MP4.box(types.stsd, STSD, MP4.mp4a(track));
        }
        if (segmentCodec === 'mp3' && track.codec === 'mp3') {
          return MP4.box(types.stsd, STSD, MP4.mp3(track));
        }
      } else {
        if (track.pps && track.sps) {
          if (segmentCodec === 'avc') {
            return MP4.box(types.stsd, STSD, MP4.avc1(track));
          }
        } else {
          throw new Error("video track missing pps or sps");
        }
      }
      throw new Error("unsupported " + track.type + " segment codec (" + segmentCodec + "/" + track.codec + ")");
    };
    MP4.tkhd = function tkhd(track) {
      var id = track.id;
      var duration = (track.duration || 0) * (track.timescale || 0);
      var width = track.width || 0;
      var height = track.height || 0;
      var upperWordDuration = Math.floor(duration / (UINT32_MAX + 1));
      var lowerWordDuration = Math.floor(duration % (UINT32_MAX + 1));
      return MP4.box(types.tkhd, new Uint8Array([0x01,
      0x00, 0x00, 0x07,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
      id >> 24 & 0xff, id >> 16 & 0xff, id >> 8 & 0xff, id & 0xff,
      0x00, 0x00, 0x00, 0x00,
      upperWordDuration >> 24, upperWordDuration >> 16 & 0xff, upperWordDuration >> 8 & 0xff, upperWordDuration & 0xff, lowerWordDuration >> 24, lowerWordDuration >> 16 & 0xff, lowerWordDuration >> 8 & 0xff, lowerWordDuration & 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00,
      0x00, 0x00,
      0x00, 0x00,
      0x00, 0x00,
      0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
      width >> 8 & 0xff, width & 0xff, 0x00, 0x00,
      height >> 8 & 0xff, height & 0xff, 0x00, 0x00
      ]));
    };
    MP4.traf = function traf(track, baseMediaDecodeTime) {
      var sampleDependencyTable = MP4.sdtp(track);
      var id = track.id;
      var upperWordBaseMediaDecodeTime = Math.floor(baseMediaDecodeTime / (UINT32_MAX + 1));
      var lowerWordBaseMediaDecodeTime = Math.floor(baseMediaDecodeTime % (UINT32_MAX + 1));
      return MP4.box(types.traf, MP4.box(types.tfhd, new Uint8Array([0x00,
      0x00, 0x00, 0x00,
      id >> 24, id >> 16 & 0xff, id >> 8 & 0xff, id & 0xff
      ])), MP4.box(types.tfdt, new Uint8Array([0x01,
      0x00, 0x00, 0x00,
      upperWordBaseMediaDecodeTime >> 24, upperWordBaseMediaDecodeTime >> 16 & 0xff, upperWordBaseMediaDecodeTime >> 8 & 0xff, upperWordBaseMediaDecodeTime & 0xff, lowerWordBaseMediaDecodeTime >> 24, lowerWordBaseMediaDecodeTime >> 16 & 0xff, lowerWordBaseMediaDecodeTime >> 8 & 0xff, lowerWordBaseMediaDecodeTime & 0xff])), MP4.trun(track, sampleDependencyTable.length + 16 +
      20 +
      8 +
      16 +
      8 +
      8),
      sampleDependencyTable);
    }

;
    MP4.trak = function trak(track) {
      track.duration = track.duration || 0xffffffff;
      return MP4.box(types.trak, MP4.tkhd(track), MP4.mdia(track));
    };
    MP4.trex = function trex(track) {
      var id = track.id;
      return MP4.box(types.trex, new Uint8Array([0x00,
      0x00, 0x00, 0x00,
      id >> 24, id >> 16 & 0xff, id >> 8 & 0xff, id & 0xff,
      0x00, 0x00, 0x00, 0x01,
      0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00,
      0x00, 0x01, 0x00, 0x01
      ]));
    };
    MP4.trun = function trun(track, offset) {
      var samples = track.samples || [];
      var len = samples.length;
      var arraylen = 12 + 16 * len;
      var array = new Uint8Array(arraylen);
      var i;
      var sample;
      var duration;
      var size;
      var flags;
      var cts;
      offset += 8 + arraylen;
      array.set([track.type === 'video' ? 0x01 : 0x00,
      0x00, 0x0f, 0x01,
      len >>> 24 & 0xff, len >>> 16 & 0xff, len >>> 8 & 0xff, len & 0xff,
      offset >>> 24 & 0xff, offset >>> 16 & 0xff, offset >>> 8 & 0xff, offset & 0xff
      ], 0);
      for (i = 0; i < len; i++) {
        sample = samples[i];
        duration = sample.duration;
        size = sample.size;
        flags = sample.flags;
        cts = sample.cts;
        array.set([duration >>> 24 & 0xff, duration >>> 16 & 0xff, duration >>> 8 & 0xff, duration & 0xff,
        size >>> 24 & 0xff, size >>> 16 & 0xff, size >>> 8 & 0xff, size & 0xff,
        flags.isLeading << 2 | flags.dependsOn, flags.isDependedOn << 6 | flags.hasRedundancy << 4 | flags.paddingValue << 1 | flags.isNonSync, flags.degradPrio & 0xf0 << 8, flags.degradPrio & 0x0f,
        cts >>> 24 & 0xff, cts >>> 16 & 0xff, cts >>> 8 & 0xff, cts & 0xff
        ], 12 + 16 * i);
      }
      return MP4.box(types.trun, array);
    };
    MP4.initSegment = function initSegment(tracks) {
      var movie = MP4.moov(tracks);
      var result = appendUint8Array(MP4.FTYP, movie);
      return result;
    };
    MP4.hvc1 = function hvc1(track) {
      {
        return new Uint8Array();
      }
    };
    return MP4;
  }();
  _MP = MP4;
  MP4.FTYP = _MP.box(types.ftyp, majorBrand, minorVersion, majorBrand, avc1Brand);
  MP4.DINF = _MP.box(types.dinf, _MP.box(types.dref, new Uint8Array([0x00,
  0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x01,
  0x00, 0x00, 0x00, 0x0c,
  0x75, 0x72, 0x6c, 0x20,
  0x00,
  0x00, 0x00, 0x01
  ])));

  var MPEG_TS_CLOCK_FREQ_HZ = 90000;
  function toTimescaleFromBase(baseTime, destScale, srcBase, round) {
    var result = baseTime * destScale * srcBase;
    return Math.round(result) ;
  }
  function toMsFromMpegTsClock(baseTime, round) {
    return toTimescaleFromBase(baseTime, 1000, 1 / MPEG_TS_CLOCK_FREQ_HZ);
  }
  function timestampToString(timestamp) {
    var baseTime = timestamp.baseTime,
      timescale = timestamp.timescale,
      trackId = timestamp.trackId;
    return baseTime / timescale + " (" + baseTime + "/" + timescale + ") trackId: " + trackId;
  }

  var MAX_SILENT_FRAME_DURATION = 10 * 1000;
  var AAC_SAMPLES_PER_FRAME = 1024;
  var MPEG_AUDIO_SAMPLE_PER_FRAME = 1152;
  var AC3_SAMPLES_PER_FRAME = 1536;
  var MPEG_TS_PTS_ROLLOVER = 8589934592;

  function createMp4Sample(isKeyframe, duration, size, cts) {
    return {
      duration: duration,
      size: size,
      cts: cts,
      flags: {
        isLeading: 0,
        isDependedOn: 0,
        hasRedundancy: 0,
        degradPrio: 0,
        dependsOn: isKeyframe ? 2 : 1,
        isNonSync: isKeyframe ? 0 : 1,
        paddingValue: 0
      }
    };
  }
  var MP4Remuxer = function (_Logger) {
    function MP4Remuxer(observer, config, typeSupported, logger) {
      var _this;
      _this = _Logger.call(this, 'mp4-remuxer', logger) || this;
      _this.observer = void 0;
      _this.config = void 0;
      _this.typeSupported = void 0;
      _this.ISGenerated = false;
      _this._initPTS = null;
      _this._initDTS = null;
      _this.nextVideoTs = null;
      _this.nextAudioTs = null;
      _this.videoSampleDuration = null;
      _this.isAudioContiguous = false;
      _this.isVideoContiguous = false;
      _this.videoTrackConfig = void 0;
      _this.observer = observer;
      _this.config = config;
      _this.typeSupported = typeSupported;
      _this.ISGenerated = false;
      return _this;
    }
    _inheritsLoose(MP4Remuxer, _Logger);
    var _proto = MP4Remuxer.prototype;
    _proto.destroy = function destroy() {
      this.config = this.videoTrackConfig = this._initPTS = this._initDTS = null;
    };
    _proto.resetTimeStamp = function resetTimeStamp(defaultTimeStamp) {
      var initPTS = this._initPTS;
      if (!initPTS ||
      !defaultTimeStamp || defaultTimeStamp.trackId !== initPTS.trackId || defaultTimeStamp.baseTime !== initPTS.baseTime || defaultTimeStamp.timescale !== initPTS.timescale) {
      }
      this._initPTS = this._initDTS = defaultTimeStamp;
    };
    _proto.resetNextTimestamp = function resetNextTimestamp() {
      this.isVideoContiguous = false;
      this.isAudioContiguous = false;
    };
    _proto.resetInitSegment = function resetInitSegment() {
      this.ISGenerated = false;
      this.videoTrackConfig = undefined;
    };
    _proto.getVideoStartPts = function getVideoStartPts(videoSamples) {
      var rolloverDetected = false;
      var firstPts = videoSamples[0].pts;
      var startPTS = videoSamples.reduce(function (minPTS, sample) {
        var pts = sample.pts;
        var delta = pts - minPTS;
        if (delta < -4294967296) {
          rolloverDetected = true;
          pts = normalizePts(pts, firstPts);
          delta = pts - minPTS;
        }
        if (delta > 0) {
          return minPTS;
        }
        return pts;
      }, firstPts);
      if (rolloverDetected) {
      }
      return startPTS;
    };
    _proto.remux = function remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOffset, flush, playlistType, chunkMeta) {
      var video;
      var audio;
      var initSegment;
      var text;
      var id3;
      var independent;
      var audioTimeOffset = timeOffset;
      var videoTimeOffset = timeOffset;

      var iframesOnly = chunkMeta.iframe;
      var hasAudio = !iframesOnly && audioTrack.pid > -1;
      var hasVideo = videoTrack.pid > -1;
      var length = videoTrack.samples.length;
      var enoughAudioSamples = audioTrack.samples.length > 0;
      var enoughVideoSamples = flush && length > 0 || length > 1;
      var canRemuxAvc = (!hasAudio || enoughAudioSamples) && (!hasVideo || enoughVideoSamples) || this.ISGenerated || flush;
      if (canRemuxAvc) {
        if (this.ISGenerated) {
          var _videoTrack$pixelRati, _config$pixelRatio, _videoTrack$pixelRati2, _config$pixelRatio2;
          var config = this.videoTrackConfig;
          if (config && (videoTrack.width !== config.width || videoTrack.height !== config.height || ((_videoTrack$pixelRati = videoTrack.pixelRatio) == null ? void 0 : _videoTrack$pixelRati[0]) !== ((_config$pixelRatio = config.pixelRatio) == null ? void 0 : _config$pixelRatio[0]) || ((_videoTrack$pixelRati2 = videoTrack.pixelRatio) == null ? void 0 : _videoTrack$pixelRati2[1]) !== ((_config$pixelRatio2 = config.pixelRatio) == null ? void 0 : _config$pixelRatio2[1])) || !config && enoughVideoSamples || this.nextAudioTs === null && enoughAudioSamples) {
            this.resetInitSegment();
          }
        }
        if (!this.ISGenerated) {
          initSegment = this.generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset);
        }
        var isVideoContiguous = this.isVideoContiguous;
        var firstKeyFrameIndex = -1;
        var firstKeyFramePTS;
        if (enoughVideoSamples) {
          firstKeyFrameIndex = findKeyframeIndex(videoTrack.samples);
          if (!isVideoContiguous && this.config.forceKeyFrameOnDiscontinuity) {
            independent = true;
            if (firstKeyFrameIndex > 0) {
              var startPTS = this.getVideoStartPts(videoTrack.samples);
              videoTrack.samples = videoTrack.samples.slice(firstKeyFrameIndex);
              videoTrack.dropped += firstKeyFrameIndex;
              videoTimeOffset += (videoTrack.samples[0].pts - startPTS) / videoTrack.inputTimeScale;
              firstKeyFramePTS = videoTimeOffset;
            } else if (firstKeyFrameIndex === -1) {
              independent = false;
            }
          }
        }
        if (this.ISGenerated) {
          if (enoughAudioSamples && enoughVideoSamples && timeOffset) {
            var _startPTS = this.getVideoStartPts(videoTrack.samples);
            var tsDelta = normalizePts(audioTrack.samples[0].pts, _startPTS) - _startPTS;
            var audiovideoTimestampDelta = tsDelta / videoTrack.inputTimeScale;
            audioTimeOffset += Math.max(0, audiovideoTimestampDelta);
            videoTimeOffset += Math.max(0, -audiovideoTimestampDelta);
          }

          if (enoughAudioSamples) {
            if (!audioTrack.samplerate) {
              initSegment = this.generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset);
            }
            audio = this.remuxAudio(audioTrack, audioTimeOffset, this.isAudioContiguous, accurateTimeOffset, hasVideo || enoughVideoSamples || playlistType === PlaylistLevelType.AUDIO ? videoTimeOffset : undefined, chunkMeta);
            if (enoughVideoSamples) {
              var audioTrackLength = audio ? audio.endPTS - audio.startPTS : 0;
              if (!videoTrack.inputTimeScale) {
                initSegment = this.generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset);
              }
              video = this.remuxVideo(videoTrack, videoTimeOffset, isVideoContiguous, audioTrackLength, chunkMeta);
            }
          } else if (enoughVideoSamples) {
            video = this.remuxVideo(videoTrack, videoTimeOffset, isVideoContiguous, 0, chunkMeta);
          }
          if (video) {
            video.firstKeyFrame = firstKeyFrameIndex;
            video.independent = firstKeyFrameIndex !== -1;
            video.firstKeyFramePTS = firstKeyFramePTS;
          }
        }
      }

      if (this.ISGenerated && this._initPTS && this._initDTS) {
        if (id3Track.samples.length) {
          id3 = flushTextTrackMetadataCueSamples(id3Track, timeOffset, this._initPTS, this._initDTS);
        }
        if (textTrack.samples.length) {
          text = flushTextTrackUserdataCueSamples(textTrack, timeOffset, this._initPTS);
        }
      }
      return {
        audio: audio,
        video: video,
        initSegment: initSegment,
        independent: independent,
        text: text,
        id3: id3
      };
    };
    _proto.computeInitPts = function computeInitPts(basetime, timescale, presentationTime, type) {
      var offset = Math.round(presentationTime * timescale);
      var timestamp = normalizePts(basetime, offset);
      if (timestamp < offset) {
        while (timestamp < offset) {
          timestamp += MPEG_TS_PTS_ROLLOVER;
        }
      }
      return timestamp - offset;
    };
    _proto.generateIS = function generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset) {
      var audioSamples = audioTrack.samples;
      var videoSamples = videoTrack.samples;
      var typeSupported = this.typeSupported;
      var tracks = {};
      var _initPTS = this._initPTS;
      var computePTSDTS = !_initPTS || accurateTimeOffset;
      var container = 'audio/mp4';
      var initPTS;
      var initDTS;
      var timescale;
      var trackId = -1;
      if (computePTSDTS) {
        initPTS = initDTS = Infinity;
      }
      if (audioTrack.config && audioSamples.length) {
        audioTrack.timescale = audioTrack.samplerate;
        switch (audioTrack.segmentCodec) {
          case 'mp3':
            if (typeSupported.mpeg) {
              container = 'audio/mpeg';
              audioTrack.codec = '';
            } else if (typeSupported.mp3) {
              audioTrack.codec = 'mp3';
            }
            break;
          case 'ac3':
            audioTrack.codec = 'ac-3';
            break;
        }
        tracks.audio = {
          id: 'audio',
          container: container,
          codec: audioTrack.codec,
          initSegment: audioTrack.segmentCodec === 'mp3' && typeSupported.mpeg ? new Uint8Array(0) : MP4.initSegment([audioTrack]),
          metadata: {
            channelCount: audioTrack.channelCount
          }
        };
        if (computePTSDTS) {
          trackId = audioTrack.id;
          timescale = audioTrack.inputTimeScale;
          var initBaseTimestamp = this.computeInitPts(audioSamples[0].pts, timescale, timeOffset, 'audio');
          if (
          !_initPTS || timescale !== _initPTS.timescale || timeOffset === 0 && initBaseTimestamp < _initPTS.baseTime) {
            initPTS = initDTS = initBaseTimestamp;
          }
        }
      }
      if (videoTrack.sps && videoTrack.pps && videoSamples.length) {
        videoTrack.timescale = videoTrack.inputTimeScale;
        tracks.video = {
          id: 'main',
          container: 'video/mp4',
          codec: videoTrack.codec,
          initSegment: MP4.initSegment([videoTrack]),
          metadata: {
            width: videoTrack.width,
            height: videoTrack.height
          }
        };
        if (computePTSDTS) {
          trackId = videoTrack.id;
          timescale = videoTrack.inputTimeScale;
          var basePTS = this.getVideoStartPts(videoSamples);
          var baseDTS = normalizePts(videoSamples[0].dts, basePTS);
          var videoInitDTS = this.computeInitPts(baseDTS, timescale, timeOffset, 'video');
          var videoInitPTS = this.computeInitPts(basePTS, timescale, timeOffset, 'video');
          if (
          !_initPTS || timescale !== _initPTS.timescale || isFiniteNumber(initPTS) || timeOffset === 0 && videoInitPTS < _initPTS.baseTime) {
            initDTS = Math.min(initDTS, videoInitDTS);
            initPTS = Math.min(initPTS, videoInitDTS);
          }
        }
        this.videoTrackConfig = {
          width: videoTrack.width,
          height: videoTrack.height,
          pixelRatio: videoTrack.pixelRatio
        };
      }
      if (Object.keys(tracks).length) {
        this.ISGenerated = true;
        if (computePTSDTS && isFiniteNumber(initPTS)) {
          if (_initPTS) {
          }
          this._initPTS = {
            baseTime: initPTS,
            timescale: timescale,
            trackId: trackId
          };
          this._initDTS = {
            baseTime: initDTS,
            timescale: timescale,
            trackId: trackId
          };
        } else {
          initPTS = timescale = undefined;
        }
        return {
          tracks: tracks,
          initPTS: initPTS,
          timescale: timescale,
          trackId: trackId
        };
      }
    };
    _proto.remuxVideo = function remuxVideo(track, timeOffset, contiguous, audioTrackLength, chunkMeta) {
      var timeScale = track.inputTimeScale;
      var inputSamples = track.samples;
      var outputSamples = [];
      var nbSamples = inputSamples.length;
      var initPTS = this._initPTS;
      var initTime = initPTS.baseTime * timeScale / initPTS.timescale;
      var nextVideoTs = this.nextVideoTs;
      var offset = 8;
      var mp4SampleDuration = this.videoSampleDuration;
      var firstDTS;
      var lastDTS;
      var minPTS = Number.POSITIVE_INFINITY;
      var maxPTS = Number.NEGATIVE_INFINITY;
      var sortSamples = false;

      if (!contiguous || nextVideoTs === null) {
        var pts = initTime + timeOffset * timeScale;
        var cts = inputSamples[0].pts - normalizePts(inputSamples[0].dts, inputSamples[0].pts);
        if (userAgentChromeVersion() && nextVideoTs !== null && Math.abs(pts - cts - (nextVideoTs + initTime)) < 15000) {
          contiguous = true;
        } else {
          nextVideoTs = pts - cts - initTime;
        }
      }
      var nextVideoPts = nextVideoTs + initTime;
      for (var i = 0; i < nbSamples; i++) {
        var sample = inputSamples[i];
        sample.pts = normalizePts(sample.pts, nextVideoPts);
        sample.dts = normalizePts(sample.dts, nextVideoPts);
        if (sample.dts < inputSamples[i > 0 ? i - 1 : i].dts) {
          sortSamples = true;
        }
      }

      if (sortSamples) {
        inputSamples.sort(function (a, b) {
          var deltadts = a.dts - b.dts;
          var deltapts = a.pts - b.pts;
          return deltadts || deltapts;
        });
      }

      firstDTS = inputSamples[0].dts;
      lastDTS = inputSamples[inputSamples.length - 1].dts;

      var inputDuration = lastDTS - firstDTS;
      var averageSampleDuration = inputDuration ? Math.round(inputDuration / (nbSamples - 1)) : mp4SampleDuration || track.inputTimeScale / 30;

      if (contiguous) {
        var delta = firstDTS - nextVideoPts;
        var foundHole = delta > averageSampleDuration;
        var foundOverlap = delta < -1;
        if (foundHole || foundOverlap) {
          if (foundHole) {
          } else {
          }
          if (!foundOverlap || nextVideoPts >= inputSamples[0].pts || userAgentChromeVersion()) {
            firstDTS = nextVideoPts;
            var firstPTS = inputSamples[0].pts - delta;
            if (foundHole) {
              inputSamples[0].dts = firstDTS;
              inputSamples[0].pts = firstPTS;
            } else {
              var isPTSOrderRetained = true;
              for (var _i = 0; _i < inputSamples.length; _i++) {
                if (inputSamples[_i].dts > firstPTS && isPTSOrderRetained) {
                  break;
                }
                var prevPTS = inputSamples[_i].pts;
                inputSamples[_i].dts -= delta;
                inputSamples[_i].pts -= delta;

                if (_i < inputSamples.length - 1) {
                  var nextSamplePTS = inputSamples[_i + 1].pts;
                  var currentSamplePTS = inputSamples[_i].pts;
                  var currentOrder = nextSamplePTS <= currentSamplePTS;
                  var prevOrder = nextSamplePTS <= prevPTS;
                  isPTSOrderRetained = currentOrder == prevOrder;
                }
              }
            }
          }
        }
      }
      firstDTS = Math.max(0, firstDTS);
      var nbNalu = 0;
      var naluLen = 0;
      var dtsStep = firstDTS;
      for (var _i2 = 0; _i2 < nbSamples; _i2++) {
        var _sample = inputSamples[_i2];
        var units = _sample.units;
        var nbUnits = units.length;
        var sampleLen = 0;
        for (var j = 0; j < nbUnits; j++) {
          sampleLen += units[j].data.length;
        }
        naluLen += sampleLen;
        nbNalu += nbUnits;
        _sample.length = sampleLen;

        if (_sample.dts < dtsStep) {
          _sample.dts = dtsStep;
          dtsStep += averageSampleDuration / 4 | 0 || 1;
        } else {
          dtsStep = _sample.dts;
        }
        minPTS = Math.min(_sample.pts, minPTS);
        maxPTS = Math.max(_sample.pts, maxPTS);
      }
      lastDTS = inputSamples[nbSamples - 1].dts;

      var mdatSize = naluLen + 4 * nbNalu + 8;
      var mdat;
      try {
        mdat = new Uint8Array(mdatSize);
      } catch (err) {
        this.observer.emit(Events.ERROR, Events.ERROR, {
          type: ErrorTypes.MUX_ERROR,
          details: ErrorDetails.REMUX_ALLOC_ERROR,
          fatal: false,
          chunkMeta: chunkMeta,
          error: err,
          bytes: mdatSize,
          reason: "fail allocating video mdat " + mdatSize
        });
        return;
      }
      writeUint32(mdat, 0, mdatSize);
      writeUint32(mdat, 4, types.mdat);
      var stretchedLastFrame = false;
      var minDtsDelta = Number.POSITIVE_INFINITY;
      var minPtsDelta = Number.POSITIVE_INFINITY;
      var maxDtsDelta = Number.NEGATIVE_INFINITY;
      var maxPtsDelta = Number.NEGATIVE_INFINITY;
      for (var _i3 = 0; _i3 < nbSamples; _i3++) {
        var VideoSample = inputSamples[_i3];
        var VideoSampleUnits = VideoSample.units;
        var mp4SampleLength = 0;
        for (var _j = 0, _nbUnits = VideoSampleUnits.length; _j < _nbUnits; _j++) {
          var unit = VideoSampleUnits[_j];
          var unitData = unit.data;
          var unitDataLen = unit.data.byteLength;
          writeUint32(mdat, offset, unitDataLen);
          offset += 4;
          mdat.set(unitData, offset);
          offset += unitDataLen;
          mp4SampleLength += 4 + unitDataLen;
        }

        var ptsDelta = void 0;
        if (_i3 < nbSamples - 1) {
          mp4SampleDuration = inputSamples[_i3 + 1].dts - VideoSample.dts;
          ptsDelta = inputSamples[_i3 + 1].pts - VideoSample.pts;
        } else {
          var config = this.config;
          var lastFrameDuration = _i3 > 0 ? VideoSample.dts - inputSamples[_i3 - 1].dts : averageSampleDuration;
          ptsDelta = _i3 > 0 ? VideoSample.pts - inputSamples[_i3 - 1].pts : averageSampleDuration;
          if (config.stretchShortVideoTrack && this.nextAudioTs !== null) {
            var gapTolerance = Math.floor(config.maxBufferHole * timeScale);
            var deltaToFrameEnd = (audioTrackLength ? minPTS + audioTrackLength * timeScale : this.nextAudioTs + initTime) - VideoSample.pts;
            if (deltaToFrameEnd > gapTolerance) {
              mp4SampleDuration = deltaToFrameEnd - lastFrameDuration;
              if (mp4SampleDuration < 0) {
                mp4SampleDuration = lastFrameDuration;
              } else {
                stretchedLastFrame = true;
              }
            } else {
              mp4SampleDuration = lastFrameDuration;
            }
          } else {
            mp4SampleDuration = lastFrameDuration;
          }
        }
        var compositionTimeOffset = Math.round(VideoSample.pts - VideoSample.dts);
        minDtsDelta = Math.min(minDtsDelta, mp4SampleDuration);
        maxDtsDelta = Math.max(maxDtsDelta, mp4SampleDuration);
        minPtsDelta = Math.min(minPtsDelta, ptsDelta);
        maxPtsDelta = Math.max(maxPtsDelta, ptsDelta);

        if (!contiguous && _i3 === 0 && compositionTimeOffset > mp4SampleDuration) {
          var tickPaddedCompositionTime = Math.round(compositionTimeOffset / averageSampleDuration) * averageSampleDuration;
          if (tickPaddedCompositionTime - compositionTimeOffset === 1) {
            compositionTimeOffset = tickPaddedCompositionTime;
          }
        }
        outputSamples.push(createMp4Sample(VideoSample.key, mp4SampleDuration, mp4SampleLength, compositionTimeOffset));
      }
      if (outputSamples.length) {
        var chromeVersion = userAgentChromeVersion();
        if (chromeVersion) {
          if (chromeVersion < 70) {
            var flags = outputSamples[0].flags;
            flags.dependsOn = 2;
            flags.isNonSync = 0;
          }
        } else if (userAgentSafariVersion()) {
          if (maxPtsDelta - minPtsDelta < maxDtsDelta - minDtsDelta && averageSampleDuration / maxDtsDelta < 0.025 && outputSamples[0].cts === 0) {
            var dts = firstDTS;
            for (var _i4 = 0, len = outputSamples.length; _i4 < len; _i4++) {
              var nextDts = dts + outputSamples[_i4].duration;
              var _pts = dts + outputSamples[_i4].cts;
              if (_i4 < len - 1) {
                var nextPts = nextDts + outputSamples[_i4 + 1].cts;
                outputSamples[_i4].duration = nextPts - _pts;
              } else {
                outputSamples[_i4].duration = _i4 ? outputSamples[_i4 - 1].duration : averageSampleDuration;
              }
              outputSamples[_i4].cts = 0;
              dts = nextDts;
            }
          }
        }
      }
      mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration;
      var endDTS = lastDTS + mp4SampleDuration;
      this.nextVideoTs = nextVideoTs = endDTS - initTime;
      this.videoSampleDuration = mp4SampleDuration;
      this.isVideoContiguous = true;
      var moof = MP4.moof(chunkMeta.sn, firstDTS, _extends(track, {
        samples: outputSamples
      }));
      var type = 'video';
      var data = {
        data1: moof,
        data2: mdat,
        startPTS: (minPTS - initTime) / timeScale,
        endPTS: (maxPTS + mp4SampleDuration - initTime) / timeScale,
        startDTS: (firstDTS - initTime) / timeScale,
        endDTS: nextVideoTs / timeScale,
        type: type,
        hasAudio: false,
        hasVideo: true,
        nb: outputSamples.length,
        dropped: track.dropped
      };
      track.samples = [];
      track.dropped = 0;
      return data;
    };
    _proto.getSamplesPerFrame = function getSamplesPerFrame(track) {
      switch (track.segmentCodec) {
        case 'mp3':
          return MPEG_AUDIO_SAMPLE_PER_FRAME;
        case 'ac3':
          return AC3_SAMPLES_PER_FRAME;
        default:
          return AAC_SAMPLES_PER_FRAME;
      }
    };
    _proto.remuxAudio = function remuxAudio(track, timeOffset, contiguous, accurateTimeOffset, videoTimeOffset, chunkMeta) {
      var inputTimeScale = track.inputTimeScale;
      var mp4timeScale = track.samplerate ? track.samplerate : inputTimeScale;
      var scaleFactor = inputTimeScale / mp4timeScale;
      var mp4SampleDuration = this.getSamplesPerFrame(track);
      var inputSampleDuration = mp4SampleDuration * scaleFactor;
      var initPTS = this._initPTS;
      var rawMPEG = track.segmentCodec === 'mp3' && this.typeSupported.mpeg;
      var outputSamples = [];
      var alignedWithVideo = videoTimeOffset !== undefined;
      var inputSamples = track.samples;
      var offset = rawMPEG ? 0 : 8;
      var nextAudioTs = this.nextAudioTs || -1;

      var initTime = initPTS.baseTime * inputTimeScale / initPTS.timescale;
      var timeOffsetMpegTS = initTime + timeOffset * inputTimeScale;
      this.isAudioContiguous = contiguous = contiguous || inputSamples.length && nextAudioTs > 0 && (accurateTimeOffset && Math.abs(timeOffsetMpegTS - (nextAudioTs + initTime)) < 9000 || Math.abs(normalizePts(inputSamples[0].pts, timeOffsetMpegTS) - (nextAudioTs + initTime)) < 20 * inputSampleDuration);

      inputSamples.forEach(function (sample) {
        sample.pts = normalizePts(sample.pts, timeOffsetMpegTS);
      });
      if (!contiguous || nextAudioTs < 0) {
        var sampleCount = inputSamples.length;
        inputSamples = inputSamples.filter(function (sample) {
          return sample.pts >= 0;
        });
        if (sampleCount !== inputSamples.length) {
        }

        if (!inputSamples.length) {
          return;
        }
        if (videoTimeOffset === 0) {
          nextAudioTs = 0;
        } else if (accurateTimeOffset && !alignedWithVideo) {
          nextAudioTs = Math.max(0, timeOffsetMpegTS - initTime);
        } else {
          nextAudioTs = inputSamples[0].pts - initTime;
        }
      }

      if (track.segmentCodec === 'aac') {
        var maxAudioFramesDrift = this.config.maxAudioFramesDrift;
        for (var i = 0, nextPts = nextAudioTs + initTime; i < inputSamples.length; i++) {
          var sample = inputSamples[i];
          var pts = sample.pts;
          var delta = pts - nextPts;
          var duration = Math.abs(1000 * delta / inputTimeScale);

          if (delta <= -maxAudioFramesDrift * inputSampleDuration && alignedWithVideo) {
            if (i === 0) {
              this.nextAudioTs = nextAudioTs = pts - initTime;
              nextPts = pts;
            }
          }

          else if (delta >= maxAudioFramesDrift * inputSampleDuration && duration < MAX_SILENT_FRAME_DURATION && alignedWithVideo) {
            var missing = Math.round(delta / inputSampleDuration);
            nextPts = pts - missing * inputSampleDuration;
            while (nextPts < 0 && missing && inputSampleDuration) {
              missing--;
              nextPts += inputSampleDuration;
            }
            if (i === 0) {
              this.nextAudioTs = nextAudioTs = nextPts - initTime;
            }
            for (var j = 0; j < missing; j++) {
              var fillFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
              if (!fillFrame) {
                fillFrame = sample.unit.subarray();
              }
              inputSamples.splice(i, 0, {
                unit: fillFrame,
                pts: nextPts
              });
              nextPts += inputSampleDuration;
              i++;
            }
          }
          sample.pts = nextPts;
          nextPts += inputSampleDuration;
        }
      }
      var firstPTS = null;
      var lastPTS = null;
      var mdat;
      var mdatSize = 0;
      var sampleLength = inputSamples.length;
      while (sampleLength--) {
        mdatSize += inputSamples[sampleLength].unit.byteLength;
      }
      for (var _j2 = 0, _nbSamples = inputSamples.length; _j2 < _nbSamples; _j2++) {
        var audioSample = inputSamples[_j2];
        var unit = audioSample.unit;
        var _pts2 = audioSample.pts;
        if (lastPTS !== null) {
          var prevSample = outputSamples[_j2 - 1];
          prevSample.duration = Math.round((_pts2 - lastPTS) / scaleFactor);
        } else {
          if (contiguous && track.segmentCodec === 'aac') {
            _pts2 = nextAudioTs + initTime;
          }
          firstPTS = _pts2;
          if (mdatSize > 0) {
            mdatSize += offset;
            try {
              mdat = new Uint8Array(mdatSize);
            } catch (err) {
              this.observer.emit(Events.ERROR, Events.ERROR, {
                type: ErrorTypes.MUX_ERROR,
                details: ErrorDetails.REMUX_ALLOC_ERROR,
                fatal: false,
                chunkMeta: chunkMeta,
                error: err,
                bytes: mdatSize,
                reason: "fail allocating audio mdat " + mdatSize
              });
              return;
            }
            if (!rawMPEG) {
              writeUint32(mdat, 0, mdatSize);
              writeUint32(mdat, 4, types.mdat);
            }
          } else {
            return;
          }
        }
        if (mdat) {
          mdat.set(unit, offset);
        }
        var unitLen = unit.byteLength;
        offset += unitLen;
        outputSamples.push(createMp4Sample(true, mp4SampleDuration, unitLen, 0));
        lastPTS = _pts2;
      }

      var nbSamples = outputSamples.length;
      if (!nbSamples) {
        return;
      }

      var lastSample = outputSamples[outputSamples.length - 1];
      nextAudioTs = lastPTS - initTime;
      this.nextAudioTs = nextAudioTs + scaleFactor * lastSample.duration;

      var moof = rawMPEG ? new Uint8Array(0) : MP4.moof(track.sequenceNumber++, firstPTS / scaleFactor, _extends({}, track, {
        samples: outputSamples
      }));

      track.samples = [];
      var start = (firstPTS - initTime) / inputTimeScale;
      var end = this.nextAudioTs / inputTimeScale;
      var type = 'audio';
      var audioData = {
        data1: moof,
        data2: mdat,
        startPTS: start,
        endPTS: end,
        startDTS: start,
        endDTS: end,
        type: type,
        hasAudio: true,
        hasVideo: false,
        nb: nbSamples
      };
      this.isAudioContiguous = true;
      return audioData;
    };
    return MP4Remuxer;
  }(Logger);
  function normalizePts(value, reference) {
    var offset;
    if (reference === null) {
      return value;
    }
    if (reference < value) {
      offset = -MPEG_TS_PTS_ROLLOVER;
    } else {
      offset = MPEG_TS_PTS_ROLLOVER;
    }
    while (Math.abs(value - reference) > 4294967296) {
      value += offset;
    }
    return value;
  }
  function findKeyframeIndex(samples) {
    for (var i = 0; i < samples.length; i++) {
      if (samples[i].key) {
        return i;
      }
    }
    return -1;
  }
  function flushTextTrackMetadataCueSamples(track, timeOffset, initPTS, initDTS) {
    var length = track.samples.length;
    if (!length) {
      return;
    }
    var inputTimeScale = track.inputTimeScale;
    for (var index = 0; index < length; index++) {
      var sample = track.samples[index];
      sample.pts = normalizePts(sample.pts - initPTS.baseTime * inputTimeScale / initPTS.timescale, timeOffset * inputTimeScale) / inputTimeScale;
      sample.dts = normalizePts(sample.dts - initDTS.baseTime * inputTimeScale / initDTS.timescale, timeOffset * inputTimeScale) / inputTimeScale;
    }
    var samples = track.samples;
    track.samples = [];
    return {
      samples: samples
    };
  }
  function flushTextTrackUserdataCueSamples(track, timeOffset, initPTS) {
    var length = track.samples.length;
    if (!length) {
      return;
    }
    var inputTimeScale = track.inputTimeScale;
    for (var index = 0; index < length; index++) {
      var sample = track.samples[index];
      sample.pts = normalizePts(sample.pts - initPTS.baseTime * inputTimeScale / initPTS.timescale, timeOffset * inputTimeScale) / inputTimeScale;
    }
    track.samples.sort(function (a, b) {
      return a.pts - b.pts;
    });
    var samples = track.samples;
    track.samples = [];
    return {
      samples: samples
    };
  }

  var PassThroughRemuxer = function (_Logger) {
    function PassThroughRemuxer(observer, config, typeSupported, logger) {
      var _this;
      _this = _Logger.call(this, 'passthrough-remuxer', logger) || this;
      _this.observer = void 0;
      _this.emitInitSegment = false;
      _this.audioCodec = void 0;
      _this.videoCodec = void 0;
      _this.initData = void 0;
      _this.initPTS = null;
      _this.initTracks = void 0;
      _this.lastEndTime = null;
      _this.isVideoContiguous = false;
      _this.videoOnlyRemux = false;
      _this.decryptdata = null;
      _this.pendingInitSegment = void 0;
      _this.observer = observer;
      return _this;
    }
    _inheritsLoose(PassThroughRemuxer, _Logger);
    var _proto = PassThroughRemuxer.prototype;
    _proto.destroy = function destroy() {
      if (this.observer) {
        this.observer.removeAllListeners();
      }
      this.observer = null;
    };
    _proto.resetTimeStamp = function resetTimeStamp(defaultInitPTS) {
      this.lastEndTime = null;
      var initPTS = this.initPTS;
      if (initPTS && defaultInitPTS) {
        if (initPTS.baseTime === defaultInitPTS.baseTime && initPTS.timescale === defaultInitPTS.timescale) {
          return;
        }
      }
      this.initPTS = defaultInitPTS;
    };
    _proto.resetNextTimestamp = function resetNextTimestamp() {
      this.isVideoContiguous = false;
      this.lastEndTime = null;
    };
    _proto.resetInitSegment = function resetInitSegment(initSegment, audioCodec, videoCodec, decryptdata) {
      this.audioCodec = audioCodec;
      this.videoCodec = videoCodec;
      this.decryptdata = decryptdata;
      this.pendingInitSegment = initSegment;
      this.videoOnlyRemux = false;
      this.initTracks = undefined;
      this.initData = undefined;
      this.emitInitSegment = true;
    };
    _proto.generateInitSegment = function generateInitSegment(initSegment, decryptdata, parsedInitData) {
      this.videoOnlyRemux = false;
      var audioCodec = this.audioCodec,
        videoCodec = this.videoCodec;
      if (!(initSegment != null && initSegment.byteLength)) {
        this.initTracks = undefined;
        this.initData = undefined;
        return;
      }
      var _this$initData = this.initData = parsedInitData || parseInitSegment(initSegment),
        audio = _this$initData.audio,
        video = _this$initData.video;
      if (decryptdata) {
        patchEncyptionData(initSegment, decryptdata);
      } else {
        var eitherTrack = audio || video;
        if (eitherTrack != null && eitherTrack.encrypted) {
        }
      }

      if (audio) {
        audioCodec = getParsedTrackCodec(audio, ElementaryStreamTypes.AUDIO, this);
      }
      if (video) {
        videoCodec = getParsedTrackCodec(video, ElementaryStreamTypes.VIDEO, this);
      }
      var tracks = {};
      if (audio && video) {
        tracks.audiovideo = {
          container: 'video/mp4',
          codec: audioCodec + ',' + videoCodec,
          supplemental: video.supplemental,
          encrypted: video.encrypted,
          initSegment: initSegment,
          id: 'main'
        };
      } else if (audio) {
        tracks.audio = {
          container: 'audio/mp4',
          codec: audioCodec,
          encrypted: audio.encrypted,
          initSegment: initSegment,
          id: 'audio'
        };
      } else if (video) {
        tracks.video = {
          container: 'video/mp4',
          codec: videoCodec,
          supplemental: video.supplemental,
          encrypted: video.encrypted,
          initSegment: initSegment,
          id: 'main'
        };
      } else {
      }
      this.initTracks = tracks;
    };
    _proto.remux = function remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOffset, flush, playlistType, chunkMeta, parsedInitData, parsedSampleData) {
      var _initData, _initData2;
      var initPTS = this.initPTS,
        lastEndTime = this.lastEndTime;
      var result = {
        audio: undefined,
        video: undefined,
        text: undefined,
        id3: id3Track,
        initSegment: undefined
      };

      if (!isFiniteNumber(lastEndTime)) {
        lastEndTime = this.lastEndTime = timeOffset || 0;
      }

      var data = videoTrack.samples;
      if (!data.length) {
        return result;
      }
      var pendingInitSegment = this.pendingInitSegment;
      if (pendingInitSegment) {
        this.pendingInitSegment = undefined;
        this.generateInitSegment(pendingInitSegment, this.decryptdata, parsedInitData);
      }
      var initSegment = {
        initPTS: undefined,
        timescale: undefined,
        trackId: undefined
      };
      var initData = this.initData;
      if (!((_initData = initData) != null && _initData.length)) {
        this.generateInitSegment(data);
        initData = this.initData;
      }
      if (!((_initData2 = initData) != null && _initData2.length)) {
        return result;
      }
      if (this.emitInitSegment) {
        initSegment.tracks = this.initTracks;
        result.initSegment = initSegment;
        this.emitInitSegment = false;
      }
      var trackSampleData = parsedSampleData && (true) ? parsedSampleData.tracks : getSampleData(data, initData, chunkMeta, this);
      var audioSampleTimestamps = initData.audio ? trackSampleData[initData.audio.id] : null;
      var videoSampleTimestamps = initData.video ? trackSampleData[initData.video.id] : null;
      var hasAudio = !!initData.audio;
      var hasVideo = !!initData.video;
      var type = '';
      if (hasAudio) {
        type += 'audio';
      }
      if (hasVideo) {
        type += 'video';
      }
      var videoStartTime = toStartEndOrDefault(videoSampleTimestamps, Infinity);
      var audioStartTime = toStartEndOrDefault(audioSampleTimestamps, Infinity);
      var videoEndTime = toStartEndOrDefault(videoSampleTimestamps, 0, true);
      var audioEndTime = toStartEndOrDefault(audioSampleTimestamps, 0, true);
      var decodeTime = timeOffset;
      var duration = 0;
      if (videoSampleTimestamps && audioSampleTimestamps && initData.audio && (audioStartTime > videoEndTime || videoStartTime > audioEndTime)) {
      }
      var syncOnAudio = !!audioSampleTimestamps && (!videoSampleTimestamps || !initPTS && audioStartTime < videoStartTime || !!initPTS && initPTS.trackId === initData.audio.id);
      var baseOffsetSamples = syncOnAudio ? audioSampleTimestamps : videoSampleTimestamps;
      if (!baseOffsetSamples) {
        return result;
      }
      var data1 = data;
      var data2;
      {
        duration = syncOnAudio ? audioEndTime - audioStartTime : videoEndTime - videoStartTime;
      }
      var timescale = baseOffsetSamples.timescale;
      var baseTime = baseOffsetSamples.start - timeOffset * timescale;
      var trackId = syncOnAudio ? initData.audio.id : initData.video.id;
      decodeTime = baseOffsetSamples.start / timescale;
      if ((accurateTimeOffset || !initPTS) && (isInvalidInitPts(initPTS, decodeTime, timeOffset, duration) || timescale !== initPTS.timescale)) {
        var detectedDrift = false;
        var trackType = syncOnAudio ? 'audio' : 'video';
        if (initPTS) {
          var driftEstimate = timeOffset !== 0 ? decodeTime / timeOffset : 1 + decodeTime / 1;
          detectedDrift = decodeTime >= 0 && Math.abs(1 - driftEstimate) < 0.001;
        }
        if (!detectedDrift) {
          initPTS = null;
          initSegment.initPTS = baseTime;
          initSegment.timescale = timescale;
          initSegment.trackId = trackId;
        }
      }
      if (!initPTS) {
        if (!initSegment.timescale || initSegment.trackId === undefined || initSegment.initPTS === undefined) {
          initSegment.initPTS = decodeTime;
          initSegment.timescale = 1;
          initSegment.trackId = -1;
        }
        this.initPTS = initPTS = {
          baseTime: initSegment.initPTS,
          timescale: initSegment.timescale,
          trackId: initSegment.trackId
        };
      } else {
        initSegment.initPTS = initPTS.baseTime;
        initSegment.timescale = initPTS.timescale;
        initSegment.trackId = initPTS.trackId;
      }
      var startDTS = decodeTime - initPTS.baseTime / initPTS.timescale;
      var endDTS = startDTS + duration;
      var startPTS = hasVideo && (baseOffsetSamples == null ? void 0 : baseOffsetSamples.ptsMin) !== undefined ? baseOffsetSamples.ptsMin / baseOffsetSamples.timescale - initPTS.baseTime / initPTS.timescale : startDTS;
      var endPTS = hasVideo && baseOffsetSamples != null && baseOffsetSamples.ptsMax ? baseOffsetSamples.ptsMax / baseOffsetSamples.timescale - initPTS.baseTime / initPTS.timescale : endDTS;

      if (duration > 0) {
        this.lastEndTime = endDTS;
      } else {
        this.resetNextTimestamp();
      }
      var encrypted = (initData.audio ? initData.audio.encrypted : false) || (initData.video ? initData.video.encrypted : false);
      var track = {
        data1: data1,
        data2: data2,
        startPTS: startPTS,
        startDTS: startDTS,
        endPTS: endPTS,
        endDTS: endDTS,
        type: type,
        hasAudio: hasAudio,
        hasVideo: hasVideo,
        nb: 1,
        dropped: 0,
        encrypted: encrypted
      };
      result.audio = hasAudio && !hasVideo ? track : undefined;
      result.video = hasVideo ? track : undefined;
      var isVideoContiguous = this.isVideoContiguous;
      var videoSampleCount = videoSampleTimestamps == null ? void 0 : videoSampleTimestamps.sampleCount;
      if (videoSampleCount) {
        var firstKeyFrame = videoSampleTimestamps.keyFrameIndex;
        var independent = firstKeyFrame !== -1;
        track.nb = videoSampleCount;
        track.dropped = firstKeyFrame === 0 || isVideoContiguous ? 0 : independent ? firstKeyFrame : videoSampleCount;
        track.independent = independent;
        track.firstKeyFrame = firstKeyFrame;
        if (independent && videoSampleTimestamps.keyFrameStart) {
          track.firstKeyFramePTS = (videoSampleTimestamps.keyFrameStart - initPTS.baseTime) / initPTS.timescale;
        }
        if (!isVideoContiguous) {
          result.independent = independent;
        }
        this.isVideoContiguous || (this.isVideoContiguous = independent);
        if (track.dropped) {
        }
      }
      result.initSegment = initSegment;
      result.id3 = flushTextTrackMetadataCueSamples(id3Track, timeOffset, initPTS, initPTS);
      if (textTrack.samples.length) {
        result.text = flushTextTrackUserdataCueSamples(textTrack, timeOffset, initPTS);
      }
      return result;
    };
    return PassThroughRemuxer;
  }(Logger);
  function toStartEndOrDefault(trackTimes, defaultValue, end) {
    if (end === void 0) {
      end = false;
    }
    return (trackTimes == null ? void 0 : trackTimes.start) !== undefined ? (trackTimes.start + (end ? trackTimes.duration : 0)) / trackTimes.timescale : defaultValue;
  }
  function isInvalidInitPts(initPTS, startDTS, timeOffset, duration) {
    if (initPTS === null) {
      return true;
    }
    var minDuration = Math.max(duration, 1);
    var startTime = startDTS - initPTS.baseTime / initPTS.timescale;
    return Math.abs(startTime - timeOffset) >= minDuration;
  }
  function getParsedTrackCodec(track, type, logger) {
    var parsedCodec = track.codec;
    if (parsedCodec && parsedCodec.length > 4) {
      return parsedCodec;
    }
    if (type === ElementaryStreamTypes.AUDIO) {
      if (parsedCodec === 'ec-3' || parsedCodec === 'ac-3' || parsedCodec === 'alac') {
        return parsedCodec;
      }
      if (parsedCodec === 'fLaC' || parsedCodec === 'Opus') {
        var preferManagedMediaSource = false;
        return getCodecCompatibleName(parsedCodec, preferManagedMediaSource);
      }
      return parsedCodec || 'mp4a';
    } else if (parsedCodec === 'mjpg') {
      return parsedCodec;
    }
    return parsedCodec || 'avc1';
  }

  var now;
  try {
    now = self.performance.now.bind(self.performance);
  } catch (err) {
    now = Date.now;
  }
  var muxConfig = [{
    demux: MP4Demuxer,
    remux: PassThroughRemuxer
  }, {
    demux: TSDemuxer,
    remux: MP4Remuxer
  }];
  var Transmuxer = function () {
    function Transmuxer(observer, typeSupported, config, vendor, id, logger) {
      this.asyncResult = false;
      this.logger = void 0;
      this.observer = void 0;
      this.typeSupported = void 0;
      this.config = void 0;
      this.id = void 0;
      this.demuxer = void 0;
      this.remuxer = void 0;
      this.decrypter = void 0;
      this.probe = void 0;
      this.decryptionPromise = null;
      this.transmuxConfig = void 0;
      this.currentTransmuxState = void 0;
      this.observer = observer;
      this.typeSupported = typeSupported;
      this.config = config;
      this.id = id;
      this.logger = logger;
    }
    var _proto = Transmuxer.prototype;
    _proto.configure = function configure(transmuxConfig) {
      this.transmuxConfig = transmuxConfig;
      if (this.decrypter) {
        this.decrypter.reset();
      }
    };
    _proto.push = function push(data, decryptdata, chunkMeta, state) {
      var _this = this;
      if (!this.observer) {
        return emptyResult(chunkMeta);
      }
      var stats = chunkMeta.transmuxing;
      stats.executeStart = now();
      var uintData = new Uint8Array(data);
      var currentTransmuxState = this.currentTransmuxState,
        transmuxConfig = this.transmuxConfig;
      if (state) {
        this.currentTransmuxState = state;
      }
      var _ref = state || currentTransmuxState,
        contiguous = _ref.contiguous,
        discontinuity = _ref.discontinuity,
        trackSwitch = _ref.trackSwitch,
        accurateTimeOffset = _ref.accurateTimeOffset,
        timeOffset = _ref.timeOffset,
        initSegmentChange = _ref.initSegmentChange;
      var audioCodec = transmuxConfig.audioCodec,
        videoCodec = transmuxConfig.videoCodec,
        defaultInitPts = transmuxConfig.defaultInitPts,
        duration = transmuxConfig.duration,
        initSegmentData = transmuxConfig.initSegmentData;
      var keyData = getEncryptionType(uintData, decryptdata);
      if (keyData && isFullSegmentEncryption(keyData.method)) {
        var decrypter = this.getDecrypter();
        var aesMode = getAesModeFromFullSegmentMethod(keyData.method);
        this.asyncResult = true;
        this.decryptionPromise = decrypter.decrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode, chunkMeta.decryptRange).then(function (decryptedData) {
          var result = _this.push(decryptedData, null, chunkMeta);
          _this.decryptionPromise = null;
          return result;
        });
        return this.decryptionPromise;
      }
      var resetMuxers = this.needsProbing(discontinuity, trackSwitch);
      if (resetMuxers) {
        var segmentFormatData = chunkMeta.iframe && initSegmentData && TSDemuxer.probe(initSegmentData, this.logger) ? initSegmentData : uintData;
        var error = this.configureTransmuxer(segmentFormatData);
        if (error) {
          this.observer.emit(Events.ERROR, Events.ERROR, {
            type: ErrorTypes.MEDIA_ERROR,
            details: ErrorDetails.FRAG_PARSING_ERROR,
            fatal: false,
            error: error,
            reason: error.message
          });
          stats.executeEnd = now();
          return emptyResult(chunkMeta);
        }
      }
      if (discontinuity || trackSwitch || initSegmentChange || resetMuxers) {
        this.resetInitSegment(initSegmentData, audioCodec, videoCodec, duration, decryptdata, chunkMeta);
      }
      if (discontinuity || initSegmentChange || resetMuxers) {
        this.resetInitialTimestamp(defaultInitPts);
      }
      if (!contiguous) {
        this.resetContiguity();
      }
      var result = this.transmux(uintData, keyData, timeOffset, accurateTimeOffset, chunkMeta);
      this.asyncResult = isPromise(result);
      var currentState = this.currentTransmuxState;
      currentState.contiguous = true;
      currentState.discontinuity = false;
      currentState.trackSwitch = false;
      stats.executeEnd = now();
      return result;
    }

  ;
    _proto.flush = function flush(chunkMeta) {
      var _this2 = this;
      var stats = chunkMeta.transmuxing;
      stats.executeStart = now();
      var decrypter = this.decrypter,
        currentTransmuxState = this.currentTransmuxState,
        decryptionPromise = this.decryptionPromise;
      if (decryptionPromise) {
        this.asyncResult = true;
        return decryptionPromise.then(function () {
          return _this2.flush(chunkMeta);
        });
      }
      var transmuxResults = [];
      var timeOffset = currentTransmuxState.timeOffset;
      if (decrypter) {
        var decryptedData = decrypter.flush();
        if (decryptedData) {
          transmuxResults.push(this.push(decryptedData.buffer, null, chunkMeta));
        }
      }
      var demuxer = this.demuxer,
        remuxer = this.remuxer;
      if (!demuxer || !remuxer) {
        stats.executeEnd = now();
        var emptyResults = [emptyResult(chunkMeta)];
        if (this.asyncResult) {
          return Promise.resolve(emptyResults);
        }
        return emptyResults;
      }
      var demuxResultOrPromise = demuxer.flush(timeOffset, chunkMeta);
      if (isPromise(demuxResultOrPromise)) {
        this.asyncResult = true;
        return demuxResultOrPromise.then(function (demuxResult) {
          _this2.flushRemux(transmuxResults, demuxResult, chunkMeta);
          return transmuxResults;
        });
      }
      this.flushRemux(transmuxResults, demuxResultOrPromise, chunkMeta);
      if (this.asyncResult) {
        return Promise.resolve(transmuxResults);
      }
      return transmuxResults;
    };
    _proto.flushRemux = function flushRemux(transmuxResults, demuxResult, chunkMeta) {
      if (!this.remuxer) {
        return;
      }
      var audioTrack = demuxResult.audioTrack,
        videoTrack = demuxResult.videoTrack,
        id3Track = demuxResult.id3Track,
        textTrack = demuxResult.textTrack;
      var _this$currentTransmux = this.currentTransmuxState,
        accurateTimeOffset = _this$currentTransmux.accurateTimeOffset,
        timeOffset = _this$currentTransmux.timeOffset;
      var remuxResult = this.remuxer.remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOffset, true, this.id, chunkMeta, demuxResult.initData, demuxResult.sampleData);
      transmuxResults.push({
        remuxResult: remuxResult,
        chunkMeta: chunkMeta
      });
      chunkMeta.transmuxing.executeEnd = now();
    };
    _proto.resetInitialTimestamp = function resetInitialTimestamp(defaultInitPts) {
      var demuxer = this.demuxer,
        remuxer = this.remuxer;
      if (!demuxer || !remuxer) {
        return;
      }
      demuxer.resetTimeStamp(defaultInitPts);
      remuxer.resetTimeStamp(defaultInitPts);
    };
    _proto.resetContiguity = function resetContiguity() {
      var demuxer = this.demuxer,
        remuxer = this.remuxer;
      if (!demuxer || !remuxer) {
        return;
      }
      demuxer.resetContiguity();
      remuxer.resetNextTimestamp();
    };
    _proto.resetInitSegment = function resetInitSegment(initSegmentData, audioCodec, videoCodec, trackDuration, decryptdata, chunkMeta) {
      var demuxer = this.demuxer,
        remuxer = this.remuxer;
      if (!demuxer || !remuxer) {
        return;
      }
      demuxer.resetInitSegment(initSegmentData, audioCodec, videoCodec, trackDuration, decryptdata, chunkMeta);
      remuxer.resetInitSegment(initSegmentData, audioCodec, videoCodec, decryptdata);
    };
    _proto.destroy = function destroy() {
      if (this.demuxer) {
        this.demuxer.destroy();
        this.demuxer = undefined;
      }
      if (this.remuxer) {
        this.remuxer.destroy();
        this.remuxer = undefined;
      }
      if (this.observer) {
        this.observer.removeAllListeners();
      }
      this.decryptionPromise = null;
      this.decrypter = this.observer = undefined;
      this.logger = this.config = this.probe = undefined;
    };
    _proto.transmux = function transmux(data, keyData, timeOffset, accurateTimeOffset, chunkMeta) {
      var result;
      if ((keyData == null ? void 0 : keyData.method) === 'SAMPLE-AES') {
        result = this.transmuxSampleAes(data, keyData, timeOffset, accurateTimeOffset, chunkMeta);
      } else {
        result = this.transmuxUnencrypted(data, timeOffset, accurateTimeOffset, chunkMeta);
      }
      return result;
    };
    _proto.transmuxUnencrypted = function transmuxUnencrypted(data, timeOffset, accurateTimeOffset, chunkMeta) {
      var demuxer = this.demuxer,
        remuxer = this.remuxer;
      if (!demuxer || !remuxer) {
        return emptyResult(chunkMeta);
      }
      var demuxResult = demuxer.demux(data, timeOffset, chunkMeta, false, !this.config.progressive);
      var audioTrack = demuxResult.audioTrack,
        videoTrack = demuxResult.videoTrack,
        id3Track = demuxResult.id3Track,
        textTrack = demuxResult.textTrack;
      var remuxResult = remuxer.remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOffset, false, this.id, chunkMeta, demuxResult.initData, demuxResult.sampleData);
      return {
        remuxResult: remuxResult,
        chunkMeta: chunkMeta
      };
    };
    _proto.transmuxSampleAes = function transmuxSampleAes(data, decryptData, timeOffset, accurateTimeOffset, chunkMeta) {
      var _this3 = this;
      if (!this.demuxer) {
        return Promise.reject(new Error('no demuxer'));
      }
      return this.demuxer.demuxSampleAes(data, decryptData, timeOffset, chunkMeta).then(function (demuxResult) {
        var remuxResult = _this3.remuxer.remux(demuxResult.audioTrack, demuxResult.videoTrack, demuxResult.id3Track, demuxResult.textTrack, timeOffset, accurateTimeOffset, false, _this3.id, chunkMeta, demuxResult.initData, demuxResult.sampleData);
        return {
          remuxResult: remuxResult,
          chunkMeta: chunkMeta
        };
      });
    };
    _proto.configureTransmuxer = function configureTransmuxer(data) {
      var config = this.config,
        observer = this.observer,
        typeSupported = this.typeSupported;
      if (!observer) {
        return;
      }
      var mux;
      try {
        for (var i = 0, len = muxConfig.length; i < len; i++) {
          var _muxConfig$i$demux;
          if ((_muxConfig$i$demux = muxConfig[i].demux) != null && _muxConfig$i$demux.probe(data, this.logger)) {
            mux = muxConfig[i];
            break;
          }
        }
      } catch (error) {
        return error;
      }
      if (!mux) {
        return new Error('Failed to find demuxer by probing fragment data');
      }
      var demuxer = this.demuxer;
      var remuxer = this.remuxer;
      var Demuxer = mux.demux;
      var Remuxer = mux.remux;
      if (!demuxer || !(demuxer instanceof Demuxer)) {
        this.demuxer = new Demuxer(observer, config, typeSupported, this.logger);
        this.probe = Demuxer.probe;
      }
      if (!remuxer || !(remuxer instanceof Remuxer)) {
        this.remuxer = new Remuxer(observer, config, typeSupported, this.logger);
      }
    };
    _proto.needsProbing = function needsProbing(discontinuity, trackSwitch) {
      return !this.demuxer || !this.remuxer || discontinuity || trackSwitch;
    };
    _proto.getDecrypter = function getDecrypter() {
      var decrypter = this.decrypter;
      if (!decrypter) {
        decrypter = this.decrypter = new Decrypter(this.config);
      }
      return decrypter;
    };
    return Transmuxer;
  }();
  function getEncryptionType(data, decryptData) {
    var encryptionType = null;
    if (data.byteLength > 0 && (decryptData == null ? void 0 : decryptData.key) != null && decryptData.iv !== null && decryptData.method != null) {
      encryptionType = decryptData;
    }
    return encryptionType;
  }
  var emptyResult = function emptyResult(chunkMeta) {
    return {
      remuxResult: {},
      chunkMeta: chunkMeta
    };
  };
  function isPromise(p) {
    return 'then' in p && p.then instanceof Function;
  }
  var TransmuxConfig = function TransmuxConfig(audioCodec, videoCodec, initSegmentData, duration, defaultInitPts) {
    this.audioCodec = void 0;
    this.videoCodec = void 0;
    this.initSegmentData = void 0;
    this.duration = void 0;
    this.defaultInitPts = void 0;
    this.audioCodec = audioCodec;
    this.videoCodec = videoCodec;
    this.initSegmentData = initSegmentData;
    this.duration = duration;
    this.defaultInitPts = defaultInitPts || null;
  };
  var TransmuxState = function TransmuxState(discontinuity, contiguous, accurateTimeOffset, trackSwitch, timeOffset, initSegmentChange) {
    this.discontinuity = void 0;
    this.contiguous = void 0;
    this.accurateTimeOffset = void 0;
    this.trackSwitch = void 0;
    this.timeOffset = void 0;
    this.initSegmentChange = void 0;
    this.discontinuity = discontinuity;
    this.contiguous = contiguous;
    this.accurateTimeOffset = accurateTimeOffset;
    this.trackSwitch = trackSwitch;
    this.timeOffset = timeOffset;
    this.initSegmentChange = initSegmentChange;
  };

  var transmuxers = [];
  if (typeof __IN_WORKER__ !== 'undefined' && __IN_WORKER__) {
    startWorker();
  }
  function startWorker() {
    self.addEventListener('message', function (ev) {
      var data = ev.data;
      var instanceNo = data.instanceNo;
      if (instanceNo === undefined) {
        return;
      }
      var transmuxer = transmuxers[instanceNo];
      if (data.cmd === 'reset') {
        delete transmuxers[data.resetNo];
        if (transmuxer) {
          transmuxer.destroy();
        }
        data.cmd = 'init';
      }
      if (data.cmd === 'init') {
        var config = JSON.parse(data.config);
        var observer = new EventEmitter();
        observer.on(Events.FRAG_DECRYPTED, function (event, data) {
          return forwardMessage(event, data, instanceNo);
        });
        observer.on(Events.ERROR, function (event, data) {
          return forwardMessage(event, data, instanceNo);
        });
        var logger = enableLogs(config.debug, data.id);
        forwardWorkerLogs(logger, instanceNo);
        transmuxers[instanceNo] = new Transmuxer(observer, data.typeSupported, config, '', data.id, logger);
        forwardMessage('init', null, instanceNo);
        return;
      }
      if (!transmuxer) {
        return;
      }
      switch (data.cmd) {
        case 'configure':
          {
            transmuxer.configure(data.config);
            break;
          }
        case 'demux':
          {
            var transmuxResult = transmuxer.push(data.data, data.decryptdata, data.chunkMeta, data.state);
            if (isPromise(transmuxResult)) {
              transmuxResult.then(function (data) {
                emitTransmuxComplete(self, data, instanceNo);
              }).catch(function (error) {
                forwardMessage(Events.ERROR, {
                  instanceNo: instanceNo,
                  type: ErrorTypes.MEDIA_ERROR,
                  details: ErrorDetails.FRAG_PARSING_ERROR,
                  chunkMeta: data.chunkMeta,
                  fatal: false,
                  error: error,
                  err: error,
                  reason: "transmuxer-worker push error"
                }, instanceNo);
              });
            } else {
              emitTransmuxComplete(self, transmuxResult, instanceNo);
            }
            break;
          }
        case 'flush':
          {
            var chunkMeta = data.chunkMeta;
            var _transmuxResult = transmuxer.flush(chunkMeta);
            if (isPromise(_transmuxResult)) {
              _transmuxResult.then(function (results) {
                handleFlushResult(self, results, chunkMeta, instanceNo);
              }).catch(function (error) {
                forwardMessage(Events.ERROR, {
                  type: ErrorTypes.MEDIA_ERROR,
                  details: ErrorDetails.FRAG_PARSING_ERROR,
                  chunkMeta: data.chunkMeta,
                  fatal: false,
                  error: error,
                  err: error,
                  reason: "transmuxer-worker flush error"
                }, instanceNo);
              });
            } else {
              handleFlushResult(self, _transmuxResult, chunkMeta, instanceNo);
            }
            break;
          }
      }
    });
  }
  function emitTransmuxComplete(self, transmuxResult, instanceNo) {
    if (isEmptyResult(transmuxResult.remuxResult)) {
      return false;
    }
    var transferable = [];
    var _transmuxResult$remux = transmuxResult.remuxResult,
      audio = _transmuxResult$remux.audio,
      video = _transmuxResult$remux.video;
    if (audio) {
      addToTransferable(transferable, audio);
    }
    if (video) {
      addToTransferable(transferable, video);
    }
    self.postMessage({
      event: 'transmuxComplete',
      data: transmuxResult,
      instanceNo: instanceNo
    }, transferable);
    return true;
  }

  function addToTransferable(transferable, track) {
    if (track.data1) {
      transferable.push(track.data1.buffer);
    }
    if (track.data2) {
      transferable.push(track.data2.buffer);
    }
  }
  function handleFlushResult(self, results, chunkMeta, instanceNo) {
    var parsed = results.reduce(function (parsed, result) {
      return emitTransmuxComplete(self, result, instanceNo) || parsed;
    }, false);
    if (!parsed) {
      self.postMessage({
        event: 'transmuxComplete',
        data: results[0],
        instanceNo: instanceNo
      });
    }
    self.postMessage({
      event: 'flush',
      data: chunkMeta,
      instanceNo: instanceNo
    });
  }
  function forwardMessage(event, data, instanceNo) {
    self.postMessage({
      event: event,
      data: data,
      instanceNo: instanceNo
    });
  }
  function forwardWorkerLogs(logger, instanceNo) {
    var _loop = function _loop(logFn) {
      logger[logFn] = function () {
        var message = Array.prototype.join.call(arguments, ' ');
        forwardMessage('workerLog', {
          logType: logFn,
          message: message
        }, instanceNo);
      };
    };
    for (var logFn in logger) {
      _loop(logFn);
    }
  }
  function isEmptyResult(remuxResult) {
    return !remuxResult.audio && !remuxResult.video && !remuxResult.text && !remuxResult.id3 && !remuxResult.initSegment;
  }

  var version = "1.7.3";

  var workerStore = {};
  function hasUMDWorker() {
    return typeof __HLS_WORKER_BUNDLE__ === 'function';
  }
  function injectWorker() {
    var workerContext = workerStore[version];
    if (workerContext) {
      workerContext.clientCount++;
      return workerContext;
    }
    var blob = new self.Blob(["var exports={};var module={exports:exports};function define(f){f()};define.amd=true;(" + __HLS_WORKER_BUNDLE__.toString() + ")(true);"], {
      type: 'text/javascript'
    });
    var objectURL = self.URL.createObjectURL(blob);
    var worker = new self.Worker(objectURL);
    var result = {
      worker: worker,
      objectURL: objectURL,
      clientCount: 1
    };
    workerStore[version] = result;
    return result;
  }
  function loadWorker(path) {
    var workerContext = workerStore[path];
    if (workerContext) {
      workerContext.clientCount++;
      return workerContext;
    }
    var scriptURL = new self.URL(path, self.location.href).href;
    var worker = new self.Worker(scriptURL);
    var result = {
      worker: worker,
      scriptURL: scriptURL,
      clientCount: 1
    };
    workerStore[path] = result;
    return result;
  }
  function removeWorkerFromStore(path) {
    var workerContext = workerStore[path || version];
    if (workerContext) {
      var clientCount = workerContext.clientCount--;
      if (clientCount === 1) {
        var worker = workerContext.worker,
          objectURL = workerContext.objectURL;
        delete workerStore[path || version];
        if (objectURL) {
          self.URL.revokeObjectURL(objectURL);
        }
        worker.terminate();
      }
    }
  }

  var transmuxerInstanceCount = 0;
  var TransmuxerInterface = function () {
    function TransmuxerInterface(_hls, id, onTransmuxComplete, onFlush) {
      var _this = this;
      this.error = null;
      this.hls = void 0;
      this.id = void 0;
      this.instanceNo = transmuxerInstanceCount++;
      this.observer = void 0;
      this.frag = null;
      this.part = null;
      this.useWorker = void 0;
      this.workerContext = null;
      this.transmuxer = null;
      this.onTransmuxComplete = void 0;
      this.onFlush = void 0;
      this.onWorkerMessage = function (event) {
        var data = event.data;
        var hls = _this.hls;
        if (!hls || !(data != null && data.event) || data.instanceNo !== _this.instanceNo) {
          return;
        }
        switch (data.event) {
          case 'init':
            {
              var _this$workerContext;
              var objectURL = (_this$workerContext = _this.workerContext) == null ? void 0 : _this$workerContext.objectURL;
              if (objectURL) {
                self.URL.revokeObjectURL(objectURL);
              }
              break;
            }
          case 'transmuxComplete':
            {
              _this.handleTransmuxComplete(data.data);
              break;
            }
          case 'flush':
            {
              _this.onFlush(data.data);
              break;
            }

          case 'workerLog':
            {
              if (hls.logger[data.data.logType]) {
                hls.logger[data.data.logType](data.data.message);
              }
              break;
            }
          default:
            {
              data.data = data.data || {};
              data.data.frag = _this.frag;
              data.data.part = _this.part;
              data.data.id = _this.id;
              hls.trigger(data.event, data.data);
              break;
            }
        }
      };
      this.onWorkerError = function (event) {
        if (!_this.hls) {
          return;
        }
        var error = new Error(event.message + "  (" + event.filename + ":" + event.lineno + ")");
        _this.hls.config.enableWorker = false;
        _this.hls.logger.warn("Error in \\"" + _this.id + "\\" Web Worker, fallback to inline");
        _this.hls.trigger(Events.ERROR, {
          type: ErrorTypes.OTHER_ERROR,
          details: ErrorDetails.INTERNAL_EXCEPTION,
          fatal: false,
          event: 'demuxerWorker',
          error: error
        });
      };
      var config = _hls.config;
      this.hls = _hls;
      this.id = id;
      this.useWorker = !!config.enableWorker;
      this.onTransmuxComplete = onTransmuxComplete;
      this.onFlush = onFlush;
      var forwardMessage = function forwardMessage(ev, data) {
        data = data || {};
        data.frag = _this.frag || undefined;
        if (ev === Events.ERROR) {
          data = data;
          data.parent = _this.id;
          data.part = _this.part;
          _this.error = data.error;
        }
        _this.hls.trigger(ev, data);
      };

      this.observer = new EventEmitter();
      this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
      this.observer.on(Events.ERROR, forwardMessage);
      var m2tsTypeSupported = getM2TSSupportedAudioTypes(config.preferManagedMediaSource);
      if (this.useWorker && typeof Worker !== 'undefined') {
        var logger = this.hls.logger;
        var canCreateWorker = config.workerPath || hasUMDWorker();
        if (canCreateWorker) {
          try {
            if (config.workerPath) {
              this.workerContext = loadWorker(config.workerPath);
            } else {
              this.workerContext = injectWorker();
            }
            var worker = this.workerContext.worker;
            worker.addEventListener('message', this.onWorkerMessage);
            worker.addEventListener('error', this.onWorkerError);
            worker.postMessage({
              instanceNo: this.instanceNo,
              cmd: 'init',
              typeSupported: m2tsTypeSupported,
              id: id,
              config: stringify(config)
            });
          } catch (err) {
            this.terminateWorker();
            this.error = null;
            this.transmuxer = new Transmuxer(this.observer, m2tsTypeSupported, config, '', id, _hls.logger);
          }
          return;
        }
      }
      this.transmuxer = new Transmuxer(this.observer, m2tsTypeSupported, config, '', id, _hls.logger);
    }
    var _proto = TransmuxerInterface.prototype;
    _proto.reset = function reset() {
      this.frag = null;
      this.part = null;
      if (this.workerContext) {
        var instanceNo = this.instanceNo;
        this.instanceNo = transmuxerInstanceCount++;
        var config = this.hls.config;
        var m2tsTypeSupported = getM2TSSupportedAudioTypes(config.preferManagedMediaSource);
        this.workerContext.worker.postMessage({
          instanceNo: this.instanceNo,
          cmd: 'reset',
          resetNo: instanceNo,
          typeSupported: m2tsTypeSupported,
          id: this.id,
          config: stringify(config)
        });
      }
    };
    _proto.terminateWorker = function terminateWorker() {
      if (this.workerContext) {
        var worker = this.workerContext.worker;
        this.workerContext = null;
        worker.removeEventListener('message', this.onWorkerMessage);
        worker.removeEventListener('error', this.onWorkerError);
        removeWorkerFromStore(this.hls.config.workerPath);
      }
    };
    _proto.destroy = function destroy() {
      if (this.workerContext) {
        this.terminateWorker();
        this.onWorkerMessage = this.onWorkerError = null;
      } else {
        var transmuxer = this.transmuxer;
        if (transmuxer) {
          transmuxer.destroy();
          this.transmuxer = null;
        }
      }
      var observer = this.observer;
      if (observer) {
        observer.removeAllListeners();
      }
      this.frag = null;
      this.part = null;
      this.observer = null;
      this.hls = null;
    };
    _proto.push = function push(data, initSegmentData, audioCodec, videoCodec, frag, part, duration, accurateTimeOffset, chunkMeta, defaultInitPTS) {
      var _frag$initSegment,
        _lastFrag$initSegment,
        _this2 = this;
      chunkMeta.transmuxing.start = self.performance.now();
      var instanceNo = this.instanceNo,
        transmuxer = this.transmuxer;
      var timeOffset = part ? part.start : frag.start;
      var decryptdata = frag.decryptdata;
      var lastFrag = this.frag;
      var discontinuity = lastFrag ? frag.cc !== lastFrag.cc : true;
      var trackSwitch = lastFrag ? chunkMeta.level !== lastFrag.level : true;
      var snDiff = lastFrag ? chunkMeta.sn - lastFrag.sn : -1;
      var partDiff = this.part ? chunkMeta.part - this.part.index : -1;
      var progressive = snDiff === 0 && chunkMeta.id > 1 && chunkMeta.id === (lastFrag == null ? void 0 : lastFrag.stats.chunkCount);
      var contiguous = !trackSwitch && (snDiff === 1 || snDiff === 0 && (partDiff === 1 || progressive && partDiff <= 0));
      var now = self.performance.now();
      if (trackSwitch || snDiff || frag.stats.parsing.start === 0) {
        frag.stats.parsing.start = now;
      }
      if (part && (partDiff || !contiguous)) {
        part.stats.parsing.start = now;
      }
      var initSegmentChange = !(lastFrag && ((_frag$initSegment = frag.initSegment) == null ? void 0 : _frag$initSegment.url) === ((_lastFrag$initSegment = lastFrag.initSegment) == null ? void 0 : _lastFrag$initSegment.url));
      var state = new TransmuxState(discontinuity, contiguous, accurateTimeOffset, trackSwitch, timeOffset, initSegmentChange);
      if (!contiguous || discontinuity || initSegmentChange) {
        this.hls.logger.log("[transmuxer-interface]: Starting new transmux session for " + frag.type + " sn: " + chunkMeta.sn + (chunkMeta.part > -1 ? ' part: ' + chunkMeta.part : '') + " " + this.id + " playlist: " + chunkMeta.level + " id: " + chunkMeta.id + "\\n        discontinuity: " + discontinuity + "\\n        trackSwitch: " + trackSwitch + "\\n        contiguous: " + contiguous + "\\n        accurateTimeOffset: " + accurateTimeOffset + "\\n        timeOffset: " + timeOffset + "\\n        initSegmentChange: " + initSegmentChange);
        var config = new TransmuxConfig(audioCodec, videoCodec, initSegmentData, duration, defaultInitPTS);
        this.configureTransmuxer(config);
      }
      this.frag = frag;
      this.part = part;

      if (this.workerContext) {
        this.workerContext.worker.postMessage({
          instanceNo: instanceNo,
          cmd: 'demux',
          data: data,
          decryptdata: decryptdata,
          chunkMeta: chunkMeta,
          state: state
        }, data instanceof ArrayBuffer ? [data] : []);
      } else if (transmuxer) {
        var transmuxResult = transmuxer.push(data, decryptdata, chunkMeta, state);
        if (isPromise(transmuxResult)) {
          transmuxResult.then(function (data) {
            _this2.handleTransmuxComplete(data);
          }).catch(function (error) {
            _this2.transmuxerError(error, chunkMeta, 'transmuxer-interface push error');
          });
        } else {
          this.handleTransmuxComplete(transmuxResult);
        }
      }
    };
    _proto.flush = function flush(chunkMeta) {
      var _this3 = this;
      chunkMeta.transmuxing.start = self.performance.now();
      var instanceNo = this.instanceNo,
        transmuxer = this.transmuxer;
      if (this.workerContext) {
        this.workerContext.worker.postMessage({
          instanceNo: instanceNo,
          cmd: 'flush',
          chunkMeta: chunkMeta
        });
      } else if (transmuxer) {
        var transmuxResult = transmuxer.flush(chunkMeta);
        if (isPromise(transmuxResult)) {
          transmuxResult.then(function (data) {
            _this3.handleFlushResult(data, chunkMeta);
          }).catch(function (error) {
            _this3.transmuxerError(error, chunkMeta, 'transmuxer-interface flush error');
          });
        } else {
          this.handleFlushResult(transmuxResult, chunkMeta);
        }
      }
    };
    _proto.transmuxerError = function transmuxerError(error, chunkMeta, reason) {
      if (!this.hls) {
        return;
      }
      this.error = error;
      this.hls.trigger(Events.ERROR, {
        type: ErrorTypes.MEDIA_ERROR,
        details: ErrorDetails.FRAG_PARSING_ERROR,
        chunkMeta: chunkMeta,
        frag: this.frag || undefined,
        part: this.part || undefined,
        fatal: false,
        error: error,
        err: error,
        reason: reason
      });
    };
    _proto.handleFlushResult = function handleFlushResult(results, chunkMeta) {
      var _this4 = this;
      results.forEach(function (result) {
        _this4.handleTransmuxComplete(result);
      });
      this.onFlush(chunkMeta);
    };
    _proto.configureTransmuxer = function configureTransmuxer(config) {
      var instanceNo = this.instanceNo,
        transmuxer = this.transmuxer;
      if (this.workerContext) {
        this.workerContext.worker.postMessage({
          instanceNo: instanceNo,
          cmd: 'configure',
          config: config
        });
      } else if (transmuxer) {
        transmuxer.configure(config);
      }
    };
    _proto.handleTransmuxComplete = function handleTransmuxComplete(result) {
      result.chunkMeta.transmuxing.end = self.performance.now();
      this.onTransmuxComplete(result);
    };
    return TransmuxerInterface;
  }();

  function getSourceBuffer() {
    return self.SourceBuffer || self.WebKitSourceBuffer;
  }
  function isMSESupported() {
    var mediaSource = getMediaSource();
    if (!mediaSource) {
      return false;
    }

    var sourceBuffer = getSourceBuffer();
    return !sourceBuffer || sourceBuffer.prototype && typeof sourceBuffer.prototype.appendBuffer === 'function' && typeof sourceBuffer.prototype.remove === 'function';
  }
  function isSupported() {
    if (!isMSESupported()) {
      return false;
    }
    var mediaSource = getMediaSource();
    return typeof (mediaSource == null ? void 0 : mediaSource.isTypeSupported) === 'function' && (['avc1.42E01E,mp4a.40.2', 'av01.0.01M.08', 'vp09.00.50.08'].some(function (codecsForVideoContainer) {
      return mediaSource.isTypeSupported(mimeTypeForCodec(codecsForVideoContainer, 'video'));
    }) || ['mp4a.40.2', 'fLaC'].some(function (codecForAudioContainer) {
      return mediaSource.isTypeSupported(mimeTypeForCodec(codecForAudioContainer, 'audio'));
    }));
  }
  function changeTypeSupported() {
    var _sourceBuffer$prototy;
    var sourceBuffer = getSourceBuffer();
    return typeof (sourceBuffer == null || (_sourceBuffer$prototy = sourceBuffer.prototype) == null ? void 0 : _sourceBuffer$prototy.changeType) === 'function';
  }

  var AlternateAudio = {
    DISABLED: 0,
    SWITCHING: 1,
    SWITCHED: 2
  };
  var TICK_INTERVAL = 100;
  var StreamController = function (_BaseStreamController) {
    function StreamController(hls, fragmentTracker, keyLoader) {
      var _this;
      _this = _BaseStreamController.call(this, hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN) || this;
      _this.audioCodecSwap = false;
      _this.level = -1;
      _this._forceStartLoad = false;
      _this._hasEnoughToStart = false;
      _this.altAudio = AlternateAudio.DISABLED;
      _this.audioOnly = false;
      _this._couldBacktrack = false;
      _this._backtrackFragment = undefined;
      _this.audioCodecSwitch = false;
      _this.videoBuffer = null;
      _this.onMediaPlaying = function () {
        _this.tick();
      };
      _this.onMediaSeeked = function () {
        var media = _this.media;
        var currentTime = media ? media.currentTime : null;
        if (currentTime === null || !isFiniteNumber(currentTime)) {
          return;
        }

        if (!_this.getBufferedFrag(currentTime)) {
          return;
        }
        var bufferInfo = _this.getFwdBufferInfoAtPos(media, currentTime, PlaylistLevelType.MAIN, 0);
        if (!bufferInfo || bufferInfo.len === 0) {
          return;
        }

        _this.tick();
      };
      _this.registerListeners();
      return _this;
    }
    _inheritsLoose(StreamController, _BaseStreamController);
    var _proto = StreamController.prototype;
    _proto.registerListeners = function registerListeners() {
      _BaseStreamController.prototype.registerListeners.call(this);
      var hls = this.hls;
      hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
      hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
      hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
      hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
      hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
      hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
      hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
      hls.on(Events.BUFFER_FLUSHED, this.onBufferFlushed, this);
      hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
      hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
    };
    _proto.unregisterListeners = function unregisterListeners() {
      _BaseStreamController.prototype.unregisterListeners.call(this);
      var hls = this.hls;
      hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
      hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
      hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
      hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
      hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
      hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
      hls.off(Events.BUFFER_FLUSHED, this.onBufferFlushed, this);
      hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
      hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
    };
    _proto.onHandlerDestroying = function onHandlerDestroying() {
      this.onMediaPlaying = this.onMediaSeeked = null;
      this.unregisterListeners();
      _BaseStreamController.prototype.onHandlerDestroying.call(this);
    };
    _proto.startLoad = function startLoad(startPosition, skipSeekToStartPosition) {
      if (this.levels) {
        var lastCurrentTime = this.lastCurrentTime,
          hls = this.hls;
        this.stopLoad();
        this.setInterval(TICK_INTERVAL);
        this.level = -1;
        if (!this.startFragRequested) {
          var startLevel = hls.startLevel;
          if (startLevel === -1) {
            if (hls.config.testBandwidth && this.levels.length > 1) {
              startLevel = 0;
              this.bitrateTest = true;
            } else {
              startLevel = hls.firstAutoLevel;
            }
          }
          hls.nextLoadLevel = startLevel;
          this.level = hls.loadLevel;
          this._hasEnoughToStart = !!skipSeekToStartPosition;
        }
        if (lastCurrentTime > 0 && startPosition === -1 && !skipSeekToStartPosition && this.initPTS.length) {
          startPosition = lastCurrentTime;
        }
        this.state = State.IDLE;
        this.nextLoadPosition = this.lastCurrentTime = startPosition + this.timelineOffset;
        this.startPosition = skipSeekToStartPosition ? -1 : startPosition;
        if (!skipSeekToStartPosition && !this.fragmentTracker.hasFragments(this.playlistType)) {
          this._hasEnoughToStart = this.startFragRequested = false;
        }
        this.tick();
      } else {
        this._forceStartLoad = true;
        this.state = State.STOPPED;
      }
    };
    _proto.stopLoad = function stopLoad() {
      this._forceStartLoad = false;
      _BaseStreamController.prototype.stopLoad.call(this);
    };
    _proto.doTick = function doTick() {
      switch (this.state) {
        case State.WAITING_LEVEL:
          {
            var levels = this.levels,
              level = this.level;
            var currentLevel = levels == null ? void 0 : levels[level];
            var details = currentLevel == null ? void 0 : currentLevel.details;
            if (details && (!details.live || this.levelLastLoaded === currentLevel && !this.waitForLive(currentLevel))) {
              if (this.waitForCdnTuneIn(details)) {
                break;
              }
              this.state = State.IDLE;
              break;
            } else if (this.hls.nextLoadLevel !== this.level) {
              this.state = State.IDLE;
              break;
            }
            break;
          }
        case State.FRAG_LOADING_WAITING_RETRY:
          this.checkRetryDate();
          break;
      }
      if (this.state === State.IDLE) {
        this.doTickIdle();
      }
      this.onTickEnd();
    };
    _proto.onTickEnd = function onTickEnd() {
      var _this$media;
      _BaseStreamController.prototype.onTickEnd.call(this);
      if ((_this$media = this.media) != null && _this$media.readyState && this.media.seeking === false) {
        this.lastCurrentTime = this.media.currentTime;
      }
      this.checkFragmentChanged();
    };
    _proto.doTickIdle = function doTickIdle() {
      var hls = this.hls,
        levelLastLoaded = this.levelLastLoaded,
        levels = this.levels,
        media = this.media;

      if (levelLastLoaded === null || !media && !this.primaryPrefetch && (this.startFragRequested || !hls.config.startFragPrefetch)) {
        return;
      }

      if (this.altAudio && this.audioOnly) {
        return;
      }
      var level = this.buffering ? hls.nextLoadLevel : hls.loadLevel;
      if (!(levels != null && levels[level])) {
        return;
      }
      var levelInfo = levels[level];

      var bufferInfo = this.getMainFwdBufferInfo();
      if (bufferInfo === null) {
        return;
      }
      var lastDetails = this.getLevelDetails();
      if (lastDetails && this._streamEnded(bufferInfo, lastDetails)) {
        var data = {
          type: this.targetBufferType()
        };
        this.hls.trigger(Events.BUFFER_EOS, data);
        this.state = State.ENDED;
        return;
      }
      if (!this.buffering) {
        return;
      }

      if (hls.loadLevel !== level && hls.manualLevel === -1) {
      }
      this.level = hls.nextLoadLevel = level;
      var levelDetails = levelInfo.details;
      if (!levelDetails || this.state === State.WAITING_LEVEL || this.waitForLive(levelInfo)) {
        this.level = level;
        this.state = State.WAITING_LEVEL;
        this.startFragRequested = false;
        return;
      }
      var bufferLen = bufferInfo.len;

      var maxBufLen = this.getMaxBufferLength(levelInfo.maxBitrate);

      if (bufferLen >= maxBufLen) {
        return;
      }
      if (this.backtrackFragment && this.backtrackFragment.start > bufferInfo.end) {
        this.backtrackFragment = undefined;
      }
      var targetBufferTime = this.backtrackFragment ? this.backtrackFragment.start : bufferInfo.end;
      var frag = this.getNextFragment(targetBufferTime, levelDetails);
      if (this.couldBacktrack && !this.fragPrevious && frag && isMediaFragment(frag) && this.fragmentTracker.getState(frag) !== FragmentState.OK) {
        var _this$backtrackFragme;
        var backtrackSn = ((_this$backtrackFragme = this.backtrackFragment) != null ? _this$backtrackFragme : frag).sn;
        var fragIdx = backtrackSn - levelDetails.startSN;
        var backtrackFrag = levelDetails.fragments[fragIdx - 1];
        if (backtrackFrag && frag.cc === backtrackFrag.cc) {
          frag = backtrackFrag;
          this.fragmentTracker.removeFragment(backtrackFrag);
        }
      } else if (this.backtrackFragment && bufferInfo.len) {
        this.backtrackFragment = undefined;
      }
      if (frag && this.isLoopLoading(frag, targetBufferTime)) {
        var gapStart = frag.gap;
        if (!gapStart) {
          var type = this.audioOnly && !this.altAudio ? ElementaryStreamTypes.AUDIO : ElementaryStreamTypes.VIDEO;
          var mediaBuffer = (type === ElementaryStreamTypes.VIDEO ? this.videoBuffer : this.mediaBuffer) || this.media;
          if (mediaBuffer) {
            this.afterBufferFlushed(mediaBuffer, type);
          }
        }
        frag = this.getNextFragmentLoopLoading(frag, levelDetails, bufferInfo, PlaylistLevelType.MAIN, maxBufLen);
      }
      if (!frag) {
        return;
      }
      if (this.exceedsMaxBuffer(bufferInfo, maxBufLen, frag)) {
        return;
      }
      this.loadFragment(frag, levelInfo, targetBufferTime);
    };
    _proto.loadFragment = function loadFragment(frag, level, targetBufferTime) {
      var fragState = this.fragmentTracker.getState(frag);
      if (fragState === FragmentState.NOT_LOADED || fragState === FragmentState.PARTIAL) {
        if (this.bitrateTest) {
          this._loadBitrateTestFrag(frag, level);
        } else {
          _BaseStreamController.prototype.loadFragment.call(this, frag, level, targetBufferTime);
        }
      } else {
        this.clearTrackerIfNeeded(frag);
      }
    }

;
    _proto.immediateLevelSwitch = function immediateLevelSwitch() {
      this.abortCurrentFrag();
      this.flushMainBuffer(0, Number.POSITIVE_INFINITY);
      if (this.altAudio !== AlternateAudio.DISABLED) {
        var _this$getLevelDetails;
        var liveWindowStart = ((_this$getLevelDetails = this.getLevelDetails()) == null ? void 0 : _this$getLevelDetails.fragmentStart) || 0;
        if (liveWindowStart > this.lastCurrentTime) {
          _BaseStreamController.prototype.flushMainBuffer.call(this, 0, Number.POSITIVE_INFINITY, 'audio');
        }
      }
    }

;
    _proto.getBufferOutput = function getBufferOutput() {
      if (this.mediaBuffer && this.altAudio === AlternateAudio.SWITCHED) {
        return this.mediaBuffer;
      }
      return this.media;
    };
    _proto.checkFragmentChanged = function checkFragmentChanged() {
      var previousFrag = this.fragPlaying;
      var fragChanged = this.checkFragPlaying();
      if (!fragChanged) {
        return false;
      }
      var fragPlaying = this.fragPlaying;
      if (fragPlaying) {
        var fragCurrentLevel = fragPlaying.level;
        this.hls.trigger(Events.FRAG_CHANGED, {
          frag: fragPlaying,
          previousFrag: previousFrag
        });
        if ((previousFrag == null ? void 0 : previousFrag.level) !== fragCurrentLevel) {
          this.hls.trigger(Events.LEVEL_SWITCHED, {
            level: fragCurrentLevel
          });
        }
      }
      return true;
    }

;
    _proto.abortCurrentFrag = function abortCurrentFrag() {
      this.backtrackFragment = undefined;
      _BaseStreamController.prototype.abortCurrentFrag.call(this);
    };
    _proto.flushMainBuffer = function flushMainBuffer(startOffset, endOffset) {
      _BaseStreamController.prototype.flushMainBuffer.call(this, startOffset, endOffset, this.targetBufferType());
    };
    _proto.targetBufferType = function targetBufferType() {
      return this.altAudio === AlternateAudio.SWITCHED && !this.audioOnly ? 'video' : null;
    };
    _proto.onMediaAttached = function onMediaAttached(event, data) {
      _BaseStreamController.prototype.onMediaAttached.call(this, event, data);
      var media = data.media;
      addEventListener(media, 'playing', this.onMediaPlaying);
      addEventListener(media, 'seeked', this.onMediaSeeked);
    };
    _proto.onMediaDetaching = function onMediaDetaching(event, data) {
      var media = this.media;
      if (media) {
        removeEventListener(media, 'playing', this.onMediaPlaying);
        removeEventListener(media, 'seeked', this.onMediaSeeked);
      }
      this.videoBuffer = null;
      _BaseStreamController.prototype.onMediaDetaching.call(this, event, data);
      var transferringMedia = !!data.transferMedia;
      if (transferringMedia) {
        return;
      }
      this._hasEnoughToStart = false;
    };
    _proto.onManifestLoading = function onManifestLoading() {
      _BaseStreamController.prototype.onManifestLoading.call(this);
      this.hls.trigger(Events.BUFFER_RESET, undefined);
      this.couldBacktrack = false;
      this.backtrackFragment = undefined;
      this.altAudio = AlternateAudio.DISABLED;
      this.audioOnly = false;
    };
    _proto.onManifestParsed = function onManifestParsed(event, data) {
      var aac = false;
      var heaac = false;
      for (var i = 0; i < data.levels.length; i++) {
        var codec = data.levels[i].audioCodec;
        if (codec) {
          aac = aac || codec.indexOf('mp4a.40.2') !== -1;
          heaac = heaac || codec.indexOf('mp4a.40.5') !== -1;
        }
      }
      this.audioCodecSwitch = aac && heaac && !changeTypeSupported();
      if (this.audioCodecSwitch) {
      }
      this.levels = data.levels;
      this.startFragRequested = false;
    };
    _proto.onLevelLoading = function onLevelLoading(event, data) {
      var levels = this.levels;
      if (!levels || this.state !== State.IDLE) {
        return;
      }
      var level = data.levelInfo;
      if (!level.details || level.details.live && (this.levelLastLoaded !== level || level.details.expired) || this.waitForCdnTuneIn(level.details)) {
        this.state = State.WAITING_LEVEL;
      }
    };
    _proto.onLevelLoaded = function onLevelLoaded(event, data) {
      var _curLevel$details,
        _this2 = this;
      var levels = this.levels,
        startFragRequested = this.startFragRequested;
      var newLevelId = data.level;
      var newDetails = data.details;
      var duration = newDetails.totalduration;
      if (!levels) {
        return;
      }
      var curLevel = data.levelInfo;
      var fragCurrent = this.fragCurrent;
      if (fragCurrent && (this.state === State.FRAG_LOADING || this.state === State.FRAG_LOADING_WAITING_RETRY)) {
        if (fragCurrent.level !== data.level && fragCurrent.loader) {
          this.abortCurrentFrag();
        }
      }
      var sliding = 0;
      if (newDetails.live || (_curLevel$details = curLevel.details) != null && _curLevel$details.live) {
        var _this$levelLastLoaded;
        this.checkLiveUpdate(newDetails);
        if (newDetails.deltaUpdateFailed) {
          return;
        }
        sliding = this.alignPlaylists(newDetails, curLevel.details, (_this$levelLastLoaded = this.levelLastLoaded) == null ? void 0 : _this$levelLastLoaded.details);
      }
      curLevel.details = newDetails;
      this.levelLastLoaded = curLevel;
      if (!startFragRequested) {
        this.setStartPosition(newDetails, sliding);
      }
      this.hls.trigger(Events.LEVEL_UPDATED, {
        details: newDetails,
        level: newLevelId
      });

      if (this.state === State.WAITING_LEVEL) {
        if (this.waitForCdnTuneIn(newDetails)) {
          return;
        }
        this.state = State.IDLE;
      }
      if (startFragRequested && newDetails.live) {
        this.synchronizeToLiveEdge(newDetails);
      }

      this.initPTS.some(function (t, i) {
        if (i >= newDetails.startCC) {
          return true;
        }
        delete _this2.initPTS[i];
      });

      this.tick();
    };
    _proto.synchronizeToLiveEdge = function synchronizeToLiveEdge(levelDetails) {
      var config = this.config,
        media = this.media;
      if (!media) {
        return;
      }
      var liveSyncPosition = this.hls.liveSyncPosition;
      var currentTime = this.playhead;
      var start = levelDetails.fragmentStart;
      var end = levelDetails.edge;
      var withinSlidingWindow = currentTime >= start - config.maxFragLookUpTolerance && currentTime <= end;
      if (liveSyncPosition !== null && media.duration > liveSyncPosition && (currentTime < liveSyncPosition || !withinSlidingWindow)) {
        var maxLatency = config.liveMaxLatencyDuration !== undefined ? config.liveMaxLatencyDuration : config.liveMaxLatencyDurationCount * levelDetails.targetduration;
        if (!withinSlidingWindow && media.readyState < 4 || currentTime < end - maxLatency) {
          if (!this._hasEnoughToStart) {
            this.nextLoadPosition = liveSyncPosition;
          }
          if (media.readyState) {
            if (this.config.liveSyncMode === 'buffered') {
              var _bufferInfo$buffered;
              var bufferInfo = BufferHelper.bufferInfo(media, liveSyncPosition, 0);
              if (!((_bufferInfo$buffered = bufferInfo.buffered) != null && _bufferInfo$buffered.length)) {
                media.currentTime = liveSyncPosition;
                return;
              }
              var isLiveSyncInBuffer = bufferInfo.start <= currentTime;
              if (isLiveSyncInBuffer) {
                media.currentTime = liveSyncPosition;
                return;
              }
              var _BufferHelper$buffere = BufferHelper.bufferedInfo(bufferInfo.buffered, currentTime, 0),
                nextStart = _BufferHelper$buffere.nextStart;
              if (nextStart) {
                media.currentTime = nextStart;
              }
            } else {
              media.currentTime = liveSyncPosition;
            }
          }
        }
      }
    };
    _proto._handleFragmentLoadProgress = function _handleFragmentLoadProgress(data) {
      var _frag$initSegment;
      var frag = data.frag;
      var part = data.part,
        payload = data.payload;
      var levels = this.levels;
      if (!levels) {
        return;
      }
      var currentLevel = levels[frag.level];
      if (!currentLevel) {
        return;
      }
      var details = currentLevel.details;
      if (!details) {
        this.fragmentTracker.removeFragment(frag);
        return;
      }
      var videoCodec = currentLevel.videoCodec;

      var accurateTimeOffset = details.PTSKnown || !details.live;
      var initSegmentData = (_frag$initSegment = frag.initSegment) == null ? void 0 : _frag$initSegment.data;
      var audioCodec = this._getAudioCodec(currentLevel);

      var transmuxer = this.transmuxer = this.transmuxer || new TransmuxerInterface(this.hls, PlaylistLevelType.MAIN, this._handleTransmuxComplete.bind(this), this._handleTransmuxerFlush.bind(this));
      var partIndex = part ? part.index : -1;
      var partial = partIndex !== -1;
      var _getAESAdjustments = getAESAdjustments(frag, part, this.iframesOnly),
        decryptRange = _getAESAdjustments.decryptRange,
        byteRange = _getAESAdjustments.byteRange;
      var chunkMeta = new ChunkMetadata(frag.level, frag.sn, frag.stats.chunkCount, byteRange ? byteRange.end - byteRange.start : payload.byteLength, partIndex, partial, frag.duration, this.iframesOnly, decryptRange);
      var initPTS = this.initPTS[frag.cc];
      transmuxer.push(payload, initSegmentData, audioCodec, videoCodec, frag, part, details.totalduration, accurateTimeOffset, chunkMeta, initPTS);
    };
    _proto.onAudioTrackSwitching = function onAudioTrackSwitching(event, data) {
      var _this3 = this;
      var hls = this.hls;
      var fromAltAudio = this.altAudio !== AlternateAudio.DISABLED;
      var altAudio = useAlternateAudio(data.url, hls);
      if (!altAudio) {
        if (this.mediaBuffer !== this.media) {
          this.mediaBuffer = this.media;
          var fragCurrent = this.fragCurrent;
          if (fragCurrent) {
            fragCurrent.abortRequests();
            this.fragmentTracker.removeFragment(fragCurrent);
          }
          this.resetTransmuxer();
          this.resetLoadingState();
        } else if (this.audioOnly) {
          this.resetTransmuxer();
        }
        if (fromAltAudio) {
          this.altAudio = AlternateAudio.DISABLED;
          this.fragmentTracker.removeAllFragments();
          hls.once(Events.BUFFER_FLUSHED, function () {
            if (!_this3.hls) {
              return;
            }
            _this3.hls.trigger(Events.AUDIO_TRACK_SWITCHED, data);
          });
          hls.trigger(Events.BUFFER_FLUSHING, {
            startOffset: 0,
            endOffset: Number.POSITIVE_INFINITY,
            type: null
          });
          return;
        }
        hls.trigger(Events.AUDIO_TRACK_SWITCHED, data);
      } else {
        this.altAudio = AlternateAudio.SWITCHING;
      }
    };
    _proto.onAudioTrackSwitched = function onAudioTrackSwitched(event, data) {
      var altAudio = useAlternateAudio(data.url, this.hls);
      if (altAudio) {
        var videoBuffer = this.videoBuffer;
        if (videoBuffer && this.mediaBuffer !== videoBuffer) {
          this.mediaBuffer = videoBuffer;
        }
      }
      this.altAudio = altAudio ? AlternateAudio.SWITCHED : AlternateAudio.DISABLED;
      this.tick();
    };
    _proto.onBufferCreated = function onBufferCreated(event, data) {
      var tracks = data.tracks;
      var mediaTrack;
      var name;
      var alternate = false;
      for (var type in tracks) {
        var track = tracks[type];
        if (track.id === 'main') {
          name = type;
          mediaTrack = track;
          if (type === 'video') {
            var videoTrack = tracks[type];
            if (videoTrack) {
              this.videoBuffer = videoTrack.buffer;
            }
          }
        } else {
          alternate = true;
        }
      }
      if (alternate && mediaTrack) {
        this.mediaBuffer = mediaTrack.buffer;
      } else {
        this.mediaBuffer = this.media;
      }
    };
    _proto.onFragBuffered = function onFragBuffered(event, data) {
      var frag = data.frag,
        part = data.part;
      var bufferedMainFragment = frag.type === PlaylistLevelType.MAIN;
      if (bufferedMainFragment) {
        if (this.fragContextChanged(frag)) {
          if (this.state === State.PARSED) {
            this.state = State.IDLE;
          }
          return;
        }
        var fragError = false;
        if (isMediaFragment(frag)) {
          fragError = !!frag.gap && !frag.tagList.some(function (tags) {
            return tags[0] === 'GAP';
          });
        }
        this.fragBufferedComplete(frag, part);
        if (fragError) {
          var _this$levels;
          frag.stats.retry++;
          var level = (_this$levels = this.levels) == null ? void 0 : _this$levels[frag.level];
          if (level) {
            level.fragmentError++;
          }
        }
      }
      var media = this.media;
      if (!media) {
        return;
      }
      if (!this._hasEnoughToStart && BufferHelper.getBuffered(media).length) {
        this._hasEnoughToStart = true;
        this.seekToStartPos();
      }
      if (bufferedMainFragment) {
        this.tick();
      }
    };
    _proto.onError = function onError(event, data) {
      var _data$context;
      if (data.fatal) {
        this.state = State.ERROR;
        return;
      }
      switch (data.details) {
        case ErrorDetails.FRAG_GAP:
        case ErrorDetails.FRAG_PARSING_ERROR:
        case ErrorDetails.FRAG_DECRYPT_ERROR:
        case ErrorDetails.FRAG_LOAD_ERROR:
        case ErrorDetails.FRAG_LOAD_TIMEOUT:
        case ErrorDetails.KEY_LOAD_ERROR:
        case ErrorDetails.KEY_LOAD_TIMEOUT:
          this.onFragmentOrKeyLoadError(PlaylistLevelType.MAIN, data);
          break;
        case ErrorDetails.LEVEL_LOAD_ERROR:
        case ErrorDetails.LEVEL_LOAD_TIMEOUT:
        case ErrorDetails.LEVEL_PARSING_ERROR:
          if (!data.levelRetry && this.state === State.WAITING_LEVEL && ((_data$context = data.context) == null ? void 0 : _data$context.type) === LoaderContextType.LEVEL) {
            this.state = State.IDLE;
          }
          break;
        case ErrorDetails.BUFFER_APPEND_NO_PROGRESS:
          if (data.parent !== 'main') {
            return;
          }
          if (data.frag && (data.appendsWithoutProgress || 0) >= this.config.appendErrorMaxRetry) {
            this.fragmentTracker.addAsGap(data.frag);
          }
          break;
        case ErrorDetails.BUFFER_ADD_CODEC_ERROR:
        case ErrorDetails.BUFFER_APPEND_ERROR:
          if (data.parent !== 'main') {
            return;
          }
          if (this.reduceLengthAndFlushBuffer(data)) {
            this.resetLoadingState();
          }
          break;
        case ErrorDetails.BUFFER_FULL_ERROR:
          if (data.parent !== 'main') {
            return;
          }
          if (this.reduceLengthAndFlushBuffer(data)) {
            var isAssetPlayer = !this.config.interstitialsController && this.config.assetPlayerId;
            if (isAssetPlayer) {
              this._hasEnoughToStart = true;
            } else {
              this.flushMainBuffer(0, Number.POSITIVE_INFINITY);
            }
          }
          break;
        case ErrorDetails.INTERNAL_EXCEPTION:
          this.recoverWorkerError(data);
          break;
      }
    };
    _proto.onFragLoadEmergencyAborted = function onFragLoadEmergencyAborted() {
      this.state = State.IDLE;
      if (!this._hasEnoughToStart) {
        this.startFragRequested = false;
        this.nextLoadPosition = this.lastCurrentTime;
      }
      this.tickImmediate();
    };
    _proto.onBufferFlushed = function onBufferFlushed(event, _ref) {
      var type = _ref.type;
      if (type !== ElementaryStreamTypes.AUDIO || !this.altAudio) {
        var mediaBuffer = (type === ElementaryStreamTypes.VIDEO ? this.videoBuffer : this.mediaBuffer) || this.media;
        if (mediaBuffer) {
          this.afterBufferFlushed(mediaBuffer, type);
          this.tick();
        }
      }
    };
    _proto.onLevelsUpdated = function onLevelsUpdated(event, data) {
      if (this.level > -1 && this.fragCurrent) {
        this.level = this.fragCurrent.level;
        if (this.level === -1) {
          this.resetWhenMissingContext(this.fragCurrent);
        }
      }
      this.levels = data.levels;
    };
    _proto.swapAudioCodec = function swapAudioCodec() {
      this.audioCodecSwap = !this.audioCodecSwap;
    }

;
    _proto.seekToStartPos = function seekToStartPos() {
      var media = this.media;
      if (!media) {
        return;
      }
      var currentTime = media.currentTime;
      var startPosition = this.startPosition;
      if (startPosition >= 0 && currentTime < startPosition) {
        if (media.seeking) {
          return;
        }

        var timelineOffset = this.timelineOffset;
        if (timelineOffset && startPosition) {
          startPosition += timelineOffset;
        }
        var details = this.getLevelDetails();
        var buffered = BufferHelper.getBuffered(media);
        var bufferStart = buffered.length ? buffered.start(0) : 0;
        var delta = bufferStart - startPosition;
        var skipTolerance = Math.max(this.config.maxBufferHole, this.config.maxFragLookUpTolerance);
        if (this.config.startOnSegmentBoundary || delta > 0 && (delta < skipTolerance || this.loadingParts && delta < 2 * ((details == null ? void 0 : details.partTarget) || 0))) {
          startPosition += delta;
          this.startPosition = startPosition;
        }
        if (currentTime < startPosition) {
          media.currentTime = startPosition;
        }
      }
    };
    _proto._getAudioCodec = function _getAudioCodec(currentLevel) {
      var audioCodec = this.config.defaultAudioCodec || currentLevel.audioCodec;
      if (this.audioCodecSwap && audioCodec) {
        if (audioCodec.indexOf('mp4a.40.5') !== -1) {
          audioCodec = 'mp4a.40.2';
        } else {
          audioCodec = 'mp4a.40.5';
        }
      }
      return audioCodec;
    };
    _proto._loadBitrateTestFrag = function _loadBitrateTestFrag(fragment, level) {
      var _this4 = this;
      fragment.bitrateTest = true;
      this._doFragLoad(fragment, level).then(function (data) {
        var hls = _this4.hls;
        var frag = data == null ? void 0 : data.frag;
        if (!frag || _this4.fragContextChanged(frag)) {
          return;
        }
        level.fragmentError = 0;
        _this4.state = State.IDLE;
        _this4.startFragRequested = false;
        _this4.bitrateTest = false;
        var stats = frag.stats;
        stats.parsing.start = stats.parsing.end = stats.buffering.start = stats.buffering.end = self.performance.now();
        hls.trigger(Events.FRAG_LOADED, data);
        frag.bitrateTest = false;
      }).catch(function (reason) {
        if (_this4.state === State.STOPPED || _this4.state === State.ERROR) {
          return;
        }
        _this4.resetFragmentLoading(fragment);
      });
    };
    _proto._handleTransmuxComplete = function _handleTransmuxComplete(transmuxResult) {
      var id = this.playlistType;
      var hls = this.hls;
      var remuxResult = transmuxResult.remuxResult,
        chunkMeta = transmuxResult.chunkMeta;
      var context = this.getCurrentContext(chunkMeta);
      if (!context) {
        this.resetWhenMissingContext(chunkMeta);
        return;
      }
      var frag = context.frag,
        part = context.part,
        level = context.level;
      var video = remuxResult.video,
        text = remuxResult.text,
        id3 = remuxResult.id3,
        initSegment = remuxResult.initSegment;
      var details = level.details;
      var audio = this.altAudio ? undefined : remuxResult.audio;

      if (this.fragContextChanged(frag)) {
        this.fragmentTracker.removeFragment(frag);
        return;
      }
      this.state = State.PARSING;
      if (initSegment) {
        var tracks = initSegment.tracks;
        if (tracks) {
          var mapFragment = frag.initSegment || frag;
          if (this.unhandledEncryptionError(initSegment, frag)) {
            return;
          }
          this._bufferInitSegment(level, tracks, mapFragment, chunkMeta);
          hls.trigger(Events.FRAG_PARSING_INIT_SEGMENT, {
            frag: mapFragment,
            id: id,
            tracks: tracks
          });
        }
        var baseTime = initSegment.initPTS;
        var timescale = initSegment.timescale;
        var initPTS = this.initPTS[frag.cc];
        if (isFiniteNumber(baseTime) && (!initPTS || initPTS.baseTime !== baseTime || initPTS.timescale !== timescale)) {
          var trackId = initSegment.trackId;
          this.initPTS[frag.cc] = {
            baseTime: baseTime,
            timescale: timescale,
            trackId: trackId
          };
          var timestampOffsets = this.initPTS.slice(0);
          hls.trigger(Events.INIT_PTS_FOUND, {
            timestampOffsets: timestampOffsets,
            frag: frag,
            id: id,
            initPTS: baseTime,
            timescale: timescale,
            trackId: trackId
          });
        }
      }

      if (video && details) {
        if (audio && video.type === 'audiovideo') {
          this.logMuxedErr(frag);
        }
        var prevFrag = details.fragments[frag.sn - 1 - details.startSN];
        var isFirstFragment = frag.sn === details.startSN;
        var isFirstInDiscontinuity = !prevFrag || frag.cc > prevFrag.cc;
        if (remuxResult.independent !== false) {
          var startPTS = video.startPTS,
            endPTS = video.endPTS,
            startDTS = video.startDTS,
            endDTS = video.endDTS;
          if (part) {
            part.elementaryStreams[video.type] = {
              startPTS: startPTS,
              endPTS: endPTS,
              startDTS: startDTS,
              endDTS: endDTS
            };
          } else {
            if (video.firstKeyFrame && video.independent && chunkMeta.id === 1 && !isFirstInDiscontinuity) {
              this.couldBacktrack = true;
            }
            if (video.dropped && video.independent) {

              var bufferInfo = this.getMainFwdBufferInfo();
              var targetBufferTime = (bufferInfo ? bufferInfo.end : this.getLoadPosition()) + this.config.maxBufferHole;
              var startTime = video.firstKeyFramePTS ? video.firstKeyFramePTS : startPTS;
              if (!isFirstFragment && targetBufferTime < startTime - this.config.maxBufferHole && !isFirstInDiscontinuity) {
                this.backtrack(frag);
                return;
              } else if (isFirstInDiscontinuity) {
                frag.gap = true;
              }
              frag.setElementaryStreamInfo(video.type, frag.start, endPTS, frag.start, endDTS, true);
            } else if (isFirstFragment && startPTS - (details.appliedTimelineOffset || 0) > MAX_START_GAP_JUMP) {
              frag.gap = true;
            }
          }
          frag.setElementaryStreamInfo(video.type, startPTS, endPTS, startDTS, endDTS);
          if (this.backtrackFragment) {
            this.backtrackFragment = frag;
          }
          this.bufferFragmentData(video, frag, part, chunkMeta, isFirstFragment || isFirstInDiscontinuity);
        } else if (isFirstFragment || isFirstInDiscontinuity) {
          frag.gap = true;
        } else {
          this.backtrack(frag);
          return;
        }
      }
      if (audio) {
        var _startPTS = audio.startPTS,
          _endPTS = audio.endPTS,
          _startDTS = audio.startDTS,
          _endDTS = audio.endDTS;
        if (part) {
          part.elementaryStreams[ElementaryStreamTypes.AUDIO] = {
            startPTS: _startPTS,
            endPTS: _endPTS,
            startDTS: _startDTS,
            endDTS: _endDTS
          };
        }
        frag.setElementaryStreamInfo(ElementaryStreamTypes.AUDIO, _startPTS, _endPTS, _startDTS, _endDTS);
        this.bufferFragmentData(audio, frag, part, chunkMeta);
      }
      if (details && id3 != null && id3.samples.length) {
        var emittedID3 = {
          id: id,
          frag: frag,
          details: details,
          samples: id3.samples
        };
        hls.trigger(Events.FRAG_PARSING_METADATA, emittedID3);
      }
      if (details && text) {
        var emittedText = {
          id: id,
          frag: frag,
          details: details,
          samples: text.samples
        };
        hls.trigger(Events.FRAG_PARSING_USERDATA, emittedText);
      }
    };
    _proto.logMuxedErr = function logMuxedErr(frag) {
    };
    _proto._bufferInitSegment = function _bufferInitSegment(currentLevel, tracks, frag, chunkMeta) {
      var _this5 = this;
      if (this.state !== State.PARSING) {
        return;
      }
      this.audioOnly = !!tracks.audio && !tracks.video;

      if (this.altAudio && !this.audioOnly) {
        delete tracks.audio;
        if (tracks.audiovideo) {
          this.logMuxedErr(frag);
        }
      }
      var audio = tracks.audio,
        video = tracks.video,
        audiovideo = tracks.audiovideo;
      if (audio) {
        var levelCodec = currentLevel.audioCodec;
        var audioCodec = pickMostCompleteCodecName(audio.codec, levelCodec);
        if (audioCodec === 'mp4a') {
          audioCodec = 'mp4a.40.5';
        }
        if (this.audioCodecSwitch) {
          if (audioCodec) {
            if (audioCodec.indexOf('mp4a.40.5') !== -1) {
              audioCodec = 'mp4a.40.2';
            } else {
              audioCodec = 'mp4a.40.5';
            }
          }
          var audioMetadata = audio.metadata;
          if (audioMetadata && 'channelCount' in audioMetadata && (audioMetadata.channelCount || 1) !== 1 && !userAgentFirefoxVersion()) {
            audioCodec = 'mp4a.40.5';
          }
        }
        if (audioCodec && audioCodec.indexOf('mp4a.40.5') !== -1 && userAgentIsAndroidLike() && audio.container !== 'audio/mpeg') {
          audioCodec = 'mp4a.40.2';
        }
        if (levelCodec && levelCodec !== audioCodec) {
        }
        audio.levelCodec = audioCodec;
        audio.id = PlaylistLevelType.MAIN;
        delete tracks.audiovideo;
      }
      if (video) {
        video.levelCodec = currentLevel.videoCodec;
        video.id = PlaylistLevelType.MAIN;
        var parsedVideoCodec = video.codec;
        if ((parsedVideoCodec == null ? void 0 : parsedVideoCodec.length) === 4) {
          switch (parsedVideoCodec) {
            case 'hvc1':
            case 'hev1':
              video.codec = 'hvc1.1.6.L120.90';
              break;
            case 'av01':
              video.codec = 'av01.0.04M.08';
              break;
            case 'avc1':
              video.codec = 'avc1.42e01e';
              break;
          }
        }
        delete tracks.audiovideo;
      }
      if (audiovideo) {
        if (this.iframesOnly) {
          this.logMuxedErr(frag);
        }
        delete tracks.video;
        delete tracks.audio;
      }
      var trackTypes = Object.keys(tracks);
      if (trackTypes.length) {
        this.hls.trigger(Events.BUFFER_CODECS, tracks);
        if (!this.hls) {
          return;
        }
        trackTypes.forEach(function (trackName) {
          var track = tracks[trackName];
          var initSegment = track.initSegment;
          if (initSegment != null && initSegment.byteLength) {
            _this5.hls.trigger(Events.BUFFER_APPENDING, {
              type: trackName,
              data: initSegment,
              frag: frag,
              part: null,
              chunkMeta: chunkMeta,
              parent: frag.type
            });
          }
        });
      }
      this.tickImmediate();
    };
    _proto.getMainFwdBufferInfo = function getMainFwdBufferInfo() {
      var bufferOutput = this.getBufferOutput();
      return this.getFwdBufferInfo(bufferOutput, PlaylistLevelType.MAIN);
    };
    _proto.backtrack = function backtrack(frag) {
      this.couldBacktrack = true;
      this.backtrackFragment = frag;
      this.resetTransmuxer();
      this.flushBufferGap(frag);
      this.fragmentTracker.removeFragment(frag);
      this.fragPrevious = null;
      this.nextLoadPosition = frag.start;
      this.state = State.IDLE;
    };
    return _createClass(StreamController, [{
      key: "backtrackFragment",
      get: function get() {
        return this._backtrackFragment;
      }

,
      set: function set(value) {
        this._backtrackFragment = value;
      }

    }, {
      key: "couldBacktrack",
      get: function get() {
        return this._couldBacktrack;
      }

,
      set: function set(value) {
        this._couldBacktrack = value;
      }
    }, {
      key: "hasEnoughToStart",
      get: function get() {
        return this._hasEnoughToStart;
      }
    }, {
      key: "maxBufferLength",
      get: function get() {
        var levels = this.levels,
          level = this.level;
        var levelInfo = levels == null ? void 0 : levels[level];
        if (!levelInfo) {
          return this.config.maxBufferLength;
        }
        return this.getMaxBufferLength(levelInfo.maxBitrate);
      }
    }, {
      key: "nextLevel",
      get: function get() {
        var frag = this.nextBufferedFrag;
        if (frag) {
          return frag.level;
        }
        return -1;
      }
    }, {
      key: "currentFrag",
      get: function get() {
        var _this$media2;
        if (this.fragPlaying) {
          return this.fragPlaying;
        }
        var currentTime = ((_this$media2 = this.media) == null ? void 0 : _this$media2.currentTime) || this.lastCurrentTime;
        if (isFiniteNumber(currentTime)) {
          return this.getAppendedFrag(currentTime);
        }
        return null;
      }
    }, {
      key: "currentProgramDateTime",
      get: function get() {
        var _this$media3;
        var currentTime = ((_this$media3 = this.media) == null ? void 0 : _this$media3.currentTime) || this.lastCurrentTime;
        if (isFiniteNumber(currentTime)) {
          var details = this.getLevelDetails();
          var frag = this.currentFrag || (details ? findFragmentByPTS(null, details.fragments, currentTime) : null);
          if (frag) {
            var programDateTime = frag.programDateTime;
            if (programDateTime !== null) {
              var epocMs = programDateTime + (currentTime - frag.start) * 1000;
              return new Date(epocMs);
            }
          }
        }
        return null;
      }
    }, {
      key: "currentLevel",
      get: function get() {
        var frag = this.currentFrag;
        if (frag) {
          return frag.level;
        }
        return -1;
      }
    }, {
      key: "nextBufferedFrag",
      get: function get() {
        var frag = this.currentFrag;
        if (frag) {
          return this.followingBufferedFrag(frag);
        }
        return null;
      }
    }, {
      key: "forceStartLoad",
      get: function get() {
        return this._forceStartLoad;
      }
    }]);
  }(BaseStreamController);

  var BaseLoader = function () {
    function BaseLoader() {
      this.requestTimeout = void 0;
      this.retryTimeout = void 0;
      this.retryDelay = 0;
      this.config = null;
      this.callbacks = null;
      this.context = null;
      this.stats = new LoadStats();
    }
    var _proto = BaseLoader.prototype;
    _proto.destroy = function destroy() {
      this.callbacks = this.context = this.config = null;
      this.abortInternal();
      this.stats = null;
    };
    _proto.load = function load(context, config, callbacks) {
      var stats = this.stats;
      if (stats.loading.start) {
        throw new Error('Loader can only be used once.');
      }
      stats.loading.start = self.performance.now();
      this.context = context;
      this.config = config;
      this.callbacks = callbacks;
      this.loadInternal();
    };
    _proto.abort = function abort() {
      var _this$callbacks;
      this.abortInternal();
      if ((_this$callbacks = this.callbacks) != null && _this$callbacks.onAbort) {
        this.callbacks.onAbort(this.stats, this.context, this.getNetworkDetails());
      }
    };
    _proto.loadtimeout = function loadtimeout() {
      if (!this.config) return;
      var retryConfig = this.config.loadPolicy.timeoutRetry;
      var retryCount = this.stats.retry;
      if (shouldRetry(retryConfig, retryCount, true)) {
        this.retry(retryConfig);
      } else {
        var _this$context;
        var callbacks = this.callbacks;
        if (callbacks) {
          this.abortInternal();
          callbacks.onTimeout(this.stats, this.context, this.getNetworkDetails());
        }
      }
    };
    _proto.retry = function retry(retryConfig) {
      var _this = this;
      var context = this.context,
        stats = this.stats;
      this.retryDelay = getRetryDelay(retryConfig, stats.retry);
      stats.retry++;
      this.abortInternal();
      this.resetInternalLoader();
      self.clearTimeout(this.retryTimeout);
      this.retryTimeout = self.setTimeout(function () {
        return _this.loadInternal();
      }, this.retryDelay);
    };
    return BaseLoader;
  }();

  var ChunkCache = function () {
    function ChunkCache() {
      this.chunks = [];
      this.dataLength = 0;
    }
    var _proto = ChunkCache.prototype;
    _proto.push = function push(chunk) {
      this.chunks.push(chunk);
      this.dataLength += chunk.length;
    };
    _proto.flush = function flush() {
      var chunks = this.chunks,
        dataLength = this.dataLength;
      var result;
      if (!chunks.length) {
        return new Uint8Array(0);
      } else if (chunks.length === 1) {
        result = chunks[0];
      } else {
        result = concatUint8Arrays(chunks, dataLength);
      }
      this.reset();
      return result;
    };
    _proto.reset = function reset() {
      this.chunks.length = 0;
      this.dataLength = 0;
    };
    return ChunkCache;
  }();
  function concatUint8Arrays(chunks, dataLength) {
    var result = new Uint8Array(dataLength);
    var offset = 0;
    for (var i = 0; i < chunks.length; i++) {
      var chunk = chunks[i];
      result.set(chunk, offset);
      offset += chunk.length;
    }
    return result;
  }

  function fetchSupported() {
    if (
    self.fetch && self.AbortController && self.ReadableStream && self.Request) {
      try {
        new self.ReadableStream({});
        return true;
      } catch (e) {
      }
    }
    return false;
  }
  var BYTERANGE = /(\\d+)-(\\d+)\\/(\\d+)/;
  var FetchLoader = function (_BaseLoader) {
    function FetchLoader(config) {
      var _this;
      _this = _BaseLoader.call(this) || this;
      _this.fetchSetup = void 0;
      _this.request = null;
      _this.response = null;
      _this.controller = null;
      _this.fetchSetup = config.fetchSetup || getRequest;
      return _this;
    }
    _inheritsLoose(FetchLoader, _BaseLoader);
    var _proto = FetchLoader.prototype;
    _proto.destroy = function destroy() {
      this.request = null;
      _BaseLoader.prototype.destroy.call(this);
      this.response = null;
      this.controller = null;
      this.fetchSetup = null;
    };
    _proto.abortInternal = function abortInternal() {
      self.clearTimeout(this.retryTimeout);
      if (this.controller && !this.stats.loading.end) {
        this.stats.aborted = true;
        this.controller.abort();
      }
    };
    _proto.getNetworkDetails = function getNetworkDetails() {
      return this.response;
    };
    _proto.resetInternalLoader = function resetInternalLoader() {
      this.response = null;
    };
    _proto.loadInternal = function loadInternal() {
      var _this2 = this;
      var config = this.config,
        context = this.context,
        stats = this.stats;
      if (!config || !context) {
        return;
      }

      stats.loading.first = 0;
      stats.loaded = 0;
      stats.aborted = false;

      this.controller = new self.AbortController();
      var initParams = getRequestParameters(context, this.controller.signal);
      var isArrayBuffer = context.responseType === 'arraybuffer';
      var LENGTH = isArrayBuffer ? 'byteLength' : 'length';
      var _config$loadPolicy = config.loadPolicy,
        maxTimeToFirstByteMs = _config$loadPolicy.maxTimeToFirstByteMs,
        maxLoadTimeMs = _config$loadPolicy.maxLoadTimeMs;
      this.request = this.fetchSetup(context, initParams);
      self.clearTimeout(this.requestTimeout);
      config.timeout = maxTimeToFirstByteMs && isFiniteNumber(maxTimeToFirstByteMs) ? maxTimeToFirstByteMs : maxLoadTimeMs;
      this.requestTimeout = self.setTimeout(function () {
        _this2.loadtimeout();
      }, config.timeout);
      var fetchPromise = isPromise(this.request) ? this.request.then(self.fetch) : self.fetch(this.request);
      fetchPromise.then(function (response) {
        var _this2$callbacks;
        _this2.response = response;
        var first = Math.max(self.performance.now(), stats.loading.start);
        self.clearTimeout(_this2.requestTimeout);
        config.timeout = maxLoadTimeMs;
        _this2.requestTimeout = self.setTimeout(function () {
          _this2.loadtimeout();
        }, maxLoadTimeMs - (first - stats.loading.start));
        if (!response.ok) {
          var status = response.status,
            statusText = response.statusText;
          throw new FetchError(statusText || 'fetch, bad network response', status, response);
        }
        stats.loading.first = first;
        stats.total = getContentLength(response.headers) || stats.total;
        var onProgress = (_this2$callbacks = _this2.callbacks) == null ? void 0 : _this2$callbacks.onProgress;
        if (onProgress && isFiniteNumber(config.highWaterMark)) {
          return _this2.loadProgressively(response, stats, context, config.highWaterMark, onProgress);
        }
        if (isArrayBuffer) {
          return response.arrayBuffer();
        }
        if (context.responseType === 'json') {
          return response.json();
        }
        return response.text();
      }).then(function (responseData) {
        var _this2$callbacks2, _this2$callbacks3;
        var response = _this2.response;
        if (!response) {
          throw new Error('loader destroyed');
        }
        self.clearTimeout(_this2.requestTimeout);
        stats.loading.end = Math.max(self.performance.now(), stats.loading.first);
        var total = responseData[LENGTH];
        if (total) {
          stats.loaded = stats.total = total;
        }
        var loaderResponse = {
          url: response.url,
          data: responseData,
          code: response.status
        };
        var onProgress = (_this2$callbacks2 = _this2.callbacks) == null ? void 0 : _this2$callbacks2.onProgress;
        if (onProgress && !isFiniteNumber(config.highWaterMark)) {
          onProgress(stats, context, responseData, response);
        }
        (_this2$callbacks3 = _this2.callbacks) == null || _this2$callbacks3.onSuccess(loaderResponse, stats, context, response);
      }).catch(function (error) {
        self.clearTimeout(_this2.requestTimeout);
        if (stats.aborted) {
          return;
        }
        var code = !error ? 0 : error.code || 0;
        var text = !error ? null : error.message;

        var retryConfig = config.loadPolicy.errorRetry;
        var retryCount = stats.retry;
        var response = {
          url: context.url,
          data: undefined,
          code: code
        };
        if (shouldRetry(retryConfig, retryCount, false, response)) {
          _this2.retry(retryConfig);
        } else {
          var _this2$callbacks4;
          logger.error(code + " while loading " + context.url);
          (_this2$callbacks4 = _this2.callbacks) == null || _this2$callbacks4.onError({
            code: code,
            text: text
          }, context, error ? error.details : null, stats);
        }
      });
    };
    _proto.getCacheAge = function getCacheAge() {
      var result = null;
      if (this.response) {
        var ageHeader = this.response.headers.get('age');
        result = ageHeader ? parseFloat(ageHeader) : null;
      }
      return result;
    };
    _proto.getResponseHeader = function getResponseHeader(name) {
      return this.response ? this.response.headers.get(name) : null;
    };
    _proto.loadProgressively = function loadProgressively(response, stats, context, highWaterMark, onProgress) {
      if (highWaterMark === void 0) {
        highWaterMark = 0;
      }
      var chunkCache = new ChunkCache();
      var reader = response.body.getReader();
      var _pump = function pump() {
        return reader.read().then(function (data) {
          if (data.done) {
            if (chunkCache.dataLength) {
              onProgress(stats, context, chunkCache.flush().buffer, response);
            }
            return Promise.resolve(new ArrayBuffer(0));
          }
          var chunk = data.value;
          var len = chunk.length;
          stats.loaded += len;
          if (len < highWaterMark || chunkCache.dataLength) {
            chunkCache.push(chunk);
            if (chunkCache.dataLength >= highWaterMark) {
              onProgress(stats, context, chunkCache.flush().buffer, response);
            }
          } else {
            onProgress(stats, context, chunk.buffer, response);
          }
          return _pump();
        }).catch(function () {
          return Promise.reject();
        });
      };
      return _pump();
    };
    return FetchLoader;
  }(BaseLoader);
  function getRequestParameters(context, signal) {
    var initParams = {
      method: 'GET',
      mode: 'cors',
      credentials: 'same-origin',
      signal: signal,
      headers: new self.Headers(_extends({}, context.headers))
    };
    if (context.rangeEnd) {
      initParams.headers.set('Range', 'bytes=' + context.rangeStart + '-' + String(context.rangeEnd - 1));
    }
    return initParams;
  }
  function getByteRangeLength(byteRangeHeader) {
    var result = BYTERANGE.exec(byteRangeHeader);
    if (result) {
      return parseInt(result[2]) - parseInt(result[1]) + 1;
    }
  }
  function getContentLength(headers) {
    var contentRange = headers.get('Content-Range');
    if (contentRange) {
      var byteRangeLength = getByteRangeLength(contentRange);
      if (isFiniteNumber(byteRangeLength)) {
        return byteRangeLength;
      }
    }
    var contentLength = headers.get('Content-Length');
    if (contentLength) {
      return parseInt(contentLength);
    }
  }
  function getRequest(context, initParams) {
    return new self.Request(context.url, initParams);
  }
  var FetchError = function (_Error) {
    function FetchError(message, code, details) {
      var _this3;
      _this3 = _Error.call(this, message) || this;
      _this3.code = void 0;
      _this3.details = void 0;
      _this3.code = code;
      _this3.details = details;
      return _this3;
    }
    _inheritsLoose(FetchError, _Error);
    return FetchError;
  }( _wrapNativeSuper(Error));

  var clamp = function clamp(val, min, max) {
    return Math.max(min, Math.min(val, max));
  };

  var AGE_HEADER_LINE_REGEX = /^age:\\s*[\\d.]+\\s*$/im;
  var XhrLoader = function (_BaseLoader) {
    function XhrLoader(config) {
      var _this;
      _this = _BaseLoader.call(this) || this;
      _this.xhrSetup = void 0;
      _this.loader = null;
      _this.xhrSetup = config.xhrSetup || null;
      return _this;
    }
    _inheritsLoose(XhrLoader, _BaseLoader);
    var _proto = XhrLoader.prototype;
    _proto.destroy = function destroy() {
      _BaseLoader.prototype.destroy.call(this);
      this.loader = null;
      this.xhrSetup = null;
    };
    _proto.abortInternal = function abortInternal() {
      var loader = this.loader;
      self.clearTimeout(this.requestTimeout);
      self.clearTimeout(this.retryTimeout);
      if (loader) {
        loader.onreadystatechange = null;
        loader.onprogress = null;
        if (loader.readyState !== 4) {
          this.stats.aborted = true;
          loader.abort();
        }
      }
    };
    _proto.getNetworkDetails = function getNetworkDetails() {
      return this.loader;
    };
    _proto.resetInternalLoader = function resetInternalLoader() {
      this.loader = null;
    };
    _proto.loadInternal = function loadInternal() {
      var _this2 = this;
      var config = this.config,
        context = this.context;
      if (!config || !context) {
        return;
      }
      var xhr = this.loader = new self.XMLHttpRequest();
      var stats = this.stats;
      stats.loading.first = 0;
      stats.loaded = 0;
      stats.aborted = false;
      var xhrSetup = this.xhrSetup;
      if (xhrSetup) {
        Promise.resolve().then(function () {
          if (_this2.loader !== xhr || _this2.stats.aborted) return;
          return xhrSetup.call(_this2, xhr, context.url, context);
        }).catch(function (error) {
          if (_this2.loader !== xhr || _this2.stats.aborted) return;
          xhr.open('GET', context.url, true);
          return xhrSetup.call(_this2, xhr, context.url, context);
        }).then(function () {
          if (_this2.loader !== xhr || _this2.stats.aborted) return;
          _this2.openAndSendXhr(xhr, context, config);
        }).catch(function (error) {
          var _this2$callbacks;
          (_this2$callbacks = _this2.callbacks) == null || _this2$callbacks.onError({
            code: xhr.status,
            text: error.message
          }, context, xhr, stats);
          return;
        });
      } else {
        this.openAndSendXhr(xhr, context, config);
      }
    };
    _proto.openAndSendXhr = function openAndSendXhr(xhr, context, config) {
      if (!xhr.readyState) {
        xhr.open('GET', context.url, true);
      }
      var headers = context.headers;
      var _config$loadPolicy = config.loadPolicy,
        maxTimeToFirstByteMs = _config$loadPolicy.maxTimeToFirstByteMs,
        maxLoadTimeMs = _config$loadPolicy.maxLoadTimeMs;
      if (headers) {
        for (var header in headers) {
          xhr.setRequestHeader(header, headers[header]);
        }
      }
      if (context.rangeEnd) {
        xhr.setRequestHeader('Range', "bytes=" + context.rangeStart + "-" + (context.rangeEnd - 1));
      }
      xhr.onreadystatechange = this.readystatechange.bind(this);
      xhr.onprogress = this.loadprogress.bind(this);
      xhr.responseType = context.responseType;
      self.clearTimeout(this.requestTimeout);
      config.timeout = maxTimeToFirstByteMs && isFiniteNumber(maxTimeToFirstByteMs) ? maxTimeToFirstByteMs : maxLoadTimeMs;
      this.requestTimeout = self.setTimeout(this.loadtimeout.bind(this), config.timeout);
      xhr.send();
    };
    _proto.readystatechange = function readystatechange() {
      var context = this.context,
        xhr = this.loader,
        stats = this.stats;
      if (!context || !xhr) {
        return;
      }
      var readyState = xhr.readyState;
      var config = this.config;

      if (stats.aborted) {
        return;
      }

      if (readyState >= 2) {
        if (stats.loading.first === 0) {
          stats.loading.first = Math.max(self.performance.now(), stats.loading.start);
          if (config.timeout !== config.loadPolicy.maxLoadTimeMs) {
            self.clearTimeout(this.requestTimeout);
            config.timeout = config.loadPolicy.maxLoadTimeMs;
            this.requestTimeout = self.setTimeout(this.loadtimeout.bind(this), config.loadPolicy.maxLoadTimeMs - (stats.loading.first - stats.loading.start));
          }
        }
        if (readyState === 4) {
          self.clearTimeout(this.requestTimeout);
          xhr.onreadystatechange = null;
          xhr.onprogress = null;
          var status = xhr.status;
          var useResponseText = xhr.responseType === 'text' ? xhr.responseText : null;
          if (status >= 200 && status < 300) {
            var data = useResponseText != null ? useResponseText : xhr.response;
            if (data != null) {
              var _this$callbacks, _this$callbacks2;
              stats.loading.end = Math.max(self.performance.now(), stats.loading.first);
              var len = xhr.responseType === 'arraybuffer' ? data.byteLength : data.length;
              stats.loaded = stats.total = len;
              stats.bwEstimate = stats.total * 8000 / (stats.loading.end - stats.loading.first);
              var onProgress = (_this$callbacks = this.callbacks) == null ? void 0 : _this$callbacks.onProgress;
              if (onProgress) {
                onProgress(stats, context, data, xhr);
              }
              var _response = {
                url: xhr.responseURL,
                data: data,
                code: status
              };
              if (context.rangeEnd && len !== context.rangeEnd - context.rangeStart) {
              }
              (_this$callbacks2 = this.callbacks) == null || _this$callbacks2.onSuccess(_response, stats, context, xhr);
              return;
            }
          }

          var retryConfig = config.loadPolicy.errorRetry;
          var retryCount = stats.retry;
          var response = {
            url: context.url,
            data: undefined,
            code: status
          };
          if (shouldRetry(retryConfig, retryCount, false, response)) {
            this.retry(retryConfig);
          } else {
            var _this$callbacks3;
            logger.error(status + " while loading " + context.url);
            (_this$callbacks3 = this.callbacks) == null || _this$callbacks3.onError({
              code: status,
              text: xhr.statusText
            }, context, xhr, stats);
          }
        }
      }
    };
    _proto.loadprogress = function loadprogress(event) {
      var stats = this.stats;
      stats.loaded = event.loaded;
      if (event.lengthComputable) {
        stats.total = event.total;
      }
    };
    _proto.getCacheAge = function getCacheAge() {
      var result = null;
      if (this.loader && AGE_HEADER_LINE_REGEX.test(this.loader.getAllResponseHeaders())) {
        var ageHeader = this.loader.getResponseHeader('age');
        result = ageHeader ? parseFloat(ageHeader) : null;
      }
      return result;
    };
    _proto.getResponseHeader = function getResponseHeader(name) {
      if (this.loader && new RegExp("^" + name + ":\\\\s*[\\\\d.]+\\\\s*$", 'im').test(this.loader.getAllResponseHeaders())) {
        return this.loader.getResponseHeader(name);
      }
      return null;
    };
    return XhrLoader;
  }(BaseLoader);

  var defaultLoadPolicy = {
    maxTimeToFirstByteMs: 8000,
    maxLoadTimeMs: 20000,
    timeoutRetry: null,
    errorRetry: null
  };

  var hlsDefaultConfig = _objectSpread2(_objectSpread2({
    autoStartLoad: true,
    startPosition: -1,
    defaultAudioCodec: undefined,
    debug: false,
    capLevelOnFPSDrop: false,
    capLevelToPlayerSize: false,
    ignoreDevicePixelRatio: false,
    maxDevicePixelRatio: Number.POSITIVE_INFINITY,
    preferManagedMediaSource: false,
    initialLiveManifestSize: 1,
    maxBufferLength: 30,
    backBufferLength: Infinity,
    frontBufferFlushThreshold: Infinity,
    loopBackBufferFlush: undefined,
    startOnSegmentBoundary: false,
    nextAudioTrackBufferFlushForwardOffset: 0.25,
    maxBufferSize: 60 * 1000 * 1000,
    maxFragLookUpTolerance: 0.25,
    maxBufferHole: 0.1,
    detectStallWithCurrentTimeMs: 1250,
    highBufferWatchdogPeriod: 2,
    nudgeOffset: 0.1,
    nudgeMaxRetry: 3,
    nudgeOnVideoHole: true,
    skipBufferHolePadding: 0.1,
    liveSyncMode: 'edge',
    liveSyncDurationCount: 3,
    liveSyncOnStallIncrease: 1,
    liveMaxLatencyDurationCount: Infinity,
    liveMaxUnchangedPlaylistRefresh: Infinity,
    liveSyncDuration: undefined,
    liveMaxLatencyDuration: undefined,
    maxLiveSyncPlaybackRate: 1,
    liveDurationInfinity: false,
    liveBackBufferLength: null,
    maxMaxBufferLength: 600,
    enableWorker: true,
    workerPath: null,
    enableSoftwareAES: true,
    startLevel: undefined,
    startFragPrefetch: false,
    fpsDroppedMonitoringPeriod: 5000,
    fpsDroppedMonitoringThreshold: 0.2,
    appendErrorMaxRetry: 3,
    appendTimeout: Infinity,
    ignorePlaylistParsingErrors: false,
    loader: XhrLoader,
    fLoader: undefined,
    pLoader: undefined,
    xhrSetup: undefined,
    licenseXhrSetup: undefined,
    licenseResponseCallback: undefined,
    stretchShortVideoTrack: false,
    maxAudioFramesDrift: 1,
    forceKeyFrameOnDiscontinuity: true,
    handleMpegTsVideoIntegrityErrors: 'process',
    abrEwmaFastLive: 3,
    abrEwmaSlowLive: 9,
    abrEwmaFastVoD: 3,
    abrEwmaSlowVoD: 9,
    abrEwmaDefaultEstimate: 5e5,
    abrEwmaDefaultEstimateMax: 5e6,
    abrBandWidthFactor: 0.95,
    abrBandWidthUpFactor: 0.7,
    abrMaxWithRealBitrate: false,
    abrSwitchInterval: 0,
    maxStarvationDelay: 4,
    maxLoadingDelay: 4,
    minAutoBitrate: 0,
    emeEnabled: false,
    widevineLicenseUrl: undefined,
    drmSystems: {},
    drmSystemOptions: {},
    requestMediaKeySystemAccessFunc: null,
    requireKeySystemAccessOnStart: false,
    testBandwidth: true,
    progressive: false,
    lowLatencyMode: true,
    cmcd: undefined,
    enableDateRangeMetadataCues: true,
    enableEmsgMetadataCues: true,
    enableEmsgKLVMetadata: false,
    enableID3MetadataCues: true,
    emsgKLVSchemaUri: undefined,
    enableInterstitialPlayback: false,
    interstitialAppendInPlace: true,
    interstitialLiveLookAhead: 10,
    iframeCacheLimit: 2 * 1024 * 1024,
    useMediaCapabilities: false,
    preserveManualLevelOnError: false,
    errorPenaltyExpireMs: 0,

    certLoadPolicy: {
      default: defaultLoadPolicy
    },
    keyLoadPolicy: {
      default: {
        maxTimeToFirstByteMs: 8000,
        maxLoadTimeMs: 20000,
        timeoutRetry: {
          maxNumRetry: 1,
          retryDelayMs: 1000,
          maxRetryDelayMs: 20000,
          backoff: 'linear'
        },
        errorRetry: {
          maxNumRetry: 8,
          retryDelayMs: 1000,
          maxRetryDelayMs: 20000,
          backoff: 'linear'
        }
      }
    },
    manifestLoadPolicy: {
      default: {
        maxTimeToFirstByteMs: Infinity,
        maxLoadTimeMs: 20000,
        timeoutRetry: {
          maxNumRetry: 2,
          retryDelayMs: 0,
          maxRetryDelayMs: 0
        },
        errorRetry: {
          maxNumRetry: 1,
          retryDelayMs: 1000,
          maxRetryDelayMs: 8000
        }
      }
    },
    playlistLoadPolicy: {
      default: {
        maxTimeToFirstByteMs: 10000,
        maxLoadTimeMs: 20000,
        timeoutRetry: {
          maxNumRetry: 2,
          retryDelayMs: 0,
          maxRetryDelayMs: 0
        },
        errorRetry: {
          maxNumRetry: 2,
          retryDelayMs: 1000,
          maxRetryDelayMs: 8000
        }
      }
    },
    fragLoadPolicy: {
      default: {
        maxTimeToFirstByteMs: 10000,
        maxLoadTimeMs: 120000,
        timeoutRetry: {
          maxNumRetry: 4,
          retryDelayMs: 0,
          maxRetryDelayMs: 0
        },
        errorRetry: {
          maxNumRetry: 6,
          retryDelayMs: 1000,
          maxRetryDelayMs: 8000
        }
      }
    },
    steeringManifestLoadPolicy: {
      default: {
        maxTimeToFirstByteMs: 10000,
        maxLoadTimeMs: 20000,
        timeoutRetry: {
          maxNumRetry: 2,
          retryDelayMs: 0,
          maxRetryDelayMs: 0
        },
        errorRetry: {
          maxNumRetry: 1,
          retryDelayMs: 1000,
          maxRetryDelayMs: 8000
        }
      } 
    },
    interstitialAssetListLoadPolicy: {
      default: defaultLoadPolicy
    },
    manifestLoadingTimeOut: 10000,
    manifestLoadingMaxRetry: 1,
    manifestLoadingRetryDelay: 1000,
    manifestLoadingMaxRetryTimeout: 64000,
    levelLoadingTimeOut: 10000,
    levelLoadingMaxRetry: 4,
    levelLoadingRetryDelay: 1000,
    levelLoadingMaxRetryTimeout: 64000,
    fragLoadingTimeOut: 20000,
    fragLoadingMaxRetry: 6,
    fragLoadingRetryDelay: 1000,
    fragLoadingMaxRetryTimeout: 64000,
    streamController: StreamController,
    abrController: AbrController,
    bufferController: BufferController,
    capLevelController: undefined,
    errorController: ErrorController,
    fpsController: undefined,
    id3TrackController: undefined,
    gapController: GapController,
    latencyController: LatencyController
  }, timelineConfig()), {}, {
    subtitleStreamController: undefined,
    subtitleTrackController: undefined,
    timelineController: undefined,
    audioStreamController: undefined,
    audioTrackController: undefined,
    emeController: undefined,
    cmcdController: undefined,
    contentSteeringController: undefined,
    iframeController: undefined,
    interstitialsController: undefined
  });
  function timelineConfig() {
    return {
      cueHandler: Cues,
      enableWebVTT: false,
      enableIMSC1: false,
      enableCEA708Captions: false,
      captionsTextTrack1Label: 'English',
      captionsTextTrack1LanguageCode: 'en',
      captionsTextTrack2Label: 'Spanish',
      captionsTextTrack2LanguageCode: 'es',
      captionsTextTrack3Label: 'Unknown CC',
      captionsTextTrack3LanguageCode: '',
      captionsTextTrack4Label: 'Unknown CC',
      captionsTextTrack4LanguageCode: '',
      renderTextTracksNatively: true
    };
  }

  function mergeConfig(defaultConfig, userConfig, logger) {
    if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
      throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
    }
    if (userConfig.liveMaxLatencyDurationCount !== undefined && (userConfig.liveSyncDurationCount === undefined || userConfig.liveMaxLatencyDurationCount <= userConfig.liveSyncDurationCount)) {
      throw new Error('Illegal hls.js config: "liveMaxLatencyDurationCount" must be greater than "liveSyncDurationCount"');
    }
    if (userConfig.liveMaxLatencyDuration !== undefined && (userConfig.liveSyncDuration === undefined || userConfig.liveMaxLatencyDuration <= userConfig.liveSyncDuration)) {
      throw new Error('Illegal hls.js config: "liveMaxLatencyDuration" must be greater than "liveSyncDuration"');
    }
    if (userConfig.liveMaxUnchangedPlaylistRefresh !== undefined) {
      var liveMaxUnchangedPlaylistRefresh = userConfig.liveMaxUnchangedPlaylistRefresh;
      var clampedValue = clamp(liveMaxUnchangedPlaylistRefresh, 2, Infinity);
      if (clampedValue !== liveMaxUnchangedPlaylistRefresh) {
      }
      userConfig.liveMaxUnchangedPlaylistRefresh = clampedValue;
    }
    var defaultsCopy = deepCpy(defaultConfig);

    var deprecatedSettingTypes = ['manifest', 'level', 'frag'];
    var deprecatedSettings = ['TimeOut', 'MaxRetry', 'RetryDelay', 'MaxRetryTimeout'];
    deprecatedSettingTypes.forEach(function (type) {
      var policyName = (type === 'level' ? 'playlist' : type) + "LoadPolicy";
      var policyNotSet = userConfig[policyName] === undefined;
      var report = [];
      deprecatedSettings.forEach(function (setting) {
        var deprecatedSetting = type + "Loading" + setting;
        var value = userConfig[deprecatedSetting];
        if (value !== undefined && policyNotSet) {
          report.push(deprecatedSetting);
          var settings = defaultsCopy[policyName].default;
          userConfig[policyName] = {
            default: settings
          };
          switch (setting) {
            case 'TimeOut':
              settings.maxLoadTimeMs = value;
              settings.maxTimeToFirstByteMs = value;
              break;
            case 'MaxRetry':
              settings.errorRetry.maxNumRetry = value;
              settings.timeoutRetry.maxNumRetry = value;
              break;
            case 'RetryDelay':
              settings.errorRetry.retryDelayMs = value;
              settings.timeoutRetry.retryDelayMs = value;
              break;
            case 'MaxRetryTimeout':
              settings.errorRetry.maxRetryDelayMs = value;
              settings.timeoutRetry.maxRetryDelayMs = value;
              break;
          }
        }
      });
      if (report.length) {
      }
    });
    return _objectSpread2(_objectSpread2({}, defaultsCopy), userConfig);
  }
  function deepCpy(obj) {
    if (obj && typeof obj === 'object') {
      if (Array.isArray(obj)) {
        return obj.map(deepCpy);
      }
      return Object.keys(obj).reduce(function (result, key) {
        result[key] = deepCpy(obj[key]);
        return result;
      }, {});
    }
    return obj;
  }

  function enableStreamingMode(config, logger) {
    var currentLoader = config.loader;
    if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
      config.progressive = false;
    } else {
      var canStreamProgressively = fetchSupported();
      if (canStreamProgressively) {
        config.loader = FetchLoader;
        config.progressive = true;
        config.enableSoftwareAES = true;
      }
    }
  }

  var BasePlaylistController = function (_Logger) {
    function BasePlaylistController(hls, logPrefix) {
      var _this;
      _this = _Logger.call(this, logPrefix, hls.logger) || this;
      _this.hls = void 0;
      _this.canLoad = false;
      _this.timer = -1;
      _this.loadingSync = false;
      _this.hls = hls;
      return _this;
    }
    _inheritsLoose(BasePlaylistController, _Logger);
    var _proto = BasePlaylistController.prototype;
    _proto.destroy = function destroy() {
      this.clearTimer();
      this.hls = null;
    };
    _proto.clearTimer = function clearTimer() {
      if (this.timer !== -1) {
        self.clearTimeout(this.timer);
        this.timer = -1;
      }
    };
    _proto.startLoad = function startLoad() {
      this.canLoad = true;
      this.loadPlaylist();
    };
    _proto.stopLoad = function stopLoad() {
      this.canLoad = false;
      this.clearTimer();
    };
    _proto.switchParams = function switchParams(playlistUri, previous, current) {
      var renditionReports = previous == null ? void 0 : previous.renditionReports;
      if (renditionReports) {
        var foundIndex = -1;
        for (var i = 0; i < renditionReports.length; i++) {
          var attr = renditionReports[i];
          var uri = void 0;
          try {
            uri = new self.URL(attr.URI, previous.url).href;
          } catch (error) {
            uri = attr.URI || '';
          }
          if (uri === playlistUri) {
            foundIndex = i;
            break;
          } else if (uri === playlistUri.substring(0, uri.length)) {
            foundIndex = i;
          }
        }
        if (foundIndex !== -1) {
          var _attr = renditionReports[foundIndex];
          var msn = parseInt(_attr['LAST-MSN']) || previous.lastPartSn;
          var part = parseInt(_attr['LAST-PART']) || previous.lastPartIndex;
          if (this.hls.config.lowLatencyMode) {
            var currentGoal = Math.min(previous.age - previous.partTarget, previous.targetduration);
            if (part >= 0 && currentGoal > previous.partTarget) {
              part += 1;
            }
          }
          var skip = current && getSkipValue(current);
          return new HlsUrlParameters(msn, part >= 0 ? part : undefined, skip);
        }
      }
    };
    _proto.loadPlaylist = function loadPlaylist(hlsUrlParameters) {
      this.clearTimer();
    };
    _proto.loadingPlaylist = function loadingPlaylist(playlist, hlsUrlParameters) {
      this.clearTimer();
    };
    _proto.shouldLoadPlaylist = function shouldLoadPlaylist(playlist) {
      return this.canLoad && !!playlist && !!playlist.url && (!playlist.details || playlist.details.live);
    };
    _proto.getUrlWithDirectives = function getUrlWithDirectives(uri, hlsUrlParameters) {
      if (hlsUrlParameters) {
        try {
          return hlsUrlParameters.addDirectives(uri);
        } catch (error) {
        }
      }
      return uri;
    };
    _proto.playlistLoaded = function playlistLoaded(index, data, previousDetails) {
      var details = data.details,
        stats = data.stats;
      var fragments = details.fragments;

      var now = self.performance.now();
      var elapsed = stats.loading.first ? Math.max(0, Math.floor(now - stats.loading.first)) : 0;
      details.advancedDateTime = Date.now() - elapsed;

      var timelineOffset = this.hls.config.timelineOffset;
      if (timelineOffset !== details.appliedTimelineOffset) {
        var offset = Math.max(timelineOffset || 0, 0);
        details.appliedTimelineOffset = offset;
        fragments.forEach(function (frag) {
          frag == null || frag.setStart(frag.playlistOffset + offset);
        });
      }

      if (details.live || previousDetails != null && previousDetails.live) {
        var _fragments;
        var levelOrTrack = 'levelInfo' in data ? data.levelInfo : data.track;
        details.reloaded(previousDetails);
        var parent = (_fragments = fragments[fragments.length - 1]) == null ? void 0 : _fragments.type;

        if (details.misses >= this.hls.config.liveMaxUnchangedPlaylistRefresh) {
          var error = new Error(parent + " playlist " + levelOrTrack.id + " hit max allowed unchanged reloads.");
          var networkDetails = data.networkDetails,
            context = data.context;
          this.hls.trigger(Events.ERROR, {
            type: ErrorTypes.NETWORK_ERROR,
            details: ErrorDetails.PLAYLIST_UNCHANGED_ERROR,
            fatal: false,
            url: details.url,
            error: error,
            reason: error.message,
            level: data.level,
            parent: parent,
            context: context,
            networkDetails: networkDetails,
            stats: stats
          });
          return;
        }

        if (previousDetails) {
          mergeDetails(previousDetails, details, this);
          var _error = details.playlistParsingError;
          if (_error) {
            var hls = this.hls;
            if (!hls.config.ignorePlaylistParsingErrors) {
              var _networkDetails = data.networkDetails;
              hls.trigger(Events.ERROR, {
                type: ErrorTypes.NETWORK_ERROR,
                details: ErrorDetails.LEVEL_PARSING_ERROR,
                fatal: false,
                url: details.url,
                error: _error,
                reason: _error.message,
                level: data.level,
                parent: parent,
                networkDetails: _networkDetails,
                stats: stats
              });
              return;
            }
            details.playlistParsingError = null;
          }
        }
        if (details.requestScheduled === -1) {
          details.requestScheduled = stats.loading.start;
        }
        var bufferInfo = this.hls.mainForwardBufferInfo;
        var position = bufferInfo ? bufferInfo.end - bufferInfo.len : 0;
        var distanceToLiveEdgeMs = (details.edge - position) * 1000;
        var reloadInterval = computeReloadInterval(details, distanceToLiveEdgeMs);
        if (details.requestScheduled + reloadInterval < now) {
          details.requestScheduled = now;
        } else if (details.requestScheduled <= now) {
          details.requestScheduled += reloadInterval;
        }
        var requestedHls = data.deliveryDirectives;
        if (!this.canLoad || !details.live) {
          return;
        }
        var deliveryDirectives;
        var msn = undefined;
        var part = undefined;
        if (details.canBlockReload && details.advanced) {
          var lowLatencyMode = this.hls.config.lowLatencyMode;
          var lastPartSn = details.lastPartSn;
          var endSn = details.endSN;
          var lastPartIndex = details.lastPartIndex;
          var hasParts = lastPartIndex !== -1;
          var atLastPartOfSegment = lastPartSn === endSn;
          if (hasParts) {
            if (atLastPartOfSegment) {
              msn = endSn + 1;
              part = lowLatencyMode ? 0 : lastPartIndex;
            } else {
              msn = lastPartSn;
              part = lowLatencyMode ? lastPartIndex + 1 : details.maxPartIndex;
            }
          } else {
            msn = endSn + 1;
          }
          var lastAdvanced = details.age;
          var cdnAge = lastAdvanced + details.ageHeader;
          var currentGoal = Math.min(cdnAge - details.partTarget, details.targetduration * 1.5);
          if (currentGoal > 0) {
            if (cdnAge > details.targetduration * 3) {
              msn = undefined;
              part = undefined;
            } else if (previousDetails != null && previousDetails.tuneInGoal && cdnAge - details.partTarget > previousDetails.tuneInGoal) {
              currentGoal = 0;
            } else {
              var segments = Math.floor(currentGoal / details.targetduration);
              msn += segments;
              if (part !== undefined) {
                var parts = Math.round(currentGoal % details.targetduration / details.partTarget);
                part += parts;
              }
            }
            details.tuneInGoal = currentGoal;
          }
          deliveryDirectives = this.getDeliveryDirectives(details, data.deliveryDirectives, msn, part);
          if (lowLatencyMode || !atLastPartOfSegment) {
            details.requestScheduled = now;
            this.loadingPlaylist(levelOrTrack, deliveryDirectives);
            return;
          }
        } else if (details.canBlockReload || details.canSkipUntil) {
          deliveryDirectives = this.getDeliveryDirectives(details, data.deliveryDirectives, msn, part);
        }
        if (deliveryDirectives && msn !== undefined && details.canBlockReload) {
          details.requestScheduled = stats.loading.first + Math.max(reloadInterval - elapsed * 2, reloadInterval / 2);
        }
        this.scheduleLoading(levelOrTrack, deliveryDirectives, details);
      } else {
        this.clearTimer();
      }
    };
    _proto.scheduleLoading = function scheduleLoading(levelOrTrack, deliveryDirectives, updatedDetails) {
      var _this2 = this;
      var details = updatedDetails || levelOrTrack.details;
      if (!details) {
        this.loadingPlaylist(levelOrTrack, deliveryDirectives);
        return;
      }
      var now = self.performance.now();
      var requestScheduled = details.requestScheduled;
      if (now >= requestScheduled && !this.loadingSync) {
        this.loadingSync = true;
        this.loadingPlaylist(levelOrTrack, deliveryDirectives);
        this.loadingSync = false;
        return;
      }
      var estimatedTimeUntilUpdate = Math.max(requestScheduled - now, 0);
      this.clearTimer();
      this.timer = self.setTimeout(function () {
        return _this2.loadingPlaylist(levelOrTrack, deliveryDirectives);
      }, estimatedTimeUntilUpdate);
    };
    _proto.getDeliveryDirectives = function getDeliveryDirectives(details, previousDeliveryDirectives, msn, part) {
      var skip = getSkipValue(details);
      if (previousDeliveryDirectives != null && previousDeliveryDirectives.skip && details.deltaUpdateFailed) {
        msn = previousDeliveryDirectives.msn;
        part = previousDeliveryDirectives.part;
        skip = HlsSkip.No;
      }
      return new HlsUrlParameters(msn, part, skip);
    };
    _proto.checkRetry = function checkRetry(errorEvent) {
      var _this3 = this;
      var errorDetails = errorEvent.details;
      var isTimeout = isTimeoutError(errorEvent);
      var errorAction = errorEvent.errorAction;
      var _ref = errorAction || {},
        action = _ref.action,
        _ref$retryCount = _ref.retryCount,
        retryCount = _ref$retryCount === void 0 ? 0 : _ref$retryCount,
        retryConfig = _ref.retryConfig;
      var retry = !!errorAction && !!retryConfig && (action === NetworkErrorAction.RetryRequest || !errorAction.resolved && action === NetworkErrorAction.SendAlternateToPenaltyBox);
      if (retry) {
        var _errorEvent$context;
        if (retryCount >= retryConfig.maxNumRetry) {
          return false;
        }
        if (isTimeout && (_errorEvent$context = errorEvent.context) != null && _errorEvent$context.deliveryDirectives) {
          this.loadPlaylist();
        } else {
          var _errorEvent$response;
          var offlineStatus = offlineHttpStatus((_errorEvent$response = errorEvent.response) == null ? void 0 : _errorEvent$response.code);
          this.clearTimer();
          if (offlineStatus) {
            errorEvent.reason = 'offline';
            this.timer = self.setTimeout(function () {
              return _this3.checkOfflineStatus();
            }, 1000);
          } else {
            var delay = getRetryDelay(retryConfig, retryCount);
            this.timer = self.setTimeout(function () {
              return _this3.loadPlaylist();
            }, delay);
          }
        }
        errorEvent.levelRetry = true;
        errorAction.resolved = true;
      }
      return retry;
    };
    _proto.checkOfflineStatus = function checkOfflineStatus() {
      var _this4 = this;
      this.clearTimer();
      if (offlineHttpStatus(0)) {
        this.timer = self.setTimeout(function () {
          return _this4.checkOfflineStatus();
        }, 1000);
      } else {
        this.loadPlaylist();
      }
    };
    return BasePlaylistController;
  }(Logger);

  var LevelController = function (_BasePlaylistControll) {
    function LevelController(hls, contentSteeringController) {
      var _this;
      _this = _BasePlaylistControll.call(this, hls, 'level-controller') || this;
      _this._levels = [];
      _this._firstLevel = -1;
      _this._maxAutoLevel = -1;
      _this._startLevel = void 0;
      _this.currentLevel = null;
      _this.currentLevelIndex = -1;
      _this.manualLevelIndex = -1;
      _this.steering = void 0;
      _this.lastABRSwitchTime = -1;
      _this._iframeVariants = [];
      _this.onParsedComplete = void 0;
      _this.steering = contentSteeringController;
      _this._registerListeners();
      return _this;
    }
    _inheritsLoose(LevelController, _BasePlaylistControll);
    var _proto = LevelController.prototype;
    _proto._registerListeners = function _registerListeners() {
      var hls = this.hls;
      hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
      hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
      hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
      hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
      hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
      hls.on(Events.ERROR, this.onError, this);
    };
    _proto._unregisterListeners = function _unregisterListeners() {
      var hls = this.hls;
      hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
      hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
      hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
      hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
      hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
      hls.off(Events.ERROR, this.onError, this);
    };
    _proto.destroy = function destroy() {
      this._unregisterListeners();
      this.steering = null;
      this.resetLevels();
      _BasePlaylistControll.prototype.destroy.call(this);
    };
    _proto.stopLoad = function stopLoad() {
      var levels = this._levels;

      levels.forEach(function (level) {
        level.loadError = 0;
        level.fragmentError = 0;
      });
      _BasePlaylistControll.prototype.stopLoad.call(this);
    };
    _proto.resetLevels = function resetLevels() {
      this._startLevel = undefined;
      this.manualLevelIndex = -1;
      this.currentLevelIndex = -1;
      this.currentLevel = null;
      this._levels = [];
      this._maxAutoLevel = -1;
      this._iframeVariants = [];
    };
    _proto.onManifestLoading = function onManifestLoading(event, data) {
      this.resetLevels();
    };
    _proto.onManifestLoaded = function onManifestLoaded(event, data) {
      var _this2 = this;
      var preferManagedMediaSource = this.hls.config.preferManagedMediaSource;
      var levels = [];
      var redundantSet = {};
      var generatePathwaySet = {};
      var resolutionFound = false;
      var videoCodecFound = false;
      var audioCodecFound = false;
      var imageCodecFound = false;
      data.levels.forEach(function (levelParsed) {
        var attributes = levelParsed.attrs;
        var supported = setTrackCodecsAndReturnSupported(levelParsed, preferManagedMediaSource);
        var audioCodec = levelParsed.audioCodec,
          videoCodec = levelParsed.videoCodec,
          imageCodec = levelParsed.imageCodec,
          width = levelParsed.width,
          height = levelParsed.height;
        if (!supported) {
          return;
        }
        resolutionFound || (resolutionFound = !!(width && height));
        videoCodecFound || (videoCodecFound = !!videoCodec);
        audioCodecFound || (audioCodecFound = !!audioCodec);
        imageCodecFound || (imageCodecFound = !!imageCodec);
        var CODECS = attributes.CODECS,
          FRAMERATE = attributes['FRAME-RATE'],
          HDCP = attributes['HDCP-LEVEL'],
          PATHWAY = attributes['PATHWAY-ID'],
          RESOLUTION = attributes.RESOLUTION,
          VIDEO_RANGE = attributes['VIDEO-RANGE'];
        var contentSteeringPrefix = (PATHWAY || '.') + "-";
        var levelKey = "" + contentSteeringPrefix + levelParsed.bitrate + "-" + RESOLUTION + "-" + FRAMERATE + "-" + CODECS + "-" + VIDEO_RANGE + "-" + HDCP;
        if (!redundantSet[levelKey]) {
          var level = _this2.createLevel(levelParsed);
          redundantSet[levelKey] = level;
          generatePathwaySet[levelKey] = 1;
          levels.push(level);
        } else if (redundantSet[levelKey].uri !== levelParsed.url && !levelParsed.attrs['PATHWAY-ID']) {
          var pathwayCount = generatePathwaySet[levelKey] += 1;
          levelParsed.attrs['PATHWAY-ID'] = new Array(pathwayCount + 1).join('.');
          var _level = _this2.createLevel(levelParsed);
          redundantSet[levelKey] = _level;
          levels.push(_level);
        } else if (!levelParsed.iframes) {
          redundantSet[levelKey].addGroupId('audio', attributes.AUDIO);
          redundantSet[levelKey].addGroupId('text', attributes.SUBTITLES);
        }
      });
      this.filterAndSortMediaOptions(levels, data, resolutionFound, videoCodecFound, audioCodecFound, imageCodecFound);
    };
    _proto.createLevel = function createLevel(levelParsed) {
      var level = new Level(levelParsed);
      var supplemental = levelParsed.supplemental;
      if (supplemental != null && supplemental.videoCodec && !isVideoSupported(supplemental.videoCodec, this.hls.config.preferManagedMediaSource)) {
        var error = new Error("SUPPLEMENTAL-CODECS not supported \\"" + supplemental.videoCodec + "\\"");
        level.supportedResult = emptyExports.getUnsupportedResult(error, []);
      }
      return level;
    };
    _proto.filterAndSortMediaOptions = function filterAndSortMediaOptions(filteredLevels, data, resolutionFound, videoCodecFound, audioCodecFound, imageCodecFound) {
      var _data$stats,
        _this3 = this;
      var audioTracks = [];
      var subtitleTracks = [];
      var levels = filteredLevels;
      var statsParsing = ((_data$stats = data.stats) == null ? void 0 : _data$stats.parsing) || {};
      var preferManagedMediaSource = this.hls.config.preferManagedMediaSource;

      if ((resolutionFound || videoCodecFound) && audioCodecFound) {
        levels = levels.filter(function (_ref) {
          var videoCodec = _ref.videoCodec,
            videoRange = _ref.videoRange,
            width = _ref.width,
            height = _ref.height;
          return (!!videoCodec || !!(width && height)) && isVideoRange(videoRange);
        });
      }

      if (videoCodecFound && imageCodecFound) {
        levels = levels.filter(function (lvl) {
          return !lvl.imageCodec;
        });
      }
      if (levels.length === 0) {
        Promise.resolve().then(function () {
          if (_this3.hls) {
            var message = 'no level with compatible codecs found in manifest';
            var reason = message;
            if (data.levels.length) {
              reason = "one or more CODECS in variant not supported: " + stringify(data.levels.map(function (level) {
                return level.attrs.CODECS;
              }).filter(function (value, index, array) {
                return array.indexOf(value) === index;
              }));
              message += " (" + reason + ")";
            }
            var error = new Error(message);
            _this3.hls.trigger(Events.ERROR, {
              type: ErrorTypes.MEDIA_ERROR,
              details: ErrorDetails.MANIFEST_INCOMPATIBLE_CODECS_ERROR,
              fatal: true,
              url: data.url,
              error: error,
              reason: reason
            });
          }
        });
        statsParsing.end = performance.now();
        return;
      }
      if (data.audioTracks) {
        audioTracks = data.audioTracks.filter(function (track) {
          return !track.audioCodec || isAudioSupported(track.audioCodec, preferManagedMediaSource);
        });
        assignTrackIdsByGroup(audioTracks);
      }
      if (data.subtitles) {
        subtitleTracks = data.subtitles;
        assignTrackIdsByGroup(subtitleTracks);
      }
      var unsortedLevels = levels.slice(0);
      levels.sort(function (a, b) {
        if (a.attrs['HDCP-LEVEL'] !== b.attrs['HDCP-LEVEL']) {
          return (a.attrs['HDCP-LEVEL'] || '') > (b.attrs['HDCP-LEVEL'] || '') ? 1 : -1;
        }
        if (resolutionFound && a.height !== b.height) {
          return a.height - b.height;
        }
        if (a.frameRate !== b.frameRate) {
          return a.frameRate - b.frameRate;
        }
        if (a.videoRange !== b.videoRange) {
          return VideoRangeValues.indexOf(a.videoRange) - VideoRangeValues.indexOf(b.videoRange);
        }
        if (a.videoCodec !== b.videoCodec) {
          var valueA = videoCodecPreferenceValue(a.videoCodec);
          var valueB = videoCodecPreferenceValue(b.videoCodec);
          if (valueA !== valueB) {
            return valueB - valueA;
          }
        }
        if (a.uri === b.uri && a.codecSet !== b.codecSet) {
          var _valueA = codecsSetSelectionPreferenceValue(a.codecSet);
          var _valueB = codecsSetSelectionPreferenceValue(b.codecSet);
          if (_valueA !== _valueB) {
            return _valueB - _valueA;
          }
        }
        if (a.averageBitrate !== b.averageBitrate) {
          return a.averageBitrate - b.averageBitrate;
        }
        return 0;
      });
      var firstLevelInPlaylist = unsortedLevels[0];
      if (this.steering) {
        levels = this.steering.filterParsedLevels(levels);
        if (levels.length !== unsortedLevels.length) {
          for (var i = 0; i < unsortedLevels.length; i++) {
            if (unsortedLevels[i].pathwayId === levels[0].pathwayId) {
              firstLevelInPlaylist = unsortedLevels[i];
              break;
            }
          }
        }
      }
      this._levels = levels;

      for (var _i = 0; _i < levels.length; _i++) {
        if (levels[_i] === firstLevelInPlaylist) {
          var _this$hls$userConfig;
          this._firstLevel = _i;
          var firstLevelBitrate = firstLevelInPlaylist.bitrate;
          var bandwidthEstimate = this.hls.bandwidthEstimate;
          if (((_this$hls$userConfig = this.hls.userConfig) == null ? void 0 : _this$hls$userConfig.abrEwmaDefaultEstimate) === undefined) {
            var startingBwEstimate = Math.min(firstLevelBitrate, this.hls.config.abrEwmaDefaultEstimateMax);
            if (startingBwEstimate > bandwidthEstimate && bandwidthEstimate === this.hls.abrEwmaDefaultEstimate) {
              this.hls.bandwidthEstimate = startingBwEstimate;
            }
          }
          break;
        }
      }
      var iframeVariants = data.iframeVariants.filter(function (parsedVariant) {
        return setTrackCodecsAndReturnSupported(parsedVariant, preferManagedMediaSource);
      });
      this._iframeVariants = iframeVariants;

      var audioOnly = audioCodecFound && !videoCodecFound;
      var config = this.hls.config;
      var altAudioEnabled = !!(config.audioStreamController && config.audioTrackController);
      var edata = {
        levels: levels,
        audioTracks: audioTracks,
        subtitleTracks: subtitleTracks,
        iframeVariants: iframeVariants,
        sessionData: data.sessionData,
        sessionKeys: data.sessionKeys,
        firstLevel: this._firstLevel,
        stats: data.stats,
        audio: audioCodecFound,
        video: videoCodecFound,
        altAudio: altAudioEnabled && !audioOnly && audioTracks.some(function (t) {
          return !!t.url;
        })
      };
      statsParsing.end = performance.now();
      this.hls.trigger(Events.MANIFEST_PARSED, edata);
    };
    _proto.onError = function onError(event, data) {
      if (data.fatal || !data.context) {
        return;
      }
      if (data.context.type === LoaderContextType.LEVEL && data.context.level === this.level) {
        this.checkRetry(data);
      }
    }

  ;
    _proto.onFragBuffered = function onFragBuffered(event, _ref2) {
      var frag = _ref2.frag;
      if (frag.type === PlaylistLevelType.MAIN) {
        var el = frag.elementaryStreams;
        if (!Object.keys(el).some(function (type) {
          return !!el[type];
        })) {
          return;
        }
        var level = this._levels[frag.level];
        if (level != null && level.loadError) {
          level.loadError = 0;
        }
      }
    };
    _proto.onLevelLoaded = function onLevelLoaded(event, data) {
      var _data$deliveryDirecti2;
      var level = data.level,
        details = data.details;
      var curLevel = data.levelInfo;
      if (!curLevel) {
        var _data$deliveryDirecti;
        if ((_data$deliveryDirecti = data.deliveryDirectives) != null && _data$deliveryDirecti.skip) {
          details.deltaUpdateFailed = true;
        }
        return;
      }

      if (curLevel === this.currentLevel || data.withoutMultiVariant) {
        if (curLevel.fragmentError === 0) {
          curLevel.loadError = 0;
        }
        var previousDetails = curLevel.details;
        if (previousDetails === data.details && previousDetails.advanced) {
          previousDetails = undefined;
        }
        this.playlistLoaded(level, data, previousDetails);
      } else if ((_data$deliveryDirecti2 = data.deliveryDirectives) != null && _data$deliveryDirecti2.skip) {
        details.deltaUpdateFailed = true;
      }
    };
    _proto.loadPlaylist = function loadPlaylist(hlsUrlParameters) {
      _BasePlaylistControll.prototype.loadPlaylist.call(this);
      if (this.shouldLoadPlaylist(this.currentLevel)) {
        this.scheduleLoading(this.currentLevel, hlsUrlParameters);
      }
    };
    _proto.loadingPlaylist = function loadingPlaylist(currentLevel, hlsUrlParameters) {
      _BasePlaylistControll.prototype.loadingPlaylist.call(this, currentLevel, hlsUrlParameters);
      var url = this.getUrlWithDirectives(currentLevel.uri, hlsUrlParameters);
      var currentLevelIndex = this.currentLevelIndex;
      var pathwayId = currentLevel.attrs['PATHWAY-ID'];
      var details = currentLevel.details;
      var age = details == null ? void 0 : details.age;
      this.hls.trigger(Events.LEVEL_LOADING, {
        url: url,
        level: currentLevelIndex,
        levelInfo: currentLevel,
        pathwayId: currentLevel.attrs['PATHWAY-ID'],
        id: 0,
        deliveryDirectives: hlsUrlParameters || null
      });
    };
    _proto.removeLevel = function removeLevel(levelIndex) {
      var _this4 = this,
        _this$currentLevel;
      if (this._levels.length === 1) {
        return;
      }
      var levels = this._levels.filter(function (level, index) {
        if (index !== levelIndex) {
          return true;
        }
        if (_this4.steering) {
          _this4.steering.removeLevel(level);
        }
        if (level === _this4.currentLevel) {
          _this4.currentLevel = null;
          _this4.currentLevelIndex = -1;
          if (level.details) {
            level.details.fragments.forEach(function (f) {
              return f.level = -1;
            });
          }
        }
        return false;
      });
      reassignFragmentLevelIndexes(levels);
      this._levels = levels;
      if (this.currentLevelIndex > -1 && (_this$currentLevel = this.currentLevel) != null && _this$currentLevel.details) {
        this.currentLevelIndex = this.currentLevel.details.fragments[0].level;
      }
      if (this.manualLevelIndex > -1) {
        this.manualLevelIndex = this.currentLevelIndex;
      }
      var maxLevel = levels.length - 1;
      this._firstLevel = Math.min(this._firstLevel, maxLevel);
      if (this._startLevel) {
        this._startLevel = Math.min(this._startLevel, maxLevel);
      }
      this.hls.trigger(Events.LEVELS_UPDATED, {
        levels: levels
      });
    };
    _proto.onLevelsUpdated = function onLevelsUpdated(event, _ref3) {
      var levels = _ref3.levels;
      this._levels = levels;
    };
    _proto.checkMaxAutoUpdated = function checkMaxAutoUpdated() {
      var _this$hls = this.hls,
        autoLevelCapping = _this$hls.autoLevelCapping,
        maxAutoLevel = _this$hls.maxAutoLevel,
        maxHdcpLevel = _this$hls.maxHdcpLevel;
      if (this._maxAutoLevel !== maxAutoLevel) {
        this._maxAutoLevel = maxAutoLevel;
        this.hls.trigger(Events.MAX_AUTO_LEVEL_UPDATED, {
          autoLevelCapping: autoLevelCapping,
          levels: this.levels,
          maxAutoLevel: maxAutoLevel,
          minAutoLevel: this.hls.minAutoLevel,
          maxHdcpLevel: maxHdcpLevel
        });
      }
    };
    return _createClass(LevelController, [{
      key: "iframeVariants",
      get: function get() {
        if (this._iframeVariants.length === 0) {
          return null;
        }
        return this._iframeVariants;
      }
    }, {
      key: "levels",
      get: function get() {
        if (this._levels.length === 0) {
          return null;
        }
        return this._levels;
      }
    }, {
      key: "loadLevelObj",
      get: function get() {
        return this.currentLevel;
      }
    }, {
      key: "level",
      get: function get() {
        return this.currentLevelIndex;
      },
      set: function set(newLevel) {
        var levels = this._levels;
        if (levels.length === 0) {
          return;
        }
        if (newLevel < 0 || newLevel >= levels.length) {
          var error = new Error('invalid level idx');
          var fatal = newLevel < 0;
          this.hls.trigger(Events.ERROR, {
            type: ErrorTypes.OTHER_ERROR,
            details: ErrorDetails.LEVEL_SWITCH_ERROR,
            level: newLevel,
            fatal: fatal,
            error: error,
            reason: error.message
          });
          if (fatal) {
            return;
          }
          newLevel = Math.min(newLevel, levels.length - 1);
        }
        var lastLevelIndex = this.currentLevelIndex;
        var lastLevel = this.currentLevel;
        var lastPathwayId = lastLevel ? lastLevel.attrs['PATHWAY-ID'] : undefined;
        var level = levels[newLevel];
        var pathwayId = level.attrs['PATHWAY-ID'];
        this.currentLevelIndex = newLevel;
        this.currentLevel = level;
        if (lastLevelIndex === newLevel && lastLevel && lastPathwayId === pathwayId) {
          return;
        }
        var levelSwitchingData = {
          level: newLevel,
          attrs: level.attrs,
          details: level.details,
          bitrate: level.bitrate,
          averageBitrate: level.averageBitrate,
          maxBitrate: level.maxBitrate,
          realBitrate: level.realBitrate,
          width: level.width,
          height: level.height,
          codecSet: level.codecSet,
          audioCodec: level.audioCodec,
          videoCodec: level.videoCodec,
          audioGroups: level.audioGroups,
          subtitleGroups: level.subtitleGroups,
          loaded: level.loaded,
          loadError: level.loadError,
          fragmentError: level.fragmentError,
          name: level.name,
          id: level.id,
          uri: level.uri,
          url: level.url,
          urlId: 0,
          audioGroupIds: level.audioGroupIds,
          textGroupIds: level.textGroupIds
        };
        this.hls.trigger(Events.LEVEL_SWITCHING, levelSwitchingData);
        var levelDetails = level.details;
        if (!levelDetails || levelDetails.live) {
          var hlsUrlParameters = this.switchParams(level.uri, lastLevel == null ? void 0 : lastLevel.details, levelDetails);
          this.loadPlaylist(hlsUrlParameters);
        }
      }
    }, {
      key: "manualLevel",
      get: function get() {
        return this.manualLevelIndex;
      },
      set: function set(newLevel) {
        this.manualLevelIndex = newLevel;
        if (this._startLevel === undefined) {
          this._startLevel = newLevel;
        }
        if (newLevel !== -1) {
          this.level = newLevel;
        }
      }
    }, {
      key: "firstLevel",
      get: function get() {
        return this._firstLevel;
      },
      set: function set(newLevel) {
        this._firstLevel = newLevel;
      }
    }, {
      key: "startLevel",
      get: function get() {
        if (this._startLevel === undefined) {
          var configStartLevel = this.hls.config.startLevel;
          if (configStartLevel !== undefined) {
            return configStartLevel;
          }
          return this.hls.firstAutoLevel;
        }
        return this._startLevel;
      },
      set: function set(newLevel) {
        this._startLevel = newLevel;
      }
    }, {
      key: "pathways",
      get: function get() {
        if (this.steering) {
          return this.steering.pathways();
        }
        return [];
      }
    }, {
      key: "pathwayPriority",
      get: function get() {
        if (this.steering) {
          return this.steering.pathwayPriority;
        }
        return null;
      },
      set: function set(pathwayPriority) {
        if (this.steering) {
          var pathwaysList = this.steering.pathways();
          var filteredPathwayPriority = pathwayPriority.filter(function (pathwayId) {
            return pathwaysList.indexOf(pathwayId) !== -1;
          });
          if (pathwayPriority.length < 1) {
            return;
          }
          this.steering.pathwayPriority = filteredPathwayPriority;
        }
      }
    }, {
      key: "nextLoadLevel",
      get: function get() {
        if (this.manualLevelIndex !== -1) {
          return this.manualLevelIndex;
        } else {
          return this.hls.nextAutoLevel;
        }
      },
      set: function set(nextLevel) {
        var currentLevel = this.currentLevelIndex;
        var isABRSwitch = this.manualLevelIndex === -1;
        if (isABRSwitch && nextLevel !== currentLevel && nextLevel !== -1) {
          var abrSwitchInterval = this.hls.config.abrSwitchInterval;
          if (abrSwitchInterval > 0) {
            var now = performance.now();
            var delta = now - this.lastABRSwitchTime;
            var intervalMs = abrSwitchInterval * 1000;
            if (this.lastABRSwitchTime > -1 && delta < intervalMs) {
              return;
            }
            this.lastABRSwitchTime = now;
          }
        }
        this.level = nextLevel;
        if (this.manualLevelIndex === -1) {
          this.hls.nextAutoLevel = nextLevel;
        }
      }
    }]);
  }(BasePlaylistController);
  function isAudioSupported(codec, preferManagedMediaSource) {
    return areCodecsMediaSourceSupported(codec, 'audio', preferManagedMediaSource);
  }
  function isVideoSupported(codec, preferManagedMediaSource) {
    return areCodecsMediaSourceSupported(codec, 'video', preferManagedMediaSource);
  }
  function setTrackCodecsAndReturnSupported(levelParsed, preferManagedMediaSource) {
    var audioCodec = levelParsed.audioCodec,
      videoCodec = levelParsed.videoCodec;
    if (audioCodec) {
      levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
    }
    if (videoCodec) {
      videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
    }
    var unknownCodecs = levelParsed.unknownCodecs;
    var unknownUnsupportedCodecCount = (unknownCodecs == null ? void 0 : unknownCodecs.length) || 0;
    if (unknownUnsupportedCodecCount || audioCodec && !isAudioSupported(audioCodec, preferManagedMediaSource) || videoCodec && !isVideoSupported(videoCodec, preferManagedMediaSource)) {
      return false;
    }
    return true;
  }
  function assignTrackIdsByGroup(tracks) {
    var groups = {};
    tracks.forEach(function (track) {
      var groupId = track.groupId || '';
      track.id = groups[groupId] = groups[groupId] || 0;
      groups[groupId]++;
    });
  }

  var KeyLoader = function (_Logger) {
    function KeyLoader(config, logger) {
      var _this;
      _this = _Logger.call(this, 'key-loader', logger) || this;
      _this.config = void 0;
      _this.keyLoaderInfo = {};
      _this.emeController = null;
      _this.config = config;
      return _this;
    }
    _inheritsLoose(KeyLoader, _Logger);
    var _proto = KeyLoader.prototype;
    _proto.abort = function abort(type) {
      for (var uri in this.keyLoaderInfo) {
        var loader = this.keyLoaderInfo[uri].loader;
        if (loader) {
          var _loader$context;
          if (type && type !== ((_loader$context = loader.context) == null ? void 0 : _loader$context.frag.type)) {
            return;
          }
          loader.abort();
        }
      }
    };
    _proto.destroy = function destroy() {
      for (var uri in this.keyLoaderInfo) {
        var loader = this.keyLoaderInfo[uri].loader;
        if (loader) {
          loader.destroy();
        }
      }
      this.emeController = null;
      this.keyLoaderInfo = {};
    };
    _proto.loadClear = function loadClear(loadingFrag, encryptedFragments, startFragRequested) {
      if (this.emeController) {
        return this.emeController.loadClear(loadingFrag, encryptedFragments, startFragRequested);
      }
      return null;
    };
    _proto.load = function load(frag, initDataPromise) {
      var _this2 = this;
      if (!frag.decryptdata && frag.encrypted && this.emeController && this.config.emeEnabled) {
        return this.emeController.selectKeySystemFormat(frag).then(function (keySystemFormat) {
          return _this2.loadInternal(frag, initDataPromise, keySystemFormat);
        });
      }
      return this.loadInternal(frag, initDataPromise);
    };
    _proto.loadInternal = function loadInternal(frag, initDataPromise, keySystemFormat) {
      var _this3 = this;
      var decryptdata = frag.decryptdata;
      if (!decryptdata) {
        var error = new Error(keySystemFormat ? "Expected frag.decryptdata to be defined after setting format " + keySystemFormat : "Missing decryption data on fragment in onKeyLoading (emeEnabled with controller: " + (this.emeController && this.config.emeEnabled) + ")");
        return Promise.reject(createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, error));
      }
      var encryptedFrag = frag;

      switch (decryptdata.method) {
        case 'SAMPLE-AES':
        case 'SAMPLE-AES-CENC':
        case 'SAMPLE-AES-CTR':
          if (decryptdata.keyFormat === 'identity') {
            return this.loadKeyHTTP(encryptedFrag);
          }
          return initDataPromise ? initDataPromise.then(function () {
            return _this3.loadKeyEME(encryptedFrag);
          }) : this.loadKeyEME(encryptedFrag);
        case 'AES-128':
        case 'AES-256':
        case 'AES-256-CTR':
          return this.loadKeyHTTP(encryptedFrag);
        default:
          return Promise.reject(createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error("Key supplied with unsupported METHOD: \\"" + decryptdata.method + "\\"")));
      }
    };
    _proto.loadKeyEME = function loadKeyEME(frag) {
      if (this.emeController && this.config.emeEnabled) {
        var _frag$initSegment;
        if (!frag.decryptdata.keyId && (_frag$initSegment = frag.initSegment) != null && _frag$initSegment.data) {
          var keyIds = parseKeyIdsFromTenc(frag.initSegment.data);
          if (keyIds.length) {
            var keyId = keyIds[0];
            var keyUri = frag.decryptdata.uri;
            if (keyId.some(function (b) {
              return b !== 0;
            })) {
              LevelKey.setKeyIdForUri(keyUri, keyId);
            } else {
              var keyIdPatch = LevelKey.addKeyIdForUri(keyUri);
              keyId = keyIdPatch;
            }
            frag.decryptdata.keyId = keyId;
          }
        }
        return this.emeController.loadKey(frag).then(function () {
          return {
            frag: frag,
            keyInfo: {
              decryptdata: frag.decryptdata
            }
          };
        });
      }
      return Promise.reject(createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error("emeEnabled with controller: " + (this.emeController && this.config.emeEnabled) + ")")));
    };
    _proto.loadKeyHTTP = function loadKeyHTTP(frag) {
      var _this4 = this;
      var decryptdata = frag.decryptdata;
      var uri = decryptdata.uri;
      if (!uri) {
        return Promise.reject(createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error("Invalid key URI: \\"" + uri + "\\"")));
      }
      var cachedKeyInfo = this.keyLoaderInfo[uri];
      if (cachedKeyInfo) {
        if (cachedKeyInfo.decryptdata.key) {
          decryptdata.key = cachedKeyInfo.decryptdata.key;
          cachedKeyInfo.keyLoadPromise = null;
          return Promise.resolve({
            frag: frag,
            keyInfo: cachedKeyInfo
          });
        }
        if (cachedKeyInfo.keyLoadPromise) {
          return cachedKeyInfo.keyLoadPromise.then(function (keyLoadedData) {
            decryptdata.key = cachedKeyInfo.decryptdata.key;
            return _objectSpread2(_objectSpread2({}, keyLoadedData), {}, {
              frag: frag
            });
          });
        }
      }

      var config = this.config;
      var Loader = config.loader;
      var keyLoader = new Loader(config);
      frag.keyLoader = keyLoader;
      var keyInfo = this.keyLoaderInfo[uri] = {
        decryptdata: decryptdata,
        keyLoadPromise: null,
        loader: keyLoader
      };
      return keyInfo.keyLoadPromise = new Promise(function (resolve, reject) {
        var loaderContext = {
          type: LoaderContextType.KEY,
          keyInfo: keyInfo,
          frag: frag,
          responseType: 'arraybuffer',
          url: uri
        };

        var loadPolicy = config.keyLoadPolicy.default;
        var loaderConfig = {
          loadPolicy: loadPolicy,
          timeout: loadPolicy.maxLoadTimeMs,
          maxRetry: 0,
          retryDelay: 0,
          maxRetryDelay: 0
        };
        var loaderCallbacks = {
          onSuccess: function onSuccess(response, stats, context, networkDetails) {
            var frag = context.frag,
              keyInfo = context.keyInfo,
              url = context.url;
            if (!frag.decryptdata || keyInfo !== _this4.keyLoaderInfo[url]) {
              return reject(createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error('after key load, decryptdata unset or changed'), networkDetails));
            }
            keyInfo.decryptdata.key = frag.decryptdata.key = new Uint8Array(response.data);

            frag.keyLoader = keyInfo.loader = keyInfo.keyLoadPromise = null;
            resolve({
              frag: frag,
              keyInfo: keyInfo
            });
          },
          onError: function onError(response, context, networkDetails, stats) {
            _this4.resetLoader(context);
            reject(createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error("HTTP Error " + response.code + " loading key " + response.text), networkDetails, _objectSpread2({
              url: uri,
              data: undefined
            }, response)));
          },
          onTimeout: function onTimeout(stats, context, networkDetails) {
            _this4.resetLoader(context);
            reject(createKeyLoadError(frag, ErrorDetails.KEY_LOAD_TIMEOUT, new Error('key loading timed out'), networkDetails));
          },
          onAbort: function onAbort(stats, context, networkDetails) {
            _this4.resetLoader(context);
            reject(createKeyLoadError(frag, ErrorDetails.INTERNAL_ABORTED, new Error('key loading aborted'), networkDetails));
          }
        };
        keyLoader.load(loaderContext, loaderConfig, loaderCallbacks);
      });
    };
    _proto.resetLoader = function resetLoader(context) {
      var frag = context.frag,
        keyInfo = context.keyInfo,
        url = context.url;
      var loader = keyInfo.loader;
      if (frag.keyLoader === loader) {
        frag.keyLoader = keyInfo.loader = keyInfo.keyLoadPromise = null;
      }
      delete this.keyLoaderInfo[url];
      if (loader) {
        loader.destroy();
      }
    };
    return KeyLoader;
  }(Logger);
  function createKeyLoadError(frag, details, error, networkDetails, response) {
    if (details === void 0) {
      details = ErrorDetails.KEY_LOAD_ERROR;
    }
    return new LoadError({
      type: ErrorTypes.NETWORK_ERROR,
      details: details,
      fatal: false,
      frag: frag,
      response: response,
      error: error,
      networkDetails: networkDetails || null
    });
  }

  function mapContextToLevelType(context) {
    var type = context.type;
    switch (type) {
      case LoaderContextType.AUDIO_TRACK:
        return PlaylistLevelType.AUDIO;
      case LoaderContextType.SUBTITLE_TRACK:
        return PlaylistLevelType.SUBTITLE;
      default:
        return PlaylistLevelType.MAIN;
    }
  }
  function getResponseUrl(response, context) {
    var url = response.url;
    if (url === undefined || url.indexOf('data:') === 0) {
      url = context.url;
    }
    return url;
  }
  var PlaylistLoader = function () {
    function PlaylistLoader(hls) {
      this.hls = void 0;
      this.loaders = Object.create(null);
      this.variableList = null;
      this.onManifestLoaded = this.checkAutostartLoad;
      this.hls = hls;
      this.registerListeners();
    }
    var _proto = PlaylistLoader.prototype;
    _proto.startLoad = function startLoad(startPosition) {};
    _proto.stopLoad = function stopLoad() {
      this.destroyInternalLoaders();
    };
    _proto.registerListeners = function registerListeners() {
      var hls = this.hls;
      hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
      hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
      hls.on(Events.AUDIO_TRACK_LOADING, this.onAudioTrackLoading, this);
      hls.on(Events.SUBTITLE_TRACK_LOADING, this.onSubtitleTrackLoading, this);
      hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
    };
    _proto.unregisterListeners = function unregisterListeners() {
      var hls = this.hls;
      hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
      hls.off(Events.LEVEL_LOADING, this.onLevelLoading, this);
      hls.off(Events.AUDIO_TRACK_LOADING, this.onAudioTrackLoading, this);
      hls.off(Events.SUBTITLE_TRACK_LOADING, this.onSubtitleTrackLoading, this);
      hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
    }

;
    _proto.createInternalLoader = function createInternalLoader(context) {
      var config = this.hls.config;
      var PLoader = config.pLoader;
      var Loader = config.loader;
      var InternalLoader = PLoader || Loader;
      var loader = new InternalLoader(config);
      this.loaders[context.type] = loader;
      return loader;
    };
    _proto.getInternalLoader = function getInternalLoader(context) {
      return this.loaders[context.type];
    };
    _proto.resetInternalLoader = function resetInternalLoader(contextType) {
      if (this.loaders[contextType]) {
        delete this.loaders[contextType];
      }
    }

;
    _proto.destroyInternalLoaders = function destroyInternalLoaders() {
      for (var contextType in this.loaders) {
        var loader = this.loaders[contextType];
        if (loader) {
          loader.destroy();
        }
        this.resetInternalLoader(contextType);
      }
    };
    _proto.destroy = function destroy() {
      this.variableList = null;
      this.unregisterListeners();
      this.destroyInternalLoaders();
    };
    _proto.onManifestLoading = function onManifestLoading(event, data) {
      var url = data.url;
      this.variableList = null;
      this.load({
        id: null,
        level: 0,
        responseType: 'text',
        type: LoaderContextType.MANIFEST,
        url: url,
        deliveryDirectives: null,
        levelOrTrack: null
      });
    };
    _proto.onLevelLoading = function onLevelLoading(event, data) {
      var id = data.id,
        level = data.level,
        pathwayId = data.pathwayId,
        url = data.url,
        deliveryDirectives = data.deliveryDirectives,
        levelInfo = data.levelInfo;
      this.load({
        id: id,
        level: level,
        pathwayId: pathwayId,
        responseType: 'text',
        type: LoaderContextType.LEVEL,
        url: url,
        deliveryDirectives: deliveryDirectives,
        levelOrTrack: levelInfo
      });
    };
    _proto.onAudioTrackLoading = function onAudioTrackLoading(event, data) {
      var id = data.id,
        groupId = data.groupId,
        url = data.url,
        deliveryDirectives = data.deliveryDirectives,
        track = data.track;
      this.load({
        id: id,
        groupId: groupId,
        level: null,
        responseType: 'text',
        type: LoaderContextType.AUDIO_TRACK,
        url: url,
        deliveryDirectives: deliveryDirectives,
        levelOrTrack: track
      });
    };
    _proto.onSubtitleTrackLoading = function onSubtitleTrackLoading(event, data) {
      var id = data.id,
        groupId = data.groupId,
        url = data.url,
        deliveryDirectives = data.deliveryDirectives,
        track = data.track;
      this.load({
        id: id,
        groupId: groupId,
        level: null,
        responseType: 'text',
        type: LoaderContextType.SUBTITLE_TRACK,
        url: url,
        deliveryDirectives: deliveryDirectives,
        levelOrTrack: track
      });
    };
    _proto.onLevelsUpdated = function onLevelsUpdated(event, data) {
      var loader = this.loaders[LoaderContextType.LEVEL];
      if (loader) {
        var context = loader.context;
        if (context && !data.levels.some(function (lvl) {
          return lvl === context.levelOrTrack;
        })) {
          loader.abort();
          delete this.loaders[LoaderContextType.LEVEL];
        }
      }
    };
    _proto.load = function load(context) {
      var _context$deliveryDire,
        _this = this;
      var config = this.hls.config;

      var loader = this.getInternalLoader(context);
      if (loader) {
        var logger = this.hls.logger;
        var loaderContext = loader.context;
        if ((loaderContext == null ? void 0 : loaderContext.levelOrTrack) === context.levelOrTrack && (loaderContext.url === context.url || loaderContext.deliveryDirectives && !context.deliveryDirectives)) {
          if (loaderContext.url === context.url) {
          } else {
          }
          return;
        }
        loader.abort();
      }

      var loadPolicy;
      if (context.type === LoaderContextType.MANIFEST) {
        loadPolicy = config.manifestLoadPolicy.default;
      } else {
        loadPolicy = _extends({}, config.playlistLoadPolicy.default, {
          timeoutRetry: null,
          errorRetry: null
        });
      }
      loader = this.createInternalLoader(context);

      if (isFiniteNumber((_context$deliveryDire = context.deliveryDirectives) == null ? void 0 : _context$deliveryDire.part)) {
        var levelDetails;
        if (context.type === LoaderContextType.LEVEL && context.level !== null) {
          levelDetails = this.hls.levels[context.level].details;
        } else if (context.type === LoaderContextType.AUDIO_TRACK && context.id !== null) {
          levelDetails = this.hls.audioTracks[context.id].details;
        } else if (context.type === LoaderContextType.SUBTITLE_TRACK && context.id !== null) {
          levelDetails = this.hls.subtitleTracks[context.id].details;
        }
        if (levelDetails) {
          var partTarget = levelDetails.partTarget;
          var targetDuration = levelDetails.targetduration;
          if (partTarget && targetDuration) {
            var maxLowLatencyPlaylistRefresh = Math.max(partTarget * 3, targetDuration * 0.8) * 1000;
            loadPolicy = _extends({}, loadPolicy, {
              maxTimeToFirstByteMs: Math.min(maxLowLatencyPlaylistRefresh, loadPolicy.maxTimeToFirstByteMs),
              maxLoadTimeMs: Math.min(maxLowLatencyPlaylistRefresh, loadPolicy.maxTimeToFirstByteMs)
            });
          }
        }
      }
      var legacyRetryCompatibility = loadPolicy.errorRetry || loadPolicy.timeoutRetry || {};
      var loaderConfig = {
        loadPolicy: loadPolicy,
        timeout: loadPolicy.maxLoadTimeMs,
        maxRetry: legacyRetryCompatibility.maxNumRetry || 0,
        retryDelay: legacyRetryCompatibility.retryDelayMs || 0,
        maxRetryDelay: legacyRetryCompatibility.maxRetryDelayMs || 0
      };
      var loaderCallbacks = {
        onSuccess: function onSuccess(response, stats, context, networkDetails) {
          var loader = _this.getInternalLoader(context);
          _this.resetInternalLoader(context.type);
          var string = response.data;
          stats.parsing.start = performance.now();
          if (M3U8Parser.isMediaPlaylist(string) || context.type !== LoaderContextType.MANIFEST) {
            _this.handleTrackOrLevelPlaylist(response, stats, context, networkDetails || null, loader);
          } else {
            _this.handleMasterPlaylist(response, stats, context, networkDetails);
          }
        },
        onError: function onError(response, context, networkDetails, stats) {
          _this.handleNetworkError(context, networkDetails, false, response, stats);
        },
        onTimeout: function onTimeout(stats, context, networkDetails) {
          _this.handleNetworkError(context, networkDetails, true, undefined, stats);
        }
      };

      loader.load(context, loaderConfig, loaderCallbacks);
    };
    _proto.checkAutostartLoad = function checkAutostartLoad() {
      if (!this.hls) {
        return;
      }
      var _this$hls = this.hls,
        _this$hls$config = _this$hls.config,
        autoStartLoad = _this$hls$config.autoStartLoad,
        startPosition = _this$hls$config.startPosition,
        forceStartLoad = _this$hls.forceStartLoad;
      if (autoStartLoad || forceStartLoad) {
        this.hls.logger.log((autoStartLoad ? 'auto' : 'force') + " startLoad with configured startPosition " + startPosition);
        this.hls.startLoad(startPosition);
      }
    };
    _proto.handleMasterPlaylist = function handleMasterPlaylist(response, stats, context, networkDetails) {
      var _this2 = this;
      var hls = this.hls;
      var string = response.data;
      var url = getResponseUrl(response, context);
      var parsedResult = M3U8Parser.parseMasterPlaylist(string, url);
      if (parsedResult.playlistParsingError) {
        stats.parsing.end = performance.now();
        this.handleManifestParsingError(response, context, parsedResult.playlistParsingError, networkDetails, stats);
        return;
      }
      var contentSteering = parsedResult.contentSteering,
        levels = parsedResult.levels,
        iframeVariants = parsedResult.iframeVariants,
        sessionData = parsedResult.sessionData,
        sessionKeys = parsedResult.sessionKeys,
        startTimeOffset = parsedResult.startTimeOffset,
        variableList = parsedResult.variableList;
      this.variableList = variableList;

      levels.forEach(function (levelParsed) {
        var unknownCodecs = levelParsed.unknownCodecs;
        if (unknownCodecs) {
          var preferManagedMediaSource = _this2.hls.config.preferManagedMediaSource;
          var audioCodec = levelParsed.audioCodec,
            videoCodec = levelParsed.videoCodec;
          for (var i = unknownCodecs.length; i--;) {
            var unknownCodec = unknownCodecs[i];
            if (areCodecsMediaSourceSupported(unknownCodec, 'audio', preferManagedMediaSource)) {
              levelParsed.audioCodec = audioCodec = audioCodec ? audioCodec + "," + unknownCodec : unknownCodec;
              sampleEntryCodesISO.audio[audioCodec.substring(0, 4)] = 2;
              unknownCodecs.splice(i, 1);
            } else if (areCodecsMediaSourceSupported(unknownCodec, 'video', preferManagedMediaSource)) {
              levelParsed.videoCodec = videoCodec = videoCodec ? videoCodec + "," + unknownCodec : unknownCodec;
              sampleEntryCodesISO.video[videoCodec.substring(0, 4)] = 2;
              unknownCodecs.splice(i, 1);
            }
          }
        }
      });
      var _M3U8Parser$parseMast = M3U8Parser.parseMasterPlaylistMedia(string, url, parsedResult),
        _M3U8Parser$parseMast2 = _M3U8Parser$parseMast.AUDIO,
        audioTracks = _M3U8Parser$parseMast2 === void 0 ? [] : _M3U8Parser$parseMast2,
        subtitles = _M3U8Parser$parseMast.SUBTITLES,
        captions = _M3U8Parser$parseMast['CLOSED-CAPTIONS'];
      if (audioTracks.length) {
        var embeddedAudioFound = audioTracks.some(function (audioTrack) {
          return !audioTrack.url;
        });

        if (!embeddedAudioFound && levels[0].audioCodec && !levels[0].attrs.AUDIO) {
          this.hls.logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
          audioTracks.unshift({
            type: 'main',
            name: 'main',
            groupId: 'main',
            default: false,
            autoselect: false,
            forced: false,
            id: -1,
            attrs: new AttrList({}),
            bitrate: 0,
            url: ''
          });
        }
      }
      hls.trigger(Events.MANIFEST_LOADED, {
        levels: levels,
        audioTracks: audioTracks,
        subtitles: subtitles,
        iframeVariants: iframeVariants,
        captions: captions,
        contentSteering: contentSteering,
        url: url,
        stats: stats,
        networkDetails: networkDetails,
        sessionData: sessionData,
        sessionKeys: sessionKeys,
        startTimeOffset: startTimeOffset,
        variableList: variableList
      });
    };
    _proto.handleTrackOrLevelPlaylist = function handleTrackOrLevelPlaylist(response, stats, context, networkDetails, loader) {
      var hls = this.hls;
      var id = context.id,
        level = context.level,
        type = context.type;
      var url = getResponseUrl(response, context);
      var levelId = isFiniteNumber(level) ? level : isFiniteNumber(id) ? id : 0;
      var levelType = mapContextToLevelType(context);
      var levelDetails = M3U8Parser.parseLevelPlaylist(response.data, url, levelId, levelType, 0, this.variableList);

      if (type === LoaderContextType.MANIFEST) {
        var singleLevel = {
          attrs: new AttrList({}),
          bitrate: 0,
          details: levelDetails,
          name: '',
          url: url
        };
        levelDetails.requestScheduled = stats.loading.start + computeReloadInterval(levelDetails, 0);
        hls.trigger(Events.MANIFEST_LOADED, {
          levels: [singleLevel],
          audioTracks: [],
          iframeVariants: [],
          url: url,
          stats: stats,
          networkDetails: networkDetails,
          sessionData: null,
          sessionKeys: null,
          contentSteering: null,
          startTimeOffset: null,
          variableList: null
        });
      }

      stats.parsing.end = performance.now();

      context.levelDetails = levelDetails;
      this.handlePlaylistLoaded(levelDetails, response, stats, context, networkDetails, loader);
    };
    _proto.handleManifestParsingError = function handleManifestParsingError(response, context, error, networkDetails, stats) {
      this.hls.trigger(Events.ERROR, {
        type: ErrorTypes.NETWORK_ERROR,
        details: ErrorDetails.MANIFEST_PARSING_ERROR,
        fatal: context.type === LoaderContextType.MANIFEST,
        url: response.url,
        err: error,
        error: error,
        reason: error.message,
        response: response,
        context: context,
        networkDetails: networkDetails,
        stats: stats
      });
    };
    _proto.handleNetworkError = function handleNetworkError(context, networkDetails, timeout, response, stats) {
      if (timeout === void 0) {
        timeout = false;
      }
      var message = "A network " + (timeout ? 'timeout' : 'error' + (response ? ' (status ' + response.code + ')' : '')) + " occurred while loading " + context.type;
      if (context.type === LoaderContextType.LEVEL) {
        message += ": " + context.level + " id: " + context.id;
      } else if (context.type === LoaderContextType.AUDIO_TRACK || context.type === LoaderContextType.SUBTITLE_TRACK) {
        message += " id: " + context.id + " group-id: \\"" + context.groupId + "\\"";
      }
      var error = new Error(message);
      this.hls.logger.warn("[playlist-loader]: " + message);
      var details = ErrorDetails.UNKNOWN;
      var fatal = false;
      var loader = this.getInternalLoader(context);
      switch (context.type) {
        case LoaderContextType.MANIFEST:
          details = timeout ? ErrorDetails.MANIFEST_LOAD_TIMEOUT : ErrorDetails.MANIFEST_LOAD_ERROR;
          fatal = true;
          break;
        case LoaderContextType.LEVEL:
          details = timeout ? ErrorDetails.LEVEL_LOAD_TIMEOUT : ErrorDetails.LEVEL_LOAD_ERROR;
          fatal = false;
          break;
        case LoaderContextType.AUDIO_TRACK:
          details = timeout ? ErrorDetails.AUDIO_TRACK_LOAD_TIMEOUT : ErrorDetails.AUDIO_TRACK_LOAD_ERROR;
          fatal = false;
          break;
        case LoaderContextType.SUBTITLE_TRACK:
          details = timeout ? ErrorDetails.SUBTITLE_TRACK_LOAD_TIMEOUT : ErrorDetails.SUBTITLE_LOAD_ERROR;
          fatal = false;
          break;
      }
      if (loader) {
        this.resetInternalLoader(context.type);
      }
      var errorData = {
        type: ErrorTypes.NETWORK_ERROR,
        details: details,
        fatal: fatal,
        url: context.url,
        loader: loader,
        context: context,
        error: error,
        networkDetails: networkDetails,
        stats: stats
      };
      if (response) {
        var url = context.url;
        if (networkDetails && 'url' in networkDetails) {
          url = networkDetails.url;
        }
        errorData.response = _objectSpread2({
          url: url,
          data: undefined
        }, response);
      }
      this.hls.trigger(Events.ERROR, errorData);
    };
    _proto.handlePlaylistLoaded = function handlePlaylistLoaded(levelDetails, response, stats, context, networkDetails, loader) {
      var hls = this.hls;
      var type = context.type,
        level = context.level,
        levelOrTrack = context.levelOrTrack,
        id = context.id,
        groupId = context.groupId,
        deliveryDirectives = context.deliveryDirectives;
      var url = getResponseUrl(response, context);
      var parent = mapContextToLevelType(context);
      var levelIndex = typeof context.level === 'number' && parent === PlaylistLevelType.MAIN ? level : undefined;
      var error = levelDetails.playlistParsingError;
      if (error) {
        this.hls.logger.warn(error + " " + levelDetails.url);
        if (!hls.config.ignorePlaylistParsingErrors) {
          hls.trigger(Events.ERROR, {
            type: ErrorTypes.NETWORK_ERROR,
            details: ErrorDetails.LEVEL_PARSING_ERROR,
            fatal: false,
            url: url,
            error: error,
            reason: error.message,
            response: response,
            context: context,
            level: levelIndex,
            parent: parent,
            networkDetails: networkDetails,
            stats: stats
          });
          return;
        }
        levelDetails.playlistParsingError = null;
      }
      if (!levelDetails.fragments.length) {
        var _error = levelDetails.playlistParsingError = new Error('No Segments found in Playlist');
        hls.trigger(Events.ERROR, {
          type: ErrorTypes.NETWORK_ERROR,
          details: ErrorDetails.LEVEL_EMPTY_ERROR,
          fatal: false,
          url: url,
          error: _error,
          reason: _error.message,
          response: response,
          context: context,
          level: levelIndex,
          parent: parent,
          networkDetails: networkDetails,
          stats: stats
        });
        return;
      }
      if (levelDetails.live && loader) {
        if (loader.getCacheAge) {
          levelDetails.ageHeader = loader.getCacheAge() || 0;
        }
        if (!loader.getCacheAge || isNaN(levelDetails.ageHeader)) {
          levelDetails.ageHeader = 0;
        }
      }
      switch (type) {
        case LoaderContextType.MANIFEST:
        case LoaderContextType.LEVEL:
          if (levelIndex) {
            if (!levelOrTrack) {
              levelIndex = 0;
            } else {
              if (levelOrTrack !== hls.levels[levelIndex]) {
                var updatedIndex = hls.levels.indexOf(levelOrTrack);
                if (updatedIndex > -1) {
                  levelIndex = updatedIndex;
                }
              }
            }
          }
          hls.trigger(Events.LEVEL_LOADED, {
            details: levelDetails,
            levelInfo: levelOrTrack || hls.levels[0],
            level: levelIndex || 0,
            id: id || 0,
            stats: stats,
            networkDetails: networkDetails,
            deliveryDirectives: deliveryDirectives,
            withoutMultiVariant: type === LoaderContextType.MANIFEST,
            context: context
          });
          break;
        case LoaderContextType.AUDIO_TRACK:
          hls.trigger(Events.AUDIO_TRACK_LOADED, {
            details: levelDetails,
            track: levelOrTrack,
            id: id || 0,
            groupId: groupId || '',
            stats: stats,
            networkDetails: networkDetails,
            deliveryDirectives: deliveryDirectives,
            context: context
          });
          break;
        case LoaderContextType.SUBTITLE_TRACK:
          hls.trigger(Events.SUBTITLE_TRACK_LOADED, {
            details: levelDetails,
            track: levelOrTrack,
            id: id || 0,
            groupId: groupId || '',
            stats: stats,
            networkDetails: networkDetails,
            deliveryDirectives: deliveryDirectives,
            context: context
          });
          break;
      }
    };
    return PlaylistLoader;
  }();

  var Hls = function () {
    function Hls(userConfig) {
      if (userConfig === void 0) {
        userConfig = {};
      }
      this.config = void 0;
      this.userConfig = void 0;
      this.logger = void 0;
      this._url = null;
      this.streamController = void 0;
      this.coreComponents = void 0;
      this.networkControllers = void 0;
      this._emitter = new EventEmitter();
      this._autoLevelCapping = -1;
      this._maxHdcpLevel = null;
      this.abrController = void 0;
      this.bufferController = void 0;
      this.capLevelController = void 0;
      this.latencyController = void 0;
      this.levelController = void 0;
      this.audioStreamController = void 0;
      this.subtititleStreamController = void 0;
      this.audioTrackController = void 0;
      this.subtitleTrackController = void 0;
      this.interstitialsController = void 0;
      this.iframeController = void 0;
      this.gapController = void 0;
      this.emeController = void 0;
      this.cmcdController = void 0;
      this._media = null;
      this._sessionId = void 0;
      this.triggeringException = void 0;
      this.started = false;
      var logger = this.logger = enableLogs(userConfig.debug || false, 'Hls instance', userConfig.loggerId || userConfig.assetPlayerId);
      var config = this.config = mergeConfig(Hls.DefaultConfig, userConfig, logger);
      this.userConfig = userConfig;
      if (config.progressive) {
        enableStreamingMode(config, logger);
      }

      var _StreamController = config.streamController,
        _AbrController = config.abrController,
        _BufferController = config.bufferController,
        _CapLevelController = config.capLevelController,
        _ErrorController = config.errorController,
        _FpsController = config.fpsController,
        _ID3TrackController = config.id3TrackController,
        _IFrameController = config.iframeController,
        _GapController = config.gapController;
      var errorController = new _ErrorController(this);
      var abrController = this.abrController = new _AbrController(this);
      var fragmentTracker = new FragmentTracker(this);
      var _InterstitialsController = config.interstitialsController;
      var interstitialsController = _InterstitialsController ? this.interstitialsController = new _InterstitialsController(this, Hls) : null;
      var bufferController = _BufferController ? this.bufferController = new _BufferController(this, fragmentTracker) : null;
      var capLevelController = _CapLevelController ? this.capLevelController = new _CapLevelController(this) : null;
      var fpsController = _FpsController ? new _FpsController(this) : null;
      var _CMCDController = config.cmcdController;
      var cmcdController = _CMCDController ? this.cmcdController = new _CMCDController(this) : null;
      var playListLoader = new PlaylistLoader(this);
      var _ContentSteeringController = config.contentSteeringController;
      var contentSteering = _ContentSteeringController ? new _ContentSteeringController(this) : null;
      var levelController = this.levelController = new LevelController(this, contentSteering);
      var id3TrackController = _ID3TrackController ? new _ID3TrackController(this) : undefined;
      var keyLoader = new KeyLoader(this.config, this.logger);
      var streamController = this.streamController = new _StreamController(this, fragmentTracker, keyLoader);
      var gapController = this.gapController = _GapController ? new _GapController(this, fragmentTracker) : undefined;

      if (capLevelController) {
        capLevelController.setStreamController(streamController);
      }
      var networkControllers = [playListLoader, levelController, streamController];
      if (interstitialsController) {
        networkControllers.splice(1, 0, interstitialsController);
      }
      if (contentSteering) {
        networkControllers.splice(1, 0, contentSteering);
      }
      this.networkControllers = networkControllers;
      var coreComponents = [abrController];
      if (bufferController) {
        coreComponents.push(bufferController);
      }
      if (gapController) {
        coreComponents.push(gapController);
      }
      if (capLevelController) {
        coreComponents.push(capLevelController);
      }
      if (fpsController) {
        fpsController.setStreamController(streamController);
        coreComponents.push(fpsController);
      }
      if (id3TrackController) {
        coreComponents.push(id3TrackController);
      }
      coreComponents.push(fragmentTracker);
      this.audioTrackController = this.createController(config.audioTrackController, networkControllers);
      var AudioStreamControllerClass = config.audioStreamController;
      if (AudioStreamControllerClass) {
        networkControllers.push(this.audioStreamController = new AudioStreamControllerClass(this, fragmentTracker, keyLoader));
      }
      this.subtitleTrackController = this.createController(config.subtitleTrackController, networkControllers);
      var SubtitleStreamControllerClass = config.subtitleStreamController;
      if (SubtitleStreamControllerClass) {
        networkControllers.push(this.subtititleStreamController = new SubtitleStreamControllerClass(this, fragmentTracker, keyLoader));
      }
      this.createController(config.timelineController, coreComponents);
      keyLoader.emeController = this.emeController = this.createController(config.emeController, coreComponents);
      if (cmcdController) {
        coreComponents.push(cmcdController);
      }
      this.latencyController = this.createController(config.latencyController, coreComponents);
      this.coreComponents = coreComponents;
      this.iframeController = _IFrameController ? new _IFrameController(this, Hls) : undefined;

      networkControllers.push(errorController);
      var onErrorOut = errorController.onErrorOut;
      if (typeof onErrorOut === 'function') {
        this.on(Events.ERROR, onErrorOut, errorController);
      }
      this.on(Events.MANIFEST_LOADED, playListLoader.onManifestLoaded, playListLoader);
    }
    Hls.isMSESupported = function isMSESupported$1() {
      return isMSESupported();
    }

;
    Hls.isSupported = function isSupported$1() {
      return isSupported();
    }

;
    Hls.getMediaSource = function getMediaSource$1() {
      return getMediaSource();
    };
    var _proto = Hls.prototype;
    _proto.createController = function createController(ControllerClass, components) {
      if (ControllerClass) {
        var controllerInstance = new ControllerClass(this);
        if (components) {
          components.push(controllerInstance);
        }
        return controllerInstance;
      }
      return null;
    }

  ;
    _proto.on = function on(event, listener, context) {
      if (context === void 0) {
        context = this;
      }
      this._emitter.on(event, listener, context);
    };
    _proto.once = function once(event, listener, context) {
      if (context === void 0) {
        context = this;
      }
      this._emitter.once(event, listener, context);
    };
    _proto.removeAllListeners = function removeAllListeners(event) {
      this._emitter.removeAllListeners(event);
    };
    _proto.off = function off(event, listener, context, once) {
      if (context === void 0) {
        context = this;
      }
      this._emitter.off(event, listener, context, once);
    };
    _proto.listeners = function listeners(event) {
      return this._emitter.listeners(event);
    };
    _proto.emit = function emit(event, name, eventObject) {
      return this._emitter.emit(event, name, eventObject);
    };
    _proto.trigger = function trigger(event, eventObject) {
      if (this.config.debug) {
        return this.emit(event, event, eventObject);
      } else {
        try {
          return this.emit(event, event, eventObject);
        } catch (error) {
          this.logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
          if (!this.triggeringException) {
            this.triggeringException = true;
            var fatal = event === Events.ERROR;
            this.trigger(Events.ERROR, {
              type: ErrorTypes.OTHER_ERROR,
              details: ErrorDetails.INTERNAL_EXCEPTION,
              fatal: fatal,
              event: event,
              error: error
            });
            this.triggeringException = false;
          }
        }
      }
      return false;
    };
    _proto.listenerCount = function listenerCount(event) {
      return this._emitter.listenerCount(event);
    }

;
    _proto.destroy = function destroy() {
      this.trigger(Events.DESTROYING, undefined);
      this.detachMedia();
      this.removeAllListeners();
      this._autoLevelCapping = -1;
      this._url = null;
      this.networkControllers.forEach(function (component) {
        return component.destroy();
      });
      this.networkControllers.length = 0;
      this.coreComponents.forEach(function (component) {
        return component.destroy();
      });
      this.coreComponents.length = 0;
      this.iframeController = undefined;

      var config = this.config;
      config.xhrSetup = config.fetchSetup = undefined;
      this.userConfig = null;
    }

;
    _proto.attachMedia = function attachMedia(data) {
      if (!data || 'media' in data && !data.media) {
        var error = new Error("attachMedia failed: invalid argument (" + data + ")");
        this.trigger(Events.ERROR, {
          type: ErrorTypes.OTHER_ERROR,
          details: ErrorDetails.ATTACH_MEDIA_ERROR,
          fatal: true,
          error: error
        });
        return;
      }
      if (this._media) {
        this.detachMedia();
      }
      var attachMediaSource = 'media' in data;
      var media = attachMediaSource ? data.media : data;
      var attachingData = attachMediaSource ? data : {
        media: media
      };
      this._media = media;
      this.trigger(Events.MEDIA_ATTACHING, attachingData);
    }

;
    _proto.detachMedia = function detachMedia() {
      var data = {};
      this.trigger(Events.MEDIA_DETACHING, data);
      this._media = null;
      this.trigger(Events.MEDIA_DETACHED, data);
    }

;
    _proto.transferMedia = function transferMedia() {
      var _this$bufferControlle;
      this._media = null;
      var transferMedia = ((_this$bufferControlle = this.bufferController) == null ? void 0 : _this$bufferControlle.transferMedia()) || null;
      var data = {
        transferMedia: transferMedia
      };
      this.trigger(Events.MEDIA_DETACHING, data);
      this.trigger(Events.MEDIA_DETACHED, data);
      return transferMedia;
    }

;
    _proto.loadSource = function loadSource(url) {
      var _this$bufferControlle2;
      this.stopLoad();
      var media = this.media;
      var loadedSource = this._url;
      var loadingSource = this._url = urlToolkitExports.buildAbsoluteURL(self.location.href, url, {
        alwaysNormalize: true
      });
      this._autoLevelCapping = -1;
      this._maxHdcpLevel = null;
      if (media && loadedSource && (loadedSource !== loadingSource || (_this$bufferControlle2 = this.bufferController) != null && _this$bufferControlle2.hasSourceTypes())) {
        this.detachMedia();
        this.attachMedia(media);
      }
      this.trigger(Events.MANIFEST_LOADING, {
        url: url
      });
    }

;
    _proto.startLoad = function startLoad(startPosition, skipSeekToStartPosition) {
      if (startPosition === void 0) {
        startPosition = -1;
      }
      this.started = true;
      this.resumeBuffering();
      for (var i = 0; i < this.networkControllers.length; i++) {
        this.networkControllers[i].startLoad(startPosition, skipSeekToStartPosition);
        if (!this.started || !this.networkControllers) {
          break;
        }
      }
    }

;
    _proto.stopLoad = function stopLoad() {
      this.started = false;
      for (var i = 0; i < this.networkControllers.length; i++) {
        this.networkControllers[i].stopLoad();
        if (this.started || !this.networkControllers) {
          break;
        }
      }
    }

;
    _proto.resumeBuffering = function resumeBuffering() {
      if (!this.bufferingEnabled) {
        this.networkControllers.forEach(function (controller) {
          if (controller.resumeBuffering) {
            controller.resumeBuffering();
          }
        });
      }
    }

;
    _proto.pauseBuffering = function pauseBuffering() {
      if (this.bufferingEnabled) {
        this.networkControllers.forEach(function (controller) {
          if (controller.pauseBuffering) {
            controller.pauseBuffering();
          }
        });
      }
    };
    _proto.swapAudioCodec = function swapAudioCodec() {
      this.streamController.swapAudioCodec();
    }

;
    _proto.recoverMediaError = function recoverMediaError() {
      var media = this._media;
      var started = this.started;
      var time = media == null ? void 0 : media.currentTime;
      this.detachMedia();
      if (media) {
        this.attachMedia(media);
        if (started) {
          if (time) {
            this.startLoad(time);
          } else if (!this.config.autoStartLoad) {
            this.startLoad();
          }
        }
      }
    };
    _proto.removeLevel = function removeLevel(levelIndex) {
      this.levelController.removeLevel(levelIndex);
    }

;
    _proto.setAudioOption = function setAudioOption(audioOption) {
      var _this$audioTrackContr;
      return ((_this$audioTrackContr = this.audioTrackController) == null ? void 0 : _this$audioTrackContr.setAudioOption(audioOption)) || null;
    }

;
    _proto.setSubtitleOption = function setSubtitleOption(subtitleOption) {
      var _this$subtitleTrackCo;
      return ((_this$subtitleTrackCo = this.subtitleTrackController) == null ? void 0 : _this$subtitleTrackCo.setSubtitleOption(subtitleOption)) || null;
    }

;
    _proto.createIFramePlayer = function createIFramePlayer(configOverride) {
      return null;
    }

;
    _proto.createImageIFramePlayer = function createImageIFramePlayer(configOverride) {
      return null;
    }

;
    _proto.getMediaDecodingInfo = function getMediaDecodingInfo(level, audioTracks) {
      if (audioTracks === void 0) {
        audioTracks = this.allAudioTracks;
      }
      var audioTracksByGroup = getAudioTracksByGroup(audioTracks);
      return emptyExports.getMediaDecodingInfoPromise(level, audioTracksByGroup, navigator.mediaCapabilities);
    };
    return _createClass(Hls, [{
      key: "url",
      get: function get() {
        return this._url;
      }

    }, {
      key: "hasEnoughToStart",
      get: function get() {
        return this.streamController.hasEnoughToStart;
      }

    }, {
      key: "startPosition",
      get: function get() {
        return this.streamController.startPositionValue;
      }
    }, {
      key: "loadingEnabled",
      get: function get() {
        return this.started && this.streamController.state !== State.STOPPED;
      }

    }, {
      key: "bufferingEnabled",
      get: function get() {
        return this.streamController.bufferingEnabled;
      }
    }, {
      key: "inFlightFragments",
      get: function get() {
        var _inFlightData;
        var inFlightData = (_inFlightData = {}, _inFlightData[PlaylistLevelType.MAIN] = this.streamController.inFlightFrag, _inFlightData);
        if (this.audioStreamController) {
          inFlightData[PlaylistLevelType.AUDIO] = this.audioStreamController.inFlightFrag;
        }
        if (this.subtititleStreamController) {
          inFlightData[PlaylistLevelType.SUBTITLE] = this.subtititleStreamController.inFlightFrag;
        }
        return inFlightData;
      }
    }, {
      key: "sessionId",
      get: function get() {
        var _sessionId = this._sessionId;
        if (!_sessionId) {
          _sessionId = this._sessionId = uuid();
        }
        return _sessionId;
      }

    }, {
      key: "levels",
      get: function get() {
        var levels = this.levelController.levels;
        return levels ? levels : [];
      }

    }, {
      key: "latestLevelDetails",
      get: function get() {
        return this.streamController.getLevelDetails() || null;
      }

    }, {
      key: "loadLevelObj",
      get: function get() {
        return this.levelController.loadLevelObj;
      }

    }, {
      key: "currentLevel",
      get: function get() {
        return this.streamController.currentLevel;
      }

,
      set: function set(newLevel) {
        this.levelController.manualLevel = newLevel;
        this.streamController.immediateLevelSwitch();
      }

    }, {
      key: "nextLevel",
      get: function get() {
        return this.streamController.nextLevel;
      }

,
      set: function set(newLevel) {
        this.levelController.manualLevel = newLevel;
        this.streamController.nextLevelSwitch();
      }

    }, {
      key: "loadLevel",
      get: function get() {
        return this.levelController.level;
      }

,
      set: function set(newLevel) {
        this.levelController.manualLevel = newLevel;
      }

    }, {
      key: "nextLoadLevel",
      get: function get() {
        return this.levelController.nextLoadLevel;
      }

,
      set: function set(level) {
        this.levelController.nextLoadLevel = level;
      }

    }, {
      key: "firstLevel",
      get: function get() {
        return Math.max(this.levelController.firstLevel, this.minAutoLevel);
      }

,
      set: function set(newLevel) {
        this.levelController.firstLevel = newLevel;
      }

    }, {
      key: "startLevel",
      get: function get() {
        var startLevel = this.levelController.startLevel;
        if (startLevel === -1 && this.abrController.forcedAutoLevel > -1) {
          return this.abrController.forcedAutoLevel;
        }
        return startLevel;
      }

,
      set: function set(newLevel) {
        if (newLevel !== -1) {
          newLevel = Math.max(newLevel, this.minAutoLevel);
        }
        this.levelController.startLevel = newLevel;
      }

    }, {
      key: "capLevelToPlayerSize",
      get: function get() {
        return this.config.capLevelToPlayerSize;
      }

,
      set: function set(shouldStartCapping) {
        var capLevelController = this.capLevelController;
        var newCapLevelToPlayerSize = !!shouldStartCapping;
        if (capLevelController && newCapLevelToPlayerSize !== this.config.capLevelToPlayerSize) {
          if (newCapLevelToPlayerSize) {
            capLevelController.startCapping();
          } else {
            capLevelController.stopCapping();
            this.autoLevelCapping = -1;
            this.streamController.nextLevelSwitch();
          }
          this.config.capLevelToPlayerSize = newCapLevelToPlayerSize;
        }
      }

    }, {
      key: "autoLevelCapping",
      get: function get() {
        return this._autoLevelCapping;
      }

,
      set:
      function set(newLevel) {
        if (this._autoLevelCapping !== newLevel) {
          this._autoLevelCapping = newLevel;
          this.levelController.checkMaxAutoUpdated();
        }
      }
    }, {
      key: "bandwidthEstimate",
      get: function get() {
        var bwEstimator = this.abrController.bwEstimator;
        if (!bwEstimator) {
          return NaN;
        }
        return bwEstimator.getEstimate();
      },
      set: function set(abrEwmaDefaultEstimate) {
        this.abrController.resetEstimator(abrEwmaDefaultEstimate);
      }
    }, {
      key: "abrEwmaDefaultEstimate",
      get: function get() {
        var bwEstimator = this.abrController.bwEstimator;
        if (!bwEstimator) {
          return NaN;
        }
        return bwEstimator.defaultEstimate;
      }

    }, {
      key: "ttfbEstimate",
      get: function get() {
        var bwEstimator = this.abrController.bwEstimator;
        if (!bwEstimator) {
          return NaN;
        }
        return bwEstimator.getEstimateTTFB();
      }
    }, {
      key: "maxHdcpLevel",
      get: function get() {
        return this._maxHdcpLevel;
      },
      set: function set(value) {
        if (isHdcpLevel(value) && this._maxHdcpLevel !== value) {
          this._maxHdcpLevel = value;
          this.levelController.checkMaxAutoUpdated();
        }
      }

    }, {
      key: "autoLevelEnabled",
      get: function get() {
        return this.levelController.manualLevel === -1;
      }

    }, {
      key: "manualLevel",
      get: function get() {
        return this.levelController.manualLevel;
      }

    }, {
      key: "minAutoLevel",
      get: function get() {
        var levels = this.levels,
          minAutoBitrate = this.config.minAutoBitrate;
        if (!levels) return 0;
        var len = levels.length;
        for (var i = 0; i < len; i++) {
          if (levels[i].maxBitrate >= minAutoBitrate) {
            return i;
          }
        }
        return 0;
      }

    }, {
      key: "maxAutoLevel",
      get: function get() {
        var levels = this.levels,
          autoLevelCapping = this.autoLevelCapping,
          maxHdcpLevel = this.maxHdcpLevel;
        var maxAutoLevel;
        if (autoLevelCapping === -1 && levels != null && levels.length) {
          maxAutoLevel = levels.length - 1;
        } else {
          maxAutoLevel = autoLevelCapping;
        }
        if (maxHdcpLevel) {
          for (var i = maxAutoLevel; i--;) {
            var hdcpLevel = levels[i].attrs['HDCP-LEVEL'];
            if (hdcpLevel && hdcpLevel <= maxHdcpLevel) {
              return i;
            }
          }
        }
        return maxAutoLevel;
      }
    }, {
      key: "firstAutoLevel",
      get: function get() {
        return this.abrController.firstAutoLevel;
      }

    }, {
      key: "nextAutoLevel",
      get: function get() {
        return this.abrController.nextAutoLevel;
      }

,
      set: function set(nextLevel) {
        this.abrController.nextAutoLevel = nextLevel;
      }

    }, {
      key: "playingDate",
      get: function get() {
        return this.streamController.currentProgramDateTime;
      }
    }, {
      key: "mainForwardBufferInfo",
      get: function get() {
        return this.streamController.getMainFwdBufferInfo();
      }
    }, {
      key: "audioForwardBufferInfo",
      get: function get() {
        var _this$audioStreamCont;
        return ((_this$audioStreamCont = this.audioStreamController) == null ? void 0 : _this$audioStreamCont.getFwdBufferInfo()) || null;
      }
    }, {
      key: "maxBufferLength",
      get: function get() {
        return this.streamController.maxBufferLength;
      }
    }, {
      key: "allAudioTracks",
      get: function get() {
        var audioTrackController = this.audioTrackController;
        return audioTrackController ? audioTrackController.allAudioTracks : [];
      }

    }, {
      key: "audioTracks",
      get: function get() {
        var audioTrackController = this.audioTrackController;
        return audioTrackController ? audioTrackController.audioTracks : [];
      }

    }, {
      key: "audioTrack",
      get: function get() {
        var audioTrackController = this.audioTrackController;
        return audioTrackController ? audioTrackController.audioTrack : -1;
      }

,
      set: function set(audioTrackId) {
        var audioTrackController = this.audioTrackController;
        if (audioTrackController) {
          audioTrackController.audioTrack = audioTrackId;
        }
      }

    }, {
      key: "nextAudioTrack",
      get: function get() {
        var _this$audioStreamCont2, _this$audioStreamCont3;
        return (_this$audioStreamCont2 = (_this$audioStreamCont3 = this.audioStreamController) == null ? void 0 : _this$audioStreamCont3.nextAudioTrack) != null ? _this$audioStreamCont2 : -1;
      }

,
      set: function set(audioTrackId) {
        var audioTrackController = this.audioTrackController;
        if (audioTrackController) {
          audioTrackController.nextAudioTrack = audioTrackId;
        }
      }

    }, {
      key: "allSubtitleTracks",
      get: function get() {
        var subtitleTrackController = this.subtitleTrackController;
        return subtitleTrackController ? subtitleTrackController.allSubtitleTracks : [];
      }

    }, {
      key: "subtitleTracks",
      get: function get() {
        var subtitleTrackController = this.subtitleTrackController;
        return subtitleTrackController ? subtitleTrackController.subtitleTracks : [];
      }

    }, {
      key: "subtitleTrack",
      get: function get() {
        var subtitleTrackController = this.subtitleTrackController;
        return subtitleTrackController ? subtitleTrackController.subtitleTrack : -1;
      },
      set:
      function set(subtitleTrackId) {
        var subtitleTrackController = this.subtitleTrackController;
        if (subtitleTrackController) {
          subtitleTrackController.subtitleTrack = subtitleTrackId;
        }
      }

    }, {
      key: "media",
      get: function get() {
        return this._media;
      }
    }, {
      key: "subtitleDisplay",
      get: function get() {
        var subtitleTrackController = this.subtitleTrackController;
        return subtitleTrackController ? subtitleTrackController.subtitleDisplay : false;
      }

,
      set: function set(value) {
        var subtitleTrackController = this.subtitleTrackController;
        if (subtitleTrackController) {
          subtitleTrackController.subtitleDisplay = value;
        }
      }

    }, {
      key: "lowLatencyMode",
      get: function get() {
        return this.config.lowLatencyMode;
      }

,
      set: function set(mode) {
        this.config.lowLatencyMode = mode;
      }

    }, {
      key: "liveSyncPosition",
      get: function get() {
        var _this$latencyControll, _this$latencyControll2;
        return (_this$latencyControll = (_this$latencyControll2 = this.latencyController) == null ? void 0 : _this$latencyControll2.liveSyncPosition) != null ? _this$latencyControll : null;
      }

    }, {
      key: "latency",
      get: function get() {
        var _this$latencyControll3;
        return ((_this$latencyControll3 = this.latencyController) == null ? void 0 : _this$latencyControll3.latency) || 0;
      }

    }, {
      key: "maxLatency",
      get: function get() {
        var _this$latencyControll4;
        return ((_this$latencyControll4 = this.latencyController) == null ? void 0 : _this$latencyControll4.maxLatency) || 0;
      }

    }, {
      key: "targetLatency",
      get: function get() {
        var _this$latencyControll5;
        return ((_this$latencyControll5 = this.latencyController) == null ? void 0 : _this$latencyControll5.targetLatency) || null;
      },
      set: function set(latency) {
        if (!this.latencyController) return;
        this.latencyController.targetLatency = latency;
      }

    }, {
      key: "drift",
      get: function get() {
        var _this$latencyControll6;
        return ((_this$latencyControll6 = this.latencyController) == null ? void 0 : _this$latencyControll6.drift) || null;
      }

    }, {
      key: "forceStartLoad",
      get: function get() {
        return this.streamController.forceStartLoad;
      }

    }, {
      key: "pathways",
      get: function get() {
        return this.levelController.pathways;
      }

    }, {
      key: "pathwayPriority",
      get: function get() {
        return this.levelController.pathwayPriority;
      },
      set: function set(pathwayPriority) {
        this.levelController.pathwayPriority = pathwayPriority;
      }

    }, {
      key: "bufferedToEnd",
      get: function get() {
        var _this$bufferControlle3;
        return !!((_this$bufferControlle3 = this.bufferController) != null && _this$bufferControlle3.bufferedToEnd);
      }

    }, {
      key: "interstitialsManager",
      get: function get() {
        return null;
      }

    }, {
      key: "iframeVariants",
      get: function get() {
        var iframeVariants = this.levelController.iframeVariants;
        return iframeVariants ? iframeVariants : [];
      }
    }], [{
      key: "version",
      get:
      function get() {
        return version;
      }
    }, {
      key: "Events",
      get: function get() {
        return Events;
      }
    }, {
      key: "MetadataSchema",
      get: function get() {
        return MetadataSchema;
      }
    }, {
      key: "ErrorTypes",
      get: function get() {
        return ErrorTypes;
      }
    }, {
      key: "ErrorDetails",
      get: function get() {
        return ErrorDetails;
      }

    }, {
      key: "DefaultConfig",
      get: function get() {
        if (!Hls.defaultConfig) {
          return hlsDefaultConfig;
        }
        return Hls.defaultConfig;
      }

,
      set: function set(defaultConfig) {
        Hls.defaultConfig = defaultConfig;
      }
    }]);
  }();
  Hls.defaultConfig = void 0;

  return Hls;

}));
})(false);
`;
const cbHoverPreview = () => {
    'use strict';
    if (document.documentElement.dataset.cbhp && document.getElementById('cbhp-css')) return;
    document.documentElement.dataset.cbhp = '1';

    const MODES = ['off', 'anim', 'video'];
    const MODE_LABEL = { off: 'выключен', anim: 'только анимация обложки', video: 'анимация + мини-видео' };
    const HEIGHTS = [240, 360, 480, 720];

    const cfg = {
        mode: MODES.includes(Store.get('cbhp_mode', 'video')) ? Store.get('cbhp_mode', 'video') : 'video',
        sound: Store.get('cbhp_sound', false) === true,
        maxH: HEIGHTS.includes(+Store.get('cbhp_maxh', 360)) ? +Store.get('cbhp_maxh', 360) : 360
    };

    let HlsLib = null, hlsLoad = null;

    const T = {
        animDelay: 140,
        videoDelay: 320,
        frameMs: 50,
        sameMs: 250,
        okTtl: 180000,
        badTtl: 45000
    };

    const CARD = '.RoomCard, li.roomCard, li.room_list_room';
    const IMG = 'img.RoomCardThumbnail__image, img[data-testid="room-card-image"], img.room_thumbnail';
    const SKIP = '[class*="thumbnail_label_c_private"], [class*="thumbnail_label_offline"], [class*="thumbnail_label_c_away"], [class*="thumbnail_label_c_hidden"]';
    const NAME_RE = /^[A-Za-z0-9_]{2,40}$/;

    const userOf = card => {
        let u = card.querySelector('a[data-room]')?.dataset.room;
        if (!u) {
            const href = card.querySelector('a[href]')?.getAttribute('href') || '';
            u = (/^(?:https?:\/\/[^/]+)?\/([A-Za-z0-9_]+)\/?(?:[?#].*)?$/.exec(href) || [])[1];
        }
        return u && NAME_RE.test(u) ? u : '';
    };

    const css = `
.cbhp-layer {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  transition: opacity .18s ease
}
.cbhp-layer.cbhp-in {
  opacity: 1
}
.cbhp-f,
.cbhp-v {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  display: block;
  margin: 0;
  padding: 0;
  border: 0;
  background: none
}
.cbhp-f.cbhp-on {
  opacity: 1
}
.cbhp-layer.cbhp-live .cbhp-v {
  opacity: 1;
  transition: opacity .35s ease
}
.cbhp-bar {
  display: none
}
.RoomCard .bcu-star,
.roomCard .bcu-star,
.RoomCard .RoomCardThumbnail__labelContainer,
.RoomCard .RoomCard__followStar,
.RoomCard .RoomCardThumbnail__followStar,
.RoomCard .RoomCardThumbnail__topBar,
.RoomCard .RoomCardThumbnail__bottomBar {
  z-index: 2147483000!important;
  transform: translateZ(0)
}
.RoomCard .RoomCardThumbnail {
  isolation: isolate
}
.cbhp-toast {
  position: fixed;
  left: 50%;
  bottom: 28px;
  transform: translateX(-50%);
  z-index: 2147483000;
  padding: 8px 14px;
  border-radius: 8px;
  background: rgba(20,20,22,.92);
  color: #fff;
  font: 13px/1.3 system-ui,sans-serif;
  pointer-events: none;
  opacity: 0;
  transition: opacity .2s
}
.cbhp-toast.cbhp-show {
  opacity: 1
}
@media (hover:none) {
  .RoomCard,
  .RoomCard a,
  .RoomCard img {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
    -webkit-user-drag: none
  }
}
@media (prefers-reduced-motion:reduce) {
  .cbhp-f,
  .cbhp-v,
  .cbhp-layer {
    transition: none
  }
}
`;
    try { addStyle('cbhp-css', css, document.documentElement); } catch (e) { dbg(e, 'cbHoverPreview'); }

    const touchy = !matchMedia('(hover:hover)').matches;
    const CBHP_DEBUG = Store.get('cbhp_debug', false) === true;
    const log = (...a) => { if (CBHP_DEBUG) { try { console.log('[cbhp]', ...a); } catch (e) { dbg(e, 'cbHoverPreview.log'); } } };
    log('init ok, mode=' + cfg.mode + ', GM_xhr=' + (typeof GM_xmlhttpRequest) + ', Hls=' + (HlsLib ? 'ready' : 'lazy'));

    let toastEl = null, toastTimer = 0;
    const toast = text => {
        if (!toastEl) { toastEl = mk('div', 'cbhp-toast'); document.body.append(toastEl); }
        toastEl.textContent = text;
        toastEl.classList.add('cbhp-show');
        clearTimeout(toastTimer);
        toastTimer = setTimeout(() => toastEl.classList.remove('cbhp-show'), 1800);
    };

    const reg = (id, caption, fn) => {
        gmMenu(caption, fn, id);
    };
    const setMode = m => {
        if (!MODES.includes(m)) return;
        cfg.mode = m;
        Store.set('cbhp_mode', m); stop(); buildMenu();
    };
    const setSound = on => {
        cfg.sound = on === true;
        Store.set('cbhp_sound', cfg.sound); stop(); buildMenu();
    };
    const setMaxH = h => {
        if (!HEIGHTS.includes(+h)) return;
        cfg.maxH = +h;
        Store.set('cbhp_maxh', cfg.maxH); stop(); buildMenu();
    };
    const buildMenu = () => {
        reg('mode', `Превью при наведении: ${MODE_LABEL[cfg.mode]}`, () => {
            setMode(MODES[(MODES.indexOf(cfg.mode) + 1) % MODES.length]); toast(`Превью: ${MODE_LABEL[cfg.mode]}`);
        });
        reg('sound', `Звук при наведении: ${cfg.sound ? 'вкл' : 'выкл'}`, () => {
            setSound(!cfg.sound); toast(`Звук превью: ${cfg.sound ? 'вкл' : 'выкл'}`);
        });
        reg('q', `Качество мини-видео: до ${cfg.maxH}p`, () => {
            setMaxH(HEIGHTS[(HEIGHTS.indexOf(cfg.maxH) + 1) % HEIGHTS.length]); toast(`Мини-видео: до ${cfg.maxH}p`);
        });
        reg('dbg', `Отладка в консоли: ${CBHP_DEBUG ? 'вкл' : 'выкл'} (нужна перезагрузка)`, () => {
            Store.set('cbhp_debug', !CBHP_DEBUG); toast(`Отладка: ${!CBHP_DEBUG ? 'вкл' : 'выкл'}, перезагрузите страницу`);
        });
    };
    buildMenu();
    CBHP.api = { modes: MODES, heights: HEIGHTS, cfg, setMode, setSound, setMaxH };
    if (typeof GM_addValueChangeListener === 'function') {
        const sync = (key, apply) => {
            try {
                GM_addValueChangeListener(`cb:${key}`, (n, o, nv, remote) => {
                    if (!remote) return;
                    try { apply(JSON.parse(nv)); stop(); buildMenu(); } catch (e) { dbg(e, 'cbHoverPreview.sync'); }
                });
            } catch (e) { dbg(e, 'cbHoverPreview.sync'); }
        };
        sync('cbhp_mode', v => { if (MODES.includes(v)) cfg.mode = v; });
        sync('cbhp_sound', v => { cfg.sound = v === true; });
        sync('cbhp_maxh', v => { if (HEIGHTS.includes(+v)) cfg.maxH = +v; });
    }

    const streams = new Map();
    const pending = new Map();

    const getStream = user => {
        const c = streams.get(user);
        if (c && Date.now() - c.t < (c.url ? T.okTtl : T.badTtl)) return Promise.resolve(c.url);
        if (pending.has(user)) return pending.get(user);
        const p = (async () => {
            try {
                const r = await fetch(`/api/chatvideocontext/${encodeURIComponent(user)}/`, {
                    credentials: 'same-origin',
                    headers: { 'X-Requested-With': 'XMLHttpRequest' }
                });
                log('chatvideocontext', user, r.status);
                if (!r.ok) return '';
                const j = await r.json();
                log('ctx', user, j && j.room_status, !!(j && j.hls_source));
                const url = j && j.room_status === 'public' && isStr(j.hls_source) && /^https:\/\//.test(j.hls_source) ? j.hls_source : '';
                streams.set(user, { t: Date.now(), url });
                if (streams.size > 300) streams.delete(streams.keys().next().value);
                return url;
            } catch (err) { log('ctx error', err && err.message); return ''; }
            finally { pending.delete(user); }
        })();
        pending.set(user, p);
        return p;
    };
    const forget = user => streams.delete(user);

    const frameUrls = u => [
        `https://thumb.live.mmcdn.com/minifwap/${u}.jpg?${Date.now()}`,
        `https://jpeg.live.mmcdn.com/stream?room=${u}&f=${Math.random()}`
    ];

    let needGM = false;
    const gmBlob = async url => {
        const r = await gmReq({ url, type: 'blob', timeout: 8000 });
        if (!(r.status >= 200 && r.status < 300 && r.body && r.body.size > 500)) throw new Error('bad');
        return { url: URL.createObjectURL(r.body), size: r.body.size };
    };

    const OFFLINE_SIZES = new Set([4824, 4456, 9505, 2688, 3189, 4411, 3847]);

    const decodeInto = async (img, src) => { img.src = src; await img.decode(); };

    async function pushFrame(s) {
        const next = s.frames[s.flip ^ 1], prev = s.frames[s.flip];
        for (const url of frameUrls(s.user)) {
            if (s.dead) return false;
            try {
                if (needGM) {
                    const b = await gmBlob(url);
                    if (s.dead) { URL.revokeObjectURL(b.url); return false; }
                    const old = next.dataset.blob;
                    if (OFFLINE_SIZES.has(b.size)) { URL.revokeObjectURL(b.url); return 'same'; }
                    if (b.size === s.lastSize) { URL.revokeObjectURL(b.url); return 'same'; }
                    s.lastSize = b.size;
                    try { await decodeInto(next, b.url); } catch (e) { URL.revokeObjectURL(b.url); throw e; }
                    next.dataset.blob = b.url;
                    if (old) URL.revokeObjectURL(old);
                } else {
                    await decodeInto(next, url);
                }
                if (s.dead) return false;
                next.classList.add('cbhp-on');
                prev.classList.remove('cbhp-on');
                s.flip ^= 1;
                s.layer.classList.add('cbhp-in');
                return 'new';
            } catch (err) {
                log('frame fail', url, needGM ? 'gm' : 'direct', err && (err.message || err.name));
                if (!needGM && typeof GM_xmlhttpRequest === 'function') {
                    try { const b = await gmBlob(url); URL.revokeObjectURL(b.url); needGM = true; } catch (e) { dbg(e, 'cbHoverPreview.pushFrame'); }
                }
            }
        }
        return false;
    }

    async function animLoop(s) {
        let fails = 0;
        while (!s.dead && !s.live) {
            if (!s.card.isConnected) { stop(); return; }
            const t0 = performance.now();
            const ok = await pushFrame(s);
            if (s.dead || s.live) return;
            fails = ok ? 0 : fails + 1;
            if (fails >= 3) return;
            await s.sleep(Math.max(ok === 'same' ? T.sameMs : T.frameMs - (performance.now() - t0), 0));
        }
    }

    function pickLevel(levels) {
        let idx = -1, best = -1;
        levels.forEach((l, i) => { const h = l.height || 0; if (h && h <= cfg.maxH && h > best) { best = h; idx = i; } });
        if (idx >= 0) return idx;
        let low = 0;
        levels.forEach((l, i) => { if ((l.height || 1e9) < (levels[low].height || 1e9)) low = i; });
        return low;
    }

    const readHls = () => {
        const src = HLSJS_SRC;
        try {
            const m = { exports: {} };
            new Function('module', 'exports', src)(m, m.exports);
            const L = m.exports;
            if (L && typeof L.isSupported === 'function') return L;
        } catch (e) { dbg(e, 'cbHoverPreview.readHls'); }
        try {
            injectPage(src);
            const L = unsafeWindow.Hls;
            if (L && typeof L.isSupported === 'function') return L;
        } catch (e) { dbg(e, 'cbHoverPreview.readHls'); }
        return null;
    };
    const loadHls = () => hlsLoad || (hlsLoad = Promise.resolve().then(() => { HlsLib = readHls(); log('hls.js', HlsLib ? 'loaded' : 'unavailable'); return !!HlsLib; }));
    const canHls = () => !!HlsLib && HlsLib.isSupported();

    function prepare(s) {
        Promise.all([getStream(s.user), loadHls()]).then(([url]) => {
            if (s.dead || !url || s.video || !canHls()) return;
            attach(s, url);
        });
    }

    async function videoStart(s) {
        if (s.dead) return;
        s.go = true;
        s.layer.classList.add('cbhp-load');
        if (s.hls && s.parsed) { s.parsed = false; s.hls.startLoad(-1); tryPlay(s.video, s); return; }
        if (s.video) return;
        const [url] = await Promise.all([getStream(s.user), loadHls()]);
        if (s.dead || s.video) return;
        if (!url) { s.layer.classList.remove('cbhp-load'); return; }
        attach(s, url);
    }

    const tryPlay = (v, s) => {
        const pr = v.play();
        if (pr && pr.catch) pr.catch(err => {
            log('play rejected', err && err.name, 'muted=' + v.muted);
            if (!v.muted && !s.dead) { v.muted = true; v.play().catch(() => {}); }
        });
    };

    function attach(s, url) {
        if (s.video || s.hls) return;
        s.parsed = false;
        try { previewDirs.add(hlsDir(url)); if (previewDirs.size > 40) previewDirs.delete(previewDirs.values().next().value); } catch (e) { dbg(e, 'cbHoverPreview.attach'); }
        const v = s.video = mk('video', 'cbhp-v');
        v.muted = !(cfg.sound && navigator.userActivation && navigator.userActivation.hasBeenActive);
        log('video attach, sound=' + cfg.sound + ', activation=' + !!(navigator.userActivation && navigator.userActivation.hasBeenActive) + ', muted=' + v.muted);
        v.playsInline = true;
        v.disablePictureInPicture = true;
        v.disableRemotePlayback = true;
        v.preload = 'auto';
        s.layer.insertBefore(v, s.layer.querySelector('.cbhp-bar'));

        const ready = () => {
            if (s.dead || s.live) return;
            s.live = true;
            s.layer.classList.remove('cbhp-load');
            s.layer.classList.add('cbhp-in', 'cbhp-live');
        };
        if ('requestVideoFrameCallback' in v) v.requestVideoFrameCallback(ready);
        else v.addEventListener('playing', ready, { once: true });

        const fail = () => { if (!s.dead) { s.layer.classList.remove('cbhp-load'); killVideo(s); } };

        if (canHls()) {
            const hls = s.hls = new HlsLib({
                autoStartLoad: false,
                lowLatencyMode: false,
                enableWorker: true,
                backBufferLength: 0,
                maxBufferLength: 4,
                maxMaxBufferLength: 8,
                maxBufferSize: 8 * 1000 * 1000,
                liveSyncDurationCount: 2,
                manifestLoadingMaxRetry: 1,
                levelLoadingMaxRetry: 1,
                fragLoadingMaxRetry: 2
            });
            hls.on(HlsLib.Events.MANIFEST_PARSED, (_, d) => {
                if (s.dead || s.hls !== hls) return;
                const idx = pickLevel(d.levels);
                hls.startLevel = idx;
                hls.currentLevel = idx;
                if (!s.go) { s.parsed = true; return; }
                hls.startLoad(-1);
                tryPlay(v, s);
            });
            hls.on(HlsLib.Events.ERROR, (_, d) => {
                log('hls error', d.type, d.details, d.fatal);
                if (!d.fatal || s.dead) return;
                if (!s.retried && d.type === HlsLib.ErrorTypes.NETWORK_ERROR) {
                    s.retried = true; forget(s.user); killVideo(s);
                    getStream(s.user).then(u => { if (!s.dead && u) attach(s, u); else fail(); });
                } else fail();
            });
            hls.loadSource(url);
            hls.attachMedia(v);
        } else if (v.canPlayType('application/vnd.apple.mpegurl')) {
            v.src = url;
            tryPlay(v, s);
            v.addEventListener('error', fail, { once: true });
        } else fail();
    }

    function killVideo(s) {
        if (s.hls) { try { s.hls.stopLoad(); s.hls.detachMedia(); s.hls.destroy(); } catch (e) { dbg(e, 'cbHoverPreview.killVideo'); } s.hls = null; }
        if (s.video) { try { unloadMedia(s.video); } catch (e) { dbg(e, 'cbHoverPreview.killVideo'); } s.video.remove(); s.video = null; }
    }

    let cur = null;

    function start(card) {
        stop();
        if (cfg.mode === 'off' || card.querySelector(SKIP)) return;
        const img = card.querySelector(IMG), user = userOf(card);
        log('start', { user, img: !!img, skip: !!card.querySelector(SKIP) });
        if (!img || !user || !img.parentElement) return;

        const host = img.parentElement;
        const s = cur = {
            card, img, host, user, dead: false, live: false, flip: 0, retried: false,
            cancel: [], layer: null, frames: [], video: null, hls: null, restorePos: false,
            sleep(ms) { return new Promise(res => { const t = setTimeout(res, ms); s.cancel.push(() => { clearTimeout(t); res(); }); }); },
            after(ms, fn) { const t = setTimeout(() => { if (!s.dead) fn(); }, ms); s.cancel.push(() => clearTimeout(t)); }
        };

        s.after(touchy ? 0 : T.animDelay, () => {
            if (getComputedStyle(host).position === 'static') { host.style.position = 'relative'; s.restorePos = true; }
            const layer = s.layer = mk('div', 'cbhp-layer');
            const a = mk('img', 'cbhp-f'), b = mk('img', 'cbhp-f');
            a.alt = b.alt = ''; a.decoding = b.decoding = 'async';
            s.frames = [a, b];
            layer.append(a, b, mk('div', 'cbhp-bar'));
            img.after(layer);
            log('layer added for', s.user);
            animLoop(s);
            if (cfg.mode === 'video') prepare(s);
            if (cfg.mode === 'video') s.after(touchy ? 150 : Math.max(0, T.videoDelay - T.animDelay), () => videoStart(s));
        });
    }

    function stop() {
        const s = cur;
        if (!s) return;
        cur = null;
        s.dead = true;
        s.cancel.forEach(fn => { try { fn(); } catch (e) { dbg(e, 'cbHoverPreview.stop'); } });
        killVideo(s);
        const layer = s.layer;
        if (layer) {
            layer.classList.remove('cbhp-in');
            setTimeout(() => {
                layer.remove();
                s.frames.forEach(f => { if (f.dataset.blob) URL.revokeObjectURL(f.dataset.blob); f.removeAttribute('src'); });
                if (s.restorePos) s.host.style.position = '';
            }, 220);
        }
    }

    const onOver = e => {
        log('event', e.type, e.pointerType || '', e.target && e.target.className);
        if (cfg.mode === 'off' || e.buttons) return;
        const card = e.target.closest && e.target.closest(CARD);
        if (!card || (cur && cur.card === card)) return;
        if (e.relatedTarget && card.contains(e.relatedTarget)) return;
        start(card);
    };
    const onOut = e => {
        if (!cur) return;
        if (e.pointerType === 'touch') return;
        const card = e.target.closest && e.target.closest(CARD);
        if (!card || card !== cur.card) return;
        if (e.relatedTarget && card.contains(e.relatedTarget)) return;
        stop();
    };
    ['pointerover', 'mouseover'].forEach(t => document.addEventListener(t, onOver, true));
    ['pointerout', 'mouseout'].forEach(t => document.addEventListener(t, onOut, true));

    document.addEventListener('contextmenu', e => {
        if (touchy && cfg.mode !== 'off' && e.target.closest && e.target.closest(CARD)) e.preventDefault();
    }, true);

    document.addEventListener('click', e => { if (!touchy && cur && e.target.closest && e.target.closest(CARD)) stop(); }, true);

    let scrollTimer = 0;
    window.addEventListener('scroll', () => {
        if (!cur) return;
        clearTimeout(scrollTimer);
        scrollTimer = setTimeout(() => { if (cur && (touchy || !cur.card.matches(':hover'))) stop(); }, 120);
    }, { passive: true, capture: true });

    window.addEventListener('blur', stop);
    document.addEventListener('visibilitychange', () => { if (document.hidden) stop(); });
};

const i18n = {
    RU: {
        blur_mute: "Размывать видео без звука",
        ago: "{n} назад",
        bg: "ФОН",
        bk_csv: "Таблицы CSV",
        bk_csv_fav: "Избранное",
        bk_csv_hist: "История",
        bk_csv_ses: "Сеансы",
        bk_exp: "Экспорт резервной копии",
        bk_imp: "Импорт резервной копии",
        bk_p_fav: "Избранное",
        bk_p_hist: "История",
        bk_p_set: "Настройки",
        bk_parts: "Состав копии",
        bm_add: "В избранное",
        bm_all: "Все",
        bm_del: "Удалить",
        bm_dup: "Уже добавлено!",
        bm_empty: "Список пуст.",
        bm_find: "Поиск по имени…",
        bm_nomatch: "Нет моделей по выбранному фильтру",
        bm_online: "Онлайн",
        bm_online_t: "Показывать только Онлайн",
        bm_pin: "Закрепить наверху",
        bm_priv: "Приват",
        bm_title: "Избранное",
        bm_unpin: "Открепить",
        bt_hints: "Подсказки",
        bt_stats: "Зрители",
        bt_vol: "Громкость",
        cal_all_tag: "Все теги",
        cal_allday: "Весь день",
        cal_c_dur: "Длит.",
        cal_c_model: "Модель",
        cal_c_n: "Заходы",
        cal_c_time: "Вход",
        cal_hist: "Просмотры",
        cal_loaded: "Расписание загружено: {a} из {b}",
        cal_models: "Модели",
        cal_no_sched: "Расписание берётся из профилей избранных. Откройте «Избранное» и подождите несколько секунд.",
        cal_none: "В этот день пусто",
        cal_reset: "Сбросить",
        cal_sched: "Расписание",
        cal_pres_btn: "Прогноз",
        cal_pres_h: "Обычно онлайн (по наблюдениям)",
        cal_pres_none: "Данных пока нет. Прогноз появится, пока вы пользуетесь сайтом.",
        cal_pres_info: "Моделей с данными: {a} из {b}",
        cal_pres_on: "Учёт онлайна",
        cal_title: "Календарь",
        cal_today: "Сегодня",
        cal_total: "Итого",
        cal_watched: "Просмотрено",
        cf_cancel: "Отмена",
        cf_ok: "Да",
        ch_avg: "В среднем в день",
        ch_btn: "Диаграммы",
        ch_days: "Время по дням, 14 дней",
        ch_hours: "Активность по часам",
        ch_peak: "Пик",
        ch_top: "Топ по времени",
        copy: "ССЫЛКА",
        dark: "Тёмная / светлая тема",
        don_copied: "Адрес скопирован",
        don_fail: "Не удалось скопировать",
        don_sub: "Спасибо, что пользуешься",
        don_tap: "Нажмите на адрес, чтобы раскрыть его целиком. Кнопка справа копирует.",
        don_tip: "Поддержать автора",
        don_title: "Угостить автора кофе",
        err: "ОШИБКА",
        favs: "ИЗБРАННОЕ",
        fr_hold: "Удерживайте — показать текущий кадр",
        fr_none: "Кадр недоступен: модель не в эфире",
        fs: "ЭКРАН",
        grp_all: "Все группы",
        grp_new: "+ Новая группа…",
        grp_none: "Без группы",
        hd_all: "Скрыть всё",
        hd_cats: "Категории",
        hd_clear: "Удалить все мои правила",
        hd_details: "Подробности",
        hd_g_chat: "Чат",
        hd_g_site: "Сайт",
        hd_g_win: "Окна",
        hd_hidden: "Скрыто {n} из {m}",
        hd_na: "пока не найдено",
        hd_none: "Правил пока нет.",
        hd_off: "Показать всё",
        hd_p_clean: "Чистый плеер",
        hd_p_min: "Минимум",
        hd_peek: "Удерживайте пункт, чтобы увидеть, что он скрывает",
        hd_pick: "Выбрать элемент",
        hd_reset: "Сбросить для этого сайта",
        hd_rules: "Мои правила",
        hd_title: "Скрытие",
        hide: "Скрыть (H)",
        hints: "M=ЗВУК F=ЭКРАН T=ТЕАТР H=СКРЫТЬ",
        hist_clear: "Очистить",
        hist_clear_q: "Удалить всю историю просмотров?",
        hist_del: "Убрать из истории",
        hist_empty: "Пока пусто.",
        hist_s_last: "Недавние",
        hist_s_time: "Время",
        hist_s_vis: "Визиты",
        hist_title: "История",
        hist_total: "Моделей: {n} · Всего: {t}",
        hist_visits: "виз.",
        home: "Домой",
        lang_auto: "Авто",
        lang_title: "Язык",
        min: "Свернуть",
        mv_abr_row: "Автокачество",
        mv_abr_t: "Автокачество: снижает качество при зависаниях или слабой сети и повышает, когда соединение ровное",
        mv_add: "Добавить",
        mv_auto: "Авто",
        mv_auto_all: "Добавлять все стримы сразу",
        mv_back: "Назад",
        mv_bg: "Играть в фоне",
        mv_bg_t: "Потоки продолжают играть при переключении вкладок, закрытии окна и выходе из браузера",
        mv_bm_h: "Закладки в эфире",
        mv_clear_q: "Закрыть все трансляции?",
        mv_close: "Закрыть",
        mv_cols: "Сетка",
        mv_cur: "Текущая трансляция",
        mv_dvr: "Буфер перемотки",
        mv_dvr_off: "Выкл",
        mv_fs: "Во весь экран",
        mv_full: "Окно заполнено",
        mv_go: "Добавить",
        mv_hint: "Добавьте трансляции, чтобы смотреть несколько сразу. Нажмите на плитку, чтобы включить на ней звук.",
        mv_link_h: "Или по ссылке",
        mv_tab_link: "По ссылке",
        mv_load: "Загрузка…",
        mv_min_u: "мин",
        mv_mute: "Выключить звук",
        mv_none: "Нет закладок в эфире",
        mv_off: "Трансляция недоступна",
        mv_pip: "Картинка в картинке",
        mv_play: "Пуск / пауза",
        mv_qual: "Качество",
        mv_retry: "нажмите, чтобы повторить",
        mv_search: "Поиск по онлайн-закладкам",
        mv_title: "Плеер",
        no_vid: "НЕТ ВИДЕО",
        note_ph: "Заметка... (#теги)",
        pk_cancel: "Отмена",
        pk_found: "совп.",
        pk_hint: "Коснитесь элемента на странице",
        pk_ok: "Скрыть",
        pk_up: "Выше",
        qual: "КАЧ-ВО",
        rad_media: "Медиа",
        rad_nav: "Избранное",
        rad_next: "След.",
        rad_page: "Страница",
        rad_prev: "Пред.",
        rad_reset: "Сбросить позицию кружка",
        rad_title: "Круговое меню",
        rad_vdn: "Тише",
        rad_view: "Экран",
        rad_vup: "Громче",
        rec: "ЗАПИСЬ",
        rec_tip: "Нажатие — запись, удержание — настройки",
        rel: "Рестарт",
        rs_cur: "Сейчас поток:",
        rs_gbh: "ГБ/ч",
        rs_hint: "Когда часть достигает выбранного размера, она сохраняется отдельным файлом, а запись продолжается в новой. Данные пишутся на диск небольшими порциями; если диск недоступен, они остаются в памяти телефона, и тогда лучше выбрать меньшие части.",
        rs_mbmin: "МБ/мин",
        rs_next: "Изменения вступают в силу со следующей части или новой записи.",
        rs_now: "Сейчас",
        rs_off: "Выкл.",
        rs_p: "часть",
        rs_part: "Размер части (файл делится)",
        rs_q_hi: "Высокое",
        rs_q_low: "Низкое",
        rs_q_mid: "Среднее",
        rs_q_min: "Экономия",
        rs_quality: "Качество (битрейт)",
        rs_recover: "Восстановить несохранённые записи",
        rs_resnote: "Захват пишет в разрешении потока: настройки выше меняют только битрейт (сжатие), а 1080p–360p показывают ожидаемое качество картинки. В режиме HLS выбирается поток не выше указанной высоты.",
        rs_src: "Источник",
        rs_src_cap: "Захват (перекодирование)",
        rs_src_hls: "HLS (без перекодирования)",
        rs_src_note: "HLS скачивает сегменты потока как есть, без перекодирования, и сохраняет MP4. Нужен адрес потока; если его нет, используется захват. Качество оригинальное, как у исходного потока. Запись пишется на диск небольшими порциями.",
        rs_t: "всего",
        rs_title: "Настройки записи",
        rs_total: "Остановить при общем размере",
        rs_upto: "до",
        search: "ПОИСК",
        seen_now: "только что",
        seen_tip: "Последний раз онлайн",
        settings: "Настройки",
        sleep_custom: "Своё время",
        sleep_hint: "Через выбранное время плеер будет выключен, а запись остановится и сохранится. Нажмите на чёрный экран, чтобы вернуть плеер.",
        sleep_left: "Осталось",
        sleep_sec: "сек",
        sleep_start: "Запустить",
        sleep_title: "Таймер сна",
        sleep_wake: "Таймер сна сработал.\nНажмите, чтобы продолжить",
        snap: "СКРИН",
        sort_date: "Дата",
        sort_last: "Был(а)",
        sort_live: "Лайв",
        sort_name: "Имя",
        sort_viewers: "Зрители",
        sound: "ЗВУК",
        st_av_refresh: "Обновить аватарки",
        st_back: "Назад",
        st_btns: "Кнопки панели",
        st_btns_hint: "Нажмите, чтобы скрыть кнопку или вернуть её на панель.",
        st_btns_reset: "Показать все кнопки",
        st_color: "Цвет панели",
        st_color_rnd: "Случайный",
        st_cols: "Колонки",
        st_custom: "Свой цвет",
        st_data: "Данные",
        st_fav: "Избранное",
        st_favnav: "Кнопки «назад / вперёд» на пульте",
        st_group: "Группа",
        st_hide_grp: "Скрыть выбор группы",
        st_hide_note: "Скрыть поле заметки (теги)",
        st_hide_topic: "Скрыть описание модели",
        st_hist: "История просмотров",
        st_hist_on: "Вести историю",
        st_hue: "Оттенок",
        st_lang: "Язык",
        st_lig: "Яркость",
        st_list: "Список",
        st_look: "Оформление",
        st_open: "Открыть",
        st_prev: "Превью моделей",
        st_prev_off: "Не обновлять",
        st_prev_spd: "Обновление превью",
        st_private: "Приват",
        st_ptags: "Скрыть теги профиля (BongaCams)",
        st_rec: "Запись видео",
        st_sat: "Насыщенность",
        st_scale: "Размер панели",
        st_site_dark: "Тёмная тема сайта",
        st_star: "Звёздочка на миниатюрах",
        star_add: "В избранное",
        star_del: "Убрать из избранного",
        star_del_q: "У этой модели есть заметка или группа. Убрать из избранного?",
        thtr: "ТЕАТР",
        lock: "БЛОК",
        tp_less: "[Свернуть]",
        tp_more: "[Ещё]",
        u_d: "д",
        u_gb: "ГБ",
        u_h: "ч",
        u_m: "мин",
        u_mb: "МБ",
        viewers_tip: "Зрителей в комнате",
        w_new: "Не просмотрено",
        w_seen: "Просмотрено",
    },
    EN: {
        blur_mute: "Blur video when muted",
        ago: "{n} ago",
        bg: "BG",
        bk_csv: "CSV tables",
        bk_csv_fav: "Favorites",
        bk_csv_hist: "History",
        bk_csv_ses: "Sessions",
        bk_exp: "Export backup",
        bk_imp: "Import backup",
        bk_p_fav: "Favorites",
        bk_p_hist: "History",
        bk_p_set: "Settings",
        bk_parts: "Backup contents",
        bm_add: "Add to favorites",
        bm_all: "All",
        bm_del: "Delete",
        bm_dup: "Already added!",
        bm_empty: "List is empty.",
        bm_find: "Search by name…",
        bm_nomatch: "No models match filter",
        bm_online: "Online",
        bm_online_t: "Show only Online",
        bm_pin: "Pin to top",
        bm_priv: "Priv",
        bm_title: "Favorites",
        bm_unpin: "Unpin",
        bt_hints: "Hints",
        bt_stats: "Viewers",
        bt_vol: "Volume",
        cal_all_tag: "All tags",
        cal_allday: "All day",
        cal_c_dur: "Duration",
        cal_c_model: "Model",
        cal_c_n: "Visits",
        cal_c_time: "Joined",
        cal_hist: "History",
        cal_loaded: "Schedule loaded: {a} of {b}",
        cal_models: "Models",
        cal_no_sched: "The schedule is read from the profiles of your favorites. Open Favorites and wait a few seconds.",
        cal_none: "Nothing on this day",
        cal_reset: "Reset",
        cal_sched: "Schedule",
        cal_pres_btn: "Forecast",
        cal_pres_h: "Usually online (from observations)",
        cal_pres_none: "No data yet. The forecast fills in as you use the site.",
        cal_pres_info: "Models with data: {a} of {b}",
        cal_pres_on: "Online tracking",
        cal_title: "Calendar",
        cal_today: "Today",
        cal_total: "Total",
        cal_watched: "Viewed",
        cf_cancel: "Cancel",
        cf_ok: "Yes",
        ch_avg: "Daily average",
        ch_btn: "Charts",
        ch_days: "Time per day, 14 days",
        ch_hours: "Activity by hour",
        ch_peak: "Peak",
        ch_top: "Top by time",
        copy: "LINK",
        dark: "Dark / light theme",
        don_copied: "Address copied",
        don_fail: "Copy failed",
        don_sub: "Thanks for using it",
        don_tap: "Tap an address to expand it. The button on the right copies it.",
        don_tip: "Support the author",
        don_title: "Buy the author a coffee",
        drag: "⁞⁞",
        err: "ERROR",
        favs: "FAVS",
        fr_hold: "Hold to show the current frame",
        fr_none: "No frame: model is offline",
        fs: "FULL",
        grp_all: "All groups",
        grp_new: "+ New group…",
        grp_none: "No group",
        hd_all: "Hide all",
        hd_cats: "Categories",
        hd_clear: "Delete all my rules",
        hd_details: "Details",
        hd_g_chat: "Chat",
        hd_g_site: "Site",
        hd_g_win: "Pop-ups",
        hd_hidden: "Hidden {n} of {m}",
        hd_na: "not found yet",
        hd_none: "No rules yet.",
        hd_off: "Show all",
        hd_p_clean: "Clean player",
        hd_p_min: "Minimal",
        hd_peek: "Hold an item to see what it hides",
        hd_pick: "Pick element",
        hd_reset: "Reset for this site",
        hd_rules: "My rules",
        hd_title: "Hiding",
        hide: "Hide (H)",
        hints: "M=MUTE F=FULL T=THTR H=HIDE",
        hist_clear: "Clear",
        hist_clear_q: "Delete the whole watch history?",
        hist_del: "Remove from history",
        hist_empty: "Empty for now.",
        hist_s_last: "Recent",
        hist_s_time: "Time",
        hist_s_vis: "Visits",
        hist_title: "History",
        hist_total: "Models: {n} · Total: {t}",
        hist_visits: "visits",
        home: "Home",
        lang_auto: "Auto",
        lang_title: "Language",
        min: "Minimize",
        mv_abr_row: "Auto quality",
        mv_abr_t: "Auto quality: lowers the quality when the video stutters or the network gets weak, raises it when the connection is stable",
        mv_add: "Add",
        mv_auto: "Auto",
        mv_auto_all: "Add all live streams automatically",
        mv_back: "Back",
        mv_bg: "Play in background",
        mv_bg_t: "Streams keep playing when you switch tabs, close this window or leave the browser",
        mv_bm_h: "Live bookmarks",
        mv_clear_q: "Close all streams?",
        mv_close: "Close",
        mv_cols: "Layout",
        mv_cur: "Current stream",
        mv_dvr: "Rewind buffer",
        mv_dvr_off: "Off",
        mv_fs: "Fullscreen",
        mv_full: "Window is full",
        mv_go: "Add",
        mv_hint: "Add streams to watch several at once. Tap a tile to move the sound to it.",
        mv_link_h: "Or by link",
        mv_tab_link: "By link",
        mv_live: "LIVE",
        mv_load: "Loading…",
        mv_min_u: "min",
        mv_mute: "Mute",
        mv_none: "No live bookmarks",
        mv_off: "Stream unavailable",
        mv_pip: "Picture-in-picture",
        mv_play: "Play / pause",
        mv_qual: "Quality",
        mv_retry: "tap to retry",
        mv_search: "Search live bookmarks",
        mv_title: "Player",
        mv_url: "https://…/playlist.m3u8",
        no_vid: "NO VIDEO",
        note_ph: "Note... (#tags)",
        pip: "PIP",
        pk_cancel: "Cancel",
        pk_found: "matches",
        pk_hint: "Tap an element on the page",
        pk_ok: "Hide",
        pk_up: "Up",
        qual: "QUAL",
        rad_media: "Media",
        rad_nav: "Favorites",
        rad_next: "Next",
        rad_page: "Page",
        rad_prev: "Prev",
        rad_reset: "Reset button position",
        rad_title: "Radial menu",
        rad_vdn: "Quieter",
        rad_view: "View",
        rad_vup: "Louder",
        rec: "REC",
        rec_tip: "Tap: record · Hold: settings",
        rel: "Reload",
        rs_cur: "Current stream:",
        rs_gbh: "GB/h",
        rs_hint: "When a part reaches the chosen size, it is saved as a separate file and recording continues in a new one. Data is written to disk in small chunks; if the disk is unavailable it stays in phone memory, so choose smaller parts.",
        rs_mbmin: "MB/min",
        rs_next: "Changes apply from the next part or a new recording.",
        rs_now: "Now",
        rs_off: "Off",
        rs_p: "part",
        rs_part: "Part size (file is split)",
        rs_q_hi: "High",
        rs_q_low: "Low",
        rs_q_mid: "Medium",
        rs_q_min: "Saver",
        rs_quality: "Quality (bitrate)",
        rs_recover: "Recover unsaved recordings",
        rs_resnote: "Capture records at the stream resolution: the options above only change the bitrate (compression), and 1080p–360p show the expected picture quality. In HLS mode, a stream no taller than the chosen height is picked.",
        rs_src: "Source",
        rs_src_cap: "Capture (re-encode)",
        rs_src_hls: "HLS (no re-encode)",
        rs_src_note: "HLS downloads the stream segments as they are, without re-encoding, and saves MP4. It needs the stream address; if it is not available, Capture is used instead. Quality is original, as in the source stream. Recording is written to disk in small checkpoints.",
        rs_t: "total",
        rs_title: "Recording settings",
        rs_total: "Stop at total size",
        rs_upto: "up to",
        search: "SEARCH",
        seen_now: "just now",
        seen_tip: "Last seen online",
        settings: "Settings",
        sleep_custom: "Custom time",
        sleep_hint: "When time is up, the player is switched off and any recording stops and is saved. Tap the black screen to bring it back.",
        sleep_left: "Left",
        sleep_sec: "sec",
        sleep_start: "Start",
        sleep_title: "Sleep timer",
        sleep_wake: "Sleep timer finished.\nTap to continue",
        snap: "SNAP",
        sort_date: "Date",
        sort_last: "Seen",
        sort_live: "Live",
        sort_name: "Name",
        sort_viewers: "Viewers",
        sound: "VOL",
        st_av_refresh: "Refresh avatars",
        st_away: "BRB",
        st_back: "Back",
        st_btns: "Panel buttons",
        st_btns_hint: "Tap to hide a button or bring it back.",
        st_btns_reset: "Show all buttons",
        st_color: "Panel color",
        st_color_rnd: "Random",
        st_cols: "Columns",
        st_custom: "Custom color",
        st_data: "Data",
        st_fav: "Favorites",
        st_favnav: "Prev / next buttons on remote",
        st_group: "Group",
        st_hide_grp: "Hide group selector",
        st_hide_note: "Hide note field (tags)",
        st_hide_topic: "Hide model description",
        st_hist: "Watch history",
        st_hist_on: "Keep history",
        st_hue: "Hue",
        st_lang: "Language",
        st_lig: "Lightness",
        st_list: "List",
        st_look: "Appearance",
        st_open: "Open",
        st_prev: "Model previews",
        st_prev_off: "No refresh",
        st_prev_spd: "Preview refresh",
        st_private: "Private",
        st_ptags: "Hide profile tags (BongaCams)",
        st_rec: "Video recording",
        st_sat: "Saturation",
        st_scale: "Panel size",
        st_site_dark: "Site dark theme",
        st_star: "Star on thumbnails",
        st_vip: "VIP",
        star_add: "Add to favorites",
        star_del: "Remove from favorites",
        star_del_q: "This model has a note or a group. Remove from favorites?",
        thtr: "THTR",
        lock: "LOCK",
        tp_less: "[Less]",
        tp_more: "[More]",
        u_d: "d",
        u_gb: "GB",
        u_h: "h",
        u_m: "min",
        u_mb: "MB",
        viewers_tip: "Viewers in the room",
        w_new: "Not watched",
        w_seen: "Watched",
    },
    UK: {
        blur_mute: "Розмивати відео без звуку",
        ago: "{n} тому",
        bg: "ФОН",
        bk_csv: "Таблиці CSV",
        bk_csv_fav: "Обране",
        bk_csv_hist: "Історія",
        bk_csv_ses: "Сеанси",
        bk_exp: "Експорт резервної копії",
        bk_imp: "Імпорт резервної копії",
        bk_p_fav: "Обране",
        bk_p_hist: "Історія",
        bk_p_set: "Налаштування",
        bk_parts: "Склад копії",
        bm_add: "В обране",
        bm_all: "Усі",
        bm_del: "Видалити",
        bm_dup: "Вже додано!",
        bm_empty: "Список порожній.",
        bm_find: "Пошук за іменем…",
        bm_nomatch: "Немає моделей за вибраним фільтром",
        bm_online: "Онлайн",
        bm_online_t: "Показувати лише Онлайн",
        bm_pin: "Закріпити нагорі",
        bm_priv: "Приват",
        bm_title: "Обране",
        bm_unpin: "Відкріпити",
        bt_hints: "Підказки",
        bt_stats: "Глядачі",
        bt_vol: "Гучність",
        cal_all_tag: "Усі теги",
        cal_allday: "Увесь день",
        cal_c_dur: "Трив.",
        cal_c_model: "Модель",
        cal_c_n: "Заходи",
        cal_c_time: "Вхід",
        cal_hist: "Перегляди",
        cal_loaded: "Розклад завантажено: {a} з {b}",
        cal_models: "Моделі",
        cal_no_sched: "Розклад береться з профілів обраних. Відкрийте «Обране» і зачекайте кілька секунд.",
        cal_none: "У цей день порожньо",
        cal_reset: "Скинути",
        cal_sched: "Розклад",
        cal_pres_btn: "Прогноз",
        cal_pres_h: "Зазвичай онлайн (за спостереженнями)",
        cal_pres_none: "Даних поки немає. Прогноз з’явиться, поки ви користуєтесь сайтом.",
        cal_pres_info: "Моделей з даними: {a} з {b}",
        cal_pres_on: "Облік онлайну",
        cal_title: "Календар",
        cal_today: "Сьогодні",
        cal_total: "Разом",
        cal_watched: "Переглянуто",
        cf_cancel: "Скасувати",
        cf_ok: "Так",
        ch_avg: "У середньому за день",
        ch_btn: "Діаграми",
        ch_days: "Час по днях, 14 днів",
        ch_hours: "Активність по годинах",
        ch_peak: "Пік",
        ch_top: "Топ за часом",
        copy: "ЛІНК",
        dark: "Темна / світла тема",
        don_copied: "Адресу скопійовано",
        don_fail: "Не вдалося скопіювати",
        don_sub: "Дякую, що користуєшся",
        don_tap: "Натисни на адресу, щоб розгорнути її повністю. Кнопка праворуч копіює її.",
        don_tip: "Підтримати автора",
        don_title: "Пригостити автора кавою",
        drag: "⁞⁞ ПУЛЬТ",
        err: "ПОМИЛКА",
        favs: "ОБРАНЕ",
        fr_hold: "Утримуйте — показати поточний кадр",
        fr_none: "Кадр недоступний: модель не в ефірі",
        fs: "ЕКРАН",
        grp_all: "Усі групи",
        grp_new: "+ Нова група…",
        grp_none: "Без групи",
        hd_all: "Сховати все",
        hd_cats: "Категорії",
        hd_clear: "Видалити всі мої правила",
        hd_details: "Подробиці",
        hd_g_chat: "Чат",
        hd_g_site: "Сайт",
        hd_g_win: "Вікна",
        hd_hidden: "Приховано {n} з {m}",
        hd_na: "поки не знайдено",
        hd_none: "Правил поки немає.",
        hd_off: "Показати все",
        hd_p_clean: "Чистий плеєр",
        hd_p_min: "Мінімум",
        hd_peek: "Утримуйте пункт, щоб побачити, що він ховає",
        hd_pick: "Обрати елемент",
        hd_reset: "Скинути для цього сайту",
        hd_rules: "Мої правила",
        hd_title: "Приховування",
        hide: "Сховати (H)",
        hints: "M=ЗВУК F=ЕКРАН T=ТЕАТР H=СХОВАТИ",
        hist_clear: "Очистити",
        hist_clear_q: "Видалити всю історію переглядів?",
        hist_del: "Прибрати з історії",
        hist_empty: "Поки порожньо.",
        hist_s_last: "Нещодавні",
        hist_s_time: "Час",
        hist_s_vis: "Візити",
        hist_title: "Історія",
        hist_total: "Моделей: {n} · Усього: {t}",
        hist_visits: "віз.",
        home: "Додому",
        lang_auto: "Авто",
        lang_title: "Мова",
        min: "Згорнути",
        mv_abr_row: "Автоякість",
        mv_abr_t: "Автоякість: знижує якість при підвисаннях або слабкій мережі та підвищує, коли з’єднання стабільне",
        mv_add: "Додати",
        mv_auto: "Авто",
        mv_auto_all: "Додавати всі стріми одразу",
        mv_back: "Назад",
        mv_bg: "Грати у фоні",
        mv_bg_t: "Потоки продовжують грати, коли ви перемикаєте вкладки, закриваєте вікно або виходите з браузера",
        mv_bm_h: "Закладки в ефірі",
        mv_clear_q: "Закрити всі трансляції?",
        mv_close: "Закрити",
        mv_cols: "Сітка",
        mv_cur: "Поточна трансляція",
        mv_dvr: "Буфер перемотування",
        mv_dvr_off: "Вимк.",
        mv_fs: "На весь екран",
        mv_full: "Вікно заповнене",
        mv_go: "Додати",
        mv_hint: "Додайте трансляції, щоб дивитися кілька одразу. Торкніться плитки, щоб увімкнути на ній звук.",
        mv_link_h: "Або за посиланням",
        mv_load: "Завантаження…",
        mv_min_u: "хв",
        mv_mute: "Вимкнути звук",
        mv_none: "Немає закладок в ефірі",
        mv_off: "Трансляція недоступна",
        mv_pip: "Картинка в картинці",
        mv_play: "Пуск / пауза",
        mv_qual: "Якість",
        mv_retry: "торкніться, щоб повторити",
        mv_search: "Пошук за закладками в ефірі",
        mv_title: "Плеєр",
        no_vid: "НЕМАЄ ВІДЕО",
        note_ph: "Нотатка... (#теги)",
        pk_cancel: "Скасувати",
        pk_found: "збіг.",
        pk_hint: "Торкніться елемента на сторінці",
        pk_ok: "Сховати",
        pk_up: "Вище",
        qual: "ЯКІСТЬ",
        rad_media: "Медіа",
        rad_nav: "Обране",
        rad_next: "Наст.",
        rad_page: "Сторінка",
        rad_prev: "Попер.",
        rad_reset: "Скинути позицію кружечка",
        rad_title: "Колове меню",
        rad_vdn: "Тихіше",
        rad_view: "Екран",
        rad_vup: "Гучніше",
        rec: "ЗАПИС",
        rec_tip: "Дотик — запис, утримання — налаштування",
        rel: "Перезапуск",
        rs_cur: "Зараз потік:",
        rs_gbh: "ГБ/год",
        rs_hint: "Коли частина досягає вибраного розміру, вона зберігається окремим файлом, а запис триває в новій. Дані пишуться на диск невеликими порціями; якщо диск недоступний, вони лишаються в памʼяті телефона, і тоді краще обрати менші частини.",
        rs_mbmin: "МБ/хв",
        rs_next: "Зміни діють з наступної частини або нового запису.",
        rs_now: "Зараз",
        rs_off: "Вимк.",
        rs_p: "частина",
        rs_part: "Розмір частини (файл ділиться)",
        rs_q_hi: "Висока",
        rs_q_low: "Низька",
        rs_q_mid: "Середня",
        rs_q_min: "Економія",
        rs_quality: "Якість (бітрейт)",
        rs_recover: "Відновити незбережені записи",
        rs_resnote: "Захоплення пише в роздільній здатності потоку: налаштування вище змінюють лише бітрейт (стиснення), а 1080p–360p показують очікувану якість картинки. У режимі HLS обирається потік не вищий за вказану висоту.",
        rs_src: "Джерело",
        rs_src_cap: "Захоплення (перекодування)",
        rs_src_hls: "HLS (без перекодування)",
        rs_src_note: "HLS завантажує сегменти потоку як є, без перекодування, і зберігає MP4. Потрібна адреса потоку; якщо її немає, використовується захоплення. Якість оригінальна, як у вихідного потоку. Запис пишеться на диск невеликими порціями.",
        rs_t: "всього",
        rs_title: "Налаштування запису",
        rs_total: "Зупинити при загальному розмірі",
        rs_upto: "до",
        search: "ПОШУК",
        seen_now: "щойно",
        seen_tip: "Востаннє онлайн",
        settings: "Налаштування",
        sleep_custom: "Свій час",
        sleep_hint: "Через обраний час плеєр буде вимкнено, а запис зупиниться й збережеться. Натисніть на чорний екран, щоб повернути плеєр.",
        sleep_left: "Залишилось",
        sleep_sec: "сек",
        sleep_start: "Запустити",
        sleep_title: "Таймер сну",
        sleep_wake: "Таймер сну спрацював.\nНатисніть, щоб продовжити",
        snap: "СКРІН",
        sort_date: "Дата",
        sort_last: "Був(ла)",
        sort_live: "Лайв",
        sort_name: "Імʼя",
        sort_viewers: "Глядачі",
        sound: "ЗВУК",
        st_av_refresh: "Оновити аватарки",
        st_back: "Назад",
        st_btns: "Кнопки панелі",
        st_btns_hint: "Натисніть, щоб сховати кнопку або повернути її на панель.",
        st_btns_reset: "Показати всі кнопки",
        st_color: "Колір панелі",
        st_color_rnd: "Випадковий",
        st_cols: "Колонки",
        st_custom: "Свій колір",
        st_data: "Дані",
        st_fav: "Обране",
        st_favnav: "Кнопки «назад / вперед» на пульті",
        st_group: "Група",
        st_hide_grp: "Сховати вибір групи",
        st_hide_note: "Сховати поле нотатки (теги)",
        st_hide_topic: "Сховати опис моделі",
        st_hist: "Історія переглядів",
        st_hist_on: "Зберігати історію",
        st_hue: "Відтінок",
        st_lang: "Мова",
        st_lig: "Яскравість",
        st_list: "Список",
        st_look: "Оформлення",
        st_open: "Відкрити",
        st_prev: "Превʼю моделей",
        st_prev_off: "Не оновлювати",
        st_prev_spd: "Оновлення превʼю",
        st_private: "Приват",
        st_ptags: "Сховати теги профілю (BongaCams)",
        st_rec: "Запис відео",
        st_sat: "Насиченість",
        st_scale: "Розмір панелі",
        st_site_dark: "Темна тема сайту",
        st_star: "Зірочка на мініатюрах",
        star_add: "В обране",
        star_del: "Прибрати з обраного",
        star_del_q: "У цієї моделі є нотатка або група. Прибрати з обраного?",
        thtr: "ТЕАТР",
        lock: "БЛОК",
        tp_less: "[Згорнути]",
        tp_more: "[Ще]",
        u_d: "д",
        u_gb: "ГБ",
        u_h: "год",
        u_m: "хв",
        u_mb: "МБ",
        viewers_tip: "Глядачів у кімнаті",
        w_new: "Не переглянуто",
        w_seen: "Переглянуто",
    },
    ES: {
        cal_pres_btn: "Previsión",
        cal_pres_h: "Normalmente en línea (según observaciones)",
        cal_pres_none: "Aún no hay datos. La previsión se completa mientras usas el sitio.",
        cal_pres_info: "Modelos con datos: {a} de {b}",
        cal_pres_on: "Seguimiento online",
        blur_mute: "Difuminar el vídeo sin sonido",
        ago: "hace {n}",
        bg: "FONDO",
        bk_csv: "Tablas CSV",
        bk_csv_fav: "Favoritos",
        bk_csv_hist: "Historial",
        bk_csv_ses: "Sesiones",
        bk_exp: "Exportar copia de seguridad",
        bk_imp: "Importar copia de seguridad",
        bk_p_fav: "Favoritos",
        bk_p_hist: "Historial",
        bk_p_set: "Ajustes",
        bk_parts: "Contenido de la copia",
        bm_add: "Añadir a favoritos",
        bm_all: "Todos",
        bm_del: "Eliminar",
        bm_dup: "¡Ya añadida!",
        bm_empty: "La lista está vacía.",
        bm_find: "Buscar por nombre…",
        bm_nomatch: "Ninguna modelo coincide con el filtro",
        bm_online_t: "Mostrar solo online",
        bm_pin: "Fijar arriba",
        bm_title: "Favoritos",
        bm_unpin: "Desfijar",
        bt_hints: "Consejos",
        bt_stats: "Espectadores",
        bt_vol: "Volumen",
        cal_all_tag: "Todas las etiquetas",
        cal_allday: "Todo el día",
        cal_c_dur: "Dur.",
        cal_c_model: "Modelo",
        cal_c_n: "Visitas",
        cal_c_time: "Entrada",
        cal_hist: "Vistos",
        cal_loaded: "Horario cargado: {a} de {b}",
        cal_models: "Modelos",
        cal_no_sched: "El horario se toma de los perfiles de tus favoritos. Abre Favoritos y espera unos segundos.",
        cal_none: "No hay nada este día",
        cal_reset: "Restablecer",
        cal_sched: "Horario",
        cal_title: "Calendario",
        cal_today: "Hoy",
        cal_watched: "Visto",
        cf_cancel: "Cancelar",
        cf_ok: "Sí",
        ch_avg: "Media diaria",
        ch_btn: "Gráficos",
        ch_days: "Tiempo por día, 14 días",
        ch_hours: "Actividad por hora",
        ch_peak: "Pico",
        ch_top: "Top por tiempo",
        copy: "ENLACE",
        dark: "Tema oscuro / claro",
        don_copied: "Dirección copiada",
        don_fail: "No se pudo copiar",
        don_sub: "Gracias por usarlo",
        don_tap: "Toca una dirección para verla completa. El botón de la derecha la copia.",
        don_tip: "Apoyar al autor",
        don_title: "Invita al autor a un café",
        drag: "⁞⁞ MANDO",
        fr_hold: "Mantén pulsado para ver el fotograma actual",
        fr_none: "Sin imagen: la modelo no está en directo",
        fs: "PANT.",
        grp_all: "Todos los grupos",
        grp_new: "+ Nuevo grupo…",
        grp_none: "Sin grupo",
        hd_all: "Ocultar todo",
        hd_cats: "Categorías",
        hd_clear: "Borrar todas mis reglas",
        hd_details: "Detalles",
        hd_g_site: "Sitio",
        hd_g_win: "Ventanas emergentes",
        hd_hidden: "Ocultos {n} de {m}",
        hd_na: "aún no encontrado",
        hd_none: "Aún no hay reglas.",
        hd_off: "Mostrar todo",
        hd_p_clean: "Reproductor limpio",
        hd_p_min: "Mínimo",
        hd_peek: "Mantén pulsado un elemento para ver qué oculta",
        hd_pick: "Elegir elemento",
        hd_reset: "Restablecer para este sitio",
        hd_rules: "Mis reglas",
        hd_title: "Ocultar",
        hide: "Ocultar (H)",
        hints: "M=SONIDO F=PANT T=CINE H=OCULTAR",
        hist_clear: "Borrar",
        hist_clear_q: "¿Borrar todo el historial de visualización?",
        hist_del: "Quitar del historial",
        hist_empty: "Vacío por ahora. El tiempo se cuenta mientras se reproduce el vídeo.",
        hist_s_last: "Recientes",
        hist_s_time: "Tiempo",
        hist_s_vis: "Visitas",
        hist_title: "Historial",
        hist_total: "Modelos: {n} · Total: {t}",
        hist_visits: "visitas",
        home: "Inicio",
        lang_auto: "Automático",
        lang_title: "Idioma",
        min: "Minimizar",
        mv_abr_row: "Calidad automática",
        mv_abr_t: "Calidad automática: baja la calidad cuando el vídeo se corta o la red es débil y la sube cuando la conexión es estable",
        mv_add: "Añadir",
        mv_auto_all: "Añadir todas las transmisiones en directo automáticamente",
        mv_back: "Atrás",
        mv_bg: "Reproducir en segundo plano",
        mv_bg_t: "Las transmisiones siguen reproduciéndose al cambiar de pestaña, cerrar esta ventana o salir del navegador",
        mv_bm_h: "Favoritos en directo",
        mv_clear_q: "¿Cerrar todas las transmisiones?",
        mv_close: "Cerrar",
        mv_cols: "Cuadrícula",
        mv_cur: "Transmisión actual",
        mv_dvr: "Búfer de retroceso",
        mv_dvr_off: "Desactivado",
        mv_fs: "Pantalla completa",
        mv_full: "La ventana está llena",
        mv_go: "Añadir",
        mv_hint: "Añade transmisiones para ver varias a la vez. Toca un recuadro para pasarle el sonido.",
        mv_link_h: "O mediante enlace",
        mv_load: "Cargando…",
        mv_mute: "Silenciar",
        mv_none: "No hay favoritos en directo",
        mv_off: "Transmisión no disponible",
        mv_pip: "Imagen en imagen",
        mv_play: "Reproducir / pausa",
        mv_qual: "Calidad",
        mv_retry: "toca para reintentar",
        mv_search: "Buscar favoritos en directo",
        mv_title: "Reproductor",
        no_vid: "SIN VÍDEO",
        note_ph: "Nota... (#etiquetas)",
        pk_cancel: "Cancelar",
        pk_found: "coinc.",
        pk_hint: "Toca un elemento de la página",
        pk_ok: "Ocultar",
        pk_up: "Subir",
        qual: "CALID.",
        rad_media: "Medios",
        rad_nav: "Favoritos",
        rad_next: "Sig.",
        rad_page: "Página",
        rad_prev: "Ant.",
        rad_reset: "Restablecer posición del botón",
        rad_title: "Menú radial",
        rad_vdn: "Más bajo",
        rad_view: "Pantalla",
        rad_vup: "Más alto",
        rec: "GRABAR",
        rec_tip: "Toque: grabar · Mantener: ajustes",
        rel: "Recargar",
        rs_cur: "Transmisión actual:",
        rs_hint: "Cuando una parte alcanza el tamaño elegido, se guarda como archivo aparte y la grabación continúa en uno nuevo. Los datos se escriben en el disco en pequeños bloques; si el disco no está disponible, permanecen en la memoria del teléfono, así que conviene elegir partes más pequeñas.",
        rs_next: "Los cambios se aplican desde la siguiente parte o una grabación nueva.",
        rs_now: "Ahora",
        rs_off: "Desactivado",
        rs_p: "parte",
        rs_part: "Tamaño de parte (divide el archivo)",
        rs_q_hi: "Alta",
        rs_q_low: "Baja",
        rs_q_mid: "Media",
        rs_q_min: "Ahorro",
        rs_quality: "Calidad (bitrate)",
        rs_recover: "Recuperar grabaciones sin guardar",
        rs_resnote: "La captura graba con la resolución de la transmisión: las opciones de arriba solo cambian el bitrate (compresión), y 1080p–360p indican la calidad de imagen esperada. En el modo HLS se elige una transmisión que no supere la altura indicada.",
        rs_src: "Origen",
        rs_src_cap: "Captura (recodificación)",
        rs_src_hls: "HLS (sin recodificar)",
        rs_src_note: "HLS descarga los segmentos de la transmisión tal cual, sin recodificar, y guarda MP4. Necesita la dirección de la transmisión; si no está disponible, se usa la captura. La calidad es la original, la del flujo de origen. La grabación se escribe en disco en pequeños puntos de control.",
        rs_title: "Ajustes de grabación",
        rs_total: "Detener al alcanzar el tamaño total",
        rs_upto: "hasta",
        search: "BUSCAR",
        seen_now: "ahora mismo",
        seen_tip: "Última vez online",
        settings: "Ajustes",
        sleep_custom: "Tiempo personalizado",
        sleep_hint: "Al terminar el tiempo, el reproductor se apaga y cualquier grabación se detiene y se guarda. Toca la pantalla negra para recuperarlo.",
        sleep_left: "Quedan",
        sleep_sec: "seg",
        sleep_start: "Iniciar",
        sleep_title: "Temporizador de apagado",
        sleep_wake: "El temporizador ha terminado.\nToca para continuar",
        snap: "CAPT.",
        sort_date: "Fecha",
        sort_last: "Visto",
        sort_live: "En vivo",
        sort_name: "Nombre",
        sort_viewers: "Espectadores",
        sound: "SONIDO",
        st_av_refresh: "Actualizar avatares",
        st_back: "Atrás",
        st_btns: "Botones del panel",
        st_btns_hint: "Toca para ocultar un botón o devolverlo.",
        st_btns_reset: "Mostrar todos los botones",
        st_color: "Color del panel",
        st_color_rnd: "Aleatorio",
        st_cols: "Columnas",
        st_custom: "Color personalizado",
        st_data: "Datos",
        st_fav: "Favoritos",
        st_favnav: "Botones «anterior / siguiente» en el mando",
        st_group: "Grupo",
        st_hide_grp: "Ocultar el selector de grupo",
        st_hide_note: "Ocultar campo de nota (etiquetas)",
        st_hide_topic: "Ocultar la descripción de la modelo",
        st_hist: "Historial de visualización",
        st_hist_on: "Guardar historial",
        st_hue: "Tono",
        st_lang: "Idioma",
        st_lig: "Luminosidad",
        st_list: "Lista",
        st_look: "Apariencia",
        st_open: "Abrir",
        st_prev: "Vistas previas de modelos",
        st_prev_off: "Sin actualizar",
        st_prev_spd: "Actualización de la vista previa",
        st_private: "Privado",
        st_ptags: "Ocultar etiquetas del perfil (BongaCams)",
        st_rec: "Grabación de vídeo",
        st_sat: "Saturación",
        st_scale: "Tamaño del panel",
        st_site_dark: "Tema oscuro del sitio",
        st_star: "Estrella en las miniaturas",
        star_add: "Añadir a favoritos",
        star_del: "Quitar de favoritos",
        star_del_q: "Esta modelo tiene una nota o un grupo. ¿Quitar de favoritos?",
        thtr: "CINE",
        lock: "BLOQ",
        tp_less: "[Menos]",
        tp_more: "[Más]",
        viewers_tip: "Espectadores en la sala",
        w_new: "No visto",
        w_seen: "Visto",
    },
    DE: {
        cal_pres_btn: "Prognose",
        cal_pres_h: "Meist online (laut Beobachtungen)",
        cal_pres_none: "Noch keine Daten. Die Prognose füllt sich, während du die Seite nutzt.",
        cal_pres_info: "Models mit Daten: {a} von {b}",
        cal_pres_on: "Online-Erfassung",
        blur_mute: "Video ohne Ton unscharf machen",
        ago: "vor {n}",
        bg: "HINT.",
        bk_csv: "CSV-Tabellen",
        bk_csv_fav: "Favoriten",
        bk_csv_hist: "Verlauf",
        bk_csv_ses: "Sitzungen",
        bk_exp: "Sicherung exportieren",
        bk_imp: "Sicherung importieren",
        bk_p_fav: "Favoriten",
        bk_p_hist: "Verlauf",
        bk_p_set: "Einstellungen",
        bk_parts: "Inhalt der Sicherung",
        bm_add: "Zu Favoriten hinzufügen",
        bm_all: "Alle",
        bm_del: "Löschen",
        bm_dup: "Schon hinzugefügt!",
        bm_empty: "Die Liste ist leer.",
        bm_find: "Nach Namen suchen…",
        bm_nomatch: "Keine Models passen zum Filter",
        bm_online_t: "Nur Online anzeigen",
        bm_pin: "Oben anheften",
        bm_title: "Favoriten",
        bm_unpin: "Anheften aufheben",
        bt_hints: "Hinweise",
        bt_stats: "Zuschauer",
        bt_vol: "Lautstärke",
        cal_all_tag: "Alle Tags",
        cal_allday: "Ganztägig",
        cal_c_dur: "Dauer",
        cal_c_n: "Besuche",
        cal_c_time: "Eintritt",
        cal_hist: "Verlauf",
        cal_loaded: "Zeitplan geladen: {a} von {b}",
        cal_no_sched: "Der Zeitplan stammt aus den Profilen deiner Favoriten. Öffne die Favoriten und warte ein paar Sekunden.",
        cal_none: "An diesem Tag ist nichts",
        cal_reset: "Zurücksetzen",
        cal_sched: "Zeitplan",
        cal_title: "Kalender",
        cal_today: "Heute",
        cal_total: "Gesamt",
        cal_watched: "Gesehen",
        cf_cancel: "Abbrechen",
        cf_ok: "Ja",
        ch_avg: "Tagesdurchschnitt",
        ch_btn: "Diagramme",
        ch_days: "Zeit pro Tag, 14 Tage",
        ch_hours: "Aktivität nach Stunde",
        ch_peak: "Spitze",
        ch_top: "Top nach Zeit",
        dark: "Dunkles / helles Design",
        don_copied: "Adresse kopiert",
        don_fail: "Kopieren fehlgeschlagen",
        don_sub: "Danke fürs Benutzen",
        don_tap: "Tippe auf eine Adresse, um sie komplett zu sehen. Die Schaltfläche rechts kopiert sie.",
        don_tip: "Autor unterstützen",
        don_title: "Spendiere dem Autor einen Kaffee",
        drag: "⁞⁞ STEUERUNG",
        err: "FEHLER",
        fr_hold: "Gedrückt halten für das aktuelle Bild",
        fr_none: "Kein Bild: Model ist offline",
        fs: "VOLL",
        grp_all: "Alle Gruppen",
        grp_new: "+ Neue Gruppe…",
        grp_none: "Keine Gruppe",
        hd_all: "Alles ausblenden",
        hd_cats: "Kategorien",
        hd_clear: "Alle meine Regeln löschen",
        hd_g_site: "Seite",
        hd_hidden: "{n} von {m} ausgeblendet",
        hd_na: "noch nicht gefunden",
        hd_none: "Noch keine Regeln.",
        hd_off: "Alles zeigen",
        hd_p_clean: "Sauberer Player",
        hd_peek: "Eintrag gedrückt halten, um zu sehen, was er ausblendet",
        hd_pick: "Element wählen",
        hd_reset: "Für diese Seite zurücksetzen",
        hd_rules: "Meine Regeln",
        hd_title: "Ausblenden",
        hide: "Ausblenden (H)",
        hints: "M=TON F=VOLL T=KINO H=AUS",
        hist_clear: "Leeren",
        hist_clear_q: "Gesamten Wiedergabeverlauf löschen?",
        hist_del: "Aus dem Verlauf entfernen",
        hist_empty: "Noch leer. Die Zeit wird gezählt, solange das Video läuft.",
        hist_s_last: "Zuletzt",
        hist_s_time: "Zeit",
        hist_s_vis: "Besuche",
        hist_title: "Verlauf",
        hist_total: "Models: {n} · Gesamt: {t}",
        hist_visits: "Besuche",
        home: "Start",
        lang_auto: "Automatisch",
        lang_title: "Sprache",
        min: "Minimieren",
        mv_abr_row: "Automatische Qualität",
        mv_abr_t: "Automatische Qualität: senkt die Qualität bei Rucklern oder schwachem Netz und erhöht sie, wenn die Verbindung stabil ist",
        mv_add: "Hinzufügen",
        mv_auto_all: "Alle Live-Streams automatisch hinzufügen",
        mv_back: "Zurück",
        mv_bg: "Im Hintergrund abspielen",
        mv_bg_t: "Streams laufen weiter, wenn du den Tab wechselst, dieses Fenster schließt oder den Browser verlässt",
        mv_bm_h: "Live-Lesezeichen",
        mv_clear_q: "Alle Streams schließen?",
        mv_close: "Schließen",
        mv_cur: "Aktueller Stream",
        mv_dvr: "Zurückspul-Puffer",
        mv_dvr_off: "Aus",
        mv_fs: "Vollbild",
        mv_full: "Fenster ist voll",
        mv_go: "Hinzufügen",
        mv_hint: "Füge Streams hinzu, um mehrere gleichzeitig zu sehen. Tippe auf eine Kachel, um den Ton dorthin zu legen.",
        mv_link_h: "Oder per Link",
        mv_load: "Wird geladen…",
        mv_min_u: "Min",
        mv_mute: "Stumm",
        mv_none: "Keine Live-Lesezeichen",
        mv_off: "Stream nicht verfügbar",
        mv_pip: "Bild-in-Bild",
        mv_play: "Wiedergabe / Pause",
        mv_qual: "Qualität",
        mv_retry: "zum Wiederholen tippen",
        mv_search: "Live-Lesezeichen durchsuchen",
        no_vid: "KEIN VIDEO",
        note_ph: "Notiz... (#Tags)",
        pk_cancel: "Abbrechen",
        pk_found: "Treffer",
        pk_hint: "Tippe auf ein Element der Seite",
        pk_ok: "Ausblenden",
        pk_up: "Höher",
        qual: "QUAL.",
        rad_media: "Medien",
        rad_nav: "Favoriten",
        rad_next: "Weiter",
        rad_page: "Seite",
        rad_prev: "Zurück",
        rad_reset: "Position des Buttons zurücksetzen",
        rad_title: "Radialmenü",
        rad_vdn: "Leiser",
        rad_view: "Ansicht",
        rad_vup: "Lauter",
        rec: "AUFN.",
        rec_tip: "Tippen: aufnehmen · Halten: Einstellungen",
        rel: "Neu laden",
        rs_cur: "Aktueller Stream:",
        rs_gbh: "GB/Std",
        rs_hint: "Erreicht ein Teil die gewählte Größe, wird er als eigene Datei gespeichert und die Aufnahme läuft in einem neuen weiter. Die Daten werden in kleinen Blöcken auf den Datenträger geschrieben; ist er nicht verfügbar, bleiben sie im Arbeitsspeicher des Handys – wähle dann kleinere Teile.",
        rs_mbmin: "MB/Min",
        rs_next: "Änderungen gelten ab dem nächsten Teil oder einer neuen Aufnahme.",
        rs_now: "Jetzt",
        rs_off: "Aus",
        rs_p: "Teil",
        rs_part: "Teilgröße (Datei wird geteilt)",
        rs_q_hi: "Hoch",
        rs_q_low: "Niedrig",
        rs_q_mid: "Mittel",
        rs_q_min: "Sparen",
        rs_quality: "Qualität (Bitrate)",
        rs_recover: "Nicht gespeicherte Aufnahmen wiederherstellen",
        rs_resnote: "Der Capture-Modus nimmt in der Stream-Auflösung auf: Die Optionen oben ändern nur die Bitrate (Kompression), und 1080p–360p zeigen die erwartete Bildqualität. Im HLS-Modus wird ein Stream gewählt, der nicht höher als die angegebene Höhe ist.",
        rs_src: "Quelle",
        rs_src_cap: "Mitschnitt (Neukodierung)",
        rs_src_hls: "HLS (ohne Neukodierung)",
        rs_src_note: "HLS lädt die Stream-Segmente unverändert ohne Neukodierung herunter und speichert MP4. Dafür wird die Stream-Adresse benötigt; ist sie nicht verfügbar, wird der Mitschnitt genutzt. Die Qualität entspricht dem Original-Stream. Die Aufzeichnung wird in kleinen Schritten auf die Festplatte geschrieben.",
        rs_t: "gesamt",
        rs_title: "Aufnahme-Einstellungen",
        rs_total: "Stopp bei Gesamtgröße",
        rs_upto: "bis zu",
        search: "SUCHE",
        seen_now: "gerade eben",
        seen_tip: "Zuletzt online",
        settings: "Einstellungen",
        sleep_custom: "Eigene Zeit",
        sleep_hint: "Nach Ablauf der Zeit wird der Player ausgeschaltet, und eine laufende Aufnahme wird gestoppt und gespeichert. Tippe auf den schwarzen Bildschirm, um ihn zurückzuholen.",
        sleep_left: "Verbleibend",
        sleep_sec: "Sek",
        sleep_start: "Starten",
        sleep_title: "Schlaf-Timer",
        sleep_wake: "Schlaf-Timer abgelaufen.\nTippen zum Fortsetzen",
        snap: "FOTO",
        sort_date: "Datum",
        sort_last: "Zuletzt",
        sort_viewers: "Zuschauer",
        sound: "TON",
        st_av_refresh: "Avatare aktualisieren",
        st_back: "Zurück",
        st_btns: "Panel-Schaltflächen",
        st_btns_hint: "Tippen, um eine Schaltfläche aus- oder wieder einzublenden.",
        st_btns_reset: "Alle Schaltflächen anzeigen",
        st_color: "Panelfarbe",
        st_color_rnd: "Zufällig",
        st_cols: "Spalten",
        st_custom: "Eigene Farbe",
        st_data: "Daten",
        st_fav: "Favoriten",
        st_favnav: "Zurück-/Weiter-Tasten an der Steuerung",
        st_group: "Gruppe",
        st_hide_grp: "Gruppenauswahl ausblenden",
        st_hide_note: "Notizfeld (Tags) ausblenden",
        st_hide_topic: "Modellbeschreibung ausblenden",
        st_hist: "Wiedergabeverlauf",
        st_hist_on: "Verlauf speichern",
        st_hue: "Farbton",
        st_lang: "Sprache",
        st_lig: "Helligkeit",
        st_list: "Liste",
        st_look: "Darstellung",
        st_open: "Öffnen",
        st_prev: "Modell-Vorschauen",
        st_prev_off: "Keine Aktualisierung",
        st_prev_spd: "Vorschau-Aktualisierung",
        st_private: "Privat",
        st_ptags: "Profil-Tags ausblenden (BongaCams)",
        st_rec: "Videoaufnahme",
        st_sat: "Sättigung",
        st_scale: "Panelgröße",
        st_site_dark: "Dunkles Seitendesign",
        st_star: "Stern auf Vorschaubildern",
        star_add: "Zu Favoriten hinzufügen",
        star_del: "Aus Favoriten entfernen",
        star_del_q: "Dieses Model hat eine Notiz oder eine Gruppe. Aus Favoriten entfernen?",
        thtr: "KINO",
        lock: "SPERRE",
        tp_less: "[Weniger]",
        tp_more: "[Mehr]",
        u_d: "T",
        u_h: "Std",
        u_m: "Min",
        viewers_tip: "Zuschauer im Raum",
        w_new: "Nicht gesehen",
        w_seen: "Gesehen",
    },
    FR: {
        cal_pres_btn: "Prévision",
        cal_pres_h: "Généralement en ligne (d’après les observations)",
        cal_pres_none: "Pas encore de données. La prévision se remplit pendant que vous utilisez le site.",
        cal_pres_info: "Modèles avec données : {a} sur {b}",
        cal_pres_on: "Suivi en ligne",
        blur_mute: "Flouter la vidéo sans son",
        ago: "il y a {n}",
        bg: "FOND",
        bk_csv: "Tableaux CSV",
        bk_csv_fav: "Favoris",
        bk_csv_hist: "Historique",
        bk_exp: "Exporter la sauvegarde",
        bk_imp: "Importer la sauvegarde",
        bk_p_fav: "Favoris",
        bk_p_hist: "Historique",
        bk_p_set: "Paramètres",
        bk_parts: "Contenu de la sauvegarde",
        bm_add: "Ajouter aux favoris",
        bm_all: "Tous",
        bm_del: "Supprimer",
        bm_dup: "Déjà ajouté !",
        bm_empty: "La liste est vide.",
        bm_find: "Rechercher par nom…",
        bm_nomatch: "Aucun modèle ne correspond au filtre",
        bm_online: "En ligne",
        bm_online_t: "Afficher seulement en ligne",
        bm_pin: "Épingler en haut",
        bm_priv: "Privé",
        bm_title: "Favoris",
        bm_unpin: "Désépingler",
        bt_hints: "Astuces",
        bt_stats: "Spectateurs",
        cal_all_tag: "Tous les tags",
        cal_allday: "Toute la journée",
        cal_c_dur: "Durée",
        cal_c_model: "Modèle",
        cal_c_n: "Visites",
        cal_c_time: "Entrée",
        cal_hist: "Historique",
        cal_loaded: "Horaires chargés : {a} sur {b}",
        cal_models: "Modèles",
        cal_no_sched: "Les horaires sont lus dans les profils de vos favoris. Ouvrez les favoris et patientez quelques secondes.",
        cal_none: "Rien ce jour-là",
        cal_reset: "Réinitialiser",
        cal_sched: "Horaires",
        cal_title: "Calendrier",
        cal_today: "Aujourd'hui",
        cal_watched: "Vu",
        cf_cancel: "Annuler",
        cf_ok: "Oui",
        ch_avg: "Moyenne quotidienne",
        ch_btn: "Graphiques",
        ch_days: "Temps par jour, 14 jours",
        ch_hours: "Activité par heure",
        ch_peak: "Pic",
        ch_top: "Top par durée",
        copy: "LIEN",
        dark: "Thème sombre / clair",
        don_copied: "Adresse copiée",
        don_fail: "Échec de la copie",
        don_sub: "Merci de l’utiliser",
        don_tap: "Touchez une adresse pour l’afficher en entier. Le bouton à droite la copie.",
        don_tip: "Soutenir l’auteur",
        don_title: "Offrir un café à l’auteur",
        drag: "⁞⁞ COMMANDE",
        err: "ERREUR",
        favs: "FAVORIS",
        fr_hold: "Maintenez pour voir l’image actuelle",
        fr_none: "Image indisponible : le modèle est hors ligne",
        fs: "PLEIN",
        grp_all: "Tous les groupes",
        grp_new: "+ Nouveau groupe…",
        grp_none: "Sans groupe",
        hd_all: "Tout masquer",
        hd_cats: "Catégories",
        hd_clear: "Supprimer toutes mes règles",
        hd_details: "Détails",
        hd_hidden: "Masqués : {n} sur {m}",
        hd_na: "pas encore trouvé",
        hd_none: "Aucune règle pour l’instant.",
        hd_off: "Tout afficher",
        hd_p_clean: "Lecteur épuré",
        hd_peek: "Maintenez un élément pour voir ce qu’il masque",
        hd_pick: "Choisir un élément",
        hd_reset: "Réinitialiser pour ce site",
        hd_rules: "Mes règles",
        hd_title: "Masquage",
        hide: "Masquer (H)",
        hints: "M=SON F=PLEIN T=CINÉ H=MASQUER",
        hist_clear: "Effacer",
        hist_clear_q: "Supprimer tout l’historique de visionnage ?",
        hist_del: "Retirer de l’historique",
        hist_empty: "Vide pour l’instant. Le temps est compté pendant la lecture de la vidéo.",
        hist_s_last: "Récents",
        hist_s_time: "Durée",
        hist_s_vis: "Visites",
        hist_title: "Historique",
        hist_total: "Modèles : {n} · Total : {t}",
        hist_visits: "visites",
        home: "Accueil",
        lang_title: "Langue",
        min: "Réduire",
        mv_abr_row: "Qualité auto",
        mv_abr_t: "Qualité auto : réduit la qualité en cas de saccades ou de réseau faible et l’augmente quand la connexion est stable",
        mv_add: "Ajouter",
        mv_auto_all: "Ajouter automatiquement tous les flux en direct",
        mv_back: "Retour",
        mv_bg: "Lecture en arrière-plan",
        mv_bg_t: "Les flux continuent de jouer quand vous changez d’onglet, fermez cette fenêtre ou quittez le navigateur",
        mv_bm_h: "Favoris en direct",
        mv_clear_q: "Fermer tous les flux ?",
        mv_close: "Fermer",
        mv_cols: "Disposition",
        mv_cur: "Flux actuel",
        mv_dvr: "Tampon de retour arrière",
        mv_dvr_off: "Désactivé",
        mv_fs: "Plein écran",
        mv_full: "La fenêtre est pleine",
        mv_go: "Ajouter",
        mv_hint: "Ajoutez des flux pour en regarder plusieurs à la fois. Touchez une vignette pour y basculer le son.",
        mv_link_h: "Ou par lien",
        mv_load: "Chargement…",
        mv_mute: "Couper le son",
        mv_none: "Aucun favori en direct",
        mv_off: "Flux indisponible",
        mv_pip: "Image dans l’image",
        mv_play: "Lecture / pause",
        mv_qual: "Qualité",
        mv_retry: "touchez pour réessayer",
        mv_search: "Rechercher dans les favoris en direct",
        mv_title: "Lecteur",
        no_vid: "PAS DE VIDÉO",
        pk_cancel: "Annuler",
        pk_found: "corresp.",
        pk_hint: "Touchez un élément de la page",
        pk_ok: "Masquer",
        pk_up: "Plus haut",
        qual: "QUAL.",
        rad_media: "Médias",
        rad_nav: "Favoris",
        rad_next: "Suiv.",
        rad_prev: "Préc.",
        rad_reset: "Réinitialiser la position du bouton",
        rad_title: "Menu radial",
        rad_vdn: "Moins fort",
        rad_view: "Affichage",
        rad_vup: "Plus fort",
        rec_tip: "Toucher : enregistrer · Maintenir : réglages",
        rel: "Recharger",
        rs_cur: "Flux actuel :",
        rs_gbh: "Go/h",
        rs_hint: "Quand une partie atteint la taille choisie, elle est enregistrée dans un fichier distinct et l’enregistrement continue dans un nouveau. Les données sont écrites sur le disque par petits blocs ; si le disque est indisponible, elles restent dans la mémoire du téléphone, mieux vaut alors choisir des parties plus petites.",
        rs_mbmin: "Mo/min",
        rs_next: "Les modifications s’appliquent à partir de la partie suivante ou d’un nouvel enregistrement.",
        rs_now: "Maintenant",
        rs_off: "Désactivé",
        rs_p: "partie",
        rs_part: "Taille d’une partie (fichier découpé)",
        rs_q_hi: "Haute",
        rs_q_low: "Basse",
        rs_q_mid: "Moyenne",
        rs_q_min: "Éco",
        rs_quality: "Qualité (débit)",
        rs_recover: "Récupérer les enregistrements non sauvegardés",
        rs_resnote: "La capture enregistre à la résolution du flux : les options ci-dessus ne modifient que le débit (compression), et 1080p–360p indiquent la qualité d’image attendue. En mode HLS, un flux dont la hauteur ne dépasse pas celle choisie est sélectionné.",
        rs_src_cap: "Capture (réencodage)",
        rs_src_hls: "HLS (sans réencodage)",
        rs_src_note: "HLS télécharge les segments du flux tels quels, sans réencodage, et enregistre en MP4. L’adresse du flux est nécessaire ; si elle est indisponible, la capture est utilisée. La qualité est celle d’origine, identique au flux source. L’enregistrement est écrit sur le disque par petits points de contrôle.",
        rs_title: "Réglages d’enregistrement",
        rs_total: "Arrêter à la taille totale",
        rs_upto: "jusqu’à",
        search: "RECH.",
        seen_now: "à l’instant",
        seen_tip: "Dernière connexion",
        settings: "Paramètres",
        sleep_custom: "Durée personnalisée",
        sleep_hint: "À la fin du temps, le lecteur s’éteint et tout enregistrement s’arrête et est sauvegardé. Touchez l’écran noir pour le rallumer.",
        sleep_left: "Restant",
        sleep_sec: "s",
        sleep_start: "Démarrer",
        sleep_title: "Minuteur de veille",
        sleep_wake: "Minuteur terminé.\nTouchez pour continuer",
        snap: "PHOTO",
        sort_last: "Vu",
        sort_live: "En direct",
        sort_name: "Nom",
        sort_viewers: "Spectateurs",
        sound: "SON",
        st_av_refresh: "Actualiser les avatars",
        st_back: "Retour",
        st_btns: "Boutons du panneau",
        st_btns_hint: "Touchez pour masquer un bouton ou le remettre.",
        st_btns_reset: "Afficher tous les boutons",
        st_color: "Couleur du panneau",
        st_color_rnd: "Aléatoire",
        st_cols: "Colonnes",
        st_custom: "Couleur personnalisée",
        st_data: "Données",
        st_fav: "Favoris",
        st_favnav: "Boutons « précédent / suivant » sur la télécommande",
        st_group: "Groupe",
        st_hide_grp: "Masquer le sélecteur de groupe",
        st_hide_note: "Masquer le champ de note (tags)",
        st_hide_topic: "Masquer la description du modèle",
        st_hist: "Historique de visionnage",
        st_hist_on: "Conserver l’historique",
        st_hue: "Teinte",
        st_lang: "Langue",
        st_lig: "Luminosité",
        st_list: "Liste",
        st_look: "Apparence",
        st_open: "Ouvrir",
        st_prev: "Aperçus des modèles",
        st_prev_off: "Pas d’actualisation",
        st_prev_spd: "Actualisation des aperçus",
        st_private: "Privé",
        st_ptags: "Masquer les tags du profil (BongaCams)",
        st_rec: "Enregistrement vidéo",
        st_scale: "Taille du panneau",
        st_site_dark: "Thème sombre du site",
        st_star: "Étoile sur les miniatures",
        star_add: "Ajouter aux favoris",
        star_del: "Retirer des favoris",
        star_del_q: "Ce modèle a une note ou un groupe. Le retirer des favoris ?",
        thtr: "CINÉMA",
        lock: "VERROU",
        tp_less: "[Moins]",
        tp_more: "[Plus]",
        u_d: "j",
        u_gb: "Go",
        u_mb: "Mo",
        viewers_tip: "Spectateurs dans le salon",
        w_new: "Non vu",
        w_seen: "Vu",
    },
    PT: {
        cal_pres_btn: "Previsão",
        cal_pres_h: "Normalmente online (segundo observações)",
        cal_pres_none: "Ainda sem dados. A previsão é preenchida enquanto você usa o site.",
        cal_pres_info: "Modelos com dados: {a} de {b}",
        cal_pres_on: "Registro online",
        blur_mute: "Desfocar o vídeo sem som",
        ago: "há {n}",
        bg: "FUNDO",
        bk_csv: "Tabelas CSV",
        bk_csv_fav: "Favoritos",
        bk_csv_hist: "Histórico",
        bk_csv_ses: "Sessões",
        bk_exp: "Exportar backup",
        bk_imp: "Importar backup",
        bk_p_fav: "Favoritos",
        bk_p_hist: "Histórico",
        bk_p_set: "Configurações",
        bk_parts: "Conteúdo do backup",
        bm_add: "Adicionar aos favoritos",
        bm_all: "Todos",
        bm_del: "Excluir",
        bm_dup: "Já adicionada!",
        bm_empty: "A lista está vazia.",
        bm_find: "Buscar por nome…",
        bm_nomatch: "Nenhuma modelo corresponde ao filtro",
        bm_online_t: "Mostrar só online",
        bm_pin: "Fixar no topo",
        bm_title: "Favoritos",
        bm_unpin: "Desafixar",
        bt_hints: "Dicas",
        bt_stats: "Espectadores",
        cal_all_tag: "Todas as tags",
        cal_allday: "O dia todo",
        cal_c_dur: "Dur.",
        cal_c_model: "Modelo",
        cal_c_n: "Visitas",
        cal_c_time: "Entrada",
        cal_hist: "Histórico",
        cal_loaded: "Horários carregados: {a} de {b}",
        cal_models: "Modelos",
        cal_no_sched: "Os horários vêm dos perfis dos seus favoritos. Abra os Favoritos e aguarde alguns segundos.",
        cal_none: "Nada neste dia",
        cal_reset: "Redefinir",
        cal_sched: "Horários",
        cal_title: "Calendário",
        cal_today: "Hoje",
        cal_watched: "Visto",
        cf_cancel: "Cancelar",
        cf_ok: "Sim",
        ch_avg: "Média diária",
        ch_btn: "Gráficos",
        ch_days: "Tempo por dia, 14 dias",
        ch_hours: "Atividade por hora",
        ch_peak: "Pico",
        ch_top: "Top por tempo",
        dark: "Tema escuro / claro",
        don_copied: "Endereço copiado",
        don_fail: "Falha ao copiar",
        don_sub: "Obrigado por usar",
        don_tap: "Toque em um endereço para vê-lo completo. O botão à direita o copia.",
        don_tip: "Apoiar o autor",
        don_title: "Pague um café ao autor",
        drag: "⁞⁞ CONTROLE",
        err: "ERRO",
        fr_hold: "Segure para ver o quadro atual",
        fr_none: "Sem imagem: a modelo está offline",
        fs: "TELA",
        grp_all: "Todos os grupos",
        grp_new: "+ Novo grupo…",
        grp_none: "Sem grupo",
        hd_all: "Ocultar tudo",
        hd_cats: "Categorias",
        hd_clear: "Apagar todas as minhas regras",
        hd_details: "Detalhes",
        hd_hidden: "{n} de {m} ocultos",
        hd_na: "ainda não encontrado",
        hd_none: "Ainda sem regras.",
        hd_off: "Mostrar tudo",
        hd_p_clean: "Player limpo",
        hd_p_min: "Mínimo",
        hd_peek: "Segure um item para ver o que ele oculta",
        hd_pick: "Escolher elemento",
        hd_reset: "Redefinir para este site",
        hd_rules: "Minhas regras",
        hd_title: "Ocultar",
        hide: "Ocultar (H)",
        hints: "M=SOM F=TELA T=CINEMA H=OCULTAR",
        hist_clear: "Limpar",
        hist_clear_q: "Apagar todo o histórico de visualização?",
        hist_del: "Remover do histórico",
        hist_empty: "Vazio por enquanto. O tempo é contado enquanto o vídeo está tocando.",
        hist_s_last: "Recentes",
        hist_s_time: "Tempo",
        hist_s_vis: "Visitas",
        hist_title: "Histórico",
        hist_total: "Modelos: {n} · Total: {t}",
        hist_visits: "visitas",
        home: "Início",
        lang_auto: "Automático",
        lang_title: "Idioma",
        min: "Minimizar",
        mv_abr_row: "Qualidade automática",
        mv_abr_t: "Qualidade automática: reduz a qualidade quando o vídeo trava ou a rede está fraca e aumenta quando a conexão está estável",
        mv_add: "Adicionar",
        mv_auto_all: "Adicionar todas as transmissões ao vivo automaticamente",
        mv_back: "Voltar",
        mv_bg: "Reproduzir em segundo plano",
        mv_bg_t: "As transmissões continuam tocando ao trocar de aba, fechar esta janela ou sair do navegador",
        mv_bm_h: "Favoritos ao vivo",
        mv_clear_q: "Fechar todas as transmissões?",
        mv_close: "Fechar",
        mv_cur: "Transmissão atual",
        mv_dvr: "Buffer de retrocesso",
        mv_dvr_off: "Desativado",
        mv_fs: "Tela cheia",
        mv_full: "A janela está cheia",
        mv_go: "Adicionar",
        mv_hint: "Adicione transmissões para assistir a várias ao mesmo tempo. Toque em um bloco para passar o som para ele.",
        mv_link_h: "Ou por link",
        mv_load: "Carregando…",
        mv_mute: "Silenciar",
        mv_none: "Nenhum favorito ao vivo",
        mv_off: "Transmissão indisponível",
        mv_pip: "Imagem em imagem",
        mv_play: "Reproduzir / pausar",
        mv_qual: "Qualidade",
        mv_retry: "toque para tentar de novo",
        mv_search: "Buscar favoritos ao vivo",
        no_vid: "SEM VÍDEO",
        note_ph: "Nota... (#tags)",
        pk_cancel: "Cancelar",
        pk_found: "result.",
        pk_hint: "Toque em um elemento da página",
        pk_ok: "Ocultar",
        pk_up: "Acima",
        qual: "QUAL.",
        rad_media: "Mídia",
        rad_nav: "Favoritos",
        rad_next: "Próx.",
        rad_page: "Página",
        rad_prev: "Ant.",
        rad_reset: "Redefinir posição do botão",
        rad_title: "Menu radial",
        rad_vdn: "Mais baixo",
        rad_view: "Tela",
        rad_vup: "Mais alto",
        rec: "GRAVAR",
        rec_tip: "Toque: gravar · Segurar: ajustes",
        rel: "Recarregar",
        rs_cur: "Transmissão atual:",
        rs_hint: "Quando uma parte atinge o tamanho escolhido, ela é salva como um arquivo separado e a gravação continua em um novo. Os dados são gravados no disco em pequenos blocos; se o disco não estiver disponível, ficam na memória do celular, e então é melhor escolher partes menores.",
        rs_next: "As alterações valem a partir da próxima parte ou de uma nova gravação.",
        rs_now: "Agora",
        rs_off: "Desativado",
        rs_p: "parte",
        rs_part: "Tamanho da parte (divide o arquivo)",
        rs_q_hi: "Alta",
        rs_q_low: "Baixa",
        rs_q_mid: "Média",
        rs_q_min: "Economia",
        rs_quality: "Qualidade (bitrate)",
        rs_recover: "Recuperar gravações não salvas",
        rs_resnote: "A captura grava na resolução da transmissão: as opções acima alteram apenas o bitrate (compressão), e 1080p–360p indicam a qualidade de imagem esperada. No modo HLS, é escolhida uma transmissão com altura não superior à definida.",
        rs_src: "Origem",
        rs_src_cap: "Captura (recodificação)",
        rs_src_hls: "HLS (sem recodificação)",
        rs_src_note: "O HLS baixa os segmentos da transmissão como estão, sem recodificar, e salva em MP4. Precisa do endereço da transmissão; se não estiver disponível, a captura é usada. A qualidade é a original, igual à da transmissão de origem. A gravação é escrita no disco em pequenos pontos de verificação.",
        rs_title: "Ajustes de gravação",
        rs_total: "Parar ao atingir o tamanho total",
        rs_upto: "até",
        search: "BUSCAR",
        seen_now: "agora mesmo",
        seen_tip: "Última vez online",
        settings: "Configurações",
        sleep_custom: "Tempo personalizado",
        sleep_hint: "Ao fim do tempo, o player é desligado e qualquer gravação é interrompida e salva. Toque na tela preta para trazê-lo de volta.",
        sleep_left: "Restante",
        sleep_sec: "seg",
        sleep_start: "Iniciar",
        sleep_title: "Temporizador de sono",
        sleep_wake: "O temporizador terminou.\nToque para continuar",
        snap: "FOTO",
        sort_date: "Data",
        sort_last: "Visto",
        sort_live: "Ao vivo",
        sort_name: "Nome",
        sort_viewers: "Espectadores",
        sound: "SOM",
        st_av_refresh: "Atualizar avatares",
        st_back: "Voltar",
        st_btns: "Botões do painel",
        st_btns_hint: "Toque para ocultar um botão ou trazê-lo de volta.",
        st_btns_reset: "Mostrar todos os botões",
        st_color: "Cor do painel",
        st_color_rnd: "Aleatória",
        st_cols: "Colunas",
        st_custom: "Cor personalizada",
        st_data: "Dados",
        st_fav: "Favoritos",
        st_favnav: "Botões «anterior / próximo» no controle",
        st_group: "Grupo",
        st_hide_grp: "Ocultar o seletor de grupo",
        st_hide_note: "Ocultar campo de nota (tags)",
        st_hide_topic: "Ocultar a descrição da modelo",
        st_hist: "Histórico de visualização",
        st_hist_on: "Manter histórico",
        st_hue: "Matiz",
        st_lang: "Idioma",
        st_lig: "Luminosidade",
        st_list: "Lista",
        st_look: "Aparência",
        st_open: "Abrir",
        st_prev: "Pré-visualizações das modelos",
        st_prev_off: "Sem atualização",
        st_prev_spd: "Atualização da pré-visualização",
        st_private: "Privado",
        st_ptags: "Ocultar tags do perfil (BongaCams)",
        st_rec: "Gravação de vídeo",
        st_sat: "Saturação",
        st_scale: "Tamanho do painel",
        st_site_dark: "Tema escuro do site",
        st_star: "Estrela nas miniaturas",
        star_add: "Adicionar aos favoritos",
        star_del: "Remover dos favoritos",
        star_del_q: "Esta modelo tem uma nota ou um grupo. Remover dos favoritos?",
        thtr: "CINEMA",
        lock: "TRAVA",
        tp_less: "[Menos]",
        tp_more: "[Mais]",
        viewers_tip: "Espectadores na sala",
        w_new: "Não visto",
        w_seen: "Visto",
    },
    IT: {
        cal_pres_btn: "Previsione",
        cal_pres_h: "Di solito online (dalle osservazioni)",
        cal_pres_none: "Ancora nessun dato. La previsione si riempie mentre usi il sito.",
        cal_pres_info: "Modelle con dati: {a} su {b}",
        cal_pres_on: "Rilevamento online",
        blur_mute: "Sfoca il video senza audio",
        ago: "{n} fa",
        bg: "SFONDO",
        bk_csv: "Tabelle CSV",
        bk_csv_fav: "Preferiti",
        bk_csv_hist: "Cronologia",
        bk_csv_ses: "Sessioni",
        bk_exp: "Esporta backup",
        bk_imp: "Importa backup",
        bk_p_fav: "Preferiti",
        bk_p_hist: "Cronologia",
        bk_p_set: "Impostazioni",
        bk_parts: "Contenuto del backup",
        bm_add: "Aggiungi ai preferiti",
        bm_all: "Tutti",
        bm_del: "Elimina",
        bm_dup: "Già aggiunta!",
        bm_empty: "L’elenco è vuoto.",
        bm_find: "Cerca per nome…",
        bm_nomatch: "Nessuna modella corrisponde al filtro",
        bm_online_t: "Mostra solo online",
        bm_pin: "Fissa in alto",
        bm_title: "Preferiti",
        bm_unpin: "Sblocca",
        bt_hints: "Suggerimenti",
        bt_stats: "Spettatori",
        cal_all_tag: "Tutti i tag",
        cal_allday: "Tutto il giorno",
        cal_c_dur: "Durata",
        cal_c_model: "Modella",
        cal_c_n: "Visite",
        cal_c_time: "Ingresso",
        cal_hist: "Cronologia",
        cal_loaded: "Orari caricati: {a} su {b}",
        cal_models: "Modelle",
        cal_no_sched: "Gli orari vengono letti dai profili dei tuoi preferiti. Apri i Preferiti e attendi qualche secondo.",
        cal_none: "Niente in questo giorno",
        cal_reset: "Reimposta",
        cal_sched: "Orari",
        cal_title: "Calendario",
        cal_today: "Oggi",
        cal_total: "Totale",
        cal_watched: "Visto",
        cf_cancel: "Annulla",
        cf_ok: "Sì",
        ch_avg: "Media giornaliera",
        ch_btn: "Grafici",
        ch_days: "Tempo per giorno, 14 giorni",
        ch_hours: "Attività per ora",
        ch_peak: "Picco",
        ch_top: "Top per tempo",
        dark: "Tema scuro / chiaro",
        don_copied: "Indirizzo copiato",
        don_fail: "Copia non riuscita",
        don_sub: "Grazie per l’uso",
        don_tap: "Tocca un indirizzo per vederlo per intero. Il pulsante a destra lo copia.",
        don_tip: "Sostieni l’autore",
        don_title: "Offri un caffè all’autore",
        drag: "⁞⁞ TELECOMANDO",
        err: "ERRORE",
        favs: "PREF.",
        fr_hold: "Tieni premuto per vedere il fotogramma attuale",
        fr_none: "Nessun fotogramma: la modella è offline",
        fs: "SCHERMO",
        grp_all: "Tutti i gruppi",
        grp_new: "+ Nuovo gruppo…",
        grp_none: "Nessun gruppo",
        hd_all: "Nascondi tutto",
        hd_cats: "Categorie",
        hd_clear: "Elimina tutte le mie regole",
        hd_details: "Dettagli",
        hd_g_site: "Sito",
        hd_g_win: "Pop-up",
        hd_hidden: "Nascosti {n} di {m}",
        hd_na: "non ancora trovato",
        hd_none: "Ancora nessuna regola.",
        hd_off: "Mostra tutto",
        hd_p_clean: "Player pulito",
        hd_p_min: "Minimo",
        hd_peek: "Tieni premuto un elemento per vedere cosa nasconde",
        hd_pick: "Scegli elemento",
        hd_reset: "Ripristina per questo sito",
        hd_rules: "Le mie regole",
        hd_title: "Nascondi",
        hide: "Nascondi (H)",
        hints: "M=AUDIO F=SCHERMO T=CINEMA H=NASCONDI",
        hist_clear: "Svuota",
        hist_clear_q: "Eliminare tutta la cronologia di visione?",
        hist_del: "Rimuovi dalla cronologia",
        hist_empty: "Ancora vuota. Il tempo viene conteggiato mentre il video è in riproduzione.",
        hist_s_last: "Recenti",
        hist_s_time: "Tempo",
        hist_s_vis: "Visite",
        hist_title: "Cronologia",
        hist_total: "Modelle: {n} · Totale: {t}",
        hist_visits: "visite",
        lang_auto: "Automatica",
        lang_title: "Lingua",
        min: "Riduci",
        mv_abr_row: "Qualità automatica",
        mv_abr_t: "Qualità automatica: riduce la qualità in caso di scatti o rete debole e la aumenta quando la connessione è stabile",
        mv_add: "Aggiungi",
        mv_auto_all: "Aggiungi automaticamente tutti gli stream in diretta",
        mv_back: "Indietro",
        mv_bg: "Riproduci in background",
        mv_bg_t: "Gli stream continuano a riprodursi quando cambi scheda, chiudi questa finestra o esci dal browser",
        mv_bm_h: "Preferiti in diretta",
        mv_clear_q: "Chiudere tutti gli stream?",
        mv_close: "Chiudi",
        mv_cols: "Griglia",
        mv_cur: "Stream attuale",
        mv_dvr: "Buffer di riavvolgimento",
        mv_dvr_off: "Disattivato",
        mv_fs: "Schermo intero",
        mv_full: "La finestra è piena",
        mv_go: "Aggiungi",
        mv_hint: "Aggiungi stream per guardarne più di uno insieme. Tocca un riquadro per portarci l’audio.",
        mv_link_h: "Oppure tramite link",
        mv_load: "Caricamento…",
        mv_mute: "Disattiva audio",
        mv_none: "Nessun preferito in diretta",
        mv_off: "Stream non disponibile",
        mv_play: "Riproduci / pausa",
        mv_qual: "Qualità",
        mv_retry: "tocca per riprovare",
        mv_search: "Cerca tra i preferiti in diretta",
        no_vid: "NESSUN VIDEO",
        note_ph: "Nota... (#tag)",
        pk_cancel: "Annulla",
        pk_found: "corrisp.",
        pk_hint: "Tocca un elemento della pagina",
        pk_ok: "Nascondi",
        pk_up: "Su",
        qual: "QUAL.",
        rad_nav: "Preferiti",
        rad_next: "Succ.",
        rad_page: "Pagina",
        rad_prev: "Prec.",
        rad_reset: "Reimposta posizione del pulsante",
        rad_title: "Menu radiale",
        rad_vdn: "Più piano",
        rad_view: "Schermo",
        rad_vup: "Più forte",
        rec_tip: "Tocco: registra · Tieni premuto: impostazioni",
        rel: "Ricarica",
        rs_cur: "Stream attuale:",
        rs_hint: "Quando una parte raggiunge la dimensione scelta, viene salvata come file separato e la registrazione continua in uno nuovo. I dati vengono scritti su disco in piccoli blocchi; se il disco non è disponibile, restano nella memoria del telefono, quindi conviene scegliere parti più piccole.",
        rs_next: "Le modifiche valgono dalla parte successiva o da una nuova registrazione.",
        rs_now: "Ora",
        rs_off: "Disattivato",
        rs_p: "parte",
        rs_part: "Dimensione parte (file diviso)",
        rs_q_hi: "Alta",
        rs_q_low: "Bassa",
        rs_q_mid: "Media",
        rs_q_min: "Risparmio",
        rs_quality: "Qualità (bitrate)",
        rs_recover: "Recupera le registrazioni non salvate",
        rs_resnote: "La cattura registra alla risoluzione dello stream: le opzioni sopra cambiano solo il bitrate (compressione), e 1080p–360p indicano la qualità d’immagine prevista. In modalità HLS viene scelto uno stream di altezza non superiore a quella indicata.",
        rs_src: "Sorgente",
        rs_src_cap: "Acquisizione (ricodifica)",
        rs_src_hls: "HLS (senza ricodifica)",
        rs_src_note: "HLS scarica i segmenti dello stream così come sono, senza ricodifica, e salva in MP4. Serve l’indirizzo dello stream; se non è disponibile, viene usata l’acquisizione. La qualità è quella originale dello stream di origine. La registrazione viene scritta su disco a piccoli passi.",
        rs_t: "totale",
        rs_title: "Impostazioni di registrazione",
        rs_total: "Ferma alla dimensione totale",
        rs_upto: "fino a",
        search: "CERCA",
        seen_now: "proprio ora",
        seen_tip: "Ultimo accesso",
        settings: "Impostazioni",
        sleep_custom: "Tempo personalizzato",
        sleep_hint: "Allo scadere del tempo il player si spegne e ogni registrazione si interrompe e viene salvata. Tocca lo schermo nero per riattivarlo.",
        sleep_left: "Rimanente",
        sleep_start: "Avvia",
        sleep_title: "Timer di spegnimento",
        sleep_wake: "Timer terminato.\nTocca per continuare",
        snap: "FOTO",
        sort_date: "Data",
        sort_last: "Visto",
        sort_name: "Nome",
        sort_viewers: "Spettatori",
        sound: "AUDIO",
        st_av_refresh: "Aggiorna avatar",
        st_back: "Indietro",
        st_btns: "Pulsanti del pannello",
        st_btns_hint: "Tocca per nascondere un pulsante o riportarlo.",
        st_btns_reset: "Mostra tutti i pulsanti",
        st_color: "Colore del pannello",
        st_color_rnd: "Casuale",
        st_cols: "Colonne",
        st_custom: "Colore personalizzato",
        st_data: "Dati",
        st_fav: "Preferiti",
        st_favnav: "Pulsanti «precedente / successivo» sul telecomando",
        st_group: "Gruppo",
        st_hide_grp: "Nascondi il selettore di gruppo",
        st_hide_note: "Nascondi il campo note (tag)",
        st_hide_topic: "Nascondi la descrizione della modella",
        st_hist: "Cronologia di visione",
        st_hist_on: "Salva cronologia",
        st_hue: "Tonalità",
        st_lang: "Lingua",
        st_lig: "Luminosità",
        st_list: "Elenco",
        st_look: "Aspetto",
        st_open: "Apri",
        st_prev: "Anteprime delle modelle",
        st_prev_off: "Nessun aggiornamento",
        st_prev_spd: "Aggiornamento anteprime",
        st_private: "Privato",
        st_ptags: "Nascondi i tag del profilo (BongaCams)",
        st_rec: "Registrazione video",
        st_sat: "Saturazione",
        st_scale: "Dimensione del pannello",
        st_site_dark: "Tema scuro del sito",
        st_star: "Stella sulle miniature",
        star_add: "Aggiungi ai preferiti",
        star_del: "Rimuovi dai preferiti",
        star_del_q: "Questa modella ha una nota o un gruppo. Rimuoverla dai preferiti?",
        thtr: "CINEMA",
        lock: "BLOCCO",
        tp_less: "[Meno]",
        tp_more: "[Altro]",
        u_d: "g",
        viewers_tip: "Spettatori nella stanza",
        w_new: "Non visto",
        w_seen: "Visto",
    },
    PL: {
        cal_pres_btn: "Prognoza",
        cal_pres_h: "Zwykle online (z obserwacji)",
        cal_pres_none: "Brak danych. Prognoza uzupełni się, gdy korzystasz ze strony.",
        cal_pres_info: "Modelki z danymi: {a} z {b}",
        cal_pres_on: "Śledzenie online",
        blur_mute: "Rozmywaj wideo bez dźwięku",
        ago: "{n} temu",
        bg: "TŁO",
        bk_csv: "Tabele CSV",
        bk_csv_fav: "Ulubione",
        bk_csv_hist: "Historia",
        bk_csv_ses: "Sesje",
        bk_exp: "Eksport kopii zapasowej",
        bk_imp: "Import kopii zapasowej",
        bk_p_fav: "Ulubione",
        bk_p_hist: "Historia",
        bk_p_set: "Ustawienia",
        bk_parts: "Zawartość kopii",
        bm_add: "Dodaj do ulubionych",
        bm_all: "Wszystkie",
        bm_del: "Usuń",
        bm_dup: "Już dodano!",
        bm_empty: "Lista jest pusta.",
        bm_find: "Szukaj po nazwie…",
        bm_nomatch: "Brak modelek pasujących do filtra",
        bm_online_t: "Pokaż tylko online",
        bm_pin: "Przypnij na górze",
        bm_title: "Ulubione",
        bm_unpin: "Odepnij",
        bt_hints: "Podpowiedzi",
        bt_stats: "Widzowie",
        bt_vol: "Głośność",
        cal_all_tag: "Wszystkie tagi",
        cal_allday: "Cały dzień",
        cal_c_dur: "Czas",
        cal_c_model: "Modelka",
        cal_c_n: "Wejścia",
        cal_c_time: "Wejście",
        cal_hist: "Historia",
        cal_loaded: "Grafik wczytany: {a} z {b}",
        cal_models: "Modelki",
        cal_no_sched: "Grafik jest pobierany z profili ulubionych. Otwórz Ulubione i poczekaj kilka sekund.",
        cal_none: "Tego dnia nic nie ma",
        cal_reset: "Resetuj",
        cal_sched: "Grafik",
        cal_title: "Kalendarz",
        cal_today: "Dziś",
        cal_total: "Razem",
        cal_watched: "Oglądane",
        cf_cancel: "Anuluj",
        cf_ok: "Tak",
        ch_avg: "Średnia dzienna",
        ch_btn: "Wykresy",
        ch_days: "Czas dziennie, 14 dni",
        ch_hours: "Aktywność według godzin",
        ch_peak: "Szczyt",
        ch_top: "Top według czasu",
        dark: "Motyw ciemny / jasny",
        don_copied: "Skopiowano adres",
        don_fail: "Nie udało się skopiować",
        don_sub: "Dzięki, że korzystasz",
        don_tap: "Dotknij adresu, aby zobaczyć go w całości. Przycisk po prawej go kopiuje.",
        don_tip: "Wesprzyj autora",
        don_title: "Postaw autorowi kawę",
        drag: "⁞⁞ PILOT",
        err: "BŁĄD",
        favs: "ULUB.",
        fr_hold: "Przytrzymaj, aby zobaczyć bieżącą klatkę",
        fr_none: "Brak klatki: modelka jest offline",
        fs: "PEŁNY",
        grp_all: "Wszystkie grupy",
        grp_new: "+ Nowa grupa…",
        grp_none: "Bez grupy",
        hd_all: "Ukryj wszystko",
        hd_cats: "Kategorie",
        hd_clear: "Usuń wszystkie moje reguły",
        hd_details: "Szczegóły",
        hd_g_chat: "Czat",
        hd_g_site: "Witryna",
        hd_g_win: "Wyskakujące okna",
        hd_hidden: "Ukryto {n} z {m}",
        hd_na: "jeszcze nie znaleziono",
        hd_none: "Na razie brak reguł.",
        hd_off: "Pokaż wszystko",
        hd_p_clean: "Czysty odtwarzacz",
        hd_p_min: "Minimum",
        hd_peek: "Przytrzymaj pozycję, aby zobaczyć, co ukrywa",
        hd_pick: "Wybierz element",
        hd_reset: "Resetuj dla tej strony",
        hd_rules: "Moje reguły",
        hd_title: "Ukrywanie",
        hide: "Ukryj (H)",
        hints: "M=DŹWIĘK F=PEŁNY T=KINO H=UKRYJ",
        hist_clear: "Wyczyść",
        hist_clear_q: "Usunąć całą historię oglądania?",
        hist_del: "Usuń z historii",
        hist_empty: "Na razie pusto. Czas jest liczony, gdy wideo jest odtwarzane.",
        hist_s_last: "Ostatnie",
        hist_s_time: "Czas",
        hist_s_vis: "Wizyty",
        hist_title: "Historia",
        hist_total: "Modelki: {n} · Razem: {t}",
        hist_visits: "wizyt",
        home: "Strona główna",
        lang_auto: "Automatycznie",
        lang_title: "Język",
        min: "Minimalizuj",
        mv_abr_row: "Jakość automatyczna",
        mv_abr_t: "Jakość automatyczna: obniża jakość przy zacięciach lub słabej sieci i podnosi, gdy połączenie jest stabilne",
        mv_add: "Dodaj",
        mv_auto_all: "Automatycznie dodawaj wszystkie transmisje na żywo",
        mv_back: "Wstecz",
        mv_bg: "Odtwarzaj w tle",
        mv_bg_t: "Transmisje grają dalej po przełączeniu karty, zamknięciu tego okna lub wyjściu z przeglądarki",
        mv_bm_h: "Ulubione na żywo",
        mv_clear_q: "Zamknąć wszystkie transmisje?",
        mv_close: "Zamknij",
        mv_cols: "Układ",
        mv_cur: "Bieżąca transmisja",
        mv_dvr: "Bufor przewijania",
        mv_dvr_off: "Wył.",
        mv_fs: "Pełny ekran",
        mv_full: "Okno jest pełne",
        mv_go: "Dodaj",
        mv_hint: "Dodaj transmisje, aby oglądać kilka naraz. Dotknij kafelka, aby przenieść na niego dźwięk.",
        mv_link_h: "Lub przez link",
        mv_load: "Ładowanie…",
        mv_mute: "Wycisz",
        mv_none: "Brak ulubionych na żywo",
        mv_off: "Transmisja niedostępna",
        mv_pip: "Obraz w obrazie",
        mv_play: "Odtwarzaj / pauza",
        mv_qual: "Jakość",
        mv_retry: "dotknij, aby spróbować ponownie",
        mv_search: "Szukaj w ulubionych na żywo",
        mv_title: "Odtwarzacz",
        no_vid: "BRAK WIDEO",
        note_ph: "Notatka... (#tagi)",
        pk_cancel: "Anuluj",
        pk_found: "dopas.",
        pk_hint: "Dotknij elementu na stronie",
        pk_ok: "Ukryj",
        pk_up: "Wyżej",
        qual: "JAKOŚĆ",
        rad_nav: "Ulubione",
        rad_next: "Nast.",
        rad_page: "Strona",
        rad_prev: "Poprz.",
        rad_reset: "Resetuj pozycję przycisku",
        rad_title: "Menu kołowe",
        rad_vdn: "Ciszej",
        rad_view: "Widok",
        rad_vup: "Głośniej",
        rec: "NAGR.",
        rec_tip: "Dotknięcie: nagrywaj · Przytrzymanie: ustawienia",
        rel: "Odśwież",
        rs_cur: "Obecny strumień:",
        rs_gbh: "GB/godz.",
        rs_hint: "Gdy część osiągnie wybrany rozmiar, jest zapisywana jako osobny plik, a nagrywanie trwa w nowym. Dane są zapisywane na dysk małymi porcjami; jeśli dysk jest niedostępny, zostają w pamięci telefonu, więc lepiej wybrać mniejsze części.",
        rs_next: "Zmiany obowiązują od następnej części lub nowego nagrania.",
        rs_now: "Teraz",
        rs_off: "Wył.",
        rs_p: "część",
        rs_part: "Rozmiar części (plik jest dzielony)",
        rs_q_hi: "Wysoka",
        rs_q_low: "Niska",
        rs_q_mid: "Średnia",
        rs_q_min: "Oszczędna",
        rs_quality: "Jakość (bitrate)",
        rs_recover: "Odzyskaj niezapisane nagrania",
        rs_resnote: "Przechwytywanie nagrywa w rozdzielczości strumienia: opcje powyżej zmieniają tylko bitrate (kompresję), a 1080p–360p pokazują oczekiwaną jakość obrazu. W trybie HLS wybierany jest strumień o wysokości nie większej niż wskazana.",
        rs_src: "Źródło",
        rs_src_cap: "Przechwytywanie (ponowne kodowanie)",
        rs_src_hls: "HLS (bez ponownego kodowania)",
        rs_src_note: "HLS pobiera segmenty strumienia bez zmian, bez ponownego kodowania, i zapisuje MP4. Potrzebny jest adres strumienia; jeśli go brak, używane jest przechwytywanie. Jakość jest oryginalna, taka jak w strumieniu źródłowym. Nagranie jest zapisywane na dysku małymi porcjami.",
        rs_t: "razem",
        rs_title: "Ustawienia nagrywania",
        rs_total: "Zatrzymaj przy łącznym rozmiarze",
        rs_upto: "do",
        search: "SZUKAJ",
        seen_now: "przed chwilą",
        seen_tip: "Ostatnio online",
        settings: "Ustawienia",
        sleep_custom: "Własny czas",
        sleep_hint: "Po upływie czasu odtwarzacz zostanie wyłączony, a nagrywanie zatrzymane i zapisane. Dotknij czarnego ekranu, aby go przywrócić.",
        sleep_left: "Pozostało",
        sleep_sec: "sek",
        sleep_title: "Wyłącznik czasowy",
        sleep_wake: "Czas minął.\nDotknij, aby kontynuować",
        snap: "FOTO",
        sort_date: "Data",
        sort_last: "Widz.",
        sort_name: "Nazwa",
        sort_viewers: "Widzowie",
        sound: "DŹWIĘK",
        st_av_refresh: "Odśwież awatary",
        st_back: "Wstecz",
        st_btns: "Przyciski panelu",
        st_btns_hint: "Dotknij, aby ukryć przycisk lub go przywrócić.",
        st_btns_reset: "Pokaż wszystkie przyciski",
        st_color: "Kolor panelu",
        st_color_rnd: "Losowy",
        st_cols: "Kolumny",
        st_custom: "Własny kolor",
        st_data: "Dane",
        st_fav: "Ulubione",
        st_favnav: "Przyciski „wstecz / dalej” na pilocie",
        st_group: "Grupowy",
        st_hide_grp: "Ukryj wybór grupy",
        st_hide_note: "Ukryj pole notatki (tagi)",
        st_hide_topic: "Ukryj opis modelki",
        st_hist: "Historia oglądania",
        st_hist_on: "Zapisuj historię",
        st_hue: "Odcień",
        st_lang: "Język",
        st_lig: "Jasność",
        st_list: "Lista",
        st_look: "Wygląd",
        st_open: "Otwórz",
        st_prev: "Podglądy modelek",
        st_prev_off: "Bez odświeżania",
        st_prev_spd: "Odświeżanie podglądu",
        st_private: "Prywatny",
        st_ptags: "Ukryj tagi profilu (BongaCams)",
        st_rec: "Nagrywanie wideo",
        st_sat: "Nasycenie",
        st_scale: "Rozmiar panelu",
        st_site_dark: "Ciemny motyw strony",
        st_star: "Gwiazdka na miniaturach",
        star_add: "Dodaj do ulubionych",
        star_del: "Usuń z ulubionych",
        star_del_q: "Ta modelka ma notatkę lub grupę. Usunąć z ulubionych?",
        thtr: "KINO",
        lock: "BLOKADA",
        tp_less: "[Mniej]",
        tp_more: "[Więcej]",
        u_h: "godz.",
        viewers_tip: "Widzowie w pokoju",
        w_new: "Nieoglądane",
        w_seen: "Oglądane",
    },
    TR: {
        cal_pres_btn: "Tahmin",
        cal_pres_h: "Genellikle çevrimiçi (gözlemlere göre)",
        cal_pres_none: "Henüz veri yok. Tahmin siteyi kullandıkça dolar.",
        cal_pres_info: "Verisi olan modeller: {a} / {b}",
        cal_pres_on: "Çevrimiçi takibi",
        blur_mute: "Sessizken videoyu bulanıklaştır",
        ago: "{n} önce",
        bg: "ARKA",
        bk_csv: "CSV tabloları",
        bk_csv_fav: "Favoriler",
        bk_csv_hist: "Geçmiş",
        bk_csv_ses: "Oturumlar",
        bk_exp: "Yedeği dışa aktar",
        bk_imp: "Yedeği içe aktar",
        bk_p_fav: "Favoriler",
        bk_p_hist: "Geçmiş",
        bk_p_set: "Ayarlar",
        bk_parts: "Yedek içeriği",
        bm_add: "Favorilere ekle",
        bm_all: "Tümü",
        bm_del: "Sil",
        bm_dup: "Zaten eklendi!",
        bm_empty: "Liste boş.",
        bm_find: "İsme göre ara…",
        bm_nomatch: "Filtreye uyan model yok",
        bm_online: "Çevrimiçi",
        bm_online_t: "Yalnızca çevrimiçi olanları göster",
        bm_pin: "En üste sabitle",
        bm_priv: "Özel",
        bm_title: "Favoriler",
        bm_unpin: "Sabitlemeyi kaldır",
        bt_hints: "İpuçları",
        bt_stats: "İzleyiciler",
        bt_vol: "Ses",
        cal_all_tag: "Tüm etiketler",
        cal_allday: "Tüm gün",
        cal_c_dur: "Süre",
        cal_c_n: "Ziyaret",
        cal_c_time: "Giriş",
        cal_hist: "Geçmiş",
        cal_loaded: "Program yüklendi: {a} / {b}",
        cal_models: "Modeller",
        cal_no_sched: "Program, favorilerinizin profillerinden alınır. Favoriler'i açın ve birkaç saniye bekleyin.",
        cal_none: "Bu gün boş",
        cal_reset: "Sıfırla",
        cal_sched: "Program",
        cal_title: "Takvim",
        cal_today: "Bugün",
        cal_total: "Toplam",
        cal_watched: "İzlendi",
        cf_cancel: "İptal",
        cf_ok: "Evet",
        ch_avg: "Günlük ortalama",
        ch_btn: "Grafikler",
        ch_days: "Günlük süre, 14 gün",
        ch_hours: "Saate göre etkinlik",
        ch_peak: "Zirve",
        ch_top: "Süreye göre en çok",
        copy: "LİNK",
        dark: "Koyu / açık tema",
        don_copied: "Adres kopyalandı",
        don_fail: "Kopyalanamadı",
        don_sub: "Kullandığın için teşekkürler",
        don_tap: "Tamamını görmek için bir adrese dokun. Sağdaki düğme adresi kopyalar.",
        don_tip: "Yazarı destekle",
        don_title: "Yazara bir kahve ısmarla",
        drag: "⁞⁞ KUMANDA",
        err: "HATA",
        favs: "FAVORİ",
        fr_hold: "Geçerli kareyi görmek için basılı tut",
        fr_none: "Kare yok: model çevrimdışı",
        fs: "TAM",
        grp_all: "Tüm gruplar",
        grp_new: "+ Yeni grup…",
        grp_none: "Grupsuz",
        hd_all: "Hepsini gizle",
        hd_cats: "Kategoriler",
        hd_clear: "Tüm kurallarımı sil",
        hd_details: "Ayrıntılar",
        hd_g_chat: "Sohbet",
        hd_g_win: "Açılır pencereler",
        hd_hidden: "{m} öğeden {n} gizli",
        hd_na: "henüz bulunamadı",
        hd_none: "Henüz kural yok.",
        hd_off: "Hepsini göster",
        hd_p_clean: "Temiz oynatıcı",
        hd_p_min: "Minimum",
        hd_peek: "Neyi gizlediğini görmek için bir öğeye basılı tutun",
        hd_pick: "Öğe seç",
        hd_reset: "Bu site için sıfırla",
        hd_rules: "Kurallarım",
        hd_title: "Gizleme",
        hide: "Gizle (H)",
        hints: "M=SES F=TAM T=SİNEMA H=GİZLE",
        hist_clear: "Temizle",
        hist_clear_q: "Tüm izleme geçmişi silinsin mi?",
        hist_del: "Geçmişten kaldır",
        hist_empty: "Şimdilik boş. Süre, video oynarken sayılır.",
        hist_s_last: "Son",
        hist_s_time: "Süre",
        hist_s_vis: "Ziyaret",
        hist_title: "Geçmiş",
        hist_total: "Modeller: {n} · Toplam: {t}",
        hist_visits: "ziyaret",
        home: "Ana sayfa",
        lang_auto: "Otomatik",
        lang_title: "Dil",
        min: "Küçült",
        mv_abr_row: "Otomatik kalite",
        mv_abr_t: "Otomatik kalite: video takıldığında veya ağ zayıfladığında kaliteyi düşürür, bağlantı kararlı olduğunda yükseltir",
        mv_add: "Ekle",
        mv_auto: "Otomatik",
        mv_auto_all: "Tüm canlı yayınları otomatik ekle",
        mv_back: "Geri",
        mv_bg: "Arka planda oynat",
        mv_bg_t: "Sekme değiştirdiğinizde, bu pencereyi kapattığınızda veya tarayıcıdan çıktığınızda yayınlar oynamaya devam eder",
        mv_bm_h: "Canlı yer imleri",
        mv_clear_q: "Tüm yayınlar kapatılsın mı?",
        mv_close: "Kapat",
        mv_cols: "Düzen",
        mv_cur: "Mevcut yayın",
        mv_dvr: "Geri sarma tamponu",
        mv_dvr_off: "Kapalı",
        mv_fs: "Tam ekran",
        mv_full: "Pencere dolu",
        mv_go: "Ekle",
        mv_hint: "Aynı anda birkaç yayın izlemek için yayın ekleyin. Sesi bir kutucuğa taşımak için ona dokunun.",
        mv_link_h: "Veya bağlantıyla",
        mv_load: "Yükleniyor…",
        mv_min_u: "dk",
        mv_mute: "Sessize al",
        mv_none: "Canlı yer imi yok",
        mv_off: "Yayın kullanılamıyor",
        mv_pip: "Resim içinde resim",
        mv_play: "Oynat / duraklat",
        mv_qual: "Kalite",
        mv_retry: "tekrar denemek için dokunun",
        mv_search: "Canlı yer imlerinde ara",
        mv_title: "Oynatıcı",
        no_vid: "VİDEO YOK",
        note_ph: "Not... (#etiket)",
        pk_cancel: "İptal",
        pk_found: "eşleşme",
        pk_hint: "Sayfadaki bir öğeye dokunun",
        pk_ok: "Gizle",
        pk_up: "Yukarı",
        qual: "KALİTE",
        rad_media: "Medya",
        rad_nav: "Favoriler",
        rad_next: "Sonraki",
        rad_page: "Sayfa",
        rad_prev: "Önceki",
        rad_reset: "Düğme konumunu sıfırla",
        rad_title: "Dairesel menü",
        rad_vdn: "Daha kısık",
        rad_view: "Ekran",
        rad_vup: "Daha yüksek",
        rec: "KAYIT",
        rec_tip: "Dokun: kaydet · Basılı tut: ayarlar",
        rel: "Yenile",
        rs_cur: "Mevcut yayın:",
        rs_gbh: "GB/sa",
        rs_hint: "Bir parça seçilen boyuta ulaştığında ayrı bir dosya olarak kaydedilir ve kayıt yeni bir parçada devam eder. Veriler diske küçük bloklar halinde yazılır; disk kullanılamazsa telefonun belleğinde kalır, bu durumda daha küçük parçalar seçmek iyi olur.",
        rs_mbmin: "MB/dk",
        rs_next: "Değişiklikler bir sonraki parçadan veya yeni kayıttan itibaren geçerli olur.",
        rs_now: "Şimdi",
        rs_off: "Kapalı",
        rs_p: "parça",
        rs_part: "Parça boyutu (dosya bölünür)",
        rs_q_hi: "Yüksek",
        rs_q_low: "Düşük",
        rs_q_mid: "Orta",
        rs_q_min: "Tasarruf",
        rs_quality: "Kalite (bit hızı)",
        rs_recover: "Kaydedilmemiş kayıtları kurtar",
        rs_resnote: "Yakalama modu yayının çözünürlüğünde kaydeder: yukarıdaki seçenekler yalnızca bit hızını (sıkıştırmayı) değiştirir; 1080p–360p beklenen görüntü kalitesini gösterir. HLS modunda, seçilen yükseklikten büyük olmayan bir yayın seçilir.",
        rs_src: "Kaynak",
        rs_src_cap: "Yakalama (yeniden kodlama)",
        rs_src_hls: "HLS (yeniden kodlamasız)",
        rs_src_note: "HLS, yayın parçalarını olduğu gibi, yeniden kodlamadan indirir ve MP4 olarak kaydeder. Yayın adresi gerekir; yoksa yakalama kullanılır. Kalite, kaynak yayınla aynı, yani orijinaldir. Kayıt diske küçük kontrol noktalarıyla yazılır.",
        rs_t: "toplam",
        rs_title: "Kayıt ayarları",
        rs_total: "Toplam boyutta durdur",
        rs_upto: "en fazla",
        search: "ARA",
        seen_now: "az önce",
        seen_tip: "Son çevrimiçi",
        settings: "Ayarlar",
        sleep_custom: "Özel süre",
        sleep_hint: "Süre dolunca oynatıcı kapanır, varsa kayıt durdurulur ve kaydedilir. Geri getirmek için siyah ekrana dokunun.",
        sleep_left: "Kalan",
        sleep_sec: "sn",
        sleep_start: "Başlat",
        sleep_title: "Uyku zamanlayıcısı",
        sleep_wake: "Zamanlayıcı bitti.\nDevam etmek için dokunun",
        snap: "FOTO",
        sort_date: "Tarih",
        sort_last: "Son",
        sort_live: "Canlı",
        sort_name: "Ad",
        sort_viewers: "İzleyiciler",
        sound: "SES",
        st_av_refresh: "Avatarları yenile",
        st_back: "Geri",
        st_btns: "Panel düğmeleri",
        st_btns_hint: "Bir düğmeyi gizlemek veya geri getirmek için dokunun.",
        st_btns_reset: "Tüm düğmeleri göster",
        st_color: "Panel rengi",
        st_color_rnd: "Rastgele",
        st_cols: "Sütunlar",
        st_custom: "Özel renk",
        st_data: "Veri",
        st_fav: "Favoriler",
        st_favnav: "Kumandada «geri / ileri» düğmeleri",
        st_group: "Grup",
        st_hide_grp: "Grup seçicisini gizle",
        st_hide_note: "Not alanını gizle (etiketler)",
        st_hide_topic: "Model açıklamasını gizle",
        st_hist: "İzleme geçmişi",
        st_hist_on: "Geçmişi tut",
        st_hue: "Ton",
        st_lang: "Dil",
        st_lig: "Parlaklık",
        st_list: "Liste",
        st_look: "Görünüm",
        st_open: "Aç",
        st_prev: "Model önizlemeleri",
        st_prev_off: "Yenileme yok",
        st_prev_spd: "Önizleme yenileme",
        st_private: "Özel",
        st_ptags: "Profil etiketlerini gizle (BongaCams)",
        st_rec: "Video kaydı",
        st_sat: "Doygunluk",
        st_scale: "Panel boyutu",
        st_site_dark: "Sitenin koyu teması",
        st_star: "Küçük resimlerde yıldız",
        star_add: "Favorilere ekle",
        star_del: "Favorilerden kaldır",
        star_del_q: "Bu modelin notu veya grubu var. Favorilerden kaldırılsın mı?",
        thtr: "SİNEMA",
        lock: "KİLİT",
        tp_less: "[Daha az]",
        tp_more: "[Daha fazla]",
        u_d: "g",
        u_h: "sa",
        u_m: "dk",
        viewers_tip: "Odadaki izleyiciler",
        w_new: "İzlenmedi",
        w_seen: "İzlendi",
    },
    NL: {
        cal_pres_btn: "Voorspelling",
        cal_pres_h: "Meestal online (op basis van waarnemingen)",
        cal_pres_none: "Nog geen gegevens. De voorspelling vult zich terwijl je de site gebruikt.",
        cal_pres_info: "Modellen met gegevens: {a} van {b}",
        cal_pres_on: "Online-registratie",
        blur_mute: "Video vervagen zonder geluid",
        ago: "{n} geleden",
        bg: "ACHT.",
        bk_csv: "CSV-tabellen",
        bk_csv_fav: "Favorieten",
        bk_csv_hist: "Geschiedenis",
        bk_csv_ses: "Sessies",
        bk_exp: "Back-up exporteren",
        bk_imp: "Back-up importeren",
        bk_p_fav: "Favorieten",
        bk_p_hist: "Geschiedenis",
        bk_p_set: "Instellingen",
        bk_parts: "Inhoud van de back-up",
        bm_add: "Toevoegen aan favorieten",
        bm_all: "Alle",
        bm_del: "Verwijderen",
        bm_dup: "Al toegevoegd!",
        bm_empty: "De lijst is leeg.",
        bm_find: "Zoeken op naam…",
        bm_nomatch: "Geen modellen voldoen aan het filter",
        bm_online_t: "Alleen online tonen",
        bm_pin: "Bovenaan vastzetten",
        bm_title: "Favorieten",
        bm_unpin: "Losmaken",
        bt_hints: "Tips",
        bt_stats: "Kijkers",
        cal_all_tag: "Alle tags",
        cal_allday: "Hele dag",
        cal_c_dur: "Duur",
        cal_c_n: "Bezoeken",
        cal_c_time: "Binnenkomst",
        cal_hist: "Geschiedenis",
        cal_loaded: "Rooster geladen: {a} van {b}",
        cal_models: "Modellen",
        cal_no_sched: "Het rooster komt uit de profielen van je favorieten. Open Favorieten en wacht een paar seconden.",
        cal_none: "Niets op deze dag",
        cal_reset: "Resetten",
        cal_sched: "Rooster",
        cal_title: "Kalender",
        cal_today: "Vandaag",
        cal_total: "Totaal",
        cal_watched: "Bekeken",
        cf_cancel: "Annuleren",
        cf_ok: "Ja",
        ch_avg: "Daggemiddelde",
        ch_btn: "Grafieken",
        ch_days: "Tijd per dag, 14 dagen",
        ch_hours: "Activiteit per uur",
        ch_peak: "Piek",
        ch_top: "Top op tijd",
        dark: "Donker / licht thema",
        don_copied: "Adres gekopieerd",
        don_fail: "Kopiëren mislukt",
        don_sub: "Bedankt voor het gebruik",
        don_tap: "Tik op een adres om het volledig te zien. De knop rechts kopieert het.",
        don_tip: "Steun de auteur",
        don_title: "Trakteer de auteur op een koffie",
        drag: "⁞⁞ BEDIENING",
        err: "FOUT",
        favs: "FAVO'S",
        fr_hold: "Houd ingedrukt voor het huidige beeld",
        fr_none: "Geen beeld: model is offline",
        fs: "VOL",
        grp_all: "Alle groepen",
        grp_new: "+ Nieuwe groep…",
        grp_none: "Geen groep",
        hd_all: "Alles verbergen",
        hd_cats: "Categorieën",
        hd_clear: "Al mijn regels wissen",
        hd_hidden: "{n} van {m} verborgen",
        hd_na: "nog niet gevonden",
        hd_none: "Nog geen regels.",
        hd_off: "Alles tonen",
        hd_p_clean: "Schone speler",
        hd_p_min: "Minimaal",
        hd_peek: "Houd een item ingedrukt om te zien wat het verbergt",
        hd_pick: "Element kiezen",
        hd_reset: "Resetten voor deze site",
        hd_rules: "Mijn regels",
        hd_title: "Verbergen",
        hide: "Verbergen (H)",
        hints: "M=GELUID F=VOL T=THEATER H=VERBERG",
        hist_clear: "Wissen",
        hist_clear_q: "De hele kijkgeschiedenis verwijderen?",
        hist_del: "Verwijderen uit geschiedenis",
        hist_empty: "Nog leeg. De tijd wordt geteld terwijl de video speelt.",
        hist_s_time: "Tijd",
        hist_s_vis: "Bezoeken",
        hist_title: "Geschiedenis",
        hist_total: "Modellen: {n} · Totaal: {t}",
        hist_visits: "bezoeken",
        lang_title: "Taal",
        min: "Minimaliseren",
        mv_abr_row: "Automatische kwaliteit",
        mv_abr_t: "Automatische kwaliteit: verlaagt de kwaliteit bij haperingen of een zwak netwerk en verhoogt hem als de verbinding stabiel is",
        mv_add: "Toevoegen",
        mv_auto_all: "Alle live streams automatisch toevoegen",
        mv_back: "Terug",
        mv_bg: "Afspelen op de achtergrond",
        mv_bg_t: "Streams blijven spelen als je van tabblad wisselt, dit venster sluit of de browser verlaat",
        mv_bm_h: "Live bladwijzers",
        mv_clear_q: "Alle streams sluiten?",
        mv_close: "Sluiten",
        mv_cols: "Indeling",
        mv_cur: "Huidige stream",
        mv_dvr: "Terugspoelbuffer",
        mv_dvr_off: "Uit",
        mv_fs: "Volledig scherm",
        mv_full: "Venster is vol",
        mv_go: "Toevoegen",
        mv_hint: "Voeg streams toe om er meerdere tegelijk te bekijken. Tik op een tegel om het geluid ernaartoe te verplaatsen.",
        mv_link_h: "Of via link",
        mv_load: "Laden…",
        mv_mute: "Dempen",
        mv_none: "Geen live bladwijzers",
        mv_off: "Stream niet beschikbaar",
        mv_pip: "Beeld-in-beeld",
        mv_play: "Afspelen / pauzeren",
        mv_qual: "Kwaliteit",
        mv_retry: "tik om opnieuw te proberen",
        mv_search: "Live bladwijzers zoeken",
        mv_title: "Speler",
        no_vid: "GEEN VIDEO",
        note_ph: "Notitie... (#tags)",
        pk_cancel: "Annuleren",
        pk_found: "treffers",
        pk_hint: "Tik op een element op de pagina",
        pk_ok: "Verbergen",
        pk_up: "Hoger",
        qual: "KWAL.",
        rad_nav: "Favorieten",
        rad_next: "Volgende",
        rad_page: "Pagina",
        rad_prev: "Vorige",
        rad_reset: "Knoppositie resetten",
        rad_title: "Radiaal menu",
        rad_vdn: "Zachter",
        rad_view: "Weergave",
        rad_vup: "Harder",
        rec: "OPN.",
        rec_tip: "Tik: opnemen · Vasthouden: instellingen",
        rel: "Herladen",
        rs_cur: "Huidige stream:",
        rs_gbh: "GB/u",
        rs_hint: "Wanneer een deel de gekozen grootte bereikt, wordt het als apart bestand opgeslagen en gaat de opname verder in een nieuw bestand. Gegevens worden in kleine blokken naar de schijf geschreven; is de schijf niet beschikbaar, dan blijven ze in het telefoongeheugen, kies dan kleinere delen.",
        rs_next: "Wijzigingen gelden vanaf het volgende deel of een nieuwe opname.",
        rs_now: "Nu",
        rs_off: "Uit",
        rs_p: "deel",
        rs_part: "Deelgrootte (bestand wordt gesplitst)",
        rs_q_hi: "Hoog",
        rs_q_low: "Laag",
        rs_q_mid: "Gemiddeld",
        rs_q_min: "Besparen",
        rs_quality: "Kwaliteit (bitrate)",
        rs_recover: "Niet-opgeslagen opnames herstellen",
        rs_resnote: "Capture neemt op in de resolutie van de stream: de opties hierboven wijzigen alleen de bitrate (compressie), en 1080p–360p geven de verwachte beeldkwaliteit aan. In HLS-modus wordt een stream gekozen die niet hoger is dan de opgegeven hoogte.",
        rs_src: "Bron",
        rs_src_cap: "Opname (hercodering)",
        rs_src_hls: "HLS (zonder hercodering)",
        rs_src_note: "HLS downloadt de streamsegmenten ongewijzigd, zonder hercodering, en slaat MP4 op. Het streamadres is nodig; is dat er niet, dan wordt opname gebruikt. De kwaliteit is origineel, zoals in de brondstream. De opname wordt in kleine tussenstappen naar schijf geschreven.",
        rs_t: "totaal",
        rs_title: "Opname-instellingen",
        rs_total: "Stop bij totale grootte",
        rs_upto: "tot",
        search: "ZOEK",
        seen_now: "zojuist",
        seen_tip: "Laatst online gezien",
        settings: "Instellingen",
        sleep_custom: "Eigen tijd",
        sleep_hint: "Als de tijd om is, wordt de speler uitgeschakeld en wordt een lopende opname gestopt en opgeslagen. Tik op het zwarte scherm om de speler terug te halen.",
        sleep_left: "Resterend",
        sleep_start: "Starten",
        sleep_title: "Slaaptimer",
        sleep_wake: "Slaaptimer afgelopen.\nTik om door te gaan",
        snap: "FOTO",
        sort_date: "Datum",
        sort_last: "Gezien",
        sort_name: "Naam",
        sort_viewers: "Kijkers",
        sound: "GELUID",
        st_av_refresh: "Avatars vernieuwen",
        st_back: "Terug",
        st_btns: "Paneelknoppen",
        st_btns_hint: "Tik om een knop te verbergen of terug te zetten.",
        st_btns_reset: "Alle knoppen tonen",
        st_color: "Paneelkleur",
        st_color_rnd: "Willekeurig",
        st_cols: "Kolommen",
        st_custom: "Eigen kleur",
        st_data: "Gegevens",
        st_fav: "Favorieten",
        st_favnav: "«Vorige / volgende»-knoppen op de bediening",
        st_group: "Groep",
        st_hide_grp: "Groepskeuze verbergen",
        st_hide_note: "Notitieveld (tags) verbergen",
        st_hide_topic: "Modelbeschrijving verbergen",
        st_hist: "Kijkgeschiedenis",
        st_hist_on: "Geschiedenis bewaren",
        st_hue: "Tint",
        st_lang: "Taal",
        st_lig: "Helderheid",
        st_list: "Lijst",
        st_look: "Uiterlijk",
        st_open: "Openen",
        st_prev: "Voorbeelden van modellen",
        st_prev_off: "Niet vernieuwen",
        st_prev_spd: "Voorbeelden vernieuwen",
        st_private: "Privé",
        st_ptags: "Profieltags verbergen (BongaCams)",
        st_rec: "Video-opname",
        st_sat: "Verzadiging",
        st_scale: "Paneelgrootte",
        st_site_dark: "Donker thema van de site",
        st_star: "Ster op miniaturen",
        star_add: "Toevoegen aan favorieten",
        star_del: "Verwijderen uit favorieten",
        star_del_q: "Dit model heeft een notitie of groep. Verwijderen uit favorieten?",
        thtr: "THEATER",
        lock: "SLOT",
        tp_less: "[Minder]",
        tp_more: "[Meer]",
        u_h: "u",
        viewers_tip: "Kijkers in de kamer",
        w_new: "Niet bekeken",
        w_seen: "Bekeken",
    },
    CS: {
        cal_pres_btn: "Předpověď",
        cal_pres_h: "Obvykle online (podle pozorování)",
        cal_pres_none: "Zatím žádná data. Předpověď se doplní, když web používáte.",
        cal_pres_info: "Modely s daty: {a} z {b}",
        cal_pres_on: "Sledování online",
        blur_mute: "Rozmazat video bez zvuku",
        ago: "před {n}",
        bg: "POZADÍ",
        bk_csv: "Tabulky CSV",
        bk_csv_fav: "Oblíbené",
        bk_csv_hist: "Historie",
        bk_csv_ses: "Relace",
        bk_exp: "Export zálohy",
        bk_imp: "Import zálohy",
        bk_p_fav: "Oblíbené",
        bk_p_hist: "Historie",
        bk_p_set: "Nastavení",
        bk_parts: "Obsah zálohy",
        bm_add: "Přidat do oblíbených",
        bm_all: "Vše",
        bm_del: "Smazat",
        bm_dup: "Už přidáno!",
        bm_empty: "Seznam je prázdný.",
        bm_find: "Hledat podle jména…",
        bm_nomatch: "Filtru neodpovídá žádná modelka",
        bm_online_t: "Zobrazit jen online",
        bm_pin: "Připnout nahoru",
        bm_title: "Oblíbené",
        bm_unpin: "Odepnout",
        bt_hints: "Tipy",
        bt_stats: "Diváci",
        bt_vol: "Hlasitost",
        cal_all_tag: "Všechny štítky",
        cal_allday: "Celý den",
        cal_c_dur: "Doba",
        cal_c_model: "Modelka",
        cal_c_n: "Vstupy",
        cal_c_time: "Vstup",
        cal_hist: "Historie",
        cal_loaded: "Rozvrh načten: {a} z {b}",
        cal_models: "Modelky",
        cal_no_sched: "Rozvrh se načítá z profilů oblíbených. Otevřete Oblíbené a chvíli počkejte.",
        cal_none: "V tento den nic není",
        cal_reset: "Obnovit",
        cal_sched: "Rozvrh",
        cal_title: "Kalendář",
        cal_today: "Dnes",
        cal_total: "Celkem",
        cal_watched: "Shlédnuto",
        cf_cancel: "Zrušit",
        cf_ok: "Ano",
        ch_avg: "Denní průměr",
        ch_btn: "Grafy",
        ch_days: "Čas za den, 14 dní",
        ch_hours: "Aktivita podle hodin",
        ch_peak: "Špička",
        ch_top: "Top podle času",
        copy: "ODKAZ",
        dark: "Tmavý / světlý motiv",
        don_copied: "Adresa zkopírována",
        don_fail: "Kopírování se nezdařilo",
        don_sub: "Děkuji, že to používáte",
        don_tap: "Klepnutím na adresu ji zobrazíte celou. Tlačítko vpravo ji zkopíruje.",
        don_tip: "Podpořit autora",
        don_title: "Kupte autorovi kávu",
        drag: "⁞⁞ OVLÁDÁNÍ",
        err: "CHYBA",
        favs: "OBLÍBENÉ",
        fr_hold: "Podržením zobrazíte aktuální snímek",
        fr_none: "Snímek není k dispozici: modelka je offline",
        fs: "CELÁ",
        grp_all: "Všechny skupiny",
        grp_new: "+ Nová skupina…",
        grp_none: "Bez skupiny",
        hd_all: "Skrýt vše",
        hd_cats: "Kategorie",
        hd_clear: "Smazat všechna moje pravidla",
        hd_details: "Podrobnosti",
        hd_g_site: "Web",
        hd_g_win: "Vyskakovací okna",
        hd_hidden: "Skryto {n} z {m}",
        hd_na: "zatím nenalezeno",
        hd_none: "Zatím žádná pravidla.",
        hd_off: "Zobrazit vše",
        hd_p_clean: "Čistý přehrávač",
        hd_p_min: "Minimum",
        hd_peek: "Podržte položku, abyste viděli, co skrývá",
        hd_pick: "Vybrat prvek",
        hd_reset: "Obnovit pro tento web",
        hd_rules: "Moje pravidla",
        hd_title: "Skrývání",
        hide: "Skrýt (H)",
        hints: "M=ZVUK F=CELÁ T=KINO H=SKRÝT",
        hist_clear: "Vymazat",
        hist_clear_q: "Smazat celou historii sledování?",
        hist_del: "Odebrat z historie",
        hist_empty: "Zatím prázdné. Čas se počítá, dokud video běží.",
        hist_s_last: "Nedávné",
        hist_s_time: "Čas",
        hist_s_vis: "Návštěvy",
        hist_title: "Historie",
        hist_total: "Modelky: {n} · Celkem: {t}",
        hist_visits: "návštěv",
        home: "Domů",
        lang_title: "Jazyk",
        min: "Minimalizovat",
        mv_abr_row: "Automatická kvalita",
        mv_abr_t: "Automatická kvalita: snižuje kvalitu při sekání nebo slabé síti a zvyšuje ji, když je připojení stabilní",
        mv_add: "Přidat",
        mv_auto_all: "Automaticky přidat všechny živé streamy",
        mv_back: "Zpět",
        mv_bg: "Přehrávat na pozadí",
        mv_bg_t: "Streamy se přehrávají dál, když přepnete kartu, zavřete toto okno nebo opustíte prohlížeč",
        mv_bm_h: "Živé záložky",
        mv_clear_q: "Zavřít všechny streamy?",
        mv_close: "Zavřít",
        mv_cols: "Rozložení",
        mv_cur: "Aktuální stream",
        mv_dvr: "Vyrovnávací paměť pro přetáčení",
        mv_dvr_off: "Vyp.",
        mv_fs: "Celá obrazovka",
        mv_full: "Okno je plné",
        mv_go: "Přidat",
        mv_hint: "Přidejte streamy, abyste mohli sledovat několik najednou. Klepnutím na dlaždici na ni přesunete zvuk.",
        mv_link_h: "Nebo odkazem",
        mv_load: "Načítání…",
        mv_mute: "Ztlumit",
        mv_none: "Žádné živé záložky",
        mv_off: "Stream není dostupný",
        mv_pip: "Obraz v obraze",
        mv_play: "Přehrát / pozastavit",
        mv_qual: "Kvalita",
        mv_retry: "klepnutím to zkusíte znovu",
        mv_search: "Hledat v živých záložkách",
        mv_title: "Přehrávač",
        no_vid: "BEZ VIDEA",
        note_ph: "Poznámka... (#tagy)",
        pk_cancel: "Zrušit",
        pk_found: "shod",
        pk_hint: "Klepněte na prvek na stránce",
        pk_ok: "Skrýt",
        pk_up: "Výše",
        qual: "KVAL.",
        rad_media: "Média",
        rad_nav: "Oblíbené",
        rad_next: "Další",
        rad_page: "Stránka",
        rad_prev: "Předch.",
        rad_reset: "Obnovit pozici tlačítka",
        rad_title: "Kruhové menu",
        rad_vdn: "Tišeji",
        rad_view: "Zobrazení",
        rad_vup: "Hlasitěji",
        rec: "NAHR.",
        rec_tip: "Klepnutí: nahrávat · Podržení: nastavení",
        rel: "Obnovit",
        rs_cur: "Aktuální stream:",
        rs_hint: "Když část dosáhne zvolené velikosti, uloží se jako samostatný soubor a nahrávání pokračuje v novém. Data se zapisují na disk po malých blocích; pokud disk není dostupný, zůstávají v paměti telefonu, a pak je lepší zvolit menší části.",
        rs_next: "Změny platí od další části nebo nové nahrávky.",
        rs_now: "Nyní",
        rs_off: "Vyp.",
        rs_p: "část",
        rs_part: "Velikost části (soubor se dělí)",
        rs_q_hi: "Vysoká",
        rs_q_low: "Nízká",
        rs_q_mid: "Střední",
        rs_q_min: "Úsporná",
        rs_quality: "Kvalita (bitrate)",
        rs_recover: "Obnovit neuložené nahrávky",
        rs_resnote: "Zachytávání nahrává v rozlišení streamu: možnosti výše mění jen bitrate (kompresi) a 1080p–360p ukazují očekávanou kvalitu obrazu. V režimu HLS se vybírá stream, který není vyšší než zadaná výška.",
        rs_src: "Zdroj",
        rs_src_cap: "Zachytávání (překódování)",
        rs_src_hls: "HLS (bez překódování)",
        rs_src_note: "HLS stahuje segmenty streamu tak, jak jsou, bez překódování, a ukládá MP4. Potřebuje adresu streamu; pokud není k dispozici, použije se zachytávání. Kvalita je původní, stejná jako ve zdrojovém streamu. Nahrávka se na disk zapisuje po malých částech.",
        rs_t: "celkem",
        rs_title: "Nastavení nahrávání",
        rs_total: "Zastavit při celkové velikosti",
        rs_upto: "až",
        search: "HLEDAT",
        seen_now: "právě teď",
        seen_tip: "Naposledy online",
        settings: "Nastavení",
        sleep_custom: "Vlastní čas",
        sleep_hint: "Po uplynutí času se přehrávač vypne a případné nahrávání se zastaví a uloží. Klepnutím na černou obrazovku ho vrátíte.",
        sleep_left: "Zbývá",
        sleep_sec: "s",
        sleep_start: "Spustit",
        sleep_title: "Časovač vypnutí",
        sleep_wake: "Časovač skončil.\nKlepnutím pokračujte",
        snap: "FOTO",
        sort_date: "Datum",
        sort_last: "Viděna",
        sort_name: "Jméno",
        sort_viewers: "Diváci",
        sound: "ZVUK",
        st_av_refresh: "Obnovit avatary",
        st_back: "Zpět",
        st_btns: "Tlačítka panelu",
        st_btns_hint: "Klepnutím tlačítko skryjete nebo vrátíte.",
        st_btns_reset: "Zobrazit všechna tlačítka",
        st_color: "Barva panelu",
        st_color_rnd: "Náhodná",
        st_cols: "Sloupce",
        st_custom: "Vlastní barva",
        st_fav: "Oblíbené",
        st_favnav: "Tlačítka „zpět / vpřed“ na ovladači",
        st_group: "Skupina",
        st_hide_grp: "Skrýt výběr skupiny",
        st_hide_note: "Skrýt pole poznámky (štítky)",
        st_hide_topic: "Skrýt popis modelky",
        st_hist: "Historie sledování",
        st_hist_on: "Ukládat historii",
        st_hue: "Odstín",
        st_lang: "Jazyk",
        st_lig: "Jas",
        st_list: "Seznam",
        st_look: "Vzhled",
        st_open: "Otevřít",
        st_prev: "Náhledy modelek",
        st_prev_off: "Neobnovovat",
        st_prev_spd: "Obnovování náhledů",
        st_private: "Privát",
        st_ptags: "Skrýt štítky profilu (BongaCams)",
        st_rec: "Nahrávání videa",
        st_sat: "Sytost",
        st_scale: "Velikost panelu",
        st_site_dark: "Tmavý motiv webu",
        st_star: "Hvězdička na náhledech",
        star_add: "Přidat do oblíbených",
        star_del: "Odebrat z oblíbených",
        star_del_q: "Tato modelka má poznámku nebo skupinu. Odebrat z oblíbených?",
        thtr: "KINO",
        lock: "ZÁMEK",
        tp_less: "[Méně]",
        tp_more: "[Více]",
        viewers_tip: "Diváci v místnosti",
        w_new: "Neshlédnuto",
        w_seen: "Shlédnuto",
    },
    RO: {
        cal_pres_btn: "Prognoză",
        cal_pres_h: "De obicei online (din observații)",
        cal_pres_none: "Încă nu sunt date. Prognoza se completează pe măsură ce folosești site-ul.",
        cal_pres_info: "Modele cu date: {a} din {b}",
        cal_pres_on: "Urmărire online",
        blur_mute: "Estompează videoul fără sunet",
        ago: "acum {n}",
        bg: "FUNDAL",
        bk_csv: "Tabele CSV",
        bk_csv_fav: "Favorite",
        bk_csv_hist: "Istoric",
        bk_csv_ses: "Sesiuni",
        bk_exp: "Exportă copia de rezervă",
        bk_imp: "Importă copia de rezervă",
        bk_p_fav: "Favorite",
        bk_p_hist: "Istoric",
        bk_p_set: "Setări",
        bk_parts: "Conținutul copiei",
        bm_add: "Adaugă la favorite",
        bm_all: "Toate",
        bm_del: "Șterge",
        bm_dup: "Deja adăugat!",
        bm_empty: "Lista este goală.",
        bm_find: "Caută după nume…",
        bm_nomatch: "Niciun model nu se potrivește filtrului",
        bm_online_t: "Arată doar online",
        bm_pin: "Fixează sus",
        bm_title: "Favorite",
        bm_unpin: "Anulează fixarea",
        bt_hints: "Sfaturi",
        bt_stats: "Spectatori",
        bt_vol: "Volum",
        cal_all_tag: "Toate etichetele",
        cal_allday: "Toată ziua",
        cal_c_dur: "Durată",
        cal_c_n: "Vizite",
        cal_c_time: "Intrare",
        cal_hist: "Istoric",
        cal_loaded: "Program încărcat: {a} din {b}",
        cal_models: "Modele",
        cal_no_sched: "Programul este preluat din profilurile favoritelor. Deschide Favorite și așteaptă câteva secunde.",
        cal_none: "Nimic în această zi",
        cal_reset: "Resetează",
        cal_sched: "Program",
        cal_today: "Azi",
        cal_watched: "Văzut",
        cf_cancel: "Anulează",
        cf_ok: "Da",
        ch_avg: "Media zilnică",
        ch_btn: "Grafice",
        ch_days: "Timp pe zi, 14 zile",
        ch_hours: "Activitate pe ore",
        ch_peak: "Vârf",
        ch_top: "Top după timp",
        dark: "Temă închisă / deschisă",
        don_copied: "Adresă copiată",
        don_fail: "Copierea a eșuat",
        don_sub: "Mulțumesc că folosești scriptul",
        don_tap: "Atinge o adresă ca să o vezi completă. Butonul din dreapta o copiază.",
        don_tip: "Susține autorul",
        don_title: "Cumpără-i autorului o cafea",
        drag: "⁞⁞ COMENZI",
        err: "EROARE",
        favs: "FAVORITE",
        fr_hold: "Ține apăsat pentru cadrul curent",
        fr_none: "Fără cadru: modelul este offline",
        fs: "ECRAN",
        grp_all: "Toate grupurile",
        grp_new: "+ Grup nou…",
        grp_none: "Fără grup",
        hd_all: "Ascunde tot",
        hd_cats: "Categorii",
        hd_clear: "Șterge toate regulile mele",
        hd_details: "Detalii",
        hd_g_win: "Ferestre pop-up",
        hd_hidden: "Ascunse {n} din {m}",
        hd_na: "încă negăsit",
        hd_none: "Încă nu există reguli.",
        hd_off: "Arată tot",
        hd_p_clean: "Player curat",
        hd_p_min: "Minim",
        hd_peek: "Ține apăsat un element ca să vezi ce ascunde",
        hd_pick: "Alege element",
        hd_reset: "Resetează pentru acest site",
        hd_rules: "Regulile mele",
        hd_title: "Ascundere",
        hide: "Ascunde (H)",
        hints: "M=SUNET F=ECRAN T=TEATRU H=ASCUNDE",
        hist_clear: "Șterge",
        hist_clear_q: "Ștergi tot istoricul de vizionări?",
        hist_del: "Elimină din istoric",
        hist_empty: "Gol deocamdată. Timpul se numără cât timp rulează videoul.",
        hist_s_last: "Recente",
        hist_s_time: "Timp",
        hist_s_vis: "Vizite",
        hist_title: "Istoric",
        hist_total: "Modele: {n} · Total: {t}",
        hist_visits: "vizite",
        home: "Acasă",
        lang_title: "Limbă",
        min: "Minimizează",
        mv_abr_row: "Calitate automată",
        mv_abr_t: "Calitate automată: scade calitatea când video se blochează sau rețeaua e slabă și o crește când conexiunea e stabilă",
        mv_add: "Adaugă",
        mv_auto_all: "Adaugă automat toate fluxurile live",
        mv_back: "Înapoi",
        mv_bg: "Redare în fundal",
        mv_bg_t: "Fluxurile continuă să ruleze când schimbi fila, închizi această fereastră sau ieși din browser",
        mv_bm_h: "Favorite în direct",
        mv_clear_q: "Închizi toate fluxurile?",
        mv_close: "Închide",
        mv_cols: "Aspect",
        mv_cur: "Fluxul curent",
        mv_dvr: "Buffer de derulare înapoi",
        mv_dvr_off: "Oprit",
        mv_fs: "Ecran complet",
        mv_full: "Fereastra este plină",
        mv_go: "Adaugă",
        mv_hint: "Adaugă fluxuri ca să urmărești mai multe deodată. Atinge o casetă ca să muți sunetul pe ea.",
        mv_link_h: "Sau prin link",
        mv_load: "Se încarcă…",
        mv_mute: "Fără sunet",
        mv_none: "Nu există favorite în direct",
        mv_off: "Flux indisponibil",
        mv_pip: "Imagine în imagine",
        mv_play: "Redare / pauză",
        mv_qual: "Calitate",
        mv_retry: "atinge pentru a reîncerca",
        mv_search: "Caută în favoritele în direct",
        no_vid: "FĂRĂ VIDEO",
        note_ph: "Notă... (#taguri)",
        pk_cancel: "Anulează",
        pk_found: "potriviri",
        pk_hint: "Atinge un element de pe pagină",
        pk_ok: "Ascunde",
        pk_up: "Mai sus",
        qual: "CALIT.",
        rad_nav: "Favorite",
        rad_next: "Urm.",
        rad_page: "Pagină",
        rad_prev: "Ant.",
        rad_reset: "Resetează poziția butonului",
        rad_title: "Meniu radial",
        rad_vdn: "Mai încet",
        rad_view: "Ecran",
        rad_vup: "Mai tare",
        rec: "ÎNREG.",
        rec_tip: "Atingere: înregistrează · Apăsare lungă: setări",
        rel: "Reîncarcă",
        rs_cur: "Fluxul curent:",
        rs_hint: "Când o parte atinge dimensiunea aleasă, este salvată ca fișier separat, iar înregistrarea continuă într-unul nou. Datele se scriu pe disc în blocuri mici; dacă discul nu este disponibil, rămân în memoria telefonului, așa că alege părți mai mici.",
        rs_next: "Modificările se aplică de la următoarea parte sau de la o înregistrare nouă.",
        rs_now: "Acum",
        rs_off: "Oprit",
        rs_p: "parte",
        rs_part: "Mărimea părții (fișierul se împarte)",
        rs_q_hi: "Înaltă",
        rs_q_low: "Scăzută",
        rs_q_mid: "Medie",
        rs_q_min: "Economie",
        rs_quality: "Calitate (bitrate)",
        rs_recover: "Recuperează înregistrările nesalvate",
        rs_resnote: "Captura înregistrează la rezoluția fluxului: opțiunile de mai sus schimbă doar bitrate-ul (compresia), iar 1080p–360p arată calitatea estimată a imaginii. În modul HLS se alege un flux cu înălțimea de cel mult cea indicată.",
        rs_src: "Sursă",
        rs_src_cap: "Captură (recodare)",
        rs_src_hls: "HLS (fără recodare)",
        rs_src_note: "HLS descarcă segmentele fluxului ca atare, fără recodare, și salvează MP4. Are nevoie de adresa fluxului; dacă nu este disponibilă, se folosește captura. Calitatea este cea originală, ca în fluxul sursă. Înregistrarea se scrie pe disc în mici puncte de control.",
        rs_title: "Setări de înregistrare",
        rs_total: "Oprește la mărimea totală",
        rs_upto: "până la",
        search: "CAUTĂ",
        seen_now: "chiar acum",
        seen_tip: "Ultima oară online",
        settings: "Setări",
        sleep_custom: "Timp personalizat",
        sleep_hint: "La expirarea timpului, playerul se oprește, iar orice înregistrare se oprește și se salvează. Atinge ecranul negru pentru a-l readuce.",
        sleep_left: "Rămas",
        sleep_start: "Pornește",
        sleep_title: "Temporizator de somn",
        sleep_wake: "Temporizatorul s-a încheiat.\nAtinge pentru a continua",
        snap: "FOTO",
        sort_date: "Data",
        sort_last: "Văzut",
        sort_name: "Nume",
        sort_viewers: "Spectatori",
        sound: "SUNET",
        st_av_refresh: "Actualizează avatarele",
        st_back: "Înapoi",
        st_btns: "Butoanele panoului",
        st_btns_hint: "Atinge pentru a ascunde un buton sau a-l readuce.",
        st_btns_reset: "Arată toate butoanele",
        st_color: "Culoarea panoului",
        st_color_rnd: "Aleatorie",
        st_cols: "Coloane",
        st_custom: "Culoare personalizată",
        st_data: "Date",
        st_fav: "Favorite",
        st_favnav: "Butoane „înapoi / înainte” pe telecomandă",
        st_group: "Grup",
        st_hide_grp: "Ascunde selectorul de grup",
        st_hide_note: "Ascunde câmpul de notițe (etichete)",
        st_hide_topic: "Ascunde descrierea modelului",
        st_hist: "Istoric vizionări",
        st_hist_on: "Păstrează istoricul",
        st_hue: "Nuanță",
        st_lang: "Limbă",
        st_lig: "Luminozitate",
        st_list: "Listă",
        st_look: "Aspect",
        st_open: "Deschide",
        st_prev: "Previzualizări ale modelelor",
        st_prev_off: "Fără actualizare",
        st_prev_spd: "Reîmprospătarea previzualizărilor",
        st_private: "Privat",
        st_ptags: "Ascunde etichetele profilului (BongaCams)",
        st_rec: "Înregistrare video",
        st_sat: "Saturație",
        st_scale: "Dimensiunea panoului",
        st_site_dark: "Tema întunecată a site-ului",
        st_star: "Stea pe miniaturi",
        star_add: "Adaugă la favorite",
        star_del: "Elimină din favorite",
        star_del_q: "Acest model are o notă sau un grup. Îl elimini din favorite?",
        thtr: "TEATRU",
        lock: "BLOCAT",
        tp_less: "[Mai puțin]",
        tp_more: "[Mai mult]",
        u_d: "z",
        viewers_tip: "Spectatori în cameră",
        w_new: "Nevăzut",
        w_seen: "Văzut",
    },
    HU: {
        cal_pres_btn: "Előrejelzés",
        cal_pres_h: "Általában online (megfigyelések alapján)",
        cal_pres_none: "Még nincs adat. Az előrejelzés az oldal használata közben töltődik fel.",
        cal_pres_info: "Adattal rendelkező modellek: {a} / {b}",
        cal_pres_on: "Online követés",
        blur_mute: "Videó elmosása hang nélkül",
        ago: "{n} ezelőtt",
        bg: "HÁTTÉR",
        bk_csv: "CSV-táblázatok",
        bk_csv_fav: "Kedvencek",
        bk_csv_hist: "Előzmények",
        bk_csv_ses: "Munkamenetek",
        bk_exp: "Biztonsági mentés exportálása",
        bk_imp: "Biztonsági mentés importálása",
        bk_p_fav: "Kedvencek",
        bk_p_hist: "Előzmények",
        bk_p_set: "Beállítások",
        bk_parts: "A mentés tartalma",
        bm_add: "Hozzáadás a kedvencekhez",
        bm_all: "Mind",
        bm_del: "Törlés",
        bm_dup: "Már hozzá van adva!",
        bm_empty: "A lista üres.",
        bm_find: "Keresés név szerint…",
        bm_nomatch: "Nincs a szűrőnek megfelelő modell",
        bm_online_t: "Csak az online-ok",
        bm_pin: "Kitűzés felülre",
        bm_title: "Kedvencek",
        bm_unpin: "Kitűzés megszüntetése",
        bt_hints: "Tippek",
        bt_stats: "Nézők",
        bt_vol: "Hangerő",
        cal_all_tag: "Minden címke",
        cal_allday: "Egész nap",
        cal_c_dur: "Idő",
        cal_c_model: "Modell",
        cal_c_n: "Alkalom",
        cal_c_time: "Belépés",
        cal_hist: "Előzmények",
        cal_loaded: "Menetrend betöltve: {a} / {b}",
        cal_models: "Modellek",
        cal_no_sched: "A menetrend a kedvencek profiljából töltődik be. Nyisd meg a Kedvenceket, és várj pár másodpercet.",
        cal_none: "Ezen a napon nincs semmi",
        cal_reset: "Visszaállítás",
        cal_sched: "Menetrend",
        cal_title: "Naptár",
        cal_today: "Ma",
        cal_total: "Összesen",
        cal_watched: "Nézett",
        cf_cancel: "Mégse",
        cf_ok: "Igen",
        ch_avg: "Napi átlag",
        ch_btn: "Diagramok",
        ch_days: "Idő naponta, 14 nap",
        ch_hours: "Aktivitás óránként",
        ch_peak: "Csúcs",
        ch_top: "Toplista idő szerint",
        dark: "Sötét / világos téma",
        don_copied: "Cím másolva",
        don_fail: "A másolás nem sikerült",
        don_sub: "Köszönöm, hogy használod",
        don_tap: "Érintsd meg a címet a teljes megjelenítéshez. A jobb oldali gomb másolja.",
        don_tip: "A szerző támogatása",
        don_title: "Fizess egy kávét a szerzőnek",
        drag: "⁞⁞ VEZÉRLŐ",
        err: "HIBA",
        favs: "KEDVENCEK",
        fr_hold: "Tartsd nyomva az aktuális képkockához",
        fr_none: "Nincs kép: a modell offline",
        fs: "TELJES",
        grp_all: "Minden csoport",
        grp_new: "+ Új csoport…",
        grp_none: "Nincs csoport",
        hd_all: "Mindent elrejt",
        hd_cats: "Kategóriák",
        hd_clear: "Összes saját szabály törlése",
        hd_details: "Részletek",
        hd_g_chat: "Csevegés",
        hd_g_site: "Oldal",
        hd_g_win: "Felugró ablakok",
        hd_hidden: "Elrejtve: {n} / {m}",
        hd_na: "még nem található",
        hd_none: "Még nincsenek szabályok.",
        hd_off: "Mindent mutat",
        hd_p_clean: "Tiszta lejátszó",
        hd_p_min: "Minimum",
        hd_peek: "Tartsd nyomva a tételt, hogy lásd, mit rejt el",
        hd_pick: "Elem kiválasztása",
        hd_reset: "Visszaállítás ezen az oldalon",
        hd_rules: "Saját szabályok",
        hd_title: "Elrejtés",
        hide: "Elrejtés (H)",
        hints: "M=HANG F=TELJES T=MOZI H=ELREJT",
        hist_clear: "Törlés",
        hist_clear_q: "Törlöd a teljes megtekintési előzményt?",
        hist_del: "Eltávolítás az előzményekből",
        hist_empty: "Egyelőre üres. Az idő a videó lejátszása közben telik.",
        hist_s_last: "Legutóbbi",
        hist_s_time: "Idő",
        hist_s_vis: "Látogatások",
        hist_title: "Előzmények",
        hist_total: "Modellek: {n} · Összesen: {t}",
        hist_visits: "látogatás",
        home: "Kezdőlap",
        lang_title: "Nyelv",
        min: "Kicsinyítés",
        mv_abr_row: "Automatikus minőség",
        mv_abr_t: "Automatikus minőség: csökkenti a minőséget akadozásnál vagy gyenge hálózatnál, és növeli, ha a kapcsolat stabil",
        mv_add: "Hozzáadás",
        mv_auto_all: "Az összes élő adás automatikus hozzáadása",
        mv_back: "Vissza",
        mv_bg: "Lejátszás a háttérben",
        mv_bg_t: "Az adások tovább szólnak, ha lapot váltasz, bezárod ezt az ablakot vagy kilépsz a böngészőből",
        mv_bm_h: "Élő könyvjelzők",
        mv_clear_q: "Bezárod az összes adást?",
        mv_close: "Bezárás",
        mv_cols: "Elrendezés",
        mv_cur: "Jelenlegi adás",
        mv_dvr: "Visszatekerési puffer",
        mv_dvr_off: "Ki",
        mv_fs: "Teljes képernyő",
        mv_full: "Az ablak megtelt",
        mv_go: "Hozzáadás",
        mv_hint: "Adj hozzá adásokat, hogy többet nézhess egyszerre. Érints meg egy csempét, hogy átvidd rá a hangot.",
        mv_link_h: "Vagy hivatkozással",
        mv_load: "Betöltés…",
        mv_min_u: "perc",
        mv_mute: "Némítás",
        mv_none: "Nincs élő könyvjelző",
        mv_off: "Az adás nem érhető el",
        mv_pip: "Kép a képben",
        mv_play: "Lejátszás / szünet",
        mv_qual: "Minőség",
        mv_retry: "érintsd meg az újrapróbáláshoz",
        mv_search: "Keresés az élő könyvjelzők között",
        mv_title: "Lejátszó",
        no_vid: "NINCS VIDEÓ",
        note_ph: "Jegyzet... (#címkék)",
        pk_cancel: "Mégse",
        pk_found: "találat",
        pk_hint: "Érints meg egy elemet az oldalon",
        pk_ok: "Elrejtés",
        pk_up: "Feljebb",
        qual: "MIN.",
        rad_media: "Média",
        rad_nav: "Kedvencek",
        rad_next: "Következő",
        rad_page: "Oldal",
        rad_prev: "Előző",
        rad_reset: "Gomb helyének visszaállítása",
        rad_title: "Körmenü",
        rad_vdn: "Halkabb",
        rad_view: "Nézet",
        rad_vup: "Hangosabb",
        rec: "FELV.",
        rec_tip: "Érintés: felvétel · Nyomva tartás: beállítások",
        rel: "Újratöltés",
        rs_cur: "Jelenlegi adás:",
        rs_gbh: "GB/ó",
        rs_hint: "Amikor egy rész eléri a kiválasztott méretet, külön fájlként mentődik, a felvétel pedig egy újban folytatódik. Az adatok kis blokkokban íródnak a lemezre; ha a lemez nem érhető el, a telefon memóriájában maradnak, ilyenkor érdemes kisebb részeket választani.",
        rs_mbmin: "MB/perc",
        rs_next: "A módosítások a következő résztől vagy új felvételtől érvényesek.",
        rs_now: "Most",
        rs_off: "Ki",
        rs_p: "rész",
        rs_part: "Részméret (a fájl darabolódik)",
        rs_q_hi: "Magas",
        rs_q_low: "Alacsony",
        rs_q_mid: "Közepes",
        rs_q_min: "Takarékos",
        rs_quality: "Minőség (bitráta)",
        rs_recover: "Nem mentett felvételek helyreállítása",
        rs_resnote: "A Capture mód az adás felbontásában rögzít: a fenti beállítások csak a bitrátát (tömörítést) módosítják, az 1080p–360p pedig a várható képminőséget mutatja. HLS módban a megadott magasságot meg nem haladó adást választja a rendszer.",
        rs_src: "Forrás",
        rs_src_cap: "Rögzítés (újrakódolás)",
        rs_src_hls: "HLS (újrakódolás nélkül)",
        rs_src_note: "A HLS az adás szegmenseit úgy tölti le, ahogy vannak, újrakódolás nélkül, és MP4-be menti. Szükség van az adás címére; ha nem érhető el, a rögzítés lesz használva. A minőség eredeti, megegyezik a forrásadáséval. A felvétel kis ellenőrzőpontokban íródik lemezre.",
        rs_t: "összes",
        rs_title: "Felvétel beállításai",
        rs_total: "Leállítás összméretnél",
        rs_upto: "legfeljebb",
        search: "KERESÉS",
        seen_now: "épp most",
        seen_tip: "Utoljára online",
        settings: "Beállítások",
        sleep_custom: "Egyéni idő",
        sleep_hint: "Az idő leteltével a lejátszó kikapcsol, a felvétel pedig leáll és mentésre kerül. Koppints a fekete képernyőre a visszakapcsoláshoz.",
        sleep_left: "Hátra van",
        sleep_sec: "mp",
        sleep_start: "Indítás",
        sleep_title: "Alvásidőzítő",
        sleep_wake: "Az időzítő lejárt.\nKoppints a folytatáshoz",
        snap: "FOTÓ",
        sort_date: "Dátum",
        sort_last: "Látva",
        sort_name: "Név",
        sort_viewers: "Nézők",
        sound: "HANG",
        st_av_refresh: "Avatarok frissítése",
        st_back: "Vissza",
        st_btns: "Panelgombok",
        st_btns_hint: "Koppints egy gomb elrejtéséhez vagy visszaállításához.",
        st_btns_reset: "Összes gomb megjelenítése",
        st_color: "Panel színe",
        st_color_rnd: "Véletlen",
        st_cols: "Oszlopok",
        st_custom: "Egyéni szín",
        st_data: "Adatok",
        st_fav: "Kedvencek",
        st_favnav: "„Vissza / előre” gombok a vezérlőn",
        st_group: "Csoport",
        st_hide_grp: "Csoportválasztó elrejtése",
        st_hide_note: "Megjegyzés mező elrejtése (címkék)",
        st_hide_topic: "Modell leírásának elrejtése",
        st_hist: "Megtekintési előzmények",
        st_hist_on: "Előzmények mentése",
        st_hue: "Árnyalat",
        st_lang: "Nyelv",
        st_lig: "Fényerő",
        st_list: "Lista",
        st_look: "Megjelenés",
        st_open: "Megnyitás",
        st_prev: "Modellek előnézete",
        st_prev_off: "Nincs frissítés",
        st_prev_spd: "Előnézet frissítése",
        st_private: "Privát",
        st_ptags: "Profilcímkék elrejtése (BongaCams)",
        st_rec: "Videófelvétel",
        st_sat: "Telítettség",
        st_scale: "Panel mérete",
        st_site_dark: "Az oldal sötét témája",
        st_star: "Csillag az előnézeteken",
        star_add: "Hozzáadás a kedvencekhez",
        star_del: "Eltávolítás a kedvencekből",
        star_del_q: "Ennek a modellnek van jegyzete vagy csoportja. Eltávolítod a kedvencekből?",
        thtr: "MOZI",
        lock: "ZÁR",
        tp_less: "[Kevesebb]",
        tp_more: "[Több]",
        u_d: "n",
        u_h: "ó",
        u_m: "perc",
        viewers_tip: "Nézők a szobában",
        w_new: "Nem nézett",
        w_seen: "Nézett",
    },
    JA: {
        cal_pres_btn: "予測",
        cal_pres_h: "通常オンライン(観測より)",
        cal_pres_none: "まだデータがありません。サイトを使うと予測が蓄積されます。",
        cal_pres_info: "データのあるモデル: {a} / {b}",
        cal_pres_on: "オンライン記録",
        blur_mute: "音声オフ時に映像をぼかす",
        ago: "{n}前",
        bg: "背景",
        bk_csv: "CSV表",
        bk_csv_fav: "お気に入り",
        bk_csv_hist: "履歴",
        bk_csv_ses: "セッション",
        bk_exp: "バックアップをエクスポート",
        bk_imp: "バックアップをインポート",
        bk_p_fav: "お気に入り",
        bk_p_hist: "履歴",
        bk_p_set: "設定",
        bk_parts: "バックアップの内容",
        bm_add: "お気に入りに追加",
        bm_all: "すべて",
        bm_del: "削除",
        bm_dup: "追加済みです!",
        bm_empty: "リストは空です。",
        bm_find: "名前で検索…",
        bm_nomatch: "条件に合うモデルがいません",
        bm_online: "オンライン",
        bm_online_t: "オンラインのみ表示",
        bm_pin: "先頭に固定",
        bm_priv: "プライベート",
        bm_title: "お気に入り",
        bm_unpin: "固定を解除",
        bt_hints: "ヒント",
        bt_stats: "視聴者",
        bt_vol: "音量",
        cal_all_tag: "すべてのタグ",
        cal_allday: "終日",
        cal_c_dur: "時間",
        cal_c_model: "モデル",
        cal_c_n: "回数",
        cal_c_time: "入室",
        cal_hist: "視聴履歴",
        cal_loaded: "スケジュール取得済み:{a}/{b}",
        cal_models: "モデル",
        cal_no_sched: "スケジュールはお気に入りのプロフィールから取得されます。お気に入りを開いて数秒お待ちください。",
        cal_none: "この日は何もありません",
        cal_reset: "リセット",
        cal_sched: "スケジュール",
        cal_title: "カレンダー",
        cal_today: "今日",
        cal_total: "合計",
        cal_watched: "視聴済み",
        cf_cancel: "キャンセル",
        cf_ok: "はい",
        ch_avg: "1日の平均",
        ch_btn: "グラフ",
        ch_days: "1日あたりの時間(14日間)",
        ch_hours: "時間帯別のアクティビティ",
        ch_peak: "ピーク",
        ch_top: "時間ランキング",
        copy: "リンク",
        dark: "ダーク / ライトテーマ",
        don_copied: "アドレスをコピーしました",
        don_fail: "コピーに失敗しました",
        don_sub: "ご利用ありがとうございます",
        don_tap: "アドレスをタップすると全体が表示されます。右のボタンでコピーできます。",
        don_tip: "作者を支援",
        don_title: "作者にコーヒーをおごる",
        drag: "⁞⁞ 操作パネル",
        err: "エラー",
        favs: "お気に入り",
        fr_hold: "長押しで現在のフレームを表示",
        fr_none: "フレームなし:モデルはオフラインです",
        fs: "全画面",
        grp_all: "すべてのグループ",
        grp_new: "+ 新しいグループ…",
        grp_none: "グループなし",
        hd_all: "すべて非表示",
        hd_cats: "カテゴリ",
        hd_clear: "マイルールをすべて削除",
        hd_details: "詳細",
        hd_g_chat: "チャット",
        hd_g_site: "サイト",
        hd_g_win: "ポップアップ",
        hd_hidden: "{m}件中{n}件を非表示",
        hd_na: "まだ見つかりません",
        hd_none: "ルールはまだありません。",
        hd_off: "すべて表示",
        hd_p_clean: "クリーンプレーヤー",
        hd_p_min: "最小",
        hd_peek: "項目を長押しすると、何を隠すか確認できます",
        hd_pick: "要素を選択",
        hd_reset: "このサイトをリセット",
        hd_rules: "マイルール",
        hd_title: "非表示設定",
        hide: "非表示 (H)",
        hints: "M=音声 F=全画面 T=シアター H=非表示",
        hist_clear: "消去",
        hist_clear_q: "視聴履歴をすべて削除しますか?",
        hist_del: "履歴から削除",
        hist_empty: "まだ空です。動画の再生中に時間がカウントされます。",
        hist_s_last: "最近",
        hist_s_time: "時間",
        hist_s_vis: "訪問",
        hist_title: "履歴",
        hist_total: "モデル: {n} · 合計: {t}",
        hist_visits: "回",
        home: "ホーム",
        lang_auto: "自動",
        lang_title: "言語",
        min: "最小化",
        mv_abr_row: "自動画質",
        mv_abr_t: "自動画質:映像がカクついたりネットワークが弱いときは画質を下げ、接続が安定したら上げます",
        mv_add: "追加",
        mv_auto: "自動",
        mv_auto_all: "ライブ配信をすべて自動で追加",
        mv_back: "戻る",
        mv_bg: "バックグラウンドで再生",
        mv_bg_t: "タブを切り替えたり、このウィンドウを閉じたり、ブラウザを離れても配信の再生が続きます",
        mv_bm_h: "ライブのブックマーク",
        mv_clear_q: "すべての配信を閉じますか?",
        mv_close: "閉じる",
        mv_cols: "レイアウト",
        mv_cur: "現在の配信",
        mv_dvr: "巻き戻しバッファ",
        mv_dvr_off: "オフ",
        mv_fs: "全画面",
        mv_full: "ウィンドウがいっぱいです",
        mv_go: "追加",
        mv_hint: "配信を追加すると複数を同時に視聴できます。タイルをタップすると、その配信に音声が切り替わります。",
        mv_link_h: "またはリンクで",
        mv_load: "読み込み中…",
        mv_min_u: "分",
        mv_mute: "ミュート",
        mv_none: "ライブのブックマークはありません",
        mv_off: "配信は利用できません",
        mv_pip: "ピクチャーインピクチャー",
        mv_play: "再生 / 一時停止",
        mv_qual: "画質",
        mv_retry: "タップして再試行",
        mv_search: "ライブのブックマークを検索",
        mv_title: "プレーヤー",
        no_vid: "動画なし",
        note_ph: "メモ... (#タグ)",
        pk_cancel: "キャンセル",
        pk_found: "件一致",
        pk_hint: "ページ上の要素をタップしてください",
        pk_ok: "非表示",
        pk_up: "上の階層",
        qual: "画質",
        rad_media: "メディア",
        rad_nav: "お気に入り",
        rad_next: "次へ",
        rad_page: "ページ",
        rad_prev: "前へ",
        rad_reset: "ボタン位置をリセット",
        rad_title: "ラジアルメニュー",
        rad_vdn: "音量ダウン",
        rad_view: "表示",
        rad_vup: "音量アップ",
        rec: "録画",
        rec_tip: "タップ: 録画 · 長押し: 設定",
        rel: "再読込",
        rs_cur: "現在のストリーム:",
        rs_gbh: "GB/時",
        rs_hint: "パートが選択したサイズに達すると、別のファイルとして保存され、録画は新しいパートで続きます。データは小さな単位でディスクに書き込まれ、ディスクが使えない場合はスマホのメモリに残るため、その場合はパートを小さめにしてください。",
        rs_mbmin: "MB/分",
        rs_next: "変更は次のパートまたは新しい録画から適用されます。",
        rs_now: "現在",
        rs_off: "オフ",
        rs_p: "パート",
        rs_part: "分割サイズ(ファイルを分割)",
        rs_q_hi: "高",
        rs_q_low: "低",
        rs_q_mid: "中",
        rs_q_min: "節約",
        rs_quality: "画質(ビットレート)",
        rs_recover: "未保存の録画を復元",
        rs_resnote: "キャプチャはストリームの解像度で録画します。上の設定で変わるのはビットレート(圧縮)だけで、1080p〜360pは期待できる画質の目安です。HLSモードでは、指定した高さ以下のストリームが選ばれます。",
        rs_src: "ソース",
        rs_src_cap: "キャプチャ(再エンコード)",
        rs_src_hls: "HLS(再エンコードなし)",
        rs_src_note: "HLSは、配信のセグメントを再エンコードせずそのままダウンロードし、MP4として保存します。配信のアドレスが必要で、利用できない場合はキャプチャが使われます。画質は元の配信と同じ、オリジナルのままです。録画は小さなチェックポイントごとにディスクへ書き込まれます。",
        rs_t: "合計",
        rs_title: "録画設定",
        rs_total: "合計サイズで停止",
        rs_upto: "最大",
        search: "検索",
        seen_now: "たった今",
        seen_tip: "最終オンライン",
        settings: "設定",
        sleep_custom: "時間を指定",
        sleep_hint: "時間になるとプレーヤーが停止し、録画中の場合は録画が止まって保存されます。黒い画面をタップすると元に戻ります。",
        sleep_left: "残り",
        sleep_sec: "秒",
        sleep_start: "開始",
        sleep_title: "スリープタイマー",
        sleep_wake: "スリープタイマーが終了しました。\nタップして続行",
        snap: "撮影",
        sort_date: "日付",
        sort_last: "最終",
        sort_live: "ライブ",
        sort_name: "名前",
        sort_viewers: "視聴者",
        sound: "音声",
        st_av_refresh: "アバターを更新",
        st_back: "戻る",
        st_btns: "パネルのボタン",
        st_btns_hint: "タップしてボタンを非表示にしたり、元に戻したりできます。",
        st_btns_reset: "すべてのボタンを表示",
        st_color: "パネルの色",
        st_color_rnd: "ランダム",
        st_cols: "列数",
        st_custom: "カスタムカラー",
        st_data: "データ",
        st_fav: "お気に入り",
        st_favnav: "操作パネルの「前へ/次へ」ボタン",
        st_group: "グループ",
        st_hide_grp: "グループ選択を非表示",
        st_hide_note: "メモ欄(タグ)を非表示",
        st_hide_topic: "モデルの説明を非表示",
        st_hist: "視聴履歴",
        st_hist_on: "履歴を保存",
        st_hue: "色相",
        st_lang: "言語",
        st_lig: "明度",
        st_list: "リスト",
        st_look: "外観",
        st_open: "開く",
        st_prev: "モデルのプレビュー",
        st_prev_off: "更新しない",
        st_prev_spd: "プレビューの更新",
        st_private: "プライベート",
        st_ptags: "プロフィールのタグを非表示 (BongaCams)",
        st_rec: "動画の録画",
        st_sat: "彩度",
        st_scale: "パネルのサイズ",
        st_site_dark: "サイトのダークテーマ",
        st_star: "サムネイルの星",
        star_add: "お気に入りに追加",
        star_del: "お気に入りから削除",
        star_del_q: "このモデルにはメモまたはグループがあります。お気に入りから削除しますか?",
        thtr: "シアター",
        lock: "ロック",
        tp_less: "[閉じる]",
        tp_more: "[もっと見る]",
        u_d: "日",
        u_h: "時間",
        u_m: "分",
        viewers_tip: "ルームの視聴者数",
        w_new: "未視聴",
        w_seen: "視聴済み",
    },
    ZH: {
        cal_pres_btn: "预测",
        cal_pres_h: "通常在线(根据观察)",
        cal_pres_none: "暂无数据。使用网站时预测会逐渐生成。",
        cal_pres_info: "有数据的模特:{a} / {b}",
        cal_pres_on: "在线记录",
        blur_mute: "静音时模糊视频",
        ago: "{n}前",
        bg: "背景",
        bk_csv: "CSV 表格",
        bk_csv_fav: "收藏夹",
        bk_csv_hist: "历史",
        bk_csv_ses: "会话",
        bk_exp: "导出备份",
        bk_imp: "导入备份",
        bk_p_fav: "收藏夹",
        bk_p_hist: "历史",
        bk_p_set: "设置",
        bk_parts: "备份内容",
        bm_add: "加入收藏夹",
        bm_all: "全部",
        bm_del: "删除",
        bm_dup: "已添加!",
        bm_empty: "列表为空。",
        bm_find: "按名称搜索…",
        bm_nomatch: "没有符合筛选条件的模特",
        bm_online: "在线",
        bm_online_t: "仅显示在线",
        bm_pin: "置顶",
        bm_priv: "私密",
        bm_title: "收藏夹",
        bm_unpin: "取消置顶",
        bt_hints: "提示",
        bt_stats: "观众",
        bt_vol: "音量",
        cal_all_tag: "所有标签",
        cal_allday: "全天",
        cal_c_dur: "时长",
        cal_c_model: "模特",
        cal_c_n: "次数",
        cal_c_time: "进入",
        cal_hist: "观看记录",
        cal_loaded: "已加载时间表:{a}/{b}",
        cal_models: "模特",
        cal_no_sched: "时间表来自收藏模特的个人资料。请打开“收藏”并稍等几秒。",
        cal_none: "这一天没有内容",
        cal_reset: "重置",
        cal_sched: "时间表",
        cal_title: "日历",
        cal_today: "今天",
        cal_total: "合计",
        cal_watched: "已观看",
        cf_cancel: "取消",
        cf_ok: "是",
        ch_avg: "日均",
        ch_btn: "图表",
        ch_days: "每日时长,14天",
        ch_hours: "按小时的活跃度",
        ch_peak: "峰值",
        ch_top: "时长排行",
        copy: "链接",
        dark: "深色 / 浅色主题",
        don_copied: "地址已复制",
        don_fail: "复制失败",
        don_sub: "感谢使用",
        don_tap: "点击地址可查看完整内容,右侧按钮可复制。",
        don_tip: "支持作者",
        don_title: "请作者喝杯咖啡",
        drag: "⁞⁞ 控制面板",
        err: "错误",
        favs: "收藏",
        fr_hold: "长按查看当前画面",
        fr_none: "无画面:模特已离线",
        fs: "全屏",
        grp_all: "所有分组",
        grp_new: "+ 新建分组…",
        grp_none: "无分组",
        hd_all: "全部隐藏",
        hd_cats: "分类",
        hd_clear: "删除我的所有规则",
        hd_details: "详情",
        hd_g_chat: "聊天",
        hd_g_site: "网站",
        hd_g_win: "弹窗",
        hd_hidden: "已隐藏 {n}/{m}",
        hd_na: "暂未找到",
        hd_none: "暂无规则。",
        hd_off: "全部显示",
        hd_p_clean: "纯净播放器",
        hd_p_min: "最简",
        hd_peek: "长按项目可查看其隐藏的内容",
        hd_pick: "选择元素",
        hd_reset: "重置此网站",
        hd_rules: "我的规则",
        hd_title: "隐藏设置",
        hide: "隐藏 (H)",
        hints: "M=声音 F=全屏 T=影院 H=隐藏",
        hist_clear: "清空",
        hist_clear_q: "删除全部观看历史?",
        hist_del: "从历史中移除",
        hist_empty: "暂无记录。视频播放时才会计时。",
        hist_s_last: "最近",
        hist_s_time: "时长",
        hist_s_vis: "访问",
        hist_title: "历史",
        hist_total: "模特:{n} · 总计:{t}",
        hist_visits: "次访问",
        home: "主页",
        lang_auto: "自动",
        lang_title: "语言",
        min: "最小化",
        mv_abr_row: "自动画质",
        mv_abr_t: "自动画质:画面卡顿或网络较弱时降低画质,连接稳定时提高画质",
        mv_add: "添加",
        mv_auto: "自动",
        mv_auto_all: "自动添加所有在线直播流",
        mv_back: "返回",
        mv_bg: "后台播放",
        mv_bg_t: "切换标签页、关闭此窗口或离开浏览器时,直播流会继续播放",
        mv_bm_h: "在线收藏",
        mv_clear_q: "要关闭所有直播流吗?",
        mv_close: "关闭",
        mv_cols: "布局",
        mv_cur: "当前直播流",
        mv_dvr: "回退缓冲",
        mv_dvr_off: "关闭",
        mv_fs: "全屏",
        mv_full: "窗口已满",
        mv_go: "添加",
        mv_hint: "添加直播流即可同时观看多个。点按某个画面,声音会切换到它。",
        mv_link_h: "或通过链接",
        mv_load: "加载中…",
        mv_min_u: "分钟",
        mv_mute: "静音",
        mv_none: "没有在线收藏",
        mv_off: "直播流不可用",
        mv_pip: "画中画",
        mv_play: "播放 / 暂停",
        mv_qual: "画质",
        mv_retry: "点按重试",
        mv_search: "搜索在线收藏",
        mv_title: "播放器",
        no_vid: "无视频",
        note_ph: "备注... (#标签)",
        pk_cancel: "取消",
        pk_found: "个匹配",
        pk_hint: "点击页面上的元素",
        pk_ok: "隐藏",
        pk_up: "上一级",
        qual: "画质",
        rad_media: "媒体",
        rad_nav: "收藏夹",
        rad_next: "下一个",
        rad_page: "页面",
        rad_prev: "上一个",
        rad_reset: "重置按钮位置",
        rad_title: "环形菜单",
        rad_vdn: "调小",
        rad_view: "显示",
        rad_vup: "调大",
        rec: "录制",
        rec_tip: "点按:录制 · 长按:设置",
        rel: "重新加载",
        rs_cur: "当前直播流:",
        rs_gbh: "GB/小时",
        rs_hint: "当某一段达到所选大小时,会保存为单独的文件,录制继续写入新的一段。数据会分小块写入磁盘;如果磁盘不可用,则保留在手机内存中,此时建议选择更小的分段。",
        rs_mbmin: "MB/分钟",
        rs_next: "更改将从下一段或新的录制开始生效。",
        rs_now: "当前",
        rs_off: "关闭",
        rs_p: "段",
        rs_part: "分段大小(文件会被拆分)",
        rs_q_hi: "高",
        rs_q_low: "低",
        rs_q_mid: "中",
        rs_q_min: "省空间",
        rs_quality: "画质(码率)",
        rs_recover: "恢复未保存的录制",
        rs_resnote: "捕获模式按直播流的分辨率录制:上面的选项只改变码率(压缩),1080p–360p 表示预期的画质。HLS 模式下会选择高度不超过所选值的直播流。",
        rs_src: "来源",
        rs_src_cap: "捕获(重新编码)",
        rs_src_hls: "HLS(不重新编码)",
        rs_src_note: "HLS 会原样下载直播流分段,不重新编码,并保存为 MP4。需要直播流地址;如果没有,则使用捕获。画质与源直播流相同,保持原始画质。录制内容会以小检查点写入磁盘。",
        rs_t: "总计",
        rs_title: "录制设置",
        rs_total: "达到总大小时停止",
        rs_upto: "最多",
        search: "搜索",
        seen_now: "刚刚",
        seen_tip: "最后在线",
        settings: "设置",
        sleep_custom: "自定义时间",
        sleep_hint: "时间到后,播放器会关闭,正在进行的录制会停止并保存。点按黑屏即可恢复播放器。",
        sleep_left: "剩余",
        sleep_sec: "秒",
        sleep_start: "开始",
        sleep_title: "睡眠定时器",
        sleep_wake: "睡眠定时已结束。\n点按继续",
        snap: "截图",
        sort_date: "日期",
        sort_last: "最近",
        sort_live: "直播",
        sort_name: "名称",
        sort_viewers: "观众",
        sound: "声音",
        st_av_refresh: "刷新头像",
        st_back: "返回",
        st_btns: "面板按钮",
        st_btns_hint: "点按可隐藏按钮或将其恢复。",
        st_btns_reset: "显示所有按钮",
        st_color: "面板颜色",
        st_color_rnd: "随机",
        st_cols: "列数",
        st_custom: "自定义颜色",
        st_data: "数据",
        st_fav: "收藏夹",
        st_favnav: "控制面板上的“上一个 / 下一个”按钮",
        st_group: "群组",
        st_hide_grp: "隐藏分组选择",
        st_hide_note: "隐藏备注栏(标签)",
        st_hide_topic: "隐藏模特简介",
        st_hist: "观看历史",
        st_hist_on: "保存历史",
        st_hue: "色相",
        st_lang: "语言",
        st_lig: "亮度",
        st_list: "列表",
        st_look: "外观",
        st_open: "打开",
        st_prev: "模特预览",
        st_prev_off: "不刷新",
        st_prev_spd: "预览刷新",
        st_private: "私密",
        st_ptags: "隐藏个人资料标签 (BongaCams)",
        st_rec: "视频录制",
        st_sat: "饱和度",
        st_scale: "面板大小",
        st_site_dark: "网站深色主题",
        st_star: "缩略图上的星标",
        star_add: "加入收藏夹",
        star_del: "从收藏夹移除",
        star_del_q: "该模特有备注或分组。要从收藏夹移除吗?",
        thtr: "影院",
        lock: "锁定",
        tp_less: "[收起]",
        tp_more: "[展开]",
        u_d: "天",
        u_h: "小时",
        u_m: "分钟",
        viewers_tip: "房间内观众",
        w_new: "未观看",
        w_seen: "已观看",
    },
    KO: {
        cal_pres_btn: "예측",
        cal_pres_h: "보통 온라인 (관찰 기준)",
        cal_pres_none: "아직 데이터가 없습니다. 사이트를 사용하면 예측이 채워집니다.",
        cal_pres_info: "데이터가 있는 모델: {a}/{b}",
        cal_pres_on: "온라인 기록",
        blur_mute: "소리가 꺼지면 영상 흐리게",
        ago: "{n} 전",
        bg: "배경",
        bk_csv: "CSV 표",
        bk_csv_fav: "즐겨찾기",
        bk_csv_hist: "기록",
        bk_csv_ses: "세션",
        bk_exp: "백업 내보내기",
        bk_imp: "백업 가져오기",
        bk_p_fav: "즐겨찾기",
        bk_p_hist: "기록",
        bk_p_set: "설정",
        bk_parts: "백업 내용",
        bm_add: "즐겨찾기에 추가",
        bm_all: "전체",
        bm_del: "삭제",
        bm_dup: "이미 추가됨!",
        bm_empty: "목록이 비어 있습니다.",
        bm_find: "이름으로 검색…",
        bm_nomatch: "필터에 맞는 모델이 없습니다",
        bm_online: "온라인",
        bm_online_t: "온라인만 표시",
        bm_pin: "맨 위에 고정",
        bm_priv: "프라이빗",
        bm_title: "즐겨찾기",
        bm_unpin: "고정 해제",
        bt_hints: "도움말",
        bt_stats: "시청자",
        bt_vol: "볼륨",
        cal_all_tag: "모든 태그",
        cal_allday: "종일",
        cal_c_dur: "시간",
        cal_c_model: "모델",
        cal_c_n: "횟수",
        cal_c_time: "입장",
        cal_hist: "시청 기록",
        cal_loaded: "일정 불러옴: {a}/{b}",
        cal_models: "모델",
        cal_no_sched: "일정은 즐겨찾기 프로필에서 가져옵니다. 즐겨찾기를 열고 몇 초 기다려 주세요.",
        cal_none: "이날은 비어 있습니다",
        cal_reset: "초기화",
        cal_sched: "일정",
        cal_title: "캘린더",
        cal_today: "오늘",
        cal_total: "합계",
        cal_watched: "시청함",
        cf_cancel: "취소",
        cf_ok: "예",
        ch_avg: "일평균",
        ch_btn: "차트",
        ch_days: "일별 시간, 14일",
        ch_hours: "시간대별 활동",
        ch_peak: "최고치",
        ch_top: "시간 순위",
        copy: "링크",
        dark: "다크 / 라이트 테마",
        don_copied: "주소가 복사되었습니다",
        don_fail: "복사 실패",
        don_sub: "이용해 주셔서 감사합니다",
        don_tap: "주소를 탭하면 전체가 표시되고, 오른쪽 버튼으로 복사할 수 있습니다.",
        don_tip: "제작자 후원",
        don_title: "제작자에게 커피 한 잔 사주기",
        drag: "⁞⁞ 컨트롤",
        err: "오류",
        favs: "즐겨찾기",
        fr_hold: "길게 눌러 현재 프레임 보기",
        fr_none: "프레임 없음: 모델이 오프라인입니다",
        fs: "전체",
        grp_all: "모든 그룹",
        grp_new: "+ 새 그룹…",
        grp_none: "그룹 없음",
        hd_all: "모두 숨기기",
        hd_cats: "카테고리",
        hd_clear: "내 규칙 모두 삭제",
        hd_details: "세부 정보",
        hd_g_chat: "채팅",
        hd_g_site: "사이트",
        hd_g_win: "팝업",
        hd_hidden: "{m}개 중 {n}개 숨김",
        hd_na: "아직 찾지 못함",
        hd_none: "아직 규칙이 없습니다.",
        hd_off: "모두 표시",
        hd_p_clean: "깔끔한 플레이어",
        hd_p_min: "최소",
        hd_peek: "항목을 길게 누르면 무엇을 숨기는지 볼 수 있습니다",
        hd_pick: "요소 선택",
        hd_reset: "이 사이트 초기화",
        hd_rules: "내 규칙",
        hd_title: "숨기기",
        hide: "숨기기 (H)",
        hints: "M=소리 F=전체 T=극장 H=숨김",
        hist_clear: "비우기",
        hist_clear_q: "시청 기록을 모두 삭제할까요?",
        hist_del: "기록에서 제거",
        hist_empty: "아직 비어 있습니다. 영상이 재생되는 동안 시간이 집계됩니다.",
        hist_s_last: "최근",
        hist_s_time: "시간",
        hist_s_vis: "방문",
        hist_title: "기록",
        hist_total: "모델: {n} · 합계: {t}",
        hist_visits: "회",
        home: "홈",
        lang_auto: "자동",
        lang_title: "언어",
        min: "최소화",
        mv_abr_row: "자동 화질",
        mv_abr_t: "자동 화질: 영상이 끊기거나 네트워크가 약하면 화질을 낮추고, 연결이 안정되면 높입니다",
        mv_add: "추가",
        mv_auto: "자동",
        mv_auto_all: "모든 라이브 스트림 자동 추가",
        mv_back: "뒤로",
        mv_bg: "백그라운드 재생",
        mv_bg_t: "탭을 전환하거나 이 창을 닫거나 브라우저를 나가도 스트림이 계속 재생됩니다",
        mv_bm_h: "라이브 즐겨찾기",
        mv_clear_q: "모든 스트림을 닫을까요?",
        mv_close: "닫기",
        mv_cols: "레이아웃",
        mv_cur: "현재 스트림",
        mv_dvr: "되감기 버퍼",
        mv_dvr_off: "끔",
        mv_fs: "전체 화면",
        mv_full: "창이 가득 찼습니다",
        mv_go: "추가",
        mv_hint: "스트림을 추가해 여러 개를 동시에 시청하세요. 타일을 탭하면 소리가 그쪽으로 옮겨집니다.",
        mv_link_h: "또는 링크로",
        mv_load: "불러오는 중…",
        mv_min_u: "분",
        mv_mute: "음소거",
        mv_none: "라이브 즐겨찾기 없음",
        mv_off: "스트림을 사용할 수 없음",
        mv_pip: "화면 속 화면",
        mv_play: "재생 / 일시정지",
        mv_qual: "화질",
        mv_retry: "탭하여 다시 시도",
        mv_search: "라이브 즐겨찾기 검색",
        mv_title: "플레이어",
        no_vid: "영상 없음",
        note_ph: "메모... (#태그)",
        pk_cancel: "취소",
        pk_found: "개 일치",
        pk_hint: "페이지의 요소를 탭하세요",
        pk_ok: "숨기기",
        pk_up: "상위",
        qual: "화질",
        rad_media: "미디어",
        rad_nav: "즐겨찾기",
        rad_next: "다음",
        rad_page: "페이지",
        rad_prev: "이전",
        rad_reset: "버튼 위치 초기화",
        rad_title: "원형 메뉴",
        rad_vdn: "소리 작게",
        rad_view: "화면",
        rad_vup: "소리 크게",
        rec: "녹화",
        rec_tip: "탭: 녹화 · 길게 누르기: 설정",
        rel: "새로고침",
        rs_cur: "현재 스트림:",
        rs_gbh: "GB/시간",
        rs_hint: "파트가 선택한 크기에 도달하면 별도 파일로 저장되고 녹화는 새 파트에서 이어집니다. 데이터는 작은 단위로 디스크에 기록되며, 디스크를 사용할 수 없으면 휴대폰 메모리에 남으므로 이때는 더 작은 파트를 고르세요.",
        rs_mbmin: "MB/분",
        rs_next: "변경 사항은 다음 파트 또는 새 녹화부터 적용됩니다.",
        rs_now: "현재",
        rs_off: "끔",
        rs_p: "파트",
        rs_part: "파트 크기(파일 분할)",
        rs_q_hi: "높음",
        rs_q_low: "낮음",
        rs_q_mid: "보통",
        rs_q_min: "절약",
        rs_quality: "화질(비트레이트)",
        rs_recover: "저장되지 않은 녹화 복구",
        rs_resnote: "캡처 모드는 스트림 해상도로 녹화합니다. 위 옵션은 비트레이트(압축)만 바꾸며, 1080p–360p는 예상 화질을 나타냅니다. HLS 모드에서는 지정한 높이를 넘지 않는 스트림이 선택됩니다.",
        rs_src: "소스",
        rs_src_cap: "캡처(재인코딩)",
        rs_src_hls: "HLS(재인코딩 없음)",
        rs_src_note: "HLS는 스트림 세그먼트를 재인코딩 없이 그대로 다운로드하여 MP4로 저장합니다. 스트림 주소가 필요하며, 없으면 캡처가 사용됩니다. 화질은 원본 스트림과 동일한 원본 화질입니다. 녹화는 작은 체크포인트 단위로 디스크에 기록됩니다.",
        rs_t: "전체",
        rs_title: "녹화 설정",
        rs_total: "총 크기에서 중지",
        rs_upto: "최대",
        search: "검색",
        seen_now: "방금",
        seen_tip: "마지막 온라인",
        settings: "설정",
        sleep_custom: "직접 설정",
        sleep_hint: "시간이 되면 플레이어가 꺼지고 녹화 중이던 영상은 중지되어 저장됩니다. 검은 화면을 탭하면 플레이어가 돌아옵니다.",
        sleep_left: "남음",
        sleep_sec: "초",
        sleep_start: "시작",
        sleep_title: "취침 타이머",
        sleep_wake: "취침 타이머가 종료되었습니다.\n탭하여 계속",
        snap: "캡처",
        sort_date: "날짜",
        sort_last: "최근",
        sort_live: "라이브",
        sort_name: "이름",
        sort_viewers: "시청자",
        sound: "소리",
        st_av_refresh: "아바타 새로고침",
        st_back: "뒤로",
        st_btns: "패널 버튼",
        st_btns_hint: "버튼을 숨기거나 다시 표시하려면 탭하세요.",
        st_btns_reset: "모든 버튼 표시",
        st_color: "패널 색상",
        st_color_rnd: "무작위",
        st_cols: "열 수",
        st_custom: "사용자 지정 색상",
        st_data: "데이터",
        st_fav: "즐겨찾기",
        st_favnav: "컨트롤의 “이전 / 다음” 버튼",
        st_group: "그룹",
        st_hide_grp: "그룹 선택 숨기기",
        st_hide_note: "메모 입력란(태그) 숨기기",
        st_hide_topic: "모델 소개 숨기기",
        st_hist: "시청 기록",
        st_hist_on: "기록 저장",
        st_hue: "색조",
        st_lang: "언어",
        st_lig: "명도",
        st_list: "목록",
        st_look: "외관",
        st_open: "열기",
        st_prev: "모델 미리보기",
        st_prev_off: "갱신 안 함",
        st_prev_spd: "미리보기 갱신",
        st_private: "프라이빗",
        st_ptags: "프로필 태그 숨기기 (BongaCams)",
        st_rec: "영상 녹화",
        st_sat: "채도",
        st_scale: "패널 크기",
        st_site_dark: "사이트 다크 테마",
        st_star: "썸네일 별표",
        star_add: "즐겨찾기에 추가",
        star_del: "즐겨찾기에서 제거",
        star_del_q: "이 모델에 메모나 그룹이 있습니다. 즐겨찾기에서 제거할까요?",
        thtr: "극장",
        lock: "잠금",
        tp_less: "[접기]",
        tp_more: "[더보기]",
        u_d: "일",
        u_h: "시간",
        u_m: "분",
        viewers_tip: "방 시청자 수",
        w_new: "미시청",
        w_seen: "시청함",
    },
    HE: {
        cal_pres_btn: "תחזית",
        cal_pres_h: "בדרך כלל מחובר (לפי תצפיות)",
        cal_pres_none: "עדיין אין נתונים. התחזית תתמלא בזמן שאתה משתמש באתר.",
        cal_pres_info: "דוגמניות עם נתונים: {a} מתוך {b}",
        cal_pres_on: "מעקב מחובר",
        blur_mute: "טשטוש וידאו כשהוא מושתק",
        ago: "לפני {n}",
        bg: "רקע",
        bk_csv: "טבלאות CSV",
        bk_csv_fav: "מועדפים",
        bk_csv_hist: "היסטוריה",
        bk_csv_ses: "סשנים",
        bk_exp: "ייצוא גיבוי",
        bk_imp: "ייבוא גיבוי",
        bk_p_fav: "מועדפים",
        bk_p_hist: "היסטוריה",
        bk_p_set: "הגדרות",
        bk_parts: "תוכן הגיבוי",
        bm_add: "הוסף למועדפים",
        bm_all: "הכול",
        bm_del: "מחק",
        bm_dup: "כבר נוסף!",
        bm_empty: "הרשימה ריקה.",
        bm_find: "חיפוש לפי שם…",
        bm_nomatch: "אין מודלים התואמים לסינון",
        bm_online: "אונליין",
        bm_online_t: "הצג רק אונליין",
        bm_pin: "הצמד למעלה",
        bm_priv: "פרטי",
        bm_title: "מועדפים",
        bm_unpin: "בטל הצמדה",
        bt_hints: "טיפים",
        bt_stats: "צופים",
        bt_vol: "עוצמת קול",
        cal_all_tag: "כל התגיות",
        cal_allday: "כל היום",
        cal_c_dur: "משך",
        cal_c_model: "מודל",
        cal_c_n: "פעמים",
        cal_c_time: "כניסה",
        cal_hist: "היסטוריה",
        cal_loaded: "לוח הזמנים נטען: {a} מתוך {b}",
        cal_models: "מודלים",
        cal_no_sched: "לוח הזמנים נלקח מפרופילי המועדפים. פתחו את המועדפים והמתינו כמה שניות.",
        cal_none: "אין דבר ביום זה",
        cal_reset: "איפוס",
        cal_sched: "לוח זמנים",
        cal_title: "לוח שנה",
        cal_today: "היום",
        cal_total: "סה״כ",
        cal_watched: "נצפה",
        cf_cancel: "ביטול",
        cf_ok: "כן",
        ch_avg: "ממוצע יומי",
        ch_btn: "תרשימים",
        ch_days: "זמן ליום, 14 ימים",
        ch_hours: "פעילות לפי שעה",
        ch_peak: "שיא",
        ch_top: "מובילים לפי זמן",
        copy: "קישור",
        dark: "ערכת נושא כהה / בהירה",
        don_copied: "הכתובת הועתקה",
        don_fail: "ההעתקה נכשלה",
        don_sub: "תודה על השימוש",
        don_tap: "הקש על כתובת כדי לראות אותה במלואה. הכפתור שלצידה מעתיק אותה.",
        don_tip: "תמיכה ביוצר",
        don_title: "קנה ליוצר כוס קפה",
        drag: "⁞⁞ בקרה",
        err: "שגיאה",
        favs: "מועדפים",
        fr_hold: "לחיצה ארוכה להצגת הפריים הנוכחי",
        fr_none: "אין פריים: המודל לא מחובר",
        fs: "מסך מלא",
        grp_all: "כל הקבוצות",
        grp_new: "+ קבוצה חדשה…",
        grp_none: "ללא קבוצה",
        hd_all: "הסתר הכול",
        hd_cats: "קטגוריות",
        hd_clear: "מחק את כל הכללים שלי",
        hd_details: "פרטים",
        hd_g_chat: "צ'אט",
        hd_g_site: "אתר",
        hd_g_win: "חלונות קופצים",
        hd_hidden: "הוסתרו {n} מתוך {m}",
        hd_na: "עדיין לא נמצא",
        hd_none: "אין כללים עדיין.",
        hd_off: "הצג הכול",
        hd_p_clean: "נגן נקי",
        hd_p_min: "מינימלי",
        hd_peek: "החזק פריט לחוץ כדי לראות מה הוא מסתיר",
        hd_pick: "בחר אלמנט",
        hd_reset: "איפוס עבור אתר זה",
        hd_rules: "הכללים שלי",
        hd_title: "הסתרה",
        hide: "הסתר (H)",
        hints: "M=קול F=מסך מלא T=תיאטרון H=הסתר",
        hist_clear: "נקה",
        hist_clear_q: "למחוק את כל היסטוריית הצפייה?",
        hist_del: "הסר מההיסטוריה",
        hist_empty: "ריק לבינתיים. הזמן נספר בזמן שהווידאו מנגן.",
        hist_s_last: "אחרונים",
        hist_s_time: "זמן",
        hist_s_vis: "ביקורים",
        hist_title: "היסטוריה",
        hist_total: "מודלים: {n} · סה״כ: {t}",
        hist_visits: "ביקורים",
        home: "בית",
        lang_auto: "אוטומטי",
        lang_title: "שפה",
        min: "מזעור",
        mv_abr_row: "איכות אוטומטית",
        mv_abr_t: "איכות אוטומטית: מורידה את האיכות כשהווידאו נתקע או כשהרשת חלשה, ומעלה אותה כשהחיבור יציב",
        mv_add: "הוסף",
        mv_auto: "אוטומטי",
        mv_auto_all: "הוסף אוטומטית את כל הסטרימים החיים",
        mv_back: "חזרה",
        mv_bg: "ניגון ברקע",
        mv_bg_t: "הסטרימים ממשיכים להתנגן כשעוברים בין לשוניות, סוגרים את החלון הזה או יוצאים מהדפדפן",
        mv_bm_h: "מועדפים בשידור חי",
        mv_clear_q: "לסגור את כל הסטרימים?",
        mv_close: "סגור",
        mv_cols: "פריסה",
        mv_cur: "הסטרים הנוכחי",
        mv_dvr: "מאגר הרצה לאחור",
        mv_dvr_off: "כבוי",
        mv_fs: "מסך מלא",
        mv_full: "החלון מלא",
        mv_go: "הוסף",
        mv_hint: "הוסף סטרימים כדי לצפות בכמה בו־זמנית. הקש על אריח כדי להעביר אליו את הקול.",
        mv_link_h: "או באמצעות קישור",
        mv_load: "טוען…",
        mv_min_u: "דק׳",
        mv_mute: "השתק",
        mv_none: "אין מועדפים בשידור חי",
        mv_off: "הסטרים אינו זמין",
        mv_pip: "תמונה בתוך תמונה",
        mv_play: "הפעל / השהה",
        mv_qual: "איכות",
        mv_retry: "הקש לניסיון חוזר",
        mv_search: "חיפוש במועדפים בשידור חי",
        mv_title: "נגן",
        no_vid: "אין וידאו",
        note_ph: "הערה... (#תגיות)",
        pk_cancel: "ביטול",
        pk_found: "התאמות",
        pk_hint: "הקש על אלמנט בדף",
        pk_ok: "הסתר",
        pk_up: "למעלה",
        qual: "איכות",
        rad_media: "מדיה",
        rad_nav: "מועדפים",
        rad_next: "הבא",
        rad_page: "עמוד",
        rad_prev: "הקודם",
        rad_reset: "איפוס מיקום הכפתור",
        rad_title: "תפריט מעגלי",
        rad_vdn: "שקט יותר",
        rad_view: "תצוגה",
        rad_vup: "חזק יותר",
        rec: "הקלטה",
        rec_tip: "הקשה: הקלטה · לחיצה ארוכה: הגדרות",
        rel: "רענון",
        rs_cur: "הסטרים הנוכחי:",
        rs_gbh: "GB/שעה",
        rs_hint: "כשחלק מגיע לגודל שנבחר, הוא נשמר כקובץ נפרד וההקלטה ממשיכה בחדש. הנתונים נכתבים לדיסק במקטעים קטנים; אם הדיסק אינו זמין, הם נשארים בזיכרון הטלפון, ולכן כדאי לבחור חלקים קטנים יותר.",
        rs_mbmin: "MB/דקה",
        rs_next: "השינויים חלים מהחלק הבא או מהקלטה חדשה.",
        rs_now: "עכשיו",
        rs_off: "כבוי",
        rs_p: "חלק",
        rs_part: "גודל חלק (הקובץ מתפצל)",
        rs_q_hi: "גבוהה",
        rs_q_low: "נמוכה",
        rs_q_mid: "בינונית",
        rs_q_min: "חיסכון",
        rs_quality: "איכות (ביטרייט)",
        rs_recover: "שחזור הקלטות שלא נשמרו",
        rs_resnote: "מצב לכידה מקליט ברזולוציית הסטרים: האפשרויות למעלה משנות רק את קצב הסיביות (דחיסה), ו-1080p–360p מציינות את איכות התמונה הצפויה. במצב HLS נבחר סטרים שגובהו אינו עולה על הגובה שנבחר.",
        rs_src: "מקור",
        rs_src_cap: "לכידה (קידוד מחדש)",
        rs_src_hls: "HLS (בלי קידוד מחדש)",
        rs_src_note: "HLS מוריד את קטעי הסטרים כפי שהם, בלי קידוד מחדש, ושומר MP4. נדרשת כתובת הסטרים; אם היא אינה זמינה, משתמשים בלכידה. האיכות מקורית, כמו בסטרים המקורי. ההקלטה נכתבת לדיסק בנקודות ביניים קטנות.",
        rs_t: "סה״כ",
        rs_title: "הגדרות הקלטה",
        rs_total: "עצור בגודל כולל",
        rs_upto: "עד",
        search: "חיפוש",
        seen_now: "הרגע",
        seen_tip: "נראה אונליין לאחרונה",
        settings: "הגדרות",
        sleep_custom: "זמן מותאם",
        sleep_hint: "בתום הזמן הנגן יכבה, וכל הקלטה תיעצר ותישמר. הקש על המסך השחור כדי להחזיר את הנגן.",
        sleep_left: "נותר",
        sleep_sec: "שנ׳",
        sleep_start: "התחל",
        sleep_title: "טיימר שינה",
        sleep_wake: "טיימר השינה הסתיים.\nהקש להמשך",
        snap: "צילום",
        sort_date: "תאריך",
        sort_last: "נראה",
        sort_live: "לייב",
        sort_name: "שם",
        sort_viewers: "צופים",
        sound: "קול",
        st_av_refresh: "רענון תמונות פרופיל",
        st_back: "חזרה",
        st_btns: "כפתורי הפאנל",
        st_btns_hint: "הקש כדי להסתיר כפתור או להחזיר אותו.",
        st_btns_reset: "הצג את כל הכפתורים",
        st_color: "צבע הפאנל",
        st_color_rnd: "אקראי",
        st_cols: "עמודות",
        st_custom: "צבע מותאם",
        st_data: "נתונים",
        st_fav: "מועדפים",
        st_favnav: "כפתורי «הקודם / הבא» בבקרה",
        st_group: "קבוצה",
        st_hide_grp: "הסתר את בחירת הקבוצה",
        st_hide_note: "הסתר שדה הערה (תגיות)",
        st_hide_topic: "הסתר את תיאור המודל",
        st_hist: "היסטוריית צפייה",
        st_hist_on: "שמור היסטוריה",
        st_hue: "גוון",
        st_lang: "שפה",
        st_lig: "בהירות",
        st_list: "רשימה",
        st_look: "מראה",
        st_open: "פתח",
        st_prev: "תצוגות מקדימות של מודלים",
        st_prev_off: "ללא רענון",
        st_prev_spd: "רענון התצוגה המקדימה",
        st_private: "פרטי",
        st_ptags: "הסתר תגיות פרופיל (BongaCams)",
        st_rec: "הקלטת וידאו",
        st_sat: "רוויה",
        st_scale: "גודל הפאנל",
        st_site_dark: "ערכת נושא כהה לאתר",
        st_star: "כוכב על התמונות הממוזערות",
        star_add: "הוסף למועדפים",
        star_del: "הסר מהמועדפים",
        star_del_q: "למודל הזה יש הערה או קבוצה. להסיר מהמועדפים?",
        thtr: "תיאטרון",
        lock: "נעול",
        tp_less: "[פחות]",
        tp_more: "[עוד]",
        u_d: "י׳",
        u_h: "שע׳",
        u_m: "דק׳",
        viewers_tip: "צופים בחדר",
        w_new: "לא נצפה",
        w_seen: "נצפה",
    },
    AR: {
        cal_pres_btn: "التوقع",
        cal_pres_h: "عادةً متصل (حسب الملاحظات)",
        cal_pres_none: "لا توجد بيانات بعد. يمتلئ التوقع أثناء استخدامك للموقع.",
        cal_pres_info: "نماذج لديها بيانات: {a} من {b}",
        cal_pres_on: "تتبع الاتصال",
        blur_mute: "تمويه الفيديو عند كتم الصوت",
        ago: "منذ {n}",
        bg: "الخلفية",
        bk_csv: "جداول CSV",
        bk_csv_fav: "المفضلة",
        bk_csv_hist: "السجل",
        bk_csv_ses: "الجلسات",
        bk_exp: "تصدير النسخة الاحتياطية",
        bk_imp: "استيراد النسخة الاحتياطية",
        bk_p_fav: "المفضلة",
        bk_p_hist: "السجل",
        bk_p_set: "الإعدادات",
        bk_parts: "محتوى النسخة الاحتياطية",
        bm_add: "إضافة إلى المفضلة",
        bm_all: "الكل",
        bm_del: "حذف",
        bm_dup: "تمت الإضافة مسبقًا!",
        bm_empty: "القائمة فارغة.",
        bm_find: "البحث بالاسم…",
        bm_nomatch: "لا يوجد موديل يطابق الفلتر",
        bm_online: "متصل",
        bm_online_t: "إظهار المتصلين فقط",
        bm_pin: "تثبيت في الأعلى",
        bm_priv: "خاص",
        bm_title: "المفضلة",
        bm_unpin: "إلغاء التثبيت",
        bt_hints: "تلميحات",
        bt_stats: "المشاهدون",
        bt_vol: "الصوت",
        cal_all_tag: "كل الوسوم",
        cal_allday: "طوال اليوم",
        cal_c_dur: "المدة",
        cal_c_model: "العارضة",
        cal_c_n: "المرات",
        cal_c_time: "الدخول",
        cal_hist: "السجل",
        cal_loaded: "تم تحميل الجدول: {a} من {b}",
        cal_models: "العارضات",
        cal_no_sched: "يؤخذ الجدول من ملفات المفضلة. افتح المفضلة وانتظر بضع ثوانٍ.",
        cal_none: "لا شيء في هذا اليوم",
        cal_reset: "إعادة ضبط",
        cal_sched: "الجدول",
        cal_title: "التقويم",
        cal_today: "اليوم",
        cal_total: "المجموع",
        cal_watched: "تمت المشاهدة",
        cf_cancel: "إلغاء",
        cf_ok: "نعم",
        ch_avg: "المتوسط اليومي",
        ch_btn: "الرسوم البيانية",
        ch_days: "الوقت يوميًا، 14 يومًا",
        ch_hours: "النشاط حسب الساعة",
        ch_peak: "الذروة",
        ch_top: "الأعلى حسب الوقت",
        copy: "رابط",
        dark: "مظهر داكن / فاتح",
        don_copied: "تم نسخ العنوان",
        don_fail: "فشل النسخ",
        don_sub: "شكرًا لاستخدامك الأداة",
        don_tap: "اضغط على العنوان لعرضه كاملًا. الزر المجاور له ينسخه.",
        don_tip: "ادعم المطوّر",
        don_title: "اشترِ للمطوّر فنجان قهوة",
        drag: "⁞⁞ التحكم",
        err: "خطأ",
        favs: "المفضلة",
        fr_hold: "اضغط مطولًا لعرض اللقطة الحالية",
        fr_none: "لا توجد لقطة: الموديل غير متصل",
        fs: "ملء الشاشة",
        grp_all: "كل المجموعات",
        grp_new: "+ مجموعة جديدة…",
        grp_none: "بدون مجموعة",
        hd_all: "إخفاء الكل",
        hd_cats: "الفئات",
        hd_clear: "حذف جميع قواعدي",
        hd_details: "التفاصيل",
        hd_g_chat: "الدردشة",
        hd_g_site: "الموقع",
        hd_g_win: "النوافذ المنبثقة",
        hd_hidden: "تم إخفاء {n} من {m}",
        hd_na: "لم يُعثر عليه بعد",
        hd_none: "لا توجد قواعد بعد.",
        hd_off: "إظهار الكل",
        hd_p_clean: "مشغّل نظيف",
        hd_p_min: "الحد الأدنى",
        hd_peek: "اضغط مطولًا على عنصر لترى ما الذي يخفيه",
        hd_pick: "اختيار عنصر",
        hd_reset: "إعادة الضبط لهذا الموقع",
        hd_rules: "قواعدي",
        hd_title: "الإخفاء",
        hide: "إخفاء (H)",
        hints: "M=الصوت F=ملء الشاشة T=المسرح H=إخفاء",
        hist_clear: "مسح",
        hist_clear_q: "حذف كل سجل المشاهدة؟",
        hist_del: "إزالة من السجل",
        hist_empty: "فارغ حتى الآن. يُحتسب الوقت أثناء تشغيل الفيديو.",
        hist_s_last: "الأحدث",
        hist_s_time: "الوقت",
        hist_s_vis: "الزيارات",
        hist_title: "السجل",
        hist_total: "الموديلات: {n} · الإجمالي: {t}",
        hist_visits: "زيارات",
        home: "الرئيسية",
        lang_auto: "تلقائي",
        lang_title: "اللغة",
        min: "تصغير",
        mv_abr_row: "جودة تلقائية",
        mv_abr_t: "جودة تلقائية: تخفّض الجودة عند التقطيع أو ضعف الشبكة وترفعها عندما يكون الاتصال مستقرًا",
        mv_add: "إضافة",
        mv_auto: "تلقائي",
        mv_auto_all: "إضافة كل البثوث المباشرة تلقائيًا",
        mv_back: "رجوع",
        mv_bg: "التشغيل في الخلفية",
        mv_bg_t: "تستمر البثوث في التشغيل عند تبديل علامة التبويب أو إغلاق هذه النافذة أو مغادرة المتصفح",
        mv_bm_h: "المفضلة المباشرة",
        mv_clear_q: "إغلاق كل البثوث؟",
        mv_close: "إغلاق",
        mv_cols: "التخطيط",
        mv_cur: "البث الحالي",
        mv_dvr: "مخزن الإرجاع",
        mv_dvr_off: "إيقاف",
        mv_fs: "ملء الشاشة",
        mv_full: "النافذة ممتلئة",
        mv_go: "إضافة",
        mv_hint: "أضف بثوثًا لمشاهدة عدة بثوث معًا. اضغط على بلاطة لنقل الصوت إليها.",
        mv_link_h: "أو عبر رابط",
        mv_load: "جارٍ التحميل…",
        mv_min_u: "د",
        mv_mute: "كتم الصوت",
        mv_none: "لا توجد مفضلة مباشرة",
        mv_off: "البث غير متاح",
        mv_pip: "صورة داخل صورة",
        mv_play: "تشغيل / إيقاف مؤقت",
        mv_qual: "الجودة",
        mv_retry: "اضغط لإعادة المحاولة",
        mv_search: "البحث في المفضلة المباشرة",
        mv_title: "المشغّل",
        no_vid: "لا فيديو",
        note_ph: "ملاحظة... (#وسوم)",
        pk_cancel: "إلغاء",
        pk_found: "تطابق",
        pk_hint: "اضغط على عنصر في الصفحة",
        pk_ok: "إخفاء",
        pk_up: "أعلى",
        qual: "الجودة",
        rad_media: "الوسائط",
        rad_nav: "المفضلة",
        rad_next: "التالي",
        rad_page: "الصفحة",
        rad_prev: "السابق",
        rad_reset: "إعادة ضبط موضع الزر",
        rad_title: "القائمة الدائرية",
        rad_vdn: "أخفض",
        rad_view: "العرض",
        rad_vup: "أعلى",
        rec: "تسجيل",
        rec_tip: "نقرة: تسجيل · ضغط مطوّل: الإعدادات",
        rel: "تحديث",
        rs_cur: "البث الحالي:",
        rs_gbh: "GB/س",
        rs_hint: "عندما يصل الجزء إلى الحجم المختار يُحفظ كملف مستقل ويتابع التسجيل في جزء جديد. تُكتب البيانات على القرص بكتل صغيرة؛ وإذا لم يكن القرص متاحًا تبقى في ذاكرة الهاتف، ولذلك يُفضَّل اختيار أجزاء أصغر.",
        rs_mbmin: "MB/د",
        rs_next: "تسري التغييرات من الجزء التالي أو من تسجيل جديد.",
        rs_now: "الآن",
        rs_off: "إيقاف",
        rs_p: "جزء",
        rs_part: "حجم الجزء (يُقسَّم الملف)",
        rs_q_hi: "عالية",
        rs_q_low: "منخفضة",
        rs_q_mid: "متوسطة",
        rs_q_min: "توفير",
        rs_quality: "الجودة (معدل البت)",
        rs_recover: "استعادة التسجيلات غير المحفوظة",
        rs_resnote: "يسجّل وضع الالتقاط بدقة البث: الخيارات أعلاه تغيّر معدل البت (الضغط) فقط، وتشير 1080p–360p إلى جودة الصورة المتوقعة. في وضع HLS يُختار بث لا يزيد ارتفاعه على الارتفاع المحدد.",
        rs_src: "المصدر",
        rs_src_cap: "الالتقاط (إعادة ترميز)",
        rs_src_hls: "HLS (بدون إعادة ترميز)",
        rs_src_note: "يقوم HLS بتنزيل مقاطع البث كما هي دون إعادة ترميز ويحفظ MP4. يحتاج إلى عنوان البث؛ وإن لم يتوفر يُستخدم الالتقاط. الجودة أصلية كما في البث المصدر. يُكتب التسجيل على القرص على نقاط حفظ صغيرة.",
        rs_t: "الإجمالي",
        rs_title: "إعدادات التسجيل",
        rs_total: "إيقاف عند الحجم الإجمالي",
        rs_upto: "حتى",
        search: "بحث",
        seen_now: "الآن",
        seen_tip: "آخر ظهور على الإنترنت",
        settings: "الإعدادات",
        sleep_custom: "وقت مخصص",
        sleep_hint: "عند انتهاء الوقت يُطفأ المشغّل ويتوقف أي تسجيل ويُحفظ. المس الشاشة السوداء لإعادته.",
        sleep_left: "المتبقي",
        sleep_sec: "ث",
        sleep_start: "ابدأ",
        sleep_title: "مؤقّت النوم",
        sleep_wake: "انتهى مؤقّت النوم.\nالمس للمتابعة",
        snap: "لقطة",
        sort_date: "التاريخ",
        sort_last: "آخر ظهور",
        sort_live: "مباشر",
        sort_name: "الاسم",
        sort_viewers: "المشاهدون",
        sound: "الصوت",
        st_av_refresh: "تحديث الصور الرمزية",
        st_back: "رجوع",
        st_btns: "أزرار اللوحة",
        st_btns_hint: "المس لإخفاء زر أو إعادته.",
        st_btns_reset: "إظهار كل الأزرار",
        st_color: "لون اللوحة",
        st_color_rnd: "عشوائي",
        st_cols: "الأعمدة",
        st_custom: "لون مخصص",
        st_data: "البيانات",
        st_fav: "المفضلة",
        st_favnav: "أزرار «السابق / التالي» في لوحة التحكم",
        st_group: "مجموعة",
        st_hide_grp: "إخفاء اختيار المجموعة",
        st_hide_note: "إخفاء حقل الملاحظة (الوسوم)",
        st_hide_topic: "إخفاء وصف الموديل",
        st_hist: "سجل المشاهدة",
        st_hist_on: "حفظ السجل",
        st_hue: "التدرج اللوني",
        st_lang: "اللغة",
        st_lig: "السطوع",
        st_list: "القائمة",
        st_look: "المظهر",
        st_open: "فتح",
        st_prev: "معاينات الموديلات",
        st_prev_off: "بدون تحديث",
        st_prev_spd: "تحديث المعاينة",
        st_private: "خاص",
        st_ptags: "إخفاء وسوم الملف الشخصي (BongaCams)",
        st_rec: "تسجيل الفيديو",
        st_sat: "التشبع",
        st_scale: "حجم اللوحة",
        st_site_dark: "الوضع الداكن للموقع",
        st_star: "نجمة على الصور المصغرة",
        star_add: "إضافة إلى المفضلة",
        star_del: "إزالة من المفضلة",
        star_del_q: "لدى هذا الموديل ملاحظة أو مجموعة. هل تريد إزالته من المفضلة؟",
        thtr: "المسرح",
        lock: "قفل",
        tp_less: "[أقل]",
        tp_more: "[المزيد]",
        u_d: "ي",
        u_h: "س",
        u_m: "د",
        viewers_tip: "المشاهدون في الغرفة",
        w_new: "لم تتم المشاهدة",
        w_seen: "تمت المشاهدة",
    },
    HI: {
        cal_pres_btn: "पूर्वानुमान",
        cal_pres_h: "आमतौर पर ऑनलाइन (अवलोकन के अनुसार)",
        cal_pres_none: "अभी कोई डेटा नहीं। साइट इस्तेमाल करने पर पूर्वानुमान भरता जाएगा।",
        cal_pres_info: "डेटा वाले मॉडल: {a} / {b}",
        cal_pres_on: "ऑनलाइन ट्रैकिंग",
        blur_mute: "बिना आवाज़ के वीडियो धुंधला करें",
        ago: "{n} पहले",
        bg: "बैकग्राउंड",
        bk_csv: "CSV तालिकाएँ",
        bk_csv_fav: "पसंदीदा",
        bk_csv_hist: "इतिहास",
        bk_csv_ses: "सेशन",
        bk_exp: "बैकअप निर्यात करें",
        bk_imp: "बैकअप आयात करें",
        bk_p_fav: "पसंदीदा",
        bk_p_hist: "इतिहास",
        bk_p_set: "सेटिंग्स",
        bk_parts: "बैकअप की सामग्री",
        bm_add: "पसंदीदा में जोड़ें",
        bm_all: "सभी",
        bm_del: "हटाएँ",
        bm_dup: "पहले ही जोड़ा जा चुका है!",
        bm_empty: "सूची खाली है।",
        bm_find: "नाम से खोजें…",
        bm_nomatch: "फ़िल्टर से कोई मॉडल मेल नहीं खाता",
        bm_online: "ऑनलाइन",
        bm_online_t: "सिर्फ़ ऑनलाइन दिखाएँ",
        bm_pin: "ऊपर पिन करें",
        bm_priv: "प्राइवेट",
        bm_title: "पसंदीदा",
        bm_unpin: "पिन हटाएँ",
        bt_hints: "सुझाव",
        bt_stats: "दर्शक",
        bt_vol: "आवाज़",
        cal_all_tag: "सभी टैग",
        cal_allday: "पूरे दिन",
        cal_c_dur: "अवधि",
        cal_c_model: "मॉडल",
        cal_c_n: "बार",
        cal_c_time: "प्रवेश",
        cal_hist: "इतिहास",
        cal_loaded: "शेड्यूल लोड हुआ: {a}/{b}",
        cal_models: "मॉडल",
        cal_no_sched: "शेड्यूल पसंदीदा की प्रोफ़ाइल से लिया जाता है। पसंदीदा खोलें और कुछ सेकंड प्रतीक्षा करें।",
        cal_none: "इस दिन कुछ नहीं है",
        cal_reset: "रीसेट",
        cal_sched: "शेड्यूल",
        cal_title: "कैलेंडर",
        cal_today: "आज",
        cal_total: "कुल",
        cal_watched: "देखा",
        cf_cancel: "रद्द करें",
        cf_ok: "हाँ",
        ch_avg: "दैनिक औसत",
        ch_btn: "चार्ट",
        ch_days: "प्रति दिन समय, 14 दिन",
        ch_hours: "घंटे के अनुसार गतिविधि",
        ch_peak: "शिखर",
        ch_top: "समय के अनुसार शीर्ष",
        copy: "लिंक",
        dark: "डार्क / लाइट थीम",
        don_copied: "पता कॉपी हो गया",
        don_fail: "कॉपी नहीं हो सका",
        don_sub: "इस्तेमाल करने के लिए धन्यवाद",
        don_tap: "पूरा पता देखने के लिए उस पर टैप करें। दाईं ओर का बटन उसे कॉपी करता है।",
        don_tip: "लेखक का समर्थन करें",
        don_title: "लेखक को एक कॉफ़ी पिलाएँ",
        drag: "⁞⁞ नियंत्रण",
        err: "त्रुटि",
        favs: "पसंदीदा",
        fr_hold: "मौजूदा फ़्रेम देखने के लिए दबाए रखें",
        fr_none: "फ़्रेम उपलब्ध नहीं: मॉडल ऑफ़लाइन है",
        fs: "फुलस्क्रीन",
        grp_all: "सभी ग्रुप",
        grp_new: "+ नया ग्रुप…",
        grp_none: "कोई ग्रुप नहीं",
        hd_all: "सब छिपाएँ",
        hd_cats: "श्रेणियाँ",
        hd_clear: "मेरे सभी नियम हटाएँ",
        hd_details: "विवरण",
        hd_g_chat: "चैट",
        hd_g_site: "साइट",
        hd_g_win: "पॉप-अप",
        hd_hidden: "{m} में से {n} छिपे",
        hd_na: "अभी नहीं मिला",
        hd_none: "अभी कोई नियम नहीं।",
        hd_off: "सब दिखाएँ",
        hd_p_clean: "साफ़ प्लेयर",
        hd_p_min: "न्यूनतम",
        hd_peek: "कोई आइटम दबाए रखें, देखने के लिए कि वह क्या छिपाता है",
        hd_pick: "एलिमेंट चुनें",
        hd_reset: "इस साइट के लिए रीसेट करें",
        hd_rules: "मेरे नियम",
        hd_title: "छिपाना",
        hide: "छिपाएँ (H)",
        hints: "M=आवाज़ F=फुलस्क्रीन T=थिएटर H=छिपाएँ",
        hist_clear: "साफ़ करें",
        hist_clear_q: "पूरा देखने का इतिहास हटाएँ?",
        hist_del: "इतिहास से हटाएँ",
        hist_empty: "अभी खाली है। वीडियो चलने पर ही समय गिना जाता है।",
        hist_s_last: "हाल के",
        hist_s_time: "समय",
        hist_s_vis: "विज़िट",
        hist_title: "इतिहास",
        hist_total: "मॉडल: {n} · कुल: {t}",
        hist_visits: "विज़िट",
        home: "होम",
        lang_auto: "ऑटो",
        lang_title: "भाषा",
        min: "छोटा करें",
        mv_abr_row: "ऑटो क्वालिटी",
        mv_abr_t: "ऑटो क्वालिटी: वीडियो अटकने या नेटवर्क कमज़ोर होने पर क्वालिटी घटाती है और कनेक्शन स्थिर होने पर बढ़ाती है",
        mv_add: "जोड़ें",
        mv_auto: "ऑटो",
        mv_auto_all: "सभी लाइव स्ट्रीम अपने-आप जोड़ें",
        mv_back: "वापस",
        mv_bg: "बैकग्राउंड में चलाएँ",
        mv_bg_t: "टैब बदलने, यह विंडो बंद करने या ब्राउज़र छोड़ने पर भी स्ट्रीम चलती रहती हैं",
        mv_bm_h: "लाइव बुकमार्क",
        mv_clear_q: "सभी स्ट्रीम बंद करें?",
        mv_close: "बंद करें",
        mv_cols: "लेआउट",
        mv_cur: "मौजूदा स्ट्रीम",
        mv_dvr: "रिवाइंड बफ़र",
        mv_dvr_off: "बंद",
        mv_fs: "फुलस्क्रीन",
        mv_full: "विंडो भरी हुई है",
        mv_go: "जोड़ें",
        mv_hint: "एक साथ कई स्ट्रीम देखने के लिए स्ट्रीम जोड़ें। आवाज़ किसी टाइल पर ले जाने के लिए उस पर टैप करें।",
        mv_link_h: "या लिंक से",
        mv_load: "लोड हो रहा है…",
        mv_min_u: "मिनट",
        mv_mute: "म्यूट",
        mv_none: "कोई लाइव बुकमार्क नहीं",
        mv_off: "स्ट्रीम उपलब्ध नहीं",
        mv_pip: "पिक्चर-इन-पिक्चर",
        mv_play: "चलाएँ / रोकें",
        mv_qual: "क्वालिटी",
        mv_retry: "दोबारा कोशिश के लिए टैप करें",
        mv_search: "लाइव बुकमार्क खोजें",
        mv_title: "प्लेयर",
        no_vid: "वीडियो नहीं",
        note_ph: "नोट... (#टैग)",
        pk_cancel: "रद्द करें",
        pk_found: "मिलान",
        pk_hint: "पेज पर किसी एलिमेंट को टैप करें",
        pk_ok: "छिपाएँ",
        pk_up: "ऊपर",
        qual: "क्वालिटी",
        rad_media: "मीडिया",
        rad_nav: "पसंदीदा",
        rad_next: "अगला",
        rad_page: "पेज",
        rad_prev: "पिछला",
        rad_reset: "बटन की स्थिति रीसेट करें",
        rad_title: "गोलाकार मेनू",
        rad_vdn: "आवाज़ घटाएँ",
        rad_view: "दृश्य",
        rad_vup: "आवाज़ बढ़ाएँ",
        rec: "रिकॉर्ड",
        rec_tip: "टैप: रिकॉर्ड · दबाए रखें: सेटिंग्स",
        rel: "रीलोड",
        rs_cur: "मौजूदा स्ट्रीम:",
        rs_gbh: "GB/घंटा",
        rs_hint: "जब कोई पार्ट चुने गए आकार तक पहुँचता है, तो वह अलग फ़ाइल के रूप में सेव हो जाता है और रिकॉर्डिंग नए पार्ट में जारी रहती है। डेटा छोटे-छोटे हिस्सों में डिस्क पर लिखा जाता है; डिस्क उपलब्ध न हो तो वह फ़ोन की मेमोरी में रहता है, तब छोटे पार्ट चुनना बेहतर है।",
        rs_mbmin: "MB/मिनट",
        rs_next: "बदलाव अगले पार्ट या नई रिकॉर्डिंग से लागू होते हैं।",
        rs_now: "अभी",
        rs_off: "बंद",
        rs_p: "पार्ट",
        rs_part: "पार्ट का आकार (फ़ाइल बँटती है)",
        rs_q_hi: "उच्च",
        rs_q_low: "कम",
        rs_q_mid: "मध्यम",
        rs_q_min: "बचत",
        rs_quality: "क्वालिटी (बिटरेट)",
        rs_recover: "सेव न हुई रिकॉर्डिंग वापस पाएँ",
        rs_resnote: "कैप्चर मोड स्ट्रीम के रेज़ोल्यूशन पर रिकॉर्ड करता है: ऊपर के विकल्प सिर्फ़ बिटरेट (कंप्रेशन) बदलते हैं, और 1080p–360p अपेक्षित तस्वीर की क्वालिटी बताते हैं। HLS मोड में चुनी गई ऊँचाई से ज़्यादा ऊँची स्ट्रीम नहीं चुनी जाती।",
        rs_src: "स्रोत",
        rs_src_cap: "कैप्चर (री-एन्कोड)",
        rs_src_hls: "HLS (बिना री-एन्कोड)",
        rs_src_note: "HLS स्ट्रीम के सेगमेंट जैसे हैं वैसे ही, बिना री-एन्कोड किए, डाउनलोड करता है और MP4 सेव करता है। इसके लिए स्ट्रीम का पता चाहिए; न हो तो कैप्चर इस्तेमाल होता है। क्वालिटी मूल रहती है, स्रोत स्ट्रीम जैसी। रिकॉर्डिंग छोटे चेकपॉइंट में डिस्क पर लिखी जाती है।",
        rs_t: "कुल",
        rs_title: "रिकॉर्डिंग सेटिंग्स",
        rs_total: "कुल आकार पर रोकें",
        rs_upto: "तक",
        search: "खोज",
        seen_now: "अभी-अभी",
        seen_tip: "आख़िरी बार ऑनलाइन",
        settings: "सेटिंग्स",
        sleep_custom: "अपना समय",
        sleep_hint: "समय पूरा होने पर प्लेयर बंद हो जाएगा, और चल रही रिकॉर्डिंग रुककर सेव हो जाएगी। प्लेयर वापस लाने के लिए काली स्क्रीन पर टैप करें।",
        sleep_left: "शेष",
        sleep_sec: "सेक",
        sleep_start: "शुरू करें",
        sleep_title: "स्लीप टाइमर",
        sleep_wake: "स्लीप टाइमर पूरा हुआ।\nजारी रखने के लिए टैप करें",
        snap: "स्क्रीनशॉट",
        sort_date: "तारीख़",
        sort_last: "आख़िरी बार",
        sort_live: "लाइव",
        sort_name: "नाम",
        sort_viewers: "दर्शक",
        sound: "आवाज़",
        st_av_refresh: "अवतार रीफ़्रेश करें",
        st_back: "वापस",
        st_btns: "पैनल बटन",
        st_btns_hint: "किसी बटन को छिपाने या वापस लाने के लिए टैप करें।",
        st_btns_reset: "सभी बटन दिखाएँ",
        st_color: "पैनल का रंग",
        st_color_rnd: "रैंडम",
        st_cols: "कॉलम",
        st_custom: "अपना रंग",
        st_data: "डेटा",
        st_fav: "पसंदीदा",
        st_favnav: "कंट्रोल पर «पिछला / अगला» बटन",
        st_group: "ग्रुप",
        st_hide_grp: "ग्रुप चयन छिपाएँ",
        st_hide_note: "नोट फ़ील्ड (टैग) छिपाएँ",
        st_hide_topic: "मॉडल का विवरण छिपाएँ",
        st_hist: "देखने का इतिहास",
        st_hist_on: "इतिहास सहेजें",
        st_hue: "रंगत",
        st_lang: "भाषा",
        st_lig: "चमक",
        st_list: "सूची",
        st_look: "दिखावट",
        st_open: "खोलें",
        st_prev: "मॉडल प्रीव्यू",
        st_prev_off: "रीफ़्रेश नहीं",
        st_prev_spd: "प्रीव्यू रीफ़्रेश",
        st_private: "प्राइवेट",
        st_ptags: "प्रोफ़ाइल टैग छिपाएँ (BongaCams)",
        st_rec: "वीडियो रिकॉर्डिंग",
        st_sat: "संतृप्ति",
        st_scale: "पैनल का आकार",
        st_site_dark: "साइट की डार्क थीम",
        st_star: "थंबनेल पर तारा",
        star_add: "पसंदीदा में जोड़ें",
        star_del: "पसंदीदा से हटाएँ",
        star_del_q: "इस मॉडल का नोट या ग्रुप है। पसंदीदा से हटाएँ?",
        thtr: "थिएटर",
        lock: "लॉक",
        tp_less: "[कम]",
        tp_more: "[और]",
        u_d: "दिन",
        u_h: "घं",
        u_m: "मिनट",
        viewers_tip: "कमरे में दर्शक",
        w_new: "नहीं देखा",
        w_seen: "देखा",
    },
    ID: {
        cal_pres_btn: "Prakiraan",
        cal_pres_h: "Biasanya online (dari pengamatan)",
        cal_pres_none: "Belum ada data. Prakiraan terisi saat Anda memakai situs.",
        cal_pres_info: "Model dengan data: {a} dari {b}",
        cal_pres_on: "Pelacakan online",
        blur_mute: "Buramkan video tanpa suara",
        ago: "{n} lalu",
        bg: "LATAR",
        bk_csv: "Tabel CSV",
        bk_csv_fav: "Favorit",
        bk_csv_hist: "Riwayat",
        bk_csv_ses: "Sesi",
        bk_exp: "Ekspor cadangan",
        bk_imp: "Impor cadangan",
        bk_p_fav: "Favorit",
        bk_p_hist: "Riwayat",
        bk_p_set: "Pengaturan",
        bk_parts: "Isi cadangan",
        bm_add: "Tambah ke favorit",
        bm_all: "Semua",
        bm_del: "Hapus",
        bm_dup: "Sudah ditambahkan!",
        bm_empty: "Daftar kosong.",
        bm_find: "Cari berdasarkan nama…",
        bm_nomatch: "Tidak ada model yang cocok dengan filter",
        bm_online_t: "Tampilkan hanya online",
        bm_pin: "Sematkan di atas",
        bm_title: "Favorit",
        bm_unpin: "Lepas sematan",
        bt_hints: "Petunjuk",
        bt_stats: "Penonton",
        cal_all_tag: "Semua tag",
        cal_allday: "Sepanjang hari",
        cal_c_dur: "Durasi",
        cal_c_n: "Kali",
        cal_c_time: "Masuk",
        cal_hist: "Riwayat",
        cal_loaded: "Jadwal dimuat: {a} dari {b}",
        cal_models: "Model",
        cal_no_sched: "Jadwal diambil dari profil favorit Anda. Buka Favorit dan tunggu beberapa detik.",
        cal_none: "Tidak ada apa pun di hari ini",
        cal_reset: "Atur ulang",
        cal_sched: "Jadwal",
        cal_title: "Kalender",
        cal_today: "Hari ini",
        cal_watched: "Ditonton",
        cf_cancel: "Batal",
        cf_ok: "Ya",
        ch_avg: "Rata-rata harian",
        ch_btn: "Grafik",
        ch_days: "Waktu per hari, 14 hari",
        ch_hours: "Aktivitas per jam",
        ch_peak: "Puncak",
        ch_top: "Teratas menurut waktu",
        copy: "TAUTAN",
        dark: "Tema gelap / terang",
        don_copied: "Alamat disalin",
        don_fail: "Gagal menyalin",
        don_sub: "Terima kasih telah menggunakannya",
        don_tap: "Ketuk alamat untuk melihatnya utuh. Tombol di kanan menyalinnya.",
        don_tip: "Dukung pembuat",
        don_title: "Traktir pembuat secangkir kopi",
        drag: "⁞⁞ KONTROL",
        favs: "FAVORIT",
        fr_hold: "Tahan untuk melihat bingkai saat ini",
        fr_none: "Tidak ada bingkai: model sedang offline",
        fs: "PENUH",
        grp_all: "Semua grup",
        grp_new: "+ Grup baru…",
        grp_none: "Tanpa grup",
        hd_all: "Sembunyikan semua",
        hd_cats: "Kategori",
        hd_clear: "Hapus semua aturan saya",
        hd_details: "Detail",
        hd_g_chat: "Obrolan",
        hd_g_site: "Situs",
        hd_g_win: "Pop-up",
        hd_hidden: "{n} dari {m} disembunyikan",
        hd_na: "belum ditemukan",
        hd_none: "Belum ada aturan.",
        hd_off: "Tampilkan semua",
        hd_p_clean: "Pemutar bersih",
        hd_peek: "Tahan item untuk melihat apa yang disembunyikannya",
        hd_pick: "Pilih elemen",
        hd_reset: "Setel ulang untuk situs ini",
        hd_rules: "Aturan saya",
        hd_title: "Penyembunyian",
        hide: "Sembunyikan (H)",
        hints: "M=SUARA F=PENUH T=TEATER H=SEMBUNYI",
        hist_clear: "Hapus",
        hist_clear_q: "Hapus seluruh riwayat tontonan?",
        hist_del: "Hapus dari riwayat",
        hist_empty: "Masih kosong. Waktu dihitung saat video diputar.",
        hist_s_last: "Terbaru",
        hist_s_time: "Waktu",
        hist_s_vis: "Kunjungan",
        hist_title: "Riwayat",
        hist_total: "Model: {n} · Total: {t}",
        hist_visits: "kunjungan",
        home: "Beranda",
        lang_auto: "Otomatis",
        lang_title: "Bahasa",
        min: "Perkecil",
        mv_abr_row: "Kualitas otomatis",
        mv_abr_t: "Kualitas otomatis: menurunkan kualitas saat video tersendat atau jaringan lemah, dan menaikkannya saat koneksi stabil",
        mv_add: "Tambah",
        mv_auto: "Otomatis",
        mv_auto_all: "Tambahkan semua stream live secara otomatis",
        mv_back: "Kembali",
        mv_bg: "Putar di latar belakang",
        mv_bg_t: "Stream tetap diputar saat Anda berpindah tab, menutup jendela ini, atau keluar dari browser",
        mv_bm_h: "Bookmark live",
        mv_clear_q: "Tutup semua stream?",
        mv_close: "Tutup",
        mv_cols: "Tata letak",
        mv_cur: "Stream saat ini",
        mv_dvr: "Buffer putar ulang",
        mv_dvr_off: "Mati",
        mv_fs: "Layar penuh",
        mv_full: "Jendela penuh",
        mv_go: "Tambah",
        mv_hint: "Tambahkan stream untuk menonton beberapa sekaligus. Ketuk sebuah kotak untuk memindahkan suara ke sana.",
        mv_link_h: "Atau lewat tautan",
        mv_load: "Memuat…",
        mv_min_u: "mnt",
        mv_mute: "Bisukan",
        mv_none: "Tidak ada bookmark live",
        mv_off: "Stream tidak tersedia",
        mv_pip: "Gambar dalam gambar",
        mv_play: "Putar / jeda",
        mv_qual: "Kualitas",
        mv_retry: "ketuk untuk mencoba lagi",
        mv_search: "Cari bookmark live",
        mv_title: "Pemutar",
        no_vid: "TANPA VIDEO",
        note_ph: "Catatan... (#tag)",
        pk_cancel: "Batal",
        pk_found: "cocok",
        pk_hint: "Ketuk elemen di halaman",
        pk_ok: "Sembunyikan",
        pk_up: "Naik",
        qual: "KUALITAS",
        rad_nav: "Favorit",
        rad_next: "Berikutnya",
        rad_page: "Halaman",
        rad_prev: "Sebelumnya",
        rad_reset: "Atur ulang posisi tombol",
        rad_title: "Menu radial",
        rad_vdn: "Lebih pelan",
        rad_view: "Tampilan",
        rad_vup: "Lebih keras",
        rec: "REKAM",
        rec_tip: "Ketuk: rekam · Tahan: pengaturan",
        rel: "Muat ulang",
        rs_cur: "Stream saat ini:",
        rs_gbh: "GB/j",
        rs_hint: "Saat sebuah bagian mencapai ukuran yang dipilih, bagian itu disimpan sebagai file terpisah dan rekaman berlanjut di bagian baru. Data ditulis ke disk dalam blok kecil; jika disk tidak tersedia, data tetap di memori ponsel, jadi pilih bagian yang lebih kecil.",
        rs_mbmin: "MB/mnt",
        rs_next: "Perubahan berlaku mulai bagian berikutnya atau rekaman baru.",
        rs_now: "Sekarang",
        rs_off: "Mati",
        rs_p: "bagian",
        rs_part: "Ukuran bagian (file dipecah)",
        rs_q_hi: "Tinggi",
        rs_q_low: "Rendah",
        rs_q_mid: "Sedang",
        rs_q_min: "Hemat",
        rs_quality: "Kualitas (bitrate)",
        rs_recover: "Pulihkan rekaman yang belum disimpan",
        rs_resnote: "Capture merekam dengan resolusi stream: opsi di atas hanya mengubah bitrate (kompresi), dan 1080p–360p menunjukkan perkiraan kualitas gambar. Pada mode HLS, dipilih stream dengan tinggi tidak melebihi yang ditetapkan.",
        rs_src: "Sumber",
        rs_src_cap: "Tangkap (encode ulang)",
        rs_src_hls: "HLS (tanpa encode ulang)",
        rs_src_note: "HLS mengunduh segmen stream apa adanya, tanpa encode ulang, dan menyimpan MP4. Memerlukan alamat stream; jika tidak tersedia, digunakan Tangkap. Kualitasnya asli, sama seperti stream sumber. Rekaman ditulis ke disk dalam titik pemeriksaan kecil.",
        rs_title: "Pengaturan rekaman",
        rs_total: "Berhenti pada ukuran total",
        rs_upto: "hingga",
        search: "CARI",
        seen_now: "baru saja",
        seen_tip: "Terakhir online",
        settings: "Pengaturan",
        sleep_custom: "Waktu kustom",
        sleep_hint: "Saat waktu habis, pemutar dimatikan dan rekaman yang berjalan dihentikan lalu disimpan. Ketuk layar hitam untuk mengembalikannya.",
        sleep_left: "Sisa",
        sleep_sec: "dtk",
        sleep_start: "Mulai",
        sleep_title: "Timer tidur",
        sleep_wake: "Timer tidur selesai.\nKetuk untuk melanjutkan",
        snap: "FOTO",
        sort_date: "Tanggal",
        sort_last: "Terakhir dilihat",
        sort_name: "Nama",
        sort_viewers: "Penonton",
        sound: "SUARA",
        st_av_refresh: "Segarkan avatar",
        st_back: "Kembali",
        st_btns: "Tombol panel",
        st_btns_hint: "Ketuk untuk menyembunyikan tombol atau memunculkannya kembali.",
        st_btns_reset: "Tampilkan semua tombol",
        st_color: "Warna panel",
        st_color_rnd: "Acak",
        st_cols: "Kolom",
        st_custom: "Warna kustom",
        st_fav: "Favorit",
        st_favnav: "Tombol «sebelumnya / berikutnya» di kontrol",
        st_group: "Grup",
        st_hide_grp: "Sembunyikan pilihan grup",
        st_hide_note: "Sembunyikan kolom catatan (tag)",
        st_hide_topic: "Sembunyikan deskripsi model",
        st_hist: "Riwayat tontonan",
        st_hist_on: "Simpan riwayat",
        st_hue: "Rona",
        st_lang: "Bahasa",
        st_lig: "Kecerahan",
        st_list: "Daftar",
        st_look: "Tampilan",
        st_open: "Buka",
        st_prev: "Pratinjau model",
        st_prev_off: "Tanpa pembaruan",
        st_prev_spd: "Penyegaran pratinjau",
        st_private: "Privat",
        st_ptags: "Sembunyikan tag profil (BongaCams)",
        st_rec: "Perekaman video",
        st_sat: "Saturasi",
        st_scale: "Ukuran panel",
        st_site_dark: "Tema gelap situs",
        st_star: "Bintang di thumbnail",
        star_add: "Tambah ke favorit",
        star_del: "Hapus dari favorit",
        star_del_q: "Model ini punya catatan atau grup. Hapus dari favorit?",
        thtr: "TEATER",
        lock: "KUNCI",
        tp_less: "[Lebih sedikit]",
        tp_more: "[Selengkapnya]",
        u_d: "h",
        u_h: "j",
        u_m: "mnt",
        viewers_tip: "Penonton di ruangan",
        w_new: "Belum ditonton",
        w_seen: "Ditonton",
    },
    VI: {
        cal_pres_btn: "Dự báo",
        cal_pres_h: "Thường trực tuyến (theo quan sát)",
        cal_pres_none: "Chưa có dữ liệu. Dự báo sẽ được bổ sung khi bạn dùng trang.",
        cal_pres_info: "Người mẫu có dữ liệu: {a}/{b}",
        cal_pres_on: "Theo dõi trực tuyến",
        blur_mute: "Làm mờ video khi tắt tiếng",
        ago: "{n} trước",
        bg: "NỀN",
        bk_csv: "Bảng CSV",
        bk_csv_fav: "Yêu thích",
        bk_csv_hist: "Lịch sử",
        bk_csv_ses: "Phiên",
        bk_exp: "Xuất bản sao lưu",
        bk_imp: "Nhập bản sao lưu",
        bk_p_fav: "Yêu thích",
        bk_p_hist: "Lịch sử",
        bk_p_set: "Cài đặt",
        bk_parts: "Nội dung bản sao lưu",
        bm_add: "Thêm vào yêu thích",
        bm_all: "Tất cả",
        bm_del: "Xóa",
        bm_dup: "Đã thêm rồi!",
        bm_empty: "Danh sách trống.",
        bm_find: "Tìm theo tên…",
        bm_nomatch: "Không có model khớp bộ lọc",
        bm_online_t: "Chỉ hiện online",
        bm_pin: "Ghim lên đầu",
        bm_priv: "Riêng",
        bm_title: "Yêu thích",
        bm_unpin: "Bỏ ghim",
        bt_hints: "Gợi ý",
        bt_stats: "Người xem",
        bt_vol: "Âm lượng",
        cal_all_tag: "Tất cả thẻ",
        cal_allday: "Cả ngày",
        cal_c_dur: "Thời lượng",
        cal_c_model: "Người mẫu",
        cal_c_n: "Lần",
        cal_c_time: "Vào",
        cal_hist: "Lịch sử",
        cal_loaded: "Đã tải lịch: {a}/{b}",
        cal_models: "Người mẫu",
        cal_no_sched: "Lịch phát lấy từ hồ sơ của mục yêu thích. Hãy mở Yêu thích và chờ vài giây.",
        cal_none: "Hôm nay không có gì",
        cal_reset: "Đặt lại",
        cal_sched: "Lịch phát",
        cal_title: "Lịch",
        cal_today: "Hôm nay",
        cal_total: "Tổng",
        cal_watched: "Đã xem",
        cf_cancel: "Hủy",
        cf_ok: "Có",
        ch_avg: "Trung bình mỗi ngày",
        ch_btn: "Biểu đồ",
        ch_days: "Thời gian mỗi ngày, 14 ngày",
        ch_hours: "Hoạt động theo giờ",
        ch_peak: "Đỉnh",
        ch_top: "Xếp hạng theo thời gian",
        copy: "LIÊN KẾT",
        dark: "Giao diện tối / sáng",
        don_copied: "Đã sao chép địa chỉ",
        don_fail: "Sao chép thất bại",
        don_sub: "Cảm ơn bạn đã sử dụng",
        don_tap: "Chạm vào địa chỉ để xem đầy đủ. Nút bên phải sẽ sao chép nó.",
        don_tip: "Ủng hộ tác giả",
        don_title: "Mời tác giả một ly cà phê",
        drag: "⁞⁞ ĐIỀU KHIỂN",
        err: "LỖI",
        favs: "YÊU THÍCH",
        fr_hold: "Giữ để xem khung hình hiện tại",
        fr_none: "Không có khung hình: model đang offline",
        fs: "TOÀN MH",
        grp_all: "Tất cả nhóm",
        grp_new: "+ Nhóm mới…",
        grp_none: "Không nhóm",
        hd_all: "Ẩn tất cả",
        hd_cats: "Danh mục",
        hd_clear: "Xóa tất cả quy tắc của tôi",
        hd_details: "Chi tiết",
        hd_g_chat: "Trò chuyện",
        hd_g_site: "Trang web",
        hd_g_win: "Cửa sổ bật lên",
        hd_hidden: "Đã ẩn {n} trên {m}",
        hd_na: "chưa tìm thấy",
        hd_none: "Chưa có quy tắc.",
        hd_off: "Hiện tất cả",
        hd_p_clean: "Trình phát sạch",
        hd_p_min: "Tối thiểu",
        hd_peek: "Giữ một mục để xem nó ẩn gì",
        hd_pick: "Chọn phần tử",
        hd_reset: "Đặt lại cho trang này",
        hd_rules: "Quy tắc của tôi",
        hd_title: "Ẩn",
        hide: "Ẩn (H)",
        hints: "M=ÂM F=TOÀN MH T=RẠP H=ẨN",
        hist_clear: "Xóa",
        hist_clear_q: "Xóa toàn bộ lịch sử xem?",
        hist_del: "Xóa khỏi lịch sử",
        hist_empty: "Hiện còn trống. Thời gian được tính khi video đang phát.",
        hist_s_last: "Gần đây",
        hist_s_time: "Thời gian",
        hist_s_vis: "Lượt xem",
        hist_title: "Lịch sử",
        hist_total: "Model: {n} · Tổng: {t}",
        hist_visits: "lượt",
        home: "Trang chủ",
        lang_auto: "Tự động",
        lang_title: "Ngôn ngữ",
        min: "Thu nhỏ",
        mv_abr_row: "Chất lượng tự động",
        mv_abr_t: "Chất lượng tự động: hạ chất lượng khi video giật hoặc mạng yếu và tăng lên khi kết nối ổn định",
        mv_add: "Thêm",
        mv_auto: "Tự động",
        mv_auto_all: "Tự động thêm tất cả luồng đang live",
        mv_back: "Quay lại",
        mv_bg: "Phát trong nền",
        mv_bg_t: "Luồng vẫn tiếp tục phát khi bạn chuyển tab, đóng cửa sổ này hoặc rời trình duyệt",
        mv_bm_h: "Đánh dấu đang live",
        mv_clear_q: "Đóng tất cả luồng?",
        mv_close: "Đóng",
        mv_cols: "Bố cục",
        mv_cur: "Luồng hiện tại",
        mv_dvr: "Bộ đệm tua lại",
        mv_dvr_off: "Tắt",
        mv_fs: "Toàn màn hình",
        mv_full: "Cửa sổ đã đầy",
        mv_go: "Thêm",
        mv_hint: "Thêm luồng để xem nhiều luồng cùng lúc. Chạm vào một ô để chuyển âm thanh sang ô đó.",
        mv_link_h: "Hoặc bằng liên kết",
        mv_load: "Đang tải…",
        mv_min_u: "phút",
        mv_mute: "Tắt tiếng",
        mv_none: "Không có đánh dấu đang live",
        mv_off: "Luồng không khả dụng",
        mv_pip: "Hình trong hình",
        mv_play: "Phát / tạm dừng",
        mv_qual: "Chất lượng",
        mv_retry: "chạm để thử lại",
        mv_search: "Tìm trong đánh dấu đang live",
        mv_title: "Trình phát",
        no_vid: "KHÔNG VIDEO",
        note_ph: "Ghi chú... (#thẻ)",
        pk_cancel: "Hủy",
        pk_found: "khớp",
        pk_hint: "Chạm vào một phần tử trên trang",
        pk_ok: "Ẩn",
        pk_up: "Lên",
        qual: "CHẤT LƯỢNG",
        rad_media: "Phương tiện",
        rad_nav: "Yêu thích",
        rad_next: "Sau",
        rad_page: "Trang",
        rad_prev: "Trước",
        rad_reset: "Đặt lại vị trí nút",
        rad_title: "Menu vòng tròn",
        rad_vdn: "Giảm âm",
        rad_view: "Hiển thị",
        rad_vup: "Tăng âm",
        rec: "GHI",
        rec_tip: "Chạm: ghi · Giữ: cài đặt",
        rel: "Tải lại",
        rs_cur: "Luồng hiện tại:",
        rs_gbh: "GB/giờ",
        rs_hint: "Khi một phần đạt kích thước đã chọn, nó được lưu thành tệp riêng và việc ghi tiếp tục ở phần mới. Dữ liệu được ghi vào ổ đĩa theo từng khối nhỏ; nếu ổ đĩa không khả dụng, dữ liệu nằm trong bộ nhớ điện thoại, khi đó nên chọn các phần nhỏ hơn.",
        rs_mbmin: "MB/phút",
        rs_next: "Thay đổi có hiệu lực từ phần tiếp theo hoặc bản ghi mới.",
        rs_now: "Hiện tại",
        rs_off: "Tắt",
        rs_p: "phần",
        rs_part: "Kích thước phần (tệp được chia)",
        rs_q_hi: "Cao",
        rs_q_low: "Thấp",
        rs_q_mid: "Vừa",
        rs_q_min: "Tiết kiệm",
        rs_quality: "Chất lượng (bitrate)",
        rs_recover: "Khôi phục bản ghi chưa lưu",
        rs_resnote: "Chế độ Capture ghi theo độ phân giải của luồng: các tùy chọn ở trên chỉ thay đổi bitrate (nén), còn 1080p–360p cho biết chất lượng hình ảnh dự kiến. Ở chế độ HLS, hệ thống chọn luồng có chiều cao không vượt quá mức đã chọn.",
        rs_src: "Nguồn",
        rs_src_cap: "Ghi hình (mã hóa lại)",
        rs_src_hls: "HLS (không mã hóa lại)",
        rs_src_note: "HLS tải các phân đoạn của luồng nguyên trạng, không mã hóa lại, và lưu thành MP4. Cần địa chỉ luồng; nếu không có, sẽ dùng ghi hình. Chất lượng là gốc, giống luồng nguồn. Bản ghi được ghi vào đĩa theo từng điểm kiểm tra nhỏ.",
        rs_t: "tổng",
        rs_title: "Cài đặt ghi hình",
        rs_total: "Dừng ở tổng dung lượng",
        rs_upto: "tối đa",
        search: "TÌM",
        seen_now: "vừa xong",
        seen_tip: "Online lần cuối",
        settings: "Cài đặt",
        sleep_custom: "Thời gian tùy chỉnh",
        sleep_hint: "Khi hết giờ, trình phát sẽ tắt và mọi bản ghi đang chạy sẽ dừng và được lưu. Chạm vào màn hình đen để bật lại.",
        sleep_left: "Còn lại",
        sleep_sec: "giây",
        sleep_start: "Bắt đầu",
        sleep_title: "Hẹn giờ tắt",
        sleep_wake: "Hẹn giờ đã kết thúc.\nChạm để tiếp tục",
        snap: "CHỤP",
        sort_date: "Ngày",
        sort_last: "Lần cuối",
        sort_name: "Tên",
        sort_viewers: "Người xem",
        sound: "ÂM",
        st_av_refresh: "Làm mới ảnh đại diện",
        st_back: "Quay lại",
        st_btns: "Nút trên bảng",
        st_btns_hint: "Chạm để ẩn nút hoặc hiện lại.",
        st_btns_reset: "Hiện tất cả nút",
        st_color: "Màu bảng điều khiển",
        st_color_rnd: "Ngẫu nhiên",
        st_cols: "Số cột",
        st_custom: "Màu tùy chỉnh",
        st_data: "Dữ liệu",
        st_fav: "Yêu thích",
        st_favnav: "Nút «trước / sau» trên bảng điều khiển",
        st_group: "Nhóm",
        st_hide_grp: "Ẩn lựa chọn nhóm",
        st_hide_note: "Ẩn ô ghi chú (thẻ)",
        st_hide_topic: "Ẩn mô tả model",
        st_hist: "Lịch sử xem",
        st_hist_on: "Lưu lịch sử",
        st_hue: "Sắc độ",
        st_lang: "Ngôn ngữ",
        st_lig: "Độ sáng",
        st_list: "Danh sách",
        st_look: "Giao diện",
        st_open: "Mở",
        st_prev: "Xem trước model",
        st_prev_off: "Không làm mới",
        st_prev_spd: "Làm mới xem trước",
        st_private: "Riêng tư",
        st_ptags: "Ẩn thẻ hồ sơ (BongaCams)",
        st_rec: "Ghi video",
        st_sat: "Độ bão hòa",
        st_scale: "Kích thước bảng",
        st_site_dark: "Giao diện tối của trang",
        st_star: "Ngôi sao trên ảnh thu nhỏ",
        star_add: "Thêm vào yêu thích",
        star_del: "Xóa khỏi yêu thích",
        star_del_q: "Người mẫu này có ghi chú hoặc nhóm. Xóa khỏi yêu thích?",
        thtr: "RẠP",
        lock: "KHÓA",
        tp_less: "[Ít hơn]",
        tp_more: "[Thêm]",
        u_d: "ngày",
        u_h: "giờ",
        u_m: "phút",
        viewers_tip: "Người xem trong phòng",
        w_new: "Chưa xem",
        w_seen: "Đã xem",
    },
    TH: {
        cal_pres_btn: "พยากรณ์",
        cal_pres_h: "มักออนไลน์ (จากการสังเกต)",
        cal_pres_none: "ยังไม่มีข้อมูล การพยากรณ์จะเพิ่มขึ้นเมื่อคุณใช้เว็บไซต์",
        cal_pres_info: "โมเดลที่มีข้อมูล: {a} จาก {b}",
        cal_pres_on: "ติดตามออนไลน์",
        blur_mute: "เบลอวิดีโอเมื่อปิดเสียง",
        ago: "{n}ที่แล้ว",
        bg: "พื้นหลัง",
        bk_csv: "ตาราง CSV",
        bk_csv_fav: "รายการโปรด",
        bk_csv_hist: "ประวัติ",
        bk_csv_ses: "เซสชัน",
        bk_exp: "ส่งออกข้อมูลสำรอง",
        bk_imp: "นำเข้าข้อมูลสำรอง",
        bk_p_fav: "รายการโปรด",
        bk_p_hist: "ประวัติ",
        bk_p_set: "การตั้งค่า",
        bk_parts: "เนื้อหาของข้อมูลสำรอง",
        bm_add: "เพิ่มในรายการโปรด",
        bm_all: "ทั้งหมด",
        bm_del: "ลบ",
        bm_dup: "เพิ่มไว้แล้ว!",
        bm_empty: "รายการว่างเปล่า",
        bm_find: "ค้นหาตามชื่อ…",
        bm_nomatch: "ไม่มีโมเดลที่ตรงกับตัวกรอง",
        bm_online: "ออนไลน์",
        bm_online_t: "แสดงเฉพาะออนไลน์",
        bm_pin: "ปักหมุดไว้บนสุด",
        bm_priv: "ส่วนตัว",
        bm_title: "รายการโปรด",
        bm_unpin: "เลิกปักหมุด",
        bt_hints: "คำแนะนำ",
        bt_stats: "ผู้ชม",
        bt_vol: "ระดับเสียง",
        cal_all_tag: "แท็กทั้งหมด",
        cal_allday: "ทั้งวัน",
        cal_c_dur: "เวลา",
        cal_c_model: "โมเดล",
        cal_c_n: "ครั้ง",
        cal_c_time: "เข้า",
        cal_hist: "ประวัติ",
        cal_loaded: "โหลดตารางแล้ว: {a}/{b}",
        cal_models: "โมเดล",
        cal_no_sched: "ตารางเวลาดึงมาจากโปรไฟล์ของรายการโปรด เปิดรายการโปรดแล้วรอสักครู่",
        cal_none: "ไม่มีรายการในวันนี้",
        cal_reset: "รีเซ็ต",
        cal_sched: "ตารางเวลา",
        cal_title: "ปฏิทิน",
        cal_today: "วันนี้",
        cal_total: "รวม",
        cal_watched: "ดูแล้ว",
        cf_cancel: "ยกเลิก",
        cf_ok: "ใช่",
        ch_avg: "ค่าเฉลี่ยรายวัน",
        ch_btn: "แผนภูมิ",
        ch_days: "เวลาต่อวัน 14 วัน",
        ch_hours: "กิจกรรมตามชั่วโมง",
        ch_peak: "สูงสุด",
        ch_top: "อันดับตามเวลา",
        copy: "ลิงก์",
        dark: "ธีมมืด / สว่าง",
        don_copied: "คัดลอกที่อยู่แล้ว",
        don_fail: "คัดลอกไม่สำเร็จ",
        don_sub: "ขอบคุณที่ใช้งาน",
        don_tap: "แตะที่อยู่เพื่อดูแบบเต็ม ปุ่มทางขวาใช้คัดลอก",
        don_tip: "สนับสนุนผู้สร้าง",
        don_title: "เลี้ยงกาแฟผู้สร้างสักแก้ว",
        drag: "⁞⁞ ควบคุม",
        err: "ผิดพลาด",
        favs: "รายการโปรด",
        fr_hold: "กดค้างเพื่อดูเฟรมปัจจุบัน",
        fr_none: "ไม่มีเฟรม: โมเดลออฟไลน์",
        fs: "เต็มจอ",
        grp_all: "ทุกกลุ่ม",
        grp_new: "+ กลุ่มใหม่…",
        grp_none: "ไม่มีกลุ่ม",
        hd_all: "ซ่อนทั้งหมด",
        hd_cats: "หมวดหมู่",
        hd_clear: "ลบกฎทั้งหมดของฉัน",
        hd_details: "รายละเอียด",
        hd_g_chat: "แชต",
        hd_g_site: "เว็บไซต์",
        hd_g_win: "ป๊อปอัป",
        hd_hidden: "ซ่อนแล้ว {n} จาก {m}",
        hd_na: "ยังไม่พบ",
        hd_none: "ยังไม่มีกฎ",
        hd_off: "แสดงทั้งหมด",
        hd_p_clean: "เครื่องเล่นสะอาด",
        hd_p_min: "น้อยที่สุด",
        hd_peek: "กดค้างที่รายการเพื่อดูว่าซ่อนอะไร",
        hd_pick: "เลือกองค์ประกอบ",
        hd_reset: "รีเซ็ตสำหรับเว็บนี้",
        hd_rules: "กฎของฉัน",
        hd_title: "การซ่อน",
        hide: "ซ่อน (H)",
        hints: "M=เสียง F=เต็มจอ T=โรงหนัง H=ซ่อน",
        hist_clear: "ล้าง",
        hist_clear_q: "ลบประวัติการรับชมทั้งหมดหรือไม่?",
        hist_del: "นำออกจากประวัติ",
        hist_empty: "ยังว่างอยู่ เวลาจะนับขณะที่วิดีโอเล่น",
        hist_s_last: "ล่าสุด",
        hist_s_time: "เวลา",
        hist_s_vis: "การเข้าชม",
        hist_title: "ประวัติ",
        hist_total: "โมเดล: {n} · รวม: {t}",
        hist_visits: "ครั้ง",
        home: "หน้าแรก",
        lang_auto: "อัตโนมัติ",
        lang_title: "ภาษา",
        min: "ย่อ",
        mv_abr_row: "คุณภาพอัตโนมัติ",
        mv_abr_t: "คุณภาพอัตโนมัติ: ลดคุณภาพเมื่อวิดีโอกระตุกหรือเครือข่ายอ่อน และเพิ่มขึ้นเมื่อการเชื่อมต่อเสถียร",
        mv_add: "เพิ่ม",
        mv_auto: "อัตโนมัติ",
        mv_auto_all: "เพิ่มสตรีมที่กำลังไลฟ์ทั้งหมดอัตโนมัติ",
        mv_back: "กลับ",
        mv_bg: "เล่นในพื้นหลัง",
        mv_bg_t: "สตรีมจะเล่นต่อเมื่อสลับแท็บ ปิดหน้าต่างนี้ หรือออกจากเบราว์เซอร์",
        mv_bm_h: "บุ๊กมาร์กที่กำลังไลฟ์",
        mv_clear_q: "ปิดทุกสตรีมหรือไม่",
        mv_close: "ปิด",
        mv_cols: "เลย์เอาต์",
        mv_cur: "สตรีมปัจจุบัน",
        mv_dvr: "บัฟเฟอร์ย้อนกลับ",
        mv_dvr_off: "ปิด",
        mv_fs: "เต็มจอ",
        mv_full: "หน้าต่างเต็มแล้ว",
        mv_go: "เพิ่ม",
        mv_hint: "เพิ่มสตรีมเพื่อดูหลายสตรีมพร้อมกัน แตะที่ช่องเพื่อย้ายเสียงไปที่ช่องนั้น",
        mv_link_h: "หรือผ่านลิงก์",
        mv_load: "กำลังโหลด…",
        mv_min_u: "นาที",
        mv_mute: "ปิดเสียง",
        mv_none: "ไม่มีบุ๊กมาร์กที่กำลังไลฟ์",
        mv_off: "สตรีมไม่พร้อมใช้งาน",
        mv_pip: "ภาพซ้อนภาพ",
        mv_play: "เล่น / หยุดชั่วคราว",
        mv_qual: "คุณภาพ",
        mv_retry: "แตะเพื่อลองใหม่",
        mv_search: "ค้นหาบุ๊กมาร์กที่กำลังไลฟ์",
        mv_title: "เครื่องเล่น",
        no_vid: "ไม่มีวิดีโอ",
        note_ph: "บันทึก... (#แท็ก)",
        pk_cancel: "ยกเลิก",
        pk_found: "ตรงกัน",
        pk_hint: "แตะองค์ประกอบบนหน้าเว็บ",
        pk_ok: "ซ่อน",
        pk_up: "ขึ้น",
        qual: "คุณภาพ",
        rad_media: "สื่อ",
        rad_nav: "รายการโปรด",
        rad_next: "ถัดไป",
        rad_page: "หน้า",
        rad_prev: "ก่อนหน้า",
        rad_reset: "รีเซ็ตตำแหน่งปุ่ม",
        rad_title: "เมนูวงกลม",
        rad_vdn: "เสียงเบาลง",
        rad_view: "มุมมอง",
        rad_vup: "เสียงดังขึ้น",
        rec: "อัด",
        rec_tip: "แตะ: อัด · กดค้าง: ตั้งค่า",
        rel: "โหลดใหม่",
        rs_cur: "สตรีมปัจจุบัน:",
        rs_gbh: "GB/ชม.",
        rs_hint: "เมื่อส่วนหนึ่งถึงขนาดที่เลือก ระบบจะบันทึกเป็นไฟล์แยก แล้วอัดต่อในส่วนใหม่ ข้อมูลถูกเขียนลงดิสก์เป็นก้อนเล็กๆ หากใช้ดิสก์ไม่ได้ ข้อมูลจะอยู่ในหน่วยความจำโทรศัพท์ ในกรณีนั้นควรเลือกส่วนที่เล็กลง",
        rs_mbmin: "MB/นาที",
        rs_next: "การเปลี่ยนแปลงมีผลตั้งแต่ส่วนถัดไปหรือการอัดครั้งใหม่",
        rs_now: "ตอนนี้",
        rs_off: "ปิด",
        rs_p: "ส่วน",
        rs_part: "ขนาดแต่ละส่วน (ไฟล์จะถูกแบ่ง)",
        rs_q_hi: "สูง",
        rs_q_low: "ต่ำ",
        rs_q_mid: "กลาง",
        rs_q_min: "ประหยัด",
        rs_quality: "คุณภาพ (บิตเรต)",
        rs_recover: "กู้คืนการบันทึกที่ยังไม่ได้บันทึก",
        rs_resnote: "โหมดแคปเจอร์อัดด้วยความละเอียดของสตรีม: ตัวเลือกด้านบนเปลี่ยนเฉพาะบิตเรต (การบีบอัด) ส่วน 1080p–360p แสดงคุณภาพภาพที่คาดไว้ ในโหมด HLS จะเลือกสตรีมที่สูงไม่เกินค่าที่กำหนด",
        rs_src: "แหล่งที่มา",
        rs_src_cap: "แคปเจอร์ (เข้ารหัสใหม่)",
        rs_src_hls: "HLS (ไม่เข้ารหัสใหม่)",
        rs_src_note: "HLS จะดาวน์โหลดส่วนของสตรีมตามที่เป็น โดยไม่เข้ารหัสใหม่ และบันทึกเป็น MP4 ต้องมีที่อยู่ของสตรีม หากไม่มีจะใช้แคปเจอร์ คุณภาพเป็นต้นฉบับ เหมือนสตรีมต้นทาง การบันทึกจะเขียนลงดิสก์เป็นจุดตรวจเล็ก ๆ",
        rs_t: "รวม",
        rs_title: "การตั้งค่าการอัด",
        rs_total: "หยุดเมื่อถึงขนาดรวม",
        rs_upto: "สูงสุด",
        search: "ค้นหา",
        seen_now: "เมื่อสักครู่",
        seen_tip: "ออนไลน์ล่าสุด",
        settings: "การตั้งค่า",
        sleep_custom: "กำหนดเวลาเอง",
        sleep_hint: "เมื่อหมดเวลา เครื่องเล่นจะปิด และการอัดที่กำลังทำอยู่จะหยุดและบันทึกไว้ แตะหน้าจอสีดำเพื่อเปิดกลับมา",
        sleep_left: "เหลือ",
        sleep_sec: "วินาที",
        sleep_start: "เริ่ม",
        sleep_title: "ตั้งเวลาปิด",
        sleep_wake: "ตั้งเวลาปิดสิ้นสุดแล้ว\nแตะเพื่อดำเนินการต่อ",
        snap: "แคป",
        sort_date: "วันที่",
        sort_last: "ล่าสุด",
        sort_live: "ไลฟ์",
        sort_name: "ชื่อ",
        sort_viewers: "ผู้ชม",
        sound: "เสียง",
        st_av_refresh: "รีเฟรชอวาตาร์",
        st_back: "ย้อนกลับ",
        st_btns: "ปุ่มบนแผง",
        st_btns_hint: "แตะเพื่อซ่อนปุ่มหรือนำกลับมา",
        st_btns_reset: "แสดงปุ่มทั้งหมด",
        st_color: "สีของแผง",
        st_color_rnd: "สุ่ม",
        st_cols: "จำนวนคอลัมน์",
        st_custom: "สีกำหนดเอง",
        st_data: "ข้อมูล",
        st_fav: "รายการโปรด",
        st_favnav: "ปุ่ม «ก่อนหน้า / ถัดไป» บนแผงควบคุม",
        st_group: "กลุ่ม",
        st_hide_grp: "ซ่อนตัวเลือกกลุ่ม",
        st_hide_note: "ซ่อนช่องบันทึก (แท็ก)",
        st_hide_topic: "ซ่อนคำอธิบายโมเดล",
        st_hist: "ประวัติการรับชม",
        st_hist_on: "เก็บประวัติ",
        st_hue: "เฉดสี",
        st_lang: "ภาษา",
        st_lig: "ความสว่าง",
        st_list: "รายการ",
        st_look: "รูปลักษณ์",
        st_open: "เปิด",
        st_prev: "พรีวิวโมเดล",
        st_prev_off: "ไม่รีเฟรช",
        st_prev_spd: "การรีเฟรชพรีวิว",
        st_private: "ส่วนตัว",
        st_ptags: "ซ่อนแท็กโปรไฟล์ (BongaCams)",
        st_rec: "การบันทึกวิดีโอ",
        st_sat: "ความอิ่มตัว",
        st_scale: "ขนาดแผง",
        st_site_dark: "ธีมมืดของเว็บไซต์",
        st_star: "ดาวบนภาพขนาดย่อ",
        star_add: "เพิ่มในรายการโปรด",
        star_del: "นำออกจากรายการโปรด",
        star_del_q: "โมเดลนี้มีโน้ตหรือกลุ่ม นำออกจากรายการโปรดหรือไม่?",
        thtr: "โรงหนัง",
        lock: "ล็อก",
        tp_less: "[น้อยลง]",
        tp_more: "[เพิ่มเติม]",
        u_d: "วัน",
        u_h: "ชม.",
        u_m: "นาที",
        viewers_tip: "ผู้ชมในห้อง",
        w_new: "ยังไม่ได้ดู",
        w_seen: "ดูแล้ว",
    },
};
LANGS.forEach(k => { i18n[k] = Object.assign({}, i18n.EN, i18n[k]); });

const HIDE_L = {
    UK: {
        actions: ["Кнопки «Приват / Чайові»","Сині (на ПК зелені) кнопки під червоною панеллю"],
        age18: ["Вікно «Старше 18»","Заставка «Заходь, ТІЛЬКИ якщо тобі вже є 18»"],
        balance: ["Баланс і меню (ПК)","Баланс і навігація на комп’ютері"],
        below: ["Панель під плеєром (ПК)","Блок кнопок під відео"],
        cb_actions: ["Кнопка чайових","Кнопка та форма надсилання токенів"],
        cb_alerts: ["Сповіщення","Попередження про мову та нотатки, що закриваються"],
        cb_footer: ["Низ сторінки","Футер і соцмережі"],
        cb_hdr: ["Верх сайту","Шапка з логотипом і меню"],
        cb_info: ["Назва кімнати","Імʼя та лічильник глядачів у шапці"],
        cb_sort: ["Сортування","Панель сортування в мобільній версії"],
        cb_status: ["Статус кімнати","Плашка про офлайн і приват з підказками"],
        cb_tags: ["Смуга тегів","Популярні теги над списком"],
        cb_tips: ["Чайові в чаті","Повідомлення про токени та чайові"],
        chat_boost: ["Бусти в чаті","Плашки «підняв модель на N секунд» (FAN BOOSTED)"],
        chat_tips: ["Чайові в чаті","Повідомлення «подарував токени», меню чайових"],
        chat_topic: ["Тема кімнати","Довгий опис «Тема: …» на початку чату"],
        chathdr: ["Плашка «Чат із …»","Рядок «Безкоштовний чат із …» і король чайових"],
        ftr: ["Низ сторінки","Футер, соцмережі, SEO-текст"],
        games: ["Кнопка «Ігри»","Іконка геймпада в червоній панелі"],
        hdr_d: ["Шапка сайту (ПК)","Верхня панель на комп’ютері"],
        hdr_m: ["Верх сайту","Логотип, меню, кнопки входу"],
        joinpop: ["Вікно реєстрації","Спливаюча форма «Створіть акаунт»"],
        lastview: ["Плашка «Продовжити перегляд»","Кнопка на превʼю останньої моделі"],
        modelchat: ["Спливаючий чат моделі","Рожеве віконце з автопривітанням моделі внизу"],
        popups: ["Спливаючі вікна","Вибір чату, група, підглядання"],
        promo: ["Реклама та банери","Банери, промо, плашка «Приєднуйся» на плеєрі, застосунок"],
        sc_about: ["Блоки під плеєром","Про мене, розклад, схожі моделі"],
        sc_cookie: ["Плашка cookies","Сповіщення про cookie внизу"],
        sc_footer: ["Низ сторінки","Футер і юридичний текст"],
        sc_goal: ["Ціль (гол)","Смуга прогресу цілі"],
        sc_hdr: ["Верх сайту","Шапка з логотипом, пошуком і кнопками входу"],
        sc_king: ["Король чайових","Плашка короля чайових"],
        sc_promo: ["Реклама та банери","Розіграші токенів, кнопка «Приєднатися» на плеєрі"],
        sc_sub: ["Рядок моделі","Аватар, підписка та меню над плеєром"],
        sc_topic: ["Тема кімнати","Текст теми над плеєром і всередині нього"],
        sc_widgets: ["Віджети на плеєрі","Опитування та реакції поверх відео"],
        sc_wm: ["Водяний знак","Логотип поверх відео"],
        seotitle: ["Заголовок-реклама","Рядок із SEO-заголовком над списком"],
        tabs: ["Вкладки та сортування","Дівчата/пари, фільтри, значок сортування"],
        tipking: ["Король чайових","Рядок короля та вікна чайових"],
        tips: ["Підказки в чаті","Спливаючі підказки та сповіщення"],
        wm: ["Водяний знак (ПК)","Логотип поверх відео"],
    },
    ES: {
        actions: ["Botones «Privado / Propinas»","Botones azules (verdes en PC) bajo la barra roja"],
        age18: ["Aviso +18","Pantalla de entrada «Solo mayores de 18»"],
        balance: ["Saldo y menú (PC)","Saldo y navegación en ordenador"],
        below: ["Barra bajo el reproductor (PC)","Fila de botones bajo el vídeo"],
        cb_actions: ["Botón de propina","Botón y formulario para enviar tokens"],
        cb_alerts: ["Avisos","Aviso de idioma y notas descartables"],
        cb_footer: ["Pie de página","Pie y redes sociales"],
        cb_hdr: ["Parte superior del sitio","Cabecera con logo y menú"],
        cb_info: ["Nombre de la sala","Nombre y contador de espectadores en la cabecera"],
        cb_sort: ["Orden","Panel de orden en la versión móvil"],
        cb_status: ["Estado de la sala","Aviso de desconexión y privado con sugerencias"],
        cb_tags: ["Franja de etiquetas","Etiquetas populares sobre la lista"],
        cb_tips: ["Propinas en el chat","Mensajes de tokens y propinas"],
        chat_boost: ["Boosts en el chat","Tarjetas de «impulsó a la modelo N segundos»"],
        chat_tips: ["Propinas en el chat","Mensajes de «dio tokens» y menú de propinas"],
        chat_topic: ["Tema de la sala","Bloque largo «Tema: …» al inicio del chat"],
        chathdr: ["Barra «Chat con …»","Franja «Chat gratis con …» y rey de propinas"],
        ftr: ["Pie de página","Pie, redes sociales, texto SEO"],
        games: ["Botón «Juegos»","Icono de mando en la barra roja"],
        hdr_d: ["Cabecera del sitio (PC)","Barra superior en ordenador"],
        hdr_m: ["Parte superior del sitio","Logo, menú, botones de acceso"],
        joinpop: ["Ventana de registro","Formulario emergente «Crea una cuenta»"],
        lastview: ["Marca «Seguir viendo»","Botón sobre la miniatura de la última modelo"],
        modelchat: ["Chat emergente de la modelo","Ventana rosa con el saludo automático abajo"],
        popups: ["Ventanas emergentes","Elección de chat, grupo, voyeur"],
        promo: ["Anuncios y banners","Banners, promos, franja «Únete» sobre el reproductor, app"],
        sc_about: ["Bloques bajo el reproductor","Sobre mí, horario, modelos similares"],
        sc_cookie: ["Aviso de cookies","Aviso de cookies en la parte inferior"],
        sc_footer: ["Pie de página","Pie y texto legal"],
        sc_goal: ["Objetivo (goal)","Barra de progreso del objetivo"],
        sc_hdr: ["Parte superior del sitio","Cabecera con logo, búsqueda y botones de acceso"],
        sc_king: ["Rey de propinas","Franja del rey de propinas"],
        sc_promo: ["Anuncios y banners","Sorteos de tokens, botón «Unirse» en el reproductor"],
        sc_sub: ["Barra de la modelo","Avatar, seguir y menú sobre el reproductor"],
        sc_topic: ["Tema de la sala","Texto del tema sobre y dentro del reproductor"],
        sc_widgets: ["Widgets del reproductor","Encuestas y reacciones sobre el vídeo"],
        sc_wm: ["Marca de agua","Logo sobre el vídeo"],
        seotitle: ["Título publicitario","Línea de título SEO sobre la lista"],
        tabs: ["Pestañas y orden","Chicas/parejas, filtros, icono de orden"],
        tipking: ["Rey de propinas","Franja del rey y ventanas de propinas"],
        tips: ["Consejos del chat","Avisos y notificaciones emergentes"],
        wm: ["Marca de agua (PC)","Logo sobre el vídeo"],
    },
    DE: {
        actions: ["Buttons „Privat / Trinkgeld“","Blaue (am PC grüne) Buttons unter der roten Leiste"],
        age18: ["18+-Hinweis","Einstiegsbildschirm „Nur ab 18“"],
        balance: ["Guthaben & Menü (PC)","Guthaben und Navigation am Computer"],
        below: ["Leiste unter dem Player (PC)","Button-Reihe unter dem Video"],
        cb_actions: ["Trinkgeld-Button","Button und Formular zum Senden von Tokens"],
        cb_alerts: ["Hinweise","Sprachhinweis und schließbare Notizen"],
        cb_footer: ["Seitenende","Footer und Social Media"],
        cb_hdr: ["Seitenkopf","Kopfzeile mit Logo und Menü"],
        cb_info: ["Raumname","Name und Zuschauerzahl in der Kopfzeile"],
        cb_sort: ["Sortierung","Sortierleiste in der mobilen Ansicht"],
        cb_status: ["Raumstatus","Hinweis zu Offline und Privat mit Tipps"],
        cb_tags: ["Tag-Leiste","Beliebte Tags über der Liste"],
        cb_tips: ["Trinkgelder im Chat","Meldungen zu Tokens und Trinkgeld"],
        chat_boost: ["Boosts im Chat","Karten „hat das Model N Sekunden geboostet“"],
        chat_tips: ["Trinkgelder im Chat","Meldungen „hat Tokens gegeben“, Tip-Menü"],
        chat_topic: ["Raumthema","Langer Block „Thema: …“ am Chatanfang"],
        chathdr: ["Leiste „Chat mit …“","Streifen „Gratis-Chat mit …“ und Tip-König"],
        ftr: ["Seitenende","Footer, Social Media, SEO-Text"],
        games: ["Button „Spiele“","Gamepad-Symbol in der roten Leiste"],
        hdr_d: ["Seitenkopf (PC)","Obere Leiste am Computer"],
        hdr_m: ["Seitenkopf","Logo, Menü, Anmelde-Buttons"],
        joinpop: ["Anmelde-Fenster","Popup „Konto erstellen“"],
        lastview: ["Hinweis „Weiter ansehen“","Button auf der Vorschau des zuletzt gesehenen Models"],
        modelchat: ["Pop-up-Chat des Models","Rosa Fenster mit der Auto-Begrüßung unten"],
        popups: ["Pop-up-Fenster","Chat-Auswahl, Gruppe, Voyeur"],
        promo: ["Werbung & Banner","Banner, Promos, „Mach mit“-Leiste am Player, App"],
        sc_about: ["Bereiche unter dem Player","Über mich, Zeitplan, ähnliche Models"],
        sc_cookie: ["Cookie-Hinweis","Cookie-Hinweis am unteren Rand"],
        sc_footer: ["Seitenende","Footer und rechtliche Texte"],
        sc_goal: ["Ziel (Goal)","Fortschrittsbalken des Ziels"],
        sc_hdr: ["Seitenkopf","Kopfzeile mit Logo, Suche und Anmelde-Buttons"],
        sc_king: ["Tip-König","Streifen des Tip-Königs"],
        sc_promo: ["Werbung & Banner","Token-Verlosungen, „Beitreten“-Button am Player"],
        sc_sub: ["Model-Leiste","Avatar, Folgen und Menü über dem Player"],
        sc_topic: ["Raumthema","Themen-Text über und im Player"],
        sc_widgets: ["Player-Widgets","Umfragen und Reaktionen über dem Video"],
        sc_wm: ["Wasserzeichen","Logo über dem Video"],
        seotitle: ["Werbe-Überschrift","SEO-Überschrift über der Liste"],
        tabs: ["Tabs & Sortierung","Mädchen/Paare, Filter, Sortier-Symbol"],
        tipking: ["Tip-König","Königs-Streifen und Trinkgeld-Fenster"],
        tips: ["Chat-Hinweise","Tooltips und Benachrichtigungen"],
        wm: ["Wasserzeichen (PC)","Logo über dem Video"],
    },
    FR: {
        actions: ["Boutons « Privé / Pourboires »","Boutons bleus (verts sur PC) sous la barre rouge"],
        age18: ["Avis 18+","Écran d’entrée « Réservé aux plus de 18 ans »"],
        balance: ["Solde et menu (PC)","Solde et navigation sur ordinateur"],
        below: ["Barre sous le lecteur (PC)","Rangée de boutons sous la vidéo"],
        cb_actions: ["Bouton de pourboire","Bouton et formulaire d’envoi de tokens"],
        cb_alerts: ["Notifications","Alerte de langue et notes fermables"],
        cb_footer: ["Bas de page","Pied de page et réseaux sociaux"],
        cb_hdr: ["Haut du site","En-tête avec logo et menu"],
        cb_info: ["Nom du salon","Nom et nombre de spectateurs dans l’en-tête"],
        cb_sort: ["Tri","Panneau de tri sur mobile"],
        cb_status: ["Statut du salon","Bandeau hors ligne et privé avec astuces"],
        cb_tags: ["Bandeau de tags","Tags populaires au-dessus de la liste"],
        cb_tips: ["Pourboires dans le chat","Messages de tokens et de pourboires"],
        chat_boost: ["Boosts dans le chat","Cartes « a boosté la modèle N secondes »"],
        chat_tips: ["Pourboires dans le chat","Messages « a donné des tokens », menu de pourboires"],
        chat_topic: ["Sujet du salon","Long bloc « Sujet : … » en haut du chat"],
        chathdr: ["Barre « Chat avec … »","Bandeau « Chat gratuit avec … » et roi des pourboires"],
        ftr: ["Bas de page","Pied de page, réseaux sociaux, texte SEO"],
        games: ["Bouton « Jeux »","Icône de manette dans la barre rouge"],
        hdr_d: ["En-tête du site (PC)","Barre supérieure sur ordinateur"],
        hdr_m: ["Haut du site","Logo, menu, boutons de connexion"],
        joinpop: ["Fenêtre d’inscription","Formulaire « Créez un compte »"],
        lastview: ["Pastille « Reprendre »","Bouton sur la miniature du dernier modèle vu"],
        modelchat: ["Chat flottant de la modèle","Fenêtre rose avec le message d’accueil automatique, en bas"],
        popups: ["Fenêtres surgissantes","Choix de chat, groupe, voyeur"],
        promo: ["Pubs et bannières","Bannières, promos, bandeau « Rejoins-moi » sur le lecteur, appli"],
        sc_about: ["Blocs sous le lecteur","À propos, planning, modèles similaires"],
        sc_cookie: ["Bandeau cookies","Avis sur les cookies en bas"],
        sc_footer: ["Bas de page","Pied de page et mentions légales"],
        sc_goal: ["Objectif (goal)","Barre de progression de l’objectif"],
        sc_hdr: ["Haut du site","En-tête avec logo, recherche et boutons de connexion"],
        sc_king: ["Roi des pourboires","Bandeau du roi des pourboires"],
        sc_promo: ["Pubs et bannières","Tirages de tokens, bouton « Rejoindre » sur le lecteur"],
        sc_sub: ["Barre du modèle","Avatar, abonnement et menu au-dessus du lecteur"],
        sc_topic: ["Sujet du salon","Texte du sujet au-dessus et dans le lecteur"],
        sc_widgets: ["Widgets du lecteur","Sondages et réactions sur la vidéo"],
        sc_wm: ["Filigrane","Logo sur la vidéo"],
        seotitle: ["Titre publicitaire","Ligne de titre SEO au-dessus de la liste"],
        tabs: ["Onglets et tri","Filles/couples, filtres, icône de tri"],
        tipking: ["Roi des pourboires","Bandeau du roi et fenêtres de pourboires"],
        tips: ["Astuces du chat","Infobulles et notifications"],
        wm: ["Filigrane (PC)","Logo sur la vidéo"],
    },
    PT: {
        actions: ["Botões «Privado / Gorjetas»","Botões azuis (verdes no PC) sob a barra vermelha"],
        age18: ["Aviso 18+","Tela de entrada «Somente maiores de 18»"],
        balance: ["Saldo e menu (PC)","Saldo e navegação no computador"],
        below: ["Barra sob o player (PC)","Fileira de botões sob o vídeo"],
        cb_actions: ["Botão de gorjeta","Botão e formulário para enviar tokens"],
        cb_alerts: ["Avisos","Aviso de idioma e notas dispensáveis"],
        cb_footer: ["Rodapé","Rodapé e redes sociais"],
        cb_hdr: ["Topo do site","Cabeçalho com logo e menu"],
        cb_info: ["Nome da sala","Nome e contador de espectadores no cabeçalho"],
        cb_sort: ["Ordenação","Painel de ordenação na versão móvel"],
        cb_status: ["Status da sala","Aviso de offline e privado com dicas"],
        cb_tags: ["Faixa de tags","Tags populares acima da lista"],
        cb_tips: ["Gorjetas no chat","Mensagens de tokens e gorjetas"],
        chat_boost: ["Boosts no chat","Cartões de «impulsionou a modelo por N segundos»"],
        chat_tips: ["Gorjetas no chat","Mensagens de «deu tokens» e menu de gorjetas"],
        chat_topic: ["Tema da sala","Bloco longo «Tema: …» no início do chat"],
        chathdr: ["Barra «Chat com …»","Faixa «Chat grátis com …» e rei das gorjetas"],
        ftr: ["Rodapé","Rodapé, redes sociais, texto SEO"],
        games: ["Botão «Jogos»","Ícone de controle na barra vermelha"],
        hdr_d: ["Cabeçalho do site (PC)","Barra superior no computador"],
        hdr_m: ["Topo do site","Logo, menu, botões de login"],
        joinpop: ["Janela de cadastro","Pop-up «Crie uma conta»"],
        lastview: ["Selo «Continuar assistindo»","Botão na miniatura da última modelo vista"],
        modelchat: ["Chat pop-up da modelo","Janela rosa com a saudação automática, embaixo"],
        popups: ["Janelas pop-up","Escolha de chat, grupo, voyeur"],
        promo: ["Anúncios e banners","Banners, promoções, faixa «Junte-se» no player, app"],
        sc_about: ["Blocos abaixo do player","Sobre mim, agenda, modelos semelhantes"],
        sc_cookie: ["Aviso de cookies","Aviso de cookies na parte inferior"],
        sc_footer: ["Rodapé","Rodapé e texto legal"],
        sc_goal: ["Meta (goal)","Barra de progresso da meta"],
        sc_hdr: ["Topo do site","Cabeçalho com logo, busca e botões de login"],
        sc_king: ["Rei das gorjetas","Faixa do rei das gorjetas"],
        sc_promo: ["Anúncios e banners","Sorteios de tokens, botão «Participar» no player"],
        sc_sub: ["Barra da modelo","Avatar, seguir e menu acima do player"],
        sc_topic: ["Tema da sala","Texto do tema acima e dentro do player"],
        sc_widgets: ["Widgets do player","Enquetes e reações sobre o vídeo"],
        sc_wm: ["Marca d’água","Logo sobre o vídeo"],
        seotitle: ["Título publicitário","Linha de título SEO acima da lista"],
        tabs: ["Abas e ordenação","Garotas/casais, filtros, ícone de ordenação"],
        tipking: ["Rei das gorjetas","Faixa do rei e janelas de gorjetas"],
        tips: ["Dicas do chat","Dicas e notificações pop-up"],
        wm: ["Marca d’água (PC)","Logo sobre o vídeo"],
    },
    IT: {
        actions: ["Pulsanti «Privato / Mance»","Pulsanti blu (verdi su PC) sotto la barra rossa"],
        age18: ["Avviso 18+","Schermata d’ingresso «Solo maggiorenni»"],
        balance: ["Saldo e menu (PC)","Saldo e navigazione su computer"],
        below: ["Barra sotto il player (PC)","Fila di pulsanti sotto il video"],
        cb_actions: ["Pulsante mancia","Pulsante e modulo per inviare token"],
        cb_alerts: ["Avvisi","Avviso sulla lingua e note chiudibili"],
        cb_footer: ["Fondo pagina","Footer e social"],
        cb_hdr: ["Parte alta del sito","Intestazione con logo e menu"],
        cb_info: ["Nome della stanza","Nome e contatore spettatori nell’intestazione"],
        cb_sort: ["Ordinamento","Pannello di ordinamento nella versione mobile"],
        cb_status: ["Stato della stanza","Avviso su offline e privato con suggerimenti"],
        cb_tags: ["Striscia dei tag","Tag popolari sopra l’elenco"],
        cb_tips: ["Mance in chat","Messaggi su token e mance"],
        chat_boost: ["Boost in chat","Schede «ha potenziato la modella per N secondi»"],
        chat_tips: ["Mance in chat","Messaggi «ha dato token» e menu mance"],
        chat_topic: ["Argomento della stanza","Lungo blocco «Argomento: …» all’inizio della chat"],
        chathdr: ["Barra «Chat con …»","Striscia «Chat gratis con …» e re delle mance"],
        ftr: ["Fondo pagina","Footer, social, testo SEO"],
        games: ["Pulsante «Giochi»","Icona del gamepad nella barra rossa"],
        hdr_d: ["Intestazione del sito (PC)","Barra superiore su computer"],
        hdr_m: ["Parte alta del sito","Logo, menu, pulsanti di accesso"],
        joinpop: ["Finestra di registrazione","Popup «Crea un account»"],
        lastview: ["Badge «Continua a guardare»","Pulsante sull’anteprima dell’ultima modella vista"],
        modelchat: ["Chat popup della modella","Finestra rosa con il saluto automatico, in basso"],
        popups: ["Finestre popup","Scelta chat, gruppo, voyeur"],
        promo: ["Pubblicità e banner","Banner, promo, striscia «Unisciti» sul player, app"],
        sc_about: ["Blocchi sotto il player","Chi sono, programma, modelle simili"],
        sc_cookie: ["Avviso cookie","Avviso sui cookie in basso"],
        sc_footer: ["Fondo pagina","Footer e testo legale"],
        sc_goal: ["Obiettivo (goal)","Barra di avanzamento dell’obiettivo"],
        sc_hdr: ["Parte alta del sito","Intestazione con logo, ricerca e pulsanti di accesso"],
        sc_king: ["Re delle mance","Striscia del re delle mance"],
        sc_promo: ["Pubblicità e banner","Estrazioni di token, pulsante «Unisciti» sul player"],
        sc_sub: ["Barra della modella","Avatar, segui e menu sopra il player"],
        sc_topic: ["Argomento della stanza","Testo dell’argomento sopra e dentro il player"],
        sc_widgets: ["Widget del player","Sondaggi e reazioni sopra il video"],
        sc_wm: ["Filigrana","Logo sopra il video"],
        seotitle: ["Titolo pubblicitario","Riga di titolo SEO sopra l’elenco"],
        tabs: ["Schede e ordinamento","Ragazze/coppie, filtri, icona di ordinamento"],
        tipking: ["Re delle mance","Striscia del re e finestre delle mance"],
        tips: ["Suggerimenti della chat","Tooltip e notifiche"],
        wm: ["Filigrana (PC)","Logo sopra il video"],
    },
    PL: {
        actions: ["Przyciski „Prywatny / Napiwki”","Niebieskie (na PC zielone) przyciski pod czerwonym paskiem"],
        age18: ["Ostrzeżenie 18+","Ekran wejściowy „Tylko dla pełnoletnich”"],
        balance: ["Saldo i menu (PC)","Saldo i nawigacja na komputerze"],
        below: ["Pasek pod odtwarzaczem (PC)","Rząd przycisków pod wideo"],
        cb_actions: ["Przycisk napiwku","Przycisk i formularz wysyłania tokenów"],
        cb_alerts: ["Powiadomienia","Ostrzeżenie o języku i zamykane notatki"],
        cb_footer: ["Dół strony","Stopka i social media"],
        cb_hdr: ["Góra strony","Nagłówek z logo i menu"],
        cb_info: ["Nazwa pokoju","Nazwa i licznik widzów w nagłówku"],
        cb_sort: ["Sortowanie","Panel sortowania w wersji mobilnej"],
        cb_status: ["Status pokoju","Plakietka offline i prywatny z podpowiedziami"],
        cb_tags: ["Pasek tagów","Popularne tagi nad listą"],
        cb_tips: ["Napiwki na czacie","Wiadomości o tokenach i napiwkach"],
        chat_boost: ["Boosty na czacie","Karty „podbił modelkę na N sekund”"],
        chat_tips: ["Napiwki na czacie","Wiadomości „dał tokeny”, menu napiwków"],
        chat_topic: ["Temat pokoju","Długi blok „Temat: …” na początku czatu"],
        chathdr: ["Pasek „Czat z …”","Pasek „Darmowy czat z …” i król napiwków"],
        ftr: ["Dół strony","Stopka, social media, tekst SEO"],
        games: ["Przycisk „Gry”","Ikona pada w czerwonym pasku"],
        hdr_d: ["Nagłówek strony (PC)","Górny pasek na komputerze"],
        hdr_m: ["Góra strony","Logo, menu, przyciski logowania"],
        joinpop: ["Okno rejestracji","Wyskakujący formularz „Załóż konto”"],
        lastview: ["Plakietka „Kontynuuj oglądanie”","Przycisk na miniaturze ostatniej modelki"],
        modelchat: ["Wyskakujący czat modelki","Różowe okienko z automatycznym powitaniem na dole"],
        popups: ["Okna wyskakujące","Wybór czatu, grupa, podglądanie"],
        promo: ["Reklamy i banery","Banery, promocje, pasek „Dołącz” na odtwarzaczu, aplikacja"],
        sc_about: ["Bloki pod odtwarzaczem","O mnie, harmonogram, podobne modelki"],
        sc_cookie: ["Komunikat o cookies","Informacja o cookies na dole"],
        sc_footer: ["Dół strony","Stopka i teksty prawne"],
        sc_goal: ["Cel (goal)","Pasek postępu celu"],
        sc_hdr: ["Góra strony","Nagłówek z logo, wyszukiwarką i przyciskami logowania"],
        sc_king: ["Król napiwków","Pasek króla napiwków"],
        sc_promo: ["Reklamy i banery","Losowania tokenów, przycisk „Dołącz” na odtwarzaczu"],
        sc_sub: ["Pasek modelki","Awatar, obserwowanie i menu nad odtwarzaczem"],
        sc_topic: ["Temat pokoju","Tekst tematu nad odtwarzaczem i w nim"],
        sc_widgets: ["Widżety odtwarzacza","Ankiety i reakcje nad wideo"],
        sc_wm: ["Znak wodny","Logo na wideo"],
        seotitle: ["Reklamowy nagłówek","Linia z nagłówkiem SEO nad listą"],
        tabs: ["Zakładki i sortowanie","Dziewczyny/pary, filtry, ikona sortowania"],
        tipking: ["Król napiwków","Pasek króla i okna napiwków"],
        tips: ["Podpowiedzi czatu","Dymki i powiadomienia"],
        wm: ["Znak wodny (PC)","Logo na wideo"],
    },
    TR: {
        actions: ["«Özel / Bahşiş» düğmeleri","Kırmızı çubuğun altındaki mavi (PC’de yeşil) düğmeler"],
        age18: ["18+ uyarısı","«Yalnızca 18 yaş üstü» giriş ekranı"],
        balance: ["Bakiye ve menü (PC)","Bilgisayarda bakiye ve gezinme"],
        below: ["Oynatıcı altı çubuk (PC)","Videonun altındaki düğme sırası"],
        cb_actions: ["Bahşiş düğmesi","Token gönderme düğmesi ve formu"],
        cb_alerts: ["Bildirimler","Dil uyarısı ve kapatılabilir notlar"],
        cb_footer: ["Sayfa altı","Alt bilgi ve sosyal ağlar"],
        cb_hdr: ["Sitenin üst kısmı","Logo ve menü olan başlık"],
        cb_info: ["Oda adı","Başlıktaki ad ve izleyici sayacı"],
        cb_sort: ["Sıralama","Mobil sürümde sıralama paneli"],
        cb_status: ["Oda durumu","Çevrimdışı ve özel şeridi ile ipuçları"],
        cb_tags: ["Etiket şeridi","Listenin üstündeki popüler etiketler"],
        cb_tips: ["Sohbette bahşişler","Token ve bahşiş mesajları"],
        chat_boost: ["Sohbette boostlar","«Modeli N saniye öne çıkardı» kartları"],
        chat_tips: ["Sohbette bahşişler","«Token verdi» mesajları ve bahşiş menüsü"],
        chat_topic: ["Oda konusu","Sohbetin başındaki uzun «Konu: …» bloğu"],
        chathdr: ["«… ile sohbet» çubuğu","«… ile ücretsiz sohbet» şeridi ve bahşiş kralı"],
        ftr: ["Sayfa altı","Alt bilgi, sosyal ağlar, SEO metni"],
        games: ["«Oyunlar» düğmesi","Kırmızı çubuktaki oyun kolu simgesi"],
        hdr_d: ["Site başlığı (PC)","Bilgisayarda üst çubuk"],
        hdr_m: ["Sitenin üst kısmı","Logo, menü, giriş düğmeleri"],
        joinpop: ["Kayıt penceresi","«Hesap oluştur» açılır formu"],
        lastview: ["«İzlemeye devam et» rozeti","Son bakılan modelin önizlemesindeki düğme"],
        modelchat: ["Modelin açılır sohbeti","Altta otomatik karşılama mesajlı pembe pencere"],
        popups: ["Açılır pencereler","Sohbet seçimi, grup, gözetleme"],
        promo: ["Reklamlar ve bannerlar","Bannerlar, promolar, oynatıcıdaki «Katıl» şeridi, uygulama"],
        sc_about: ["Oynatıcı altı bloklar","Hakkımda, program, benzer modeller"],
        sc_cookie: ["Çerez uyarısı","Altta çerez bildirimi"],
        sc_footer: ["Sayfa altı","Alt bilgi ve yasal metin"],
        sc_goal: ["Hedef (goal)","Hedef ilerleme çubuğu"],
        sc_hdr: ["Sitenin üst kısmı","Logo, arama ve giriş düğmeleri olan başlık"],
        sc_king: ["Bahşiş kralı","Bahşiş kralı şeridi"],
        sc_promo: ["Reklamlar ve bannerlar","Token çekilişleri, oynatıcıdaki «Katıl» düğmesi"],
        sc_sub: ["Model çubuğu","Oynatıcının üstünde avatar, takip ve menü"],
        sc_topic: ["Oda konusu","Oynatıcının üstündeki ve içindeki konu metni"],
        sc_widgets: ["Oynatıcı widget’ları","Videonun üstünde anketler ve tepkiler"],
        sc_wm: ["Filigran","Videonun üzerindeki logo"],
        seotitle: ["Reklam başlığı","Listenin üstündeki SEO başlık satırı"],
        tabs: ["Sekmeler ve sıralama","Kızlar/çiftler, filtreler, sıralama simgesi"],
        tipking: ["Bahşiş kralı","Kral şeridi ve bahşiş pencereleri"],
        tips: ["Sohbet ipuçları","Açılır ipuçları ve bildirimler"],
        wm: ["Filigran (PC)","Videonun üzerindeki logo"],
    },
    NL: {
        actions: ["Knoppen „Privé / Tip”","Blauwe (op pc groene) knoppen onder de rode balk"],
        age18: ["18+-melding","Startscherm „Alleen 18+”"],
        balance: ["Saldo en menu (pc)","Saldo en navigatie op de computer"],
        below: ["Balk onder de speler (pc)","Knoppenrij onder de video"],
        cb_actions: ["Tipknop","Knop en formulier om tokens te sturen"],
        cb_alerts: ["Meldingen","Taalwaarschuwing en sluitbare notities"],
        cb_footer: ["Voettekst","Footer en socials"],
        cb_hdr: ["Site-header","Header met logo en menu"],
        cb_info: ["Kamernaam","Naam en kijkersteller in de header"],
        cb_sort: ["Sortering","Sorteerpaneel in de mobiele versie"],
        cb_status: ["Kamerstatus","Offline- en privémelding met hints"],
        cb_tags: ["Tagbalk","Populaire tags boven de lijst"],
        cb_tips: ["Tips in de chat","Berichten over tokens en tips"],
        chat_boost: ["Boosts in de chat","Kaarten „heeft het model N seconden geboost”"],
        chat_tips: ["Tips in de chat","Meldingen „gaf tokens”, tipmenu"],
        chat_topic: ["Kamerthema","Lang blok „Thema: …” aan het begin van de chat"],
        chathdr: ["Balk „Chat met …”","Strook „Gratis chat met …” en tipkoning"],
        ftr: ["Voettekst","Footer, socials, SEO-tekst"],
        games: ["Knop „Spelletjes”","Gamepad-icoon in de rode balk"],
        hdr_d: ["Site-header (pc)","Bovenbalk op de computer"],
        hdr_m: ["Site-header","Logo, menu, inlogknoppen"],
        joinpop: ["Aanmeldvenster","Pop-up „Account maken”"],
        lastview: ["Melding „Verder kijken”","Knop op de preview van het laatst bekeken model"],
        modelchat: ["Pop-upchat van model","Roze venster met automatische begroeting onderaan"],
        popups: ["Pop-upvensters","Chatkeuze, groep, voyeur"],
        promo: ["Reclame en banners","Banners, promo's, „Doe mee”-balk op de speler, app"],
        sc_about: ["Blokken onder de speler","Over mij, schema, vergelijkbare modellen"],
        sc_cookie: ["Cookiemelding","Cookiemelding onderaan"],
        sc_footer: ["Voettekst","Footer en juridische tekst"],
        sc_goal: ["Doel (goal)","Voortgangsbalk van het doel"],
        sc_hdr: ["Site-header","Header met logo, zoekfunctie en inlogknoppen"],
        sc_king: ["Tipkoning","Strook van de tipkoning"],
        sc_promo: ["Reclame en banners","Tokenverlotingen, «Doe mee»-knop op de speler"],
        sc_sub: ["Modelbalk","Avatar, volgen en menu boven de speler"],
        sc_topic: ["Kamerthema","Themateksten boven en in de speler"],
        sc_widgets: ["Speler-widgets","Polls en reacties over de video"],
        sc_wm: ["Watermerk","Logo over de video"],
        seotitle: ["Reclamekop","SEO-kop boven de lijst"],
        tabs: ["Tabs en sortering","Meisjes/koppels, filters, sorteericoon"],
        tipking: ["Tipkoning","Koningsstrook en tipvenster"],
        tips: ["Chat-hints","Tooltips en meldingen"],
        wm: ["Watermerk (pc)","Logo over de video"],
    },
    CS: {
        actions: ["Tlačítka „Privát / Tip“","Modrá (na PC zelená) tlačítka pod červenou lištou"],
        age18: ["Upozornění 18+","Úvodní obrazovka „Pouze 18+“"],
        balance: ["Zůstatek a menu (PC)","Zůstatek a navigace na počítači"],
        below: ["Lišta pod přehrávačem (PC)","Řada tlačítek pod videem"],
        cb_actions: ["Tlačítko tipu","Tlačítko a formulář pro odeslání tokenů"],
        cb_alerts: ["Oznámení","Upozornění na jazyk a zavíratelné poznámky"],
        cb_footer: ["Patička","Patička a sociální sítě"],
        cb_hdr: ["Hlavička webu","Hlavička s logem a menu"],
        cb_info: ["Název místnosti","Jméno a počet diváků v hlavičce"],
        cb_sort: ["Řazení","Panel řazení v mobilní verzi"],
        cb_status: ["Stav místnosti","Lišta offline a privát s nápovědami"],
        cb_tags: ["Pruh štítků","Oblíbené štítky nad seznamem"],
        cb_tips: ["Tipy v chatu","Zprávy o tokenech a tipech"],
        chat_boost: ["Boosty v chatu","Karty „boostl modelku na N sekund“"],
        chat_tips: ["Tipy v chatu","Zprávy „dal tokeny“, menu tipů"],
        chat_topic: ["Téma místnosti","Dlouhý blok „Téma: …“ na začátku chatu"],
        chathdr: ["Lišta „Chat s …“","Pruh „Zdarma chat s …“ a král tipů"],
        ftr: ["Patička","Patička, sítě, SEO text"],
        games: ["Tlačítko „Hry“","Ikona gamepadu v červené liště"],
        hdr_d: ["Hlavička webu (PC)","Horní panel na počítači"],
        hdr_m: ["Hlavička webu","Logo, menu, tlačítka přihlášení"],
        joinpop: ["Registrační okno","Vyskakovací formulář „Vytvořit účet“"],
        lastview: ["Lišta „Pokračovat ve sledování“","Tlačítko na náhledu poslední modelky"],
        modelchat: ["Vyskakovací chat modelky","Růžové okno s automatickým pozdravem dole"],
        popups: ["Vyskakovací okna","Výběr chatu, skupina, voyeur"],
        promo: ["Reklama a bannery","Bannery, promo, lišta „Připoj se“ na přehrávači, aplikace"],
        sc_about: ["Bloky pod přehrávačem","O mně, rozvrh, podobné modelky"],
        sc_cookie: ["Lišta cookies","Oznámení o cookies dole"],
        sc_footer: ["Patička","Patička a právní text"],
        sc_goal: ["Cíl (goal)","Ukazatel postupu cíle"],
        sc_hdr: ["Hlavička webu","Hlavička s logem, hledáním a tlačítky přihlášení"],
        sc_king: ["Král tipů","Pruh krále tipů"],
        sc_promo: ["Reklama a bannery","Losování tokenů, tlačítko „Připojit se“ na přehrávači"],
        sc_sub: ["Lišta modelky","Avatar, sledování a menu nad přehrávačem"],
        sc_topic: ["Téma místnosti","Text tématu nad přehrávačem i v něm"],
        sc_widgets: ["Widgety přehrávače","Ankety a reakce přes video"],
        sc_wm: ["Vodoznak","Logo přes video"],
        seotitle: ["Reklamní nadpis","SEO nadpis nad seznamem"],
        tabs: ["Záložky a řazení","Dívky/páry, filtry, ikona řazení"],
        tipking: ["Král tipů","Královský pruh a okno tipů"],
        tips: ["Nápovědy v chatu","Bubliny a oznámení"],
        wm: ["Vodoznak (PC)","Logo přes video"],
    },
    RO: {
        actions: ["Butoanele „Privat / Tip”","Butoane albastre (pe PC verzi) sub bara roșie"],
        age18: ["Avertisment 18+","Ecranul de intrare „Doar 18+”"],
        balance: ["Sold și meniu (PC)","Soldul și navigarea pe computer"],
        below: ["Bara de sub player (PC)","Rândul de butoane de sub video"],
        cb_actions: ["Butonul de tip","Buton și formular pentru trimiterea tokenilor"],
        cb_alerts: ["Notificări","Avertisment de limbă și note închise"],
        cb_footer: ["Subsolul paginii","Footer și rețele sociale"],
        cb_hdr: ["Antetul site-ului","Antet cu logo și meniu"],
        cb_info: ["Numele camerei","Nume și contor de spectatori în antet"],
        cb_sort: ["Sortare","Panou de sortare în versiunea mobilă"],
        cb_status: ["Starea camerei","Banda offline și privat cu indicii"],
        cb_tags: ["Banda de taguri","Taguri populare deasupra listei"],
        cb_tips: ["Tipuri în chat","Mesaje despre tokeni și tipuri"],
        chat_boost: ["Boost-uri în chat","Carduri „a boostat modelul N secunde”"],
        chat_tips: ["Tipuri în chat","Mesaje „a dat tokeni”, meniul de tip"],
        chat_topic: ["Tema camerei","Bloc lung „Temă: …” la începutul chatului"],
        chathdr: ["Bara „Chat cu …”","Banda „Chat gratuit cu …” și regele tipurilor"],
        ftr: ["Subsolul paginii","Footer, rețele sociale, text SEO"],
        games: ["Butonul „Jocuri”","Pictograma de gamepad din bara roșie"],
        hdr_d: ["Antetul site-ului (PC)","Bara de sus pe computer"],
        hdr_m: ["Antetul site-ului","Logo, meniu, butoane de autentificare"],
        joinpop: ["Fereastra de înregistrare","Formularul pop-up „Creează cont”"],
        lastview: ["Mesajul „Continuă vizionarea”","Buton pe previzualizarea ultimului model"],
        modelchat: ["Chatul pop-up al modelului","Fereastră roz cu salut automat jos"],
        popups: ["Ferestre pop-up","Alegerea chatului, grup, voyeur"],
        promo: ["Reclame și bannere","Bannere, promo, bara „Alătură-te” de pe player, aplicația"],
        sc_about: ["Blocuri sub player","Despre mine, program, modele similare"],
        sc_cookie: ["Aviz cookie","Notificare cookie în partea de jos"],
        sc_footer: ["Subsolul paginii","Footer și text legal"],
        sc_goal: ["Obiectiv (goal)","Bara de progres a obiectivului"],
        sc_hdr: ["Antetul site-ului","Antet cu logo, căutare și butoane de autentificare"],
        sc_king: ["Regele tipurilor","Banda regelui tipurilor"],
        sc_promo: ["Reclame și bannere","Tombole de tokeni, butonul „Alătură-te” de pe player"],
        sc_sub: ["Bara modelului","Avatar, urmărire și meniu deasupra playerului"],
        sc_topic: ["Tema camerei","Textul temei deasupra și în player"],
        sc_widgets: ["Widgeturi pe player","Sondaje și reacții peste video"],
        sc_wm: ["Filigran","Logo peste video"],
        seotitle: ["Titlu publicitar","Titlu SEO deasupra listei"],
        tabs: ["Taburi și sortare","Fete/cupluri, filtre, pictogramă de sortare"],
        tipking: ["Regele tipurilor","Banda regelui și fereastra de tip"],
        tips: ["Sfaturi în chat","Tooltip-uri și notificări"],
        wm: ["Filigran (PC)","Logo peste video"],
    },
    HU: {
        actions: ["„Privát / Borravaló” gombok","Kék (PC-n zöld) gombok a piros sáv alatt"],
        age18: ["18+ figyelmeztetés","„Csak 18+” belépő képernyő"],
        balance: ["Egyenleg és menü (PC)","Egyenleg és navigáció számítógépen"],
        below: ["Sáv a lejátszó alatt (PC)","Gombsor a videó alatt"],
        cb_actions: ["Borravaló gomb","Gomb és űrlap tokenek küldéséhez"],
        cb_alerts: ["Értesítések","Nyelvi figyelmeztetés és bezárható jegyzetek"],
        cb_footer: ["Oldal alja","Lábléc és közösségi média"],
        cb_hdr: ["Oldal fejléce","Fejléc logóval és menüvel"],
        cb_info: ["Szoba neve","Név és nézőszámláló a fejlécben"],
        cb_sort: ["Rendezés","Rendezési panel mobil nézetben"],
        cb_status: ["Szoba állapota","Offline és privát sáv tippekkel"],
        cb_tags: ["Címkesáv","Népszerű címkék a lista felett"],
        cb_tips: ["Borravaló a csevegésben","Token és borravaló üzenetek"],
        chat_boost: ["Boostok a csevegésben","„N másodpercre boostolta a modellt” kártyák"],
        chat_tips: ["Borravaló a csevegésben","„tokent adott” üzenetek, borravaló menü"],
        chat_topic: ["Szoba témája","Hosszú „Téma: …” blokk a csevegés elején"],
        chathdr: ["„Csevegés vele: …” sáv","„Ingyenes csevegés vele: …” csík és borravalókirály"],
        ftr: ["Oldal alja","Lábléc, közösségi média, SEO-szöveg"],
        games: ["„Játékok” gomb","Gamepad ikon a piros sávban"],
        hdr_d: ["Oldal fejléce (PC)","Felső sáv számítógépen"],
        hdr_m: ["Oldal fejléce","Logó, menü, belépés gombok"],
        joinpop: ["Regisztrációs ablak","„Fiók létrehozása” felugró űrlap"],
        lastview: ["„Folytatás” jelzés","Gomb az utoljára nézett modell előnézetén"],
        modelchat: ["A modell felugró csevegése","Rózsaszín ablak automatikus köszöntéssel alul"],
        popups: ["Felugró ablakok","Csevegés választás, csoport, voyeur"],
        promo: ["Reklámok és bannerek","Bannerek, promók, „Csatlakozz” sáv a lejátszón, app"],
        sc_about: ["Blokkok a lejátszó alatt","Rólam, menetrend, hasonló modellek"],
        sc_cookie: ["Süti értesítés","Süti figyelmeztetés alul"],
        sc_footer: ["Oldal alja","Lábléc és jogi szöveg"],
        sc_goal: ["Cél (goal)","A cél haladási sávja"],
        sc_hdr: ["Oldal fejléce","Fejléc logóval, kereséssel és belépés gombokkal"],
        sc_king: ["Borravalókirály","A borravalókirály sávja"],
        sc_promo: ["Reklámok és bannerek","Token-sorsolások, „Csatlakozz” gomb a lejátszón"],
        sc_sub: ["Modell sáv","Avatar, követés és menü a lejátszó felett"],
        sc_topic: ["Szoba témája","A téma szövege a lejátszó felett és benne"],
        sc_widgets: ["Lejátszó widgetek","Szavazások és reakciók a videó felett"],
        sc_wm: ["Vízjel","Logó a videó felett"],
        seotitle: ["Reklámcím","SEO-cím a lista felett"],
        tabs: ["Fülek és rendezés","Lányok/párok, szűrők, rendezés ikon"],
        tipking: ["Borravalókirály","Király csík és borravaló ablak"],
        tips: ["Csevegési tippek","Buborékok és értesítések"],
        wm: ["Vízjel (PC)","Logó a videó felett"],
    },
    JA: {
        actions: ["「プライベート / チップ」ボタン","赤いバー下の青い(PCでは緑の)ボタン"],
        age18: ["18歳以上の確認","「18歳以上のみ」入口画面"],
        balance: ["残高とメニュー(PC)","パソコン表示の残高とナビゲーション"],
        below: ["プレーヤー下のバー(PC)","動画下のボタン列"],
        cb_actions: ["チップボタン","トークン送信のボタンとフォーム"],
        cb_alerts: ["通知","言語の警告と閉じられるメモ"],
        cb_footer: ["ページ下部","フッターとSNS"],
        cb_hdr: ["サイトのヘッダー","ロゴとメニューのあるヘッダー"],
        cb_info: ["ルーム名","ヘッダーの名前と視聴者数"],
        cb_sort: ["並べ替え","モバイル版の並べ替えパネル"],
        cb_status: ["ルームの状態","オフラインとプライベートの案内帯"],
        cb_tags: ["タグの帯","リスト上の人気タグ"],
        cb_tips: ["チャットのチップ","トークンとチップのメッセージ"],
        chat_boost: ["チャットのブースト","「モデルをN秒ブースト」カード"],
        chat_tips: ["チャットのチップ","「トークンを贈りました」メッセージ、チップメニュー"],
        chat_topic: ["ルームのトピック","チャット冒頭の長い「トピック: …」ブロック"],
        chathdr: ["「〜とチャット」バー","「〜と無料チャット」帯とチップキング"],
        ftr: ["ページ下部","フッター、SNS、SEOテキスト"],
        games: ["「ゲーム」ボタン","赤いバーのゲームパッドアイコン"],
        hdr_d: ["サイトのヘッダー(PC)","パソコン表示の上部バー"],
        hdr_m: ["サイトのヘッダー","ロゴ、メニュー、ログインボタン"],
        joinpop: ["登録ウィンドウ","「アカウント作成」ポップアップ"],
        lastview: ["「続きを見る」表示","最後に見たモデルのプレビュー上のボタン"],
        modelchat: ["モデルのポップアップチャット","下部に自動あいさつが出るピンクのウィンドウ"],
        popups: ["ポップアップウィンドウ","チャット選択、グループ、のぞき見"],
        promo: ["広告とバナー","バナー、プロモ、プレーヤー上の「参加」バー、アプリ"],
        sc_about: ["プレーヤー下のブロック","自己紹介、スケジュール、似たモデル"],
        sc_cookie: ["Cookie通知","下部のCookie通知"],
        sc_footer: ["ページ下部","フッターと法的テキスト"],
        sc_goal: ["目標(ゴール)","目標の進行バー"],
        sc_hdr: ["サイトのヘッダー","ロゴ、検索、ログインボタンのあるヘッダー"],
        sc_king: ["チップキング","チップキングの帯"],
        sc_promo: ["広告とバナー","トークン抽選、プレーヤー上の「参加」ボタン"],
        sc_sub: ["モデルバー","プレーヤー上のアバター、フォロー、メニュー"],
        sc_topic: ["ルームのトピック","プレーヤー上と内部のトピック文"],
        sc_widgets: ["プレーヤーのウィジェット","動画上のアンケートとリアクション"],
        sc_wm: ["透かし","動画上のロゴ"],
        seotitle: ["宣伝見出し","リスト上のSEO見出し"],
        tabs: ["タブと並べ替え","女の子/カップル、フィルター、並べ替えアイコン"],
        tipking: ["チップキング","キングの帯とチップウィンドウ"],
        tips: ["チャットのヒント","ツールチップと通知"],
        wm: ["透かし(PC)","動画上のロゴ"],
    },
    ZH: {
        actions: ["“私密 / 打赏”按钮","红色栏下方的蓝色(电脑端为绿色)按钮"],
        age18: ["18+ 提示","“仅限 18 岁以上”入口页"],
        balance: ["余额和菜单(电脑)","电脑端的余额和导航"],
        below: ["播放器下方栏(电脑)","视频下方的按钮行"],
        cb_actions: ["打赏按钮","发送代币的按钮和表单"],
        cb_alerts: ["通知","语言提示和可关闭的说明"],
        cb_footer: ["页面底部","页脚和社交媒体"],
        cb_hdr: ["网站顶部","带有 Logo 和菜单的页头"],
        cb_info: ["房间名称","页头中的名称和观众数"],
        cb_sort: ["排序","移动版的排序面板"],
        cb_status: ["房间状态","离线和私密提示条"],
        cb_tags: ["标签条","列表上方的热门标签"],
        cb_tips: ["聊天打赏","代币和打赏消息"],
        chat_boost: ["聊天加成","“为主播加成 N 秒”卡片"],
        chat_tips: ["聊天打赏","“赠送了代币”消息、打赏菜单"],
        chat_topic: ["房间话题","聊天开头很长的“话题:…”块"],
        chathdr: ["“与…聊天”栏","“与…免费聊天”条和打赏之王"],
        ftr: ["页面底部","页脚、社交媒体、SEO 文字"],
        games: ["“游戏”按钮","红色栏中的手柄图标"],
        hdr_d: ["网站顶部(电脑)","电脑端的顶部栏"],
        hdr_m: ["网站顶部","Logo、菜单、登录按钮"],
        joinpop: ["注册窗口","“创建账号”弹窗"],
        lastview: ["“继续观看”提示","最近观看主播预览上的按钮"],
        modelchat: ["主播弹出聊天","底部带自动问候的粉色窗口"],
        popups: ["弹出窗口","聊天选择、群组、偷看"],
        promo: ["广告和横幅","横幅、推广、播放器上的“加入”条、应用"],
        sc_about: ["播放器下方区块","关于我、日程、相似主播"],
        sc_cookie: ["Cookie 提示","底部的 Cookie 通知"],
        sc_footer: ["页面底部","页脚和法律文字"],
        sc_goal: ["目标(goal)","目标进度条"],
        sc_hdr: ["网站顶部","带有 Logo、搜索和登录按钮的页头"],
        sc_king: ["打赏之王","打赏之王条"],
        sc_promo: ["广告和横幅","代币抽奖、播放器上的“加入”按钮"],
        sc_sub: ["主播栏","播放器上方的头像、关注和菜单"],
        sc_topic: ["房间话题","播放器上方和内部的话题文字"],
        sc_widgets: ["播放器小组件","视频上的投票和互动"],
        sc_wm: ["水印","视频上的 Logo"],
        seotitle: ["广告标题","列表上方的 SEO 标题"],
        tabs: ["标签页和排序","女生/情侣、筛选、排序图标"],
        tipking: ["打赏之王","国王条和打赏窗口"],
        tips: ["聊天提示","提示气泡和通知"],
        wm: ["水印(电脑)","视频上的 Logo"],
    },
    KO: {
        actions: ["“프라이빗 / 팁” 버튼","빨간 바 아래의 파란색(PC에서는 녹색) 버튼"],
        age18: ["18+ 안내","“18세 이상만” 입장 화면"],
        balance: ["잔액 및 메뉴(PC)","컴퓨터 화면의 잔액과 내비게이션"],
        below: ["플레이어 아래 바(PC)","영상 아래의 버튼 줄"],
        cb_actions: ["팁 버튼","토큰 전송 버튼과 양식"],
        cb_alerts: ["알림","언어 경고와 닫을 수 있는 메모"],
        cb_footer: ["페이지 하단","푸터와 SNS"],
        cb_hdr: ["사이트 상단","로고와 메뉴가 있는 헤더"],
        cb_info: ["방 이름","헤더의 이름과 시청자 수"],
        cb_sort: ["정렬","모바일 버전의 정렬 패널"],
        cb_status: ["방 상태","오프라인과 프라이빗 안내 띠"],
        cb_tags: ["태그 띠","목록 위의 인기 태그"],
        cb_tips: ["채팅 팁","토큰과 팁 메시지"],
        chat_boost: ["채팅 부스트","“모델을 N초 부스트했습니다” 카드"],
        chat_tips: ["채팅 팁","“토큰을 선물했습니다” 메시지, 팁 메뉴"],
        chat_topic: ["방 주제","채팅 시작 부분의 긴 “주제: …” 블록"],
        chathdr: ["“…와 채팅” 바","“…와 무료 채팅” 띠와 팁 킹"],
        ftr: ["페이지 하단","푸터, SNS, SEO 텍스트"],
        games: ["“게임” 버튼","빨간 바의 게임패드 아이콘"],
        hdr_d: ["사이트 헤더(PC)","컴퓨터 화면의 상단 바"],
        hdr_m: ["사이트 헤더","로고, 메뉴, 로그인 버튼"],
        joinpop: ["가입 창","“계정 만들기” 팝업"],
        lastview: ["“이어보기” 표시","마지막으로 본 모델 미리보기의 버튼"],
        modelchat: ["모델 팝업 채팅","하단에 자동 인사가 있는 분홍색 창"],
        popups: ["팝업 창","채팅 선택, 그룹, 훔쳐보기"],
        promo: ["광고 및 배너","배너, 프로모션, 플레이어의 “가입” 바, 앱"],
        sc_about: ["플레이어 아래 블록","소개, 일정, 비슷한 모델"],
        sc_cookie: ["쿠키 안내","하단의 쿠키 알림"],
        sc_footer: ["페이지 하단","푸터와 법적 문구"],
        sc_goal: ["목표(골)","목표 진행 바"],
        sc_hdr: ["사이트 상단","로고, 검색, 로그인 버튼이 있는 헤더"],
        sc_king: ["팁 킹","팁 킹 띠"],
        sc_promo: ["광고 및 배너","토큰 추첨, 플레이어의 “참여” 버튼"],
        sc_sub: ["모델 바","플레이어 위의 아바타, 팔로우, 메뉴"],
        sc_topic: ["방 주제","플레이어 위와 안의 주제 문구"],
        sc_widgets: ["플레이어 위젯","영상 위의 투표와 반응"],
        sc_wm: ["워터마크","영상 위의 로고"],
        seotitle: ["광고성 제목","목록 위의 SEO 제목"],
        tabs: ["탭과 정렬","여성/커플, 필터, 정렬 아이콘"],
        tipking: ["팁 킹","킹 띠와 팁 창"],
        tips: ["채팅 안내","툴팁과 알림"],
        wm: ["워터마크(PC)","영상 위의 로고"],
    },
    HE: {
        actions: ["כפתורי “פרטי / טיפ”","כפתורים כחולים (במחשב ירוקים) מתחת לפס האדום"],
        age18: ["הודעת 18+","מסך כניסה “18 ומעלה בלבד”"],
        balance: ["יתרה ותפריט (מחשב)","יתרה וניווט במחשב"],
        below: ["פס מתחת לנגן (מחשב)","שורת כפתורים מתחת לוידאו"],
        cb_actions: ["כפתור טיפ","כפתור וטופס לשליחת טוקנים"],
        cb_alerts: ["התראות","אזהרת שפה והערות שניתן לסגור"],
        cb_footer: ["תחתית העמוד","פוטר ורשתות חברתיות"],
        cb_hdr: ["כותרת האתר","כותרת עם לוגו ותפריט"],
        cb_info: ["שם החדר","שם ומונה צופים בכותרת"],
        cb_sort: ["מיון","לוח מיון בגרסה לנייד"],
        cb_status: ["סטטוס החדר","רצועת לא מחובר ופרטי עם רמזים"],
        cb_tags: ["רצועת תגיות","תגיות פופולריות מעל הרשימה"],
        cb_tips: ["טיפים בצ׳אט","הודעות על טוקנים וטיפים"],
        chat_boost: ["בוסטים בצ׳אט","כרטיסים “הגביר את המודל ל-N שניות”"],
        chat_tips: ["טיפים בצ׳אט","הודעות “שלח טוקנים”, תפריט טיפים"],
        chat_topic: ["נושא החדר","בלוק ארוך “נושא: …” בתחילת הצ׳אט"],
        chathdr: ["פס “צ׳אט עם …”","רצועת “צ׳אט חינם עם …” ומלך הטיפים"],
        ftr: ["תחתית העמוד","פוטר, רשתות חברתיות, טקסט SEO"],
        games: ["כפתור “משחקים”","סמל שלט משחק בפס האדום"],
        hdr_d: ["כותרת האתר (מחשב)","הסרגל העליון במחשב"],
        hdr_m: ["כותרת האתר","לוגו, תפריט, כפתורי כניסה"],
        joinpop: ["חלון הרשמה","חלון קופץ “יצירת חשבון”"],
        lastview: ["הודעת “המשך צפייה”","כפתור על התצוגה המקדימה של המודל האחרון"],
        modelchat: ["צ׳אט קופץ של המודל","חלון ורוד עם ברכה אוטומטית בתחתית"],
        popups: ["חלונות קופצים","בחירת צ׳אט, קבוצה, מציצן"],
        promo: ["פרסומות ובאנרים","באנרים, מבצעים, פס “הצטרף” בנגן, אפליקציה"],
        sc_about: ["בלוקים מתחת לנגן","אודותיי, לוח זמנים, מודלים דומים"],
        sc_cookie: ["הודעת קוקיז","הודעת קוקיז בתחתית"],
        sc_footer: ["תחתית העמוד","פוטר וטקסט משפטי"],
        sc_goal: ["יעד (goal)","סרגל התקדמות היעד"],
        sc_hdr: ["כותרת האתר","כותרת עם לוגו, חיפוש וכפתורי כניסה"],
        sc_king: ["מלך הטיפים","רצועת מלך הטיפים"],
        sc_promo: ["פרסומות ובאנרים","הגרלות טוקנים, כפתור “הצטרף” בנגן"],
        sc_sub: ["סרגל המודל","אווטאר, מעקב ותפריט מעל הנגן"],
        sc_topic: ["נושא החדר","טקסט הנושא מעל הנגן ובתוכו"],
        sc_widgets: ["ווידג׳טים בנגן","סקרים ותגובות מעל הוידאו"],
        sc_wm: ["סימן מים","לוגו מעל הוידאו"],
        seotitle: ["כותרת פרסומית","כותרת SEO מעל הרשימה"],
        tabs: ["לשוניות ומיון","בנות/זוגות, סינון, סמל מיון"],
        tipking: ["מלך הטיפים","רצועת המלך וחלון הטיפים"],
        tips: ["רמזים בצ׳אט","טיפים צפים והתראות"],
        wm: ["סימן מים (מחשב)","לוגו מעל הוידאו"],
    },
    AR: {
        actions: ["أزرار «خاص / بقشيش»","أزرار زرقاء (خضراء على الكمبيوتر) تحت الشريط الأحمر"],
        age18: ["تنبيه +18","شاشة الدخول «للكبار فقط 18+»"],
        balance: ["الرصيد والقائمة (الكمبيوتر)","الرصيد والتنقل على الكمبيوتر"],
        below: ["الشريط تحت المشغّل (الكمبيوتر)","صف الأزرار تحت الفيديو"],
        cb_actions: ["زر البقشيش","زر ونموذج إرسال الرموز"],
        cb_alerts: ["الإشعارات","تنبيه اللغة وملاحظات قابلة للإغلاق"],
        cb_footer: ["أسفل الصفحة","التذييل والشبكات الاجتماعية"],
        cb_hdr: ["رأس الموقع","رأس مع الشعار والقائمة"],
        cb_info: ["اسم الغرفة","الاسم وعدّاد المشاهدين في الرأس"],
        cb_sort: ["الفرز","لوحة الفرز في نسخة الجوال"],
        cb_status: ["حالة الغرفة","شريط غير متصل وخاص مع تلميحات"],
        cb_tags: ["شريط الوسوم","وسوم شائعة فوق القائمة"],
        cb_tips: ["البقشيش في الدردشة","رسائل الرموز والبقشيش"],
        chat_boost: ["التعزيزات في الدردشة","بطاقات «عزّز الموديل لمدة N ثانية»"],
        chat_tips: ["البقشيش في الدردشة","رسائل «أهدى رموزًا»، قائمة البقشيش"],
        chat_topic: ["موضوع الغرفة","كتلة طويلة «الموضوع: …» في بداية الدردشة"],
        chathdr: ["شريط «دردشة مع …»","شريط «دردشة مجانية مع …» وملك البقشيش"],
        ftr: ["أسفل الصفحة","التذييل، الشبكات الاجتماعية، نص SEO"],
        games: ["زر «الألعاب»","أيقونة يد التحكم في الشريط الأحمر"],
        hdr_d: ["رأس الموقع (الكمبيوتر)","الشريط العلوي على الكمبيوتر"],
        hdr_m: ["رأس الموقع","الشعار والقائمة وأزرار الدخول"],
        joinpop: ["نافذة التسجيل","نافذة منبثقة «إنشاء حساب»"],
        lastview: ["تنبيه «متابعة المشاهدة»","زر على معاينة آخر موديل"],
        modelchat: ["دردشة الموديل المنبثقة","نافذة وردية بتحية تلقائية في الأسفل"],
        popups: ["النوافذ المنبثقة","اختيار الدردشة، المجموعة، المتلصص"],
        promo: ["الإعلانات واللافتات","لافتات، عروض، شريط «انضم» على المشغّل، التطبيق"],
        sc_about: ["كتل أسفل المشغّل","نبذة عني والجدول وموديلات مشابهة"],
        sc_cookie: ["إشعار الكوكيز","إشعار الكوكيز في الأسفل"],
        sc_footer: ["أسفل الصفحة","التذييل والنص القانوني"],
        sc_goal: ["الهدف (goal)","شريط تقدّم الهدف"],
        sc_hdr: ["رأس الموقع","رأس مع الشعار والبحث وأزرار الدخول"],
        sc_king: ["ملك البقشيش","شريط ملك البقشيش"],
        sc_promo: ["الإعلانات واللافتات","سحوبات الرموز وزر «انضم» على المشغّل"],
        sc_sub: ["شريط الموديل","الصورة الرمزية والمتابعة والقائمة فوق المشغّل"],
        sc_topic: ["موضوع الغرفة","نص الموضوع فوق المشغّل وداخله"],
        sc_widgets: ["أدوات المشغّل","استطلاعات وتفاعلات فوق الفيديو"],
        sc_wm: ["العلامة المائية","الشعار فوق الفيديو"],
        seotitle: ["عنوان إعلاني","عنوان SEO فوق القائمة"],
        tabs: ["التبويبات والفرز","فتيات/أزواج، فلاتر، أيقونة الفرز"],
        tipking: ["ملك البقشيش","شريط الملك ونافذة البقشيش"],
        tips: ["تلميحات الدردشة","تلميحات وإشعارات"],
        wm: ["العلامة المائية (الكمبيوتر)","الشعار فوق الفيديو"],
    },
    HI: {
        actions: ["“प्राइवेट / टिप” बटन","लाल पट्टी के नीचे नीले (PC पर हरे) बटन"],
        age18: ["18+ चेतावनी","“केवल 18+” एंट्री स्क्रीन"],
        balance: ["बैलेंस और मेन्यू (PC)","कंप्यूटर पर बैलेंस और नेविगेशन"],
        below: ["प्लेयर के नीचे की पट्टी (PC)","वीडियो के नीचे बटनों की पंक्ति"],
        cb_actions: ["टिप बटन","टोकन भेजने का बटन और फ़ॉर्म"],
        cb_alerts: ["सूचनाएँ","भाषा की चेतावनी और बंद होने वाले नोट"],
        cb_footer: ["पेज का निचला हिस्सा","फ़ुटर और सोशल मीडिया"],
        cb_hdr: ["साइट का हेडर","लोगो और मेन्यू वाला हेडर"],
        cb_info: ["रूम का नाम","हेडर में नाम और दर्शकों की गिनती"],
        cb_sort: ["सॉर्टिंग","मोबाइल संस्करण में सॉर्ट पैनल"],
        cb_status: ["रूम की स्थिति","ऑफ़लाइन और प्राइवेट पट्टी, सुझावों के साथ"],
        cb_tags: ["टैग पट्टी","सूची के ऊपर लोकप्रिय टैग"],
        cb_tips: ["चैट में टिप","टोकन और टिप के संदेश"],
        chat_boost: ["चैट में बूस्ट","“मॉडल को N सेकंड बूस्ट किया” कार्ड"],
        chat_tips: ["चैट में टिप","“टोकन दिए” संदेश, टिप मेन्यू"],
        chat_topic: ["रूम का विषय","चैट की शुरुआत में लंबा “विषय: …” ब्लॉक"],
        chathdr: ["“… के साथ चैट” पट्टी","“… के साथ मुफ़्त चैट” पट्टी और टिप किंग"],
        ftr: ["पेज का निचला हिस्सा","फ़ुटर, सोशल मीडिया, SEO टेक्स्ट"],
        games: ["“गेम” बटन","लाल पट्टी में गेमपैड आइकन"],
        hdr_d: ["साइट का हेडर (PC)","कंप्यूटर पर ऊपर की पट्टी"],
        hdr_m: ["साइट का हेडर","लोगो, मेन्यू, लॉगिन बटन"],
        joinpop: ["रजिस्ट्रेशन विंडो","“खाता बनाएँ” पॉप-अप"],
        lastview: ["“देखना जारी रखें” सूचना","आख़िरी देखी मॉडल के प्रीव्यू पर बटन"],
        modelchat: ["मॉडल का पॉप-अप चैट","नीचे ऑटो-अभिवादन वाली गुलाबी विंडो"],
        popups: ["पॉप-अप विंडो","चैट चयन, ग्रुप, वॉयर"],
        promo: ["विज्ञापन और बैनर","बैनर, प्रोमो, प्लेयर पर “जुड़ें” पट्टी, ऐप"],
        sc_about: ["प्लेयर के नीचे के ब्लॉक","मेरे बारे में, शेड्यूल, मिलती-जुलती मॉडल"],
        sc_cookie: ["कुकी सूचना","नीचे कुकी की सूचना"],
        sc_footer: ["पेज का निचला हिस्सा","फ़ुटर और कानूनी टेक्स्ट"],
        sc_goal: ["लक्ष्य (गोल)","लक्ष्य की प्रगति पट्टी"],
        sc_hdr: ["साइट का हेडर","लोगो, खोज और लॉगिन बटन वाला हेडर"],
        sc_king: ["टिप किंग","टिप किंग की पट्टी"],
        sc_promo: ["विज्ञापन और बैनर","टोकन ड्रॉ, प्लेयर पर “जुड़ें” बटन"],
        sc_sub: ["मॉडल बार","प्लेयर के ऊपर अवतार, फ़ॉलो और मेन्यू"],
        sc_topic: ["रूम का विषय","प्लेयर के ऊपर और अंदर विषय का टेक्स्ट"],
        sc_widgets: ["प्लेयर विजेट","वीडियो के ऊपर पोल और रिएक्शन"],
        sc_wm: ["वॉटरमार्क","वीडियो के ऊपर लोगो"],
        seotitle: ["विज्ञापन शीर्षक","सूची के ऊपर SEO शीर्षक"],
        tabs: ["टैब और सॉर्टिंग","लड़कियाँ/जोड़े, फ़िल्टर, सॉर्ट आइकन"],
        tipking: ["टिप किंग","किंग पट्टी और टिप विंडो"],
        tips: ["चैट सुझाव","टूलटिप और सूचनाएँ"],
        wm: ["वॉटरमार्क (PC)","वीडियो के ऊपर लोगो"],
    },
    ID: {
        actions: ["Tombol “Privat / Tip”","Tombol biru (hijau di PC) di bawah bilah merah"],
        age18: ["Peringatan 18+","Layar masuk “Khusus 18+”"],
        balance: ["Saldo dan menu (PC)","Saldo dan navigasi di komputer"],
        below: ["Bilah di bawah pemutar (PC)","Baris tombol di bawah video"],
        cb_actions: ["Tombol tip","Tombol dan formulir kirim token"],
        cb_alerts: ["Pemberitahuan","Peringatan bahasa dan catatan yang bisa ditutup"],
        cb_footer: ["Bagian bawah halaman","Footer dan media sosial"],
        cb_hdr: ["Header situs","Header dengan logo dan menu"],
        cb_info: ["Nama ruangan","Nama dan penghitung penonton di header"],
        cb_sort: ["Pengurutan","Panel urutkan di versi seluler"],
        cb_status: ["Status ruangan","Pita offline dan privat dengan petunjuk"],
        cb_tags: ["Pita tag","Tag populer di atas daftar"],
        cb_tips: ["Tip di chat","Pesan token dan tip"],
        chat_boost: ["Boost di chat","Kartu “mem-boost model selama N detik”"],
        chat_tips: ["Tip di chat","Pesan “memberi token”, menu tip"],
        chat_topic: ["Topik ruangan","Blok panjang “Topik: …” di awal chat"],
        chathdr: ["Bilah “Chat dengan …”","Pita “Chat gratis dengan …” dan raja tip"],
        ftr: ["Bagian bawah halaman","Footer, media sosial, teks SEO"],
        games: ["Tombol “Game”","Ikon gamepad di bilah merah"],
        hdr_d: ["Header situs (PC)","Bilah atas di komputer"],
        hdr_m: ["Header situs","Logo, menu, tombol masuk"],
        joinpop: ["Jendela pendaftaran","Pop-up “Buat akun”"],
        lastview: ["Pemberitahuan “Lanjutkan menonton”","Tombol di pratinjau model terakhir"],
        modelchat: ["Chat pop-up model","Jendela pink dengan sapaan otomatis di bawah"],
        popups: ["Jendela pop-up","Pilihan chat, grup, voyeur"],
        promo: ["Iklan dan banner","Banner, promo, bilah “Bergabung” di pemutar, aplikasi"],
        sc_about: ["Blok di bawah pemutar","Tentang saya, jadwal, model serupa"],
        sc_cookie: ["Pemberitahuan cookie","Pemberitahuan cookie di bagian bawah"],
        sc_footer: ["Bagian bawah halaman","Footer dan teks hukum"],
        sc_goal: ["Target (goal)","Bilah progres target"],
        sc_hdr: ["Header situs","Header dengan logo, pencarian, dan tombol masuk"],
        sc_king: ["Raja tip","Pita raja tip"],
        sc_promo: ["Iklan dan banner","Undian token, tombol “Bergabung” di pemutar"],
        sc_sub: ["Bilah model","Avatar, ikuti, dan menu di atas pemutar"],
        sc_topic: ["Topik ruangan","Teks topik di atas dan di dalam pemutar"],
        sc_widgets: ["Widget pemutar","Polling dan reaksi di atas video"],
        sc_wm: ["Watermark","Logo di atas video"],
        seotitle: ["Judul iklan","Judul SEO di atas daftar"],
        tabs: ["Tab dan pengurutan","Cewek/pasangan, filter, ikon urutkan"],
        tipking: ["Raja tip","Pita raja dan jendela tip"],
        tips: ["Petunjuk chat","Tooltip dan notifikasi"],
        wm: ["Watermark (PC)","Logo di atas video"],
    },
    VI: {
        actions: ["Nút “Riêng tư / Tip”","Nút xanh dương (xanh lá trên PC) dưới thanh đỏ"],
        age18: ["Cảnh báo 18+","Màn hình vào “Chỉ dành cho 18+”"],
        balance: ["Số dư và menu (PC)","Số dư và điều hướng trên máy tính"],
        below: ["Thanh dưới trình phát (PC)","Hàng nút bên dưới video"],
        cb_actions: ["Nút tip","Nút và biểu mẫu gửi token"],
        cb_alerts: ["Thông báo","Cảnh báo ngôn ngữ và ghi chú có thể đóng"],
        cb_footer: ["Chân trang","Footer và mạng xã hội"],
        cb_hdr: ["Phần đầu trang","Header có logo và menu"],
        cb_info: ["Tên phòng","Tên và số người xem trong header"],
        cb_sort: ["Sắp xếp","Bảng sắp xếp trên bản di động"],
        cb_status: ["Trạng thái phòng","Dải offline và riêng tư kèm gợi ý"],
        cb_tags: ["Dải thẻ","Thẻ phổ biến phía trên danh sách"],
        cb_tips: ["Tip trong chat","Tin nhắn về token và tip"],
        chat_boost: ["Boost trong chat","Thẻ “đã boost model N giây”"],
        chat_tips: ["Tip trong chat","Tin nhắn “đã tặng token”, menu tip"],
        chat_topic: ["Chủ đề phòng","Khối dài “Chủ đề: …” ở đầu chat"],
        chathdr: ["Thanh “Chat với …”","Dải “Chat miễn phí với …” và vua tip"],
        ftr: ["Chân trang","Footer, mạng xã hội, văn bản SEO"],
        games: ["Nút “Trò chơi”","Biểu tượng tay cầm ở thanh đỏ"],
        hdr_d: ["Phần đầu trang (PC)","Thanh trên cùng trên máy tính"],
        hdr_m: ["Phần đầu trang","Logo, menu, nút đăng nhập"],
        joinpop: ["Cửa sổ đăng ký","Cửa sổ bật lên “Tạo tài khoản”"],
        lastview: ["Thông báo “Xem tiếp”","Nút trên bản xem trước của model xem gần nhất"],
        modelchat: ["Chat bật lên của model","Cửa sổ màu hồng có lời chào tự động ở dưới"],
        popups: ["Cửa sổ bật lên","Chọn chat, nhóm, voyeur"],
        promo: ["Quảng cáo và banner","Banner, khuyến mãi, thanh “Tham gia” trên trình phát, ứng dụng"],
        sc_about: ["Khối dưới trình phát","Giới thiệu, lịch, model tương tự"],
        sc_cookie: ["Thông báo cookie","Thông báo cookie ở phía dưới"],
        sc_footer: ["Chân trang","Footer và văn bản pháp lý"],
        sc_goal: ["Mục tiêu (goal)","Thanh tiến độ mục tiêu"],
        sc_hdr: ["Phần đầu trang","Header có logo, tìm kiếm và nút đăng nhập"],
        sc_king: ["Vua tip","Dải vua tip"],
        sc_promo: ["Quảng cáo và banner","Rút thăm token, nút “Tham gia” trên trình phát"],
        sc_sub: ["Thanh model","Avatar, theo dõi và menu phía trên trình phát"],
        sc_topic: ["Chủ đề phòng","Văn bản chủ đề phía trên và trong trình phát"],
        sc_widgets: ["Tiện ích trên trình phát","Bình chọn và phản ứng trên video"],
        sc_wm: ["Hình mờ","Logo trên video"],
        seotitle: ["Tiêu đề quảng cáo","Tiêu đề SEO phía trên danh sách"],
        tabs: ["Tab và sắp xếp","Các cô gái/cặp đôi, bộ lọc, biểu tượng sắp xếp"],
        tipking: ["Vua tip","Dải vua và cửa sổ tip"],
        tips: ["Gợi ý trong chat","Tooltip và thông báo"],
        wm: ["Hình mờ (PC)","Logo phía trên video"],
    },
    TH: {
        actions: ["ปุ่ม “ส่วนตัว / ทิป”","ปุ่มสีน้ำเงิน (บน PC เป็นสีเขียว) ใต้แถบสีแดง"],
        age18: ["คำเตือน 18+","หน้าจอเข้าสู่ระบบ “เฉพาะ 18+”"],
        balance: ["ยอดเงินและเมนู (PC)","ยอดเงินและการนำทางบนคอมพิวเตอร์"],
        below: ["แถบใต้เครื่องเล่น (PC)","แถวปุ่มใต้วิดีโอ"],
        cb_actions: ["ปุ่มทิป","ปุ่มและฟอร์มส่งโทเคน"],
        cb_alerts: ["การแจ้งเตือน","คำเตือนเรื่องภาษาและโน้ตที่ปิดได้"],
        cb_footer: ["ท้ายหน้า","ส่วนท้ายและโซเชียล"],
        cb_hdr: ["ส่วนหัวเว็บไซต์","ส่วนหัวที่มีโลโก้และเมนู"],
        cb_info: ["ชื่อห้อง","ชื่อและตัวนับผู้ชมในส่วนหัว"],
        cb_sort: ["การเรียง","แผงเรียงในเวอร์ชันมือถือ"],
        cb_status: ["สถานะห้อง","แถบออฟไลน์และส่วนตัวพร้อมคำแนะนำ"],
        cb_tags: ["แถบแท็ก","แท็กยอดนิยมเหนือรายการ"],
        cb_tips: ["ทิปในแชต","ข้อความเกี่ยวกับโทเคนและทิป"],
        chat_boost: ["บูสต์ในแชต","การ์ด “บูสต์โมเดล N วินาที”"],
        chat_tips: ["ทิปในแชต","ข้อความ “ให้โทเคน” เมนูทิป"],
        chat_topic: ["หัวข้อห้อง","บล็อกยาว “หัวข้อ: …” ตอนต้นแชต"],
        chathdr: ["แถบ “แชตกับ …”","แถบ “แชตฟรีกับ …” และราชาทิป"],
        ftr: ["ท้ายหน้า","ส่วนท้าย โซเชียล ข้อความ SEO"],
        games: ["ปุ่ม “เกม”","ไอคอนจอยเกมในแถบสีแดง"],
        hdr_d: ["ส่วนหัวเว็บไซต์ (PC)","แถบด้านบนบนคอมพิวเตอร์"],
        hdr_m: ["ส่วนหัวเว็บไซต์","โลโก้ เมนู ปุ่มเข้าสู่ระบบ"],
        joinpop: ["หน้าต่างสมัครสมาชิก","ป๊อปอัป “สร้างบัญชี”"],
        lastview: ["แจ้งเตือน “ดูต่อ”","ปุ่มบนตัวอย่างของโมเดลที่ดูล่าสุด"],
        modelchat: ["แชตป๊อปอัปของโมเดล","หน้าต่างสีชมพูพร้อมคำทักทายอัตโนมัติด้านล่าง"],
        popups: ["หน้าต่างป๊อปอัป","เลือกแชต กลุ่ม คนแอบดู"],
        promo: ["โฆษณาและแบนเนอร์","แบนเนอร์ โปรโมชัน แถบ “เข้าร่วม” บนเครื่องเล่น แอป"],
        sc_about: ["บล็อกใต้เครื่องเล่น","เกี่ยวกับฉัน ตารางเวลา โมเดลที่คล้ายกัน"],
        sc_cookie: ["แจ้งเตือนคุกกี้","การแจ้งเตือนคุกกี้ด้านล่าง"],
        sc_footer: ["ท้ายหน้า","ส่วนท้ายและข้อความทางกฎหมาย"],
        sc_goal: ["เป้าหมาย (goal)","แถบความคืบหน้าของเป้าหมาย"],
        sc_hdr: ["ส่วนหัวเว็บไซต์","ส่วนหัวที่มีโลโก้ ค้นหา และปุ่มเข้าสู่ระบบ"],
        sc_king: ["ราชาทิป","แถบราชาทิป"],
        sc_promo: ["โฆษณาและแบนเนอร์","จับรางวัลโทเคน ปุ่ม “เข้าร่วม” บนเครื่องเล่น"],
        sc_sub: ["แถบโมเดล","อวาตาร์ ติดตาม และเมนูเหนือเครื่องเล่น"],
        sc_topic: ["หัวข้อห้อง","ข้อความหัวข้อเหนือและในเครื่องเล่น"],
        sc_widgets: ["วิดเจ็ตบนเครื่องเล่น","โพลและรีแอคชันเหนือวิดีโอ"],
        sc_wm: ["ลายน้ำ","โลโก้เหนือวิดีโอ"],
        seotitle: ["หัวข้อโฆษณา","หัวข้อ SEO เหนือรายการ"],
        tabs: ["แท็บและการเรียง","สาวๆ/คู่รัก ตัวกรอง ไอคอนเรียง"],
        tipking: ["ราชาทิป","แถบราชาและหน้าต่างทิป"],
        tips: ["คำแนะนำในแชต","ทูลทิปและการแจ้งเตือน"],
        wm: ["ลายน้ำ (PC)","โลโก้เหนือวิดีโอ"],
    },
};

const SUPPORT_TXT = {
    RU: { or: "Chaturbate · реферальная ссылка", sub: "Создать аккаунт и поддержать автора скрипта", btn: "Открыть", note: "Это реферальная ссылка автора скрипта. Если вы зарегистрируетесь на Chaturbate по ней, автор получит вознаграждение от Chaturbate. Для вас это бесплатно: цена и условия не меняются. Ссылка откроется в новой вкладке только после вашего нажатия." },
    EN: { or: "Chaturbate · referral link", sub: "Create an account and support the script author", btn: "Open", note: "This is the script author's referral link. If you sign up on Chaturbate through it, the author receives a reward from Chaturbate. It's free for you: the price and terms don't change. The link opens in a new tab only after you click." },
    UK: { or: "Chaturbate · реферальне посилання", sub: "Створити акаунт і підтримати автора скрипта", btn: "Відкрити", note: "Це реферальне посилання автора скрипта. Якщо ви зареєструєтеся на Chaturbate за ним, автор отримає винагороду від Chaturbate. Для вас це безкоштовно: ціна й умови не змінюються. Посилання відкриється в новій вкладці лише після вашого натискання." },
    ES: { or: "Chaturbate · enlace de referido", sub: "Crear una cuenta y apoyar al autor del script", btn: "Abrir", note: "Este es el enlace de referido del autor del script. Si te registras en Chaturbate con él, el autor recibe una recompensa de Chaturbate. Para ti es gratis: el precio y las condiciones no cambian. El enlace se abre en una pestaña nueva solo cuando lo pulsas." },
    DE: { or: "Chaturbate · Empfehlungslink", sub: "Konto erstellen und den Autor des Skripts unterstützen", btn: "Öffnen", note: "Das ist der Empfehlungslink des Skript-Autors. Wenn du dich über ihn bei Chaturbate registrierst, erhält der Autor eine Vergütung von Chaturbate. Für dich ist das kostenlos: Preis und Bedingungen ändern sich nicht. Der Link öffnet sich nur nach deinem Klick in einem neuen Tab." },
    FR: { or: "Chaturbate · lien de parrainage", sub: "Créer un compte et soutenir l’auteur du script", btn: "Ouvrir", note: "Ceci est le lien de parrainage de l’auteur du script. Si vous vous inscrivez sur Chaturbate via ce lien, l’auteur reçoit une rémunération de Chaturbate. C’est gratuit pour vous : le prix et les conditions ne changent pas. Le lien s’ouvre dans un nouvel onglet uniquement après votre clic." },
    PT: { or: "Chaturbate · link de indicação", sub: "Criar uma conta e apoiar o autor do script", btn: "Abrir", note: "Este é o link de indicação do autor do script. Se você se cadastrar no Chaturbate por ele, o autor recebe uma recompensa do Chaturbate. Para você é grátis: o preço e as condições não mudam. O link só abre em uma nova aba depois do seu clique." },
    IT: { or: "Chaturbate · link di affiliazione", sub: "Crea un account e sostieni l’autore dello script", btn: "Apri", note: "Questo è il link di affiliazione dell’autore dello script. Se ti registri su Chaturbate tramite questo link, l’autore riceve un compenso da Chaturbate. Per te è gratis: prezzo e condizioni non cambiano. Il link si apre in una nuova scheda solo dopo il tuo clic." },
    PL: { or: "Chaturbate · link polecający", sub: "Załóż konto i wesprzyj autora skryptu", btn: "Otwórz", note: "To link polecający autora skryptu. Jeśli zarejestrujesz się na Chaturbate przez ten link, autor otrzyma wynagrodzenie od Chaturbate. Dla ciebie jest to bezpłatne: cena i warunki się nie zmieniają. Link otworzy się w nowej karcie dopiero po kliknięciu." },
    TR: { or: "Chaturbate · referans bağlantısı", sub: "Hesap oluştur ve betiğin yazarını destekle", btn: "Aç", note: "Bu, betiğin yazarının referans bağlantısıdır. Bu bağlantı üzerinden Chaturbate’e kayıt olursanız, yazar Chaturbate’ten bir ödül alır. Sizin için ücretsizdir: fiyat ve koşullar değişmez. Bağlantı yalnızca tıkladığınızda yeni sekmede açılır." },
    NL: { or: "Chaturbate · uitnodigingslink", sub: "Maak een account aan en steun de auteur van het script", btn: "Openen", note: "Dit is de uitnodigingslink van de scriptauteur. Als je je via deze link registreert op Chaturbate, ontvangt de auteur een vergoeding van Chaturbate. Voor jou is het gratis: prijs en voorwaarden veranderen niet. De link opent pas na jouw klik in een nieuw tabblad." },
    CS: { or: "Chaturbate · odkaz s doporučením", sub: "Vytvořit účet a podpořit autora skriptu", btn: "Otevřít", note: "Toto je odkaz autora skriptu s doporučením. Pokud se přes něj zaregistrujete na Chaturbate, autor získá odměnu od Chaturbate. Pro vás je to zdarma: cena ani podmínky se nemění. Odkaz se otevře v nové záložce až po kliknutí." },
    RO: { or: "Chaturbate · link de recomandare", sub: "Creează un cont și susține autorul scriptului", btn: "Deschide", note: "Acesta este linkul de recomandare al autorului scriptului. Dacă te înregistrezi pe Chaturbate prin el, autorul primește o recompensă de la Chaturbate. Pentru tine este gratuit: prețul și condițiile nu se schimbă. Linkul se deschide într-o filă nouă doar după ce dai clic." },
    HU: { or: "Chaturbate · meghívólink", sub: "Fiók létrehozása és a szkript szerzőjének támogatása", btn: "Megnyitás", note: "Ez a szkript szerzőjének meghívólinkje. Ha ezen keresztül regisztrálsz a Chaturbate-en, a szerző jutalmat kap a Chaturbate-től. Neked ingyenes: az ár és a feltételek nem változnak. A link csak a kattintásod után nyílik meg új lapon." },
    JA: { or: "Chaturbate · 紹介リンク", sub: "アカウントを作成して、スクリプトの作者を支援", btn: "開く", note: "これはスクリプト作者の紹介リンクです。このリンクからChaturbateに登録すると、作者はChaturbateから報酬を受け取ります。あなたは無料で、料金や条件は変わりません。リンクはクリックしたときだけ新しいタブで開きます。" },
    ZH: { or: "Chaturbate · 推荐链接", sub: "创建账号并支持脚本作者", btn: "打开", note: "这是脚本作者的推荐链接。如果你通过它注册 Chaturbate,作者会从 Chaturbate 获得奖励。对你来说完全免费,价格和条款不会改变。链接只会在你点击后于新标签页打开。" },
    KO: { or: "Chaturbate · 추천 링크", sub: "계정을 만들고 스크립트 제작자를 후원하기", btn: "열기", note: "스크립트 작성자의 추천 링크입니다. 이 링크로 Chaturbate에 가입하면 작성자가 Chaturbate로부터 보상을 받습니다. 사용자에게는 무료이며 가격과 조건은 달라지지 않습니다. 링크는 클릭한 경우에만 새 탭에서 열립니다." },
    HE: { or: "Chaturbate · קישור הפניה", sub: "יצירת חשבון ותמיכה ביוצר הסקריפט", btn: "פתח", note: "זה קישור ההפניה של יוצר הסקריפט. אם תירשמו ל-Chaturbate דרכו, היוצר יקבל תגמול מ-Chaturbate. עבורכם זה בחינם: המחיר והתנאים לא משתנים. הקישור נפתח בלשונית חדשה רק לאחר הלחיצה שלכם." },
    AR: { or: "Chaturbate · رابط الإحالة", sub: "أنشئ حسابًا وادعم مطوّر السكربت", btn: "فتح", note: "هذا رابط الإحالة الخاص بمطوّر السكربت. إذا سجّلت في Chaturbate عبره، يحصل المطوّر على مكافأة من Chaturbate. هذا مجاني بالنسبة لك: السعر والشروط لا يتغيران. يُفتح الرابط في علامة تبويب جديدة فقط بعد نقرك عليه." },
    HI: { or: "Chaturbate · रेफ़रल लिंक", sub: "खाता बनाएँ और स्क्रिप्ट के लेखक का समर्थन करें", btn: "खोलें", note: "यह स्क्रिप्ट के लेखक का रेफ़रल लिंक है। अगर आप इसके ज़रिए Chaturbate पर रजिस्टर करते हैं, तो लेखक को Chaturbate से पुरस्कार मिलता है। आपके लिए यह मुफ़्त है: कीमत और शर्तें नहीं बदलतीं। लिंक आपके क्लिक करने के बाद ही नए टैब में खुलता है।" },
    ID: { or: "Chaturbate · tautan referral", sub: "Buat akun dan dukung pembuat skrip", btn: "Buka", note: "Ini adalah tautan referral pembuat skrip. Jika kamu mendaftar di Chaturbate lewat tautan ini, pembuat menerima imbalan dari Chaturbate. Untukmu ini gratis: harga dan ketentuan tidak berubah. Tautan hanya terbuka di tab baru setelah kamu mengkliknya." },
    VI: { or: "Chaturbate · liên kết giới thiệu", sub: "Tạo tài khoản và ủng hộ tác giả của script", btn: "Mở", note: "Đây là liên kết giới thiệu của tác giả tập lệnh. Nếu bạn đăng ký Chaturbate qua liên kết này, tác giả sẽ nhận phần thưởng từ Chaturbate. Với bạn thì hoàn toàn miễn phí: giá và điều khoản không thay đổi. Liên kết chỉ mở trong tab mới sau khi bạn nhấn." },
    TH: { or: "Chaturbate · ลิงก์แนะนำ", sub: "สร้างบัญชีและสนับสนุนผู้สร้างสคริปต์", btn: "เปิด", note: "นี่คือลิงก์แนะนำของผู้สร้างสคริปต์ หากคุณสมัคร Chaturbate ผ่านลิงก์นี้ ผู้สร้างจะได้รับรางวัลจาก Chaturbate สำหรับคุณไม่มีค่าใช้จ่าย ราคาและเงื่อนไขไม่เปลี่ยนแปลง ลิงก์จะเปิดในแท็บใหม่ก็ต่อเมื่อคุณกดเท่านั้น" }
};

const REC_FMT_T = {
    EN: ['Format', 'MP4 is not supported by this browser, WebM will be used'],
    RU: ['Формат', 'Этот браузер не поддерживает MP4, будет использован WebM'],
    UK: ['Формат', 'Цей браузер не підтримує MP4, буде використано WebM'],
    ES: ['Formato', 'Este navegador no admite MP4, se usará WebM'],
    DE: ['Format', 'MP4 wird von diesem Browser nicht unterstützt, es wird WebM verwendet'],
    FR: ['Format', 'MP4 n’est pas pris en charge par ce navigateur, WebM sera utilisé'],
    PT: ['Formato', 'Este navegador não suporta MP4, será usado WebM'],
    IT: ['Formato', 'MP4 non è supportato da questo browser, verrà usato WebM'],
    PL: ['Format', 'Ta przeglądarka nie obsługuje MP4, zostanie użyty WebM'],
    TR: ['Biçim', 'Bu tarayıcı MP4 desteklemiyor, WebM kullanılacak'],
    NL: ['Formaat', 'Deze browser ondersteunt MP4 niet, WebM wordt gebruikt'],
    CS: ['Formát', 'Tento prohlížeč nepodporuje MP4, bude použit WebM'],
    RO: ['Format', 'Acest browser nu acceptă MP4, se va folosi WebM'],
    HU: ['Formátum', 'Ez a böngésző nem támogatja az MP4-et, WebM lesz használva'],
    JA: ['形式', 'このブラウザはMP4に対応していないため、WebMが使用されます'],
    ZH: ['格式', '此浏览器不支持 MP4,将使用 WebM'],
    KO: ['형식', '이 브라우저는 MP4를 지원하지 않아 WebM이 사용됩니다'],
    HE: ['פורמט', 'הדפדפן הזה אינו תומך ב-MP4, ייעשה שימוש ב-WebM'],
    AR: ['الصيغة', 'هذا المتصفح لا يدعم MP4، وسيتم استخدام WebM'],
    HI: ['फ़ॉर्मैट', 'यह ब्राउज़र MP4 का समर्थन नहीं करता, WebM का उपयोग किया जाएगा'],
    ID: ['Format', 'Browser ini tidak mendukung MP4, WebM akan digunakan'],
    VI: ['Định dạng', 'Trình duyệt này không hỗ trợ MP4, sẽ dùng WebM'],
    TH: ['รูปแบบ', 'เบราว์เซอร์นี้ไม่รองรับ MP4 จะใช้ WebM แทน']
};

const PD_T = {
    RU: {
        title: "Отчёт парсера",
        copy: "Копировать",
        copied: "Скопировано",
        reset: "Сбросить",
        nodata: "Данных пока нет: отчёт заполняется по мере обновления закладок.",
        iok: "Профили «Инфо» (Stripchat): разобрано {k} из {n}.",
        iwarn: "Профили «Инфо» (Stripchat): не разобрано {u} из {n}, через запасной путь {a}, с пропусками полей {d}, новых кодов {c}{list}. Нажмите «{copy}» и пришлите JSON, чтобы поправить разбор.",
        itoast: "Профили Stripchat: не удалось разобрать {u} из {n}. Возможно, сайт изменил формат данных. Нажмите, чтобы открыть отчёт.",
        ok: "Всё в порядке: без статуса {u} из {n}.",
        warn: "У {u} из {n} моделей статус не определён (порог {p}%). Скорее всего, сайт изменил ответ. Нажмите «{copy}» и пришлите JSON, чтобы поправить таблицу ключей BC_SCHEMA.",
        age: "возраст",
        unknown: "неизвестно",
        diag: "Диагностика",
        nostatus: "без статуса",
        sumT: "Сводка",
        models: "Модели",
        dash: "«—» оранжевым: у онлайн-модели поле пустое.",
        gaps: "Статус определён, но у онлайн-моделей пусто: {list}. Нажмите «{copy}» и пришлите JSON, чтобы поправить парсер.",
        few: "Данных мало ({n} из {m}), вывод неточный.",
        toast: "Парсер {site}: у {u} из {n} моделей не определился статус. Возможно, сайт изменил разметку. Нажмите, чтобы открыть отчёт.",
    },
    EN: {
        title: "Parser report",
        copy: "Copy",
        copied: "Copied",
        reset: "Reset",
        nodata: "No data yet: the report fills in as bookmarks refresh.",
        iok: "Info profiles (Stripchat): {k} of {n} parsed.",
        iwarn: "Info profiles (Stripchat): {u} of {n} not parsed, {a} via fallback path, {d} with missing fields, {c} new codes{list}. Press \"{copy}\" and send the JSON to fix the parser.",
        itoast: "Stripchat profiles: {u} of {n} could not be parsed. The site may have changed its data format. Click to open the report.",
        ok: "All good: {u} of {n} without status.",
        warn: "Status is unknown for {u} of {n} models (threshold {p}%). The site likely changed its response. Press \"{copy}\" and send the JSON to adjust the BC_SCHEMA key table.",
        age: "age",
        unknown: "unknown",
        diag: "Diagnostics",
        nostatus: "no status",
        sumT: "Summary",
        models: "Models",
        dash: "Orange \"—\": an online model has this field empty.",
        gaps: "Status is detected, but online models have empty: {list}. Press \"{copy}\" and send the JSON to fix the parser.",
        few: "Little data ({n} of {m}), the verdict is rough.",
        toast: "{site} parser: status is unknown for {u} of {n} models. The site markup may have changed. Click to open the report.",
    },
    UK: {
        title: "Звіт парсера",
        copy: "Копіювати",
        copied: "Скопійовано",
        reset: "Скинути",
        nodata: "Даних поки немає: звіт заповнюється в міру оновлення закладок.",
        ok: "Усе гаразд: без статусу {u} із {n}.",
        warn: "У {u} із {n} моделей статус не визначено (поріг {p}%). Найімовірніше, сайт змінив відповідь. Натисніть «{copy}» і надішліть JSON, щоб виправити таблицю ключів BC_SCHEMA.",
        age: "вік",
        unknown: "невідомо",
        diag: "Діагностика",
        nostatus: "без статусу",
        toast: "Парсер {site}: у {u} із {n} моделей не визначився статус. Можливо, сайт змінив розмітку. Натисніть, щоб відкрити звіт.",
    },
    ES: {
        title: "Informe del analizador",
        copy: "Copiar",
        copied: "Copiado",
        reset: "Restablecer",
        nodata: "Aún no hay datos: el informe se rellena a medida que se actualizan los favoritos.",
        ok: "Todo en orden: {u} de {n} sin estado.",
        warn: "El estado es desconocido en {u} de {n} modelos (umbral {p}%). Probablemente el sitio cambió su respuesta. Pulsa «{copy}» y envía el JSON para ajustar la tabla de claves BC_SCHEMA.",
        age: "antigüedad",
        unknown: "desconocido",
        diag: "Diagnóstico",
        nostatus: "sin estado",
        toast: "Analizador {site}: estado desconocido en {u} de {n} modelos. Puede que el sitio haya cambiado su estructura. Haz clic para abrir el informe.",
    },
    DE: {
        title: "Parser-Bericht",
        copy: "Kopieren",
        copied: "Kopiert",
        reset: "Zurücksetzen",
        nodata: "Noch keine Daten: Der Bericht füllt sich beim Aktualisieren der Favoriten.",
        ok: "Alles in Ordnung: {u} von {n} ohne Status.",
        warn: "Bei {u} von {n} Models ist der Status unbekannt (Schwelle {p} %). Wahrscheinlich hat die Seite ihre Antwort geändert. Klicke auf „{copy}“ und sende das JSON, um die Schlüsseltabelle BC_SCHEMA anzupassen.",
        age: "Alter",
        unknown: "unbekannt",
        diag: "Diagnose",
        nostatus: "ohne Status",
        toast: "{site}-Parser: Bei {u} von {n} Models ist der Status unbekannt. Möglicherweise hat sich das Seitenlayout geändert. Klicken, um den Bericht zu öffnen.",
    },
    FR: {
        title: "Rapport de l’analyseur",
        copy: "Copier",
        copied: "Copié",
        reset: "Réinitialiser",
        nodata: "Pas encore de données : le rapport se remplit à mesure que les favoris sont actualisés.",
        ok: "Tout va bien : {u} sur {n} sans statut.",
        warn: "Le statut est inconnu pour {u} modèles sur {n} (seuil {p} %). Le site a probablement modifié sa réponse. Cliquez sur « {copy} » et envoyez le JSON pour ajuster la table de clés BC_SCHEMA.",
        age: "âge",
        unknown: "inconnu",
        diag: "Diagnostic",
        nostatus: "sans statut",
        toast: "Analyseur {site} : statut inconnu pour {u} modèles sur {n}. La structure du site a peut-être changé. Cliquez pour ouvrir le rapport.",
    },
    PT: {
        title: "Relatório do analisador",
        copy: "Copiar",
        copied: "Copiado",
        reset: "Redefinir",
        nodata: "Ainda sem dados: o relatório é preenchido conforme os favoritos são atualizados.",
        ok: "Tudo certo: {u} de {n} sem status.",
        warn: "O status é desconhecido em {u} de {n} modelos (limite {p}%). Provavelmente o site mudou a resposta. Clique em “{copy}” e envie o JSON para ajustar a tabela de chaves BC_SCHEMA.",
        age: "idade",
        unknown: "desconhecido",
        diag: "Diagnóstico",
        nostatus: "sem status",
        toast: "Analisador {site}: status desconhecido em {u} de {n} modelos. A estrutura do site pode ter mudado. Clique para abrir o relatório.",
    },
    IT: {
        title: "Report del parser",
        copy: "Copia",
        copied: "Copiato",
        reset: "Reimposta",
        nodata: "Ancora nessun dato: il report si compila man mano che i preferiti si aggiornano.",
        ok: "Tutto a posto: {u} su {n} senza stato.",
        warn: "Lo stato è sconosciuto per {u} profili su {n} (soglia {p}%). Probabilmente il sito ha cambiato la risposta. Premi «{copy}» e invia il JSON per correggere la tabella di chiavi BC_SCHEMA.",
        age: "età",
        unknown: "sconosciuto",
        diag: "Diagnostica",
        nostatus: "senza stato",
        toast: "Parser {site}: stato sconosciuto per {u} profili su {n}. Il sito potrebbe aver cambiato struttura. Clicca per aprire il report.",
    },
    PL: {
        title: "Raport parsera",
        copy: "Kopiuj",
        copied: "Skopiowano",
        reset: "Resetuj",
        nodata: "Brak danych: raport wypełnia się w miarę odświeżania zakładek.",
        ok: "Wszystko w porządku: {u} z {n} bez statusu.",
        warn: "Status jest nieznany dla {u} z {n} profili (próg {p}%). Prawdopodobnie serwis zmienił odpowiedź. Kliknij „{copy}” i wyślij JSON, aby poprawić tabelę kluczy BC_SCHEMA.",
        age: "wiek",
        unknown: "nieznany",
        diag: "Diagnostyka",
        nostatus: "bez statusu",
        toast: "Parser {site}: status nieznany dla {u} z {n} profili. Układ strony mógł się zmienić. Kliknij, aby otworzyć raport.",
    },
    TR: {
        title: "Ayrıştırıcı raporu",
        copy: "Kopyala",
        copied: "Kopyalandı",
        reset: "Sıfırla",
        nodata: "Henüz veri yok: rapor, yer imleri yenilendikçe dolar.",
        ok: "Her şey yolunda: {n} modelden {u} tanesinin durumu yok.",
        warn: "{n} modelden {u} tanesinin durumu bilinmiyor (eşik %{p}). Site büyük olasılıkla yanıtını değiştirdi. “{copy}” düğmesine basıp JSON’u gönderin; BC_SCHEMA anahtar tablosu düzeltilsin.",
        age: "yaş",
        unknown: "bilinmiyor",
        diag: "Tanılama",
        nostatus: "durum yok",
        toast: "{site} ayrıştırıcı: {n} modelden {u} tanesinin durumu bilinmiyor. Sitenin yapısı değişmiş olabilir. Raporu açmak için tıklayın.",
    },
    NL: {
        title: "Parserrapport",
        copy: "Kopiëren",
        copied: "Gekopieerd",
        reset: "Resetten",
        nodata: "Nog geen gegevens: het rapport vult zich naarmate favorieten worden vernieuwd.",
        ok: "Alles in orde: {u} van {n} zonder status.",
        warn: "De status is onbekend bij {u} van {n} modellen (drempel {p}%). De site heeft waarschijnlijk zijn respons gewijzigd. Klik op “{copy}” en stuur de JSON om de BC_SCHEMA-sleuteltabel aan te passen.",
        age: "leeftijd",
        unknown: "onbekend",
        diag: "Diagnostiek",
        nostatus: "geen status",
        toast: "{site}-parser: status onbekend bij {u} van {n} modellen. De opmaak van de site is mogelijk gewijzigd. Klik om het rapport te openen.",
    },
    CS: {
        title: "Zpráva parseru",
        copy: "Kopírovat",
        copied: "Zkopírováno",
        reset: "Obnovit",
        nodata: "Zatím žádná data: zpráva se plní s aktualizací záložek.",
        ok: "Vše v pořádku: {u} z {n} bez stavu.",
        warn: "U {u} z {n} modelů není stav známý (práh {p} %). Web pravděpodobně změnil odpověď. Klikněte na „{copy}“ a pošlete JSON, aby šla upravit tabulka klíčů BC_SCHEMA.",
        age: "stáří",
        unknown: "neznámý",
        diag: "Diagnostika",
        nostatus: "bez stavu",
        toast: "Parser {site}: u {u} z {n} modelů není stav známý. Web možná změnil strukturu. Kliknutím otevřete zprávu.",
    },
    RO: {
        title: "Raport parser",
        copy: "Copiază",
        copied: "Copiat",
        reset: "Resetează",
        nodata: "Încă nu există date: raportul se completează pe măsură ce favoritele se actualizează.",
        ok: "Totul e în regulă: {u} din {n} fără stare.",
        warn: "Starea este necunoscută pentru {u} din {n} modele (prag {p}%). Probabil site-ul și-a schimbat răspunsul. Apasă „{copy}” și trimite JSON-ul pentru a ajusta tabelul de chei BC_SCHEMA.",
        age: "vârstă",
        unknown: "necunoscut",
        diag: "Diagnostic",
        nostatus: "fără stare",
        toast: "Parser {site}: stare necunoscută pentru {u} din {n} modele. Structura site-ului s-ar putea să se fi schimbat. Dă clic pentru a deschide raportul.",
    },
    HU: {
        title: "Elemző jelentés",
        copy: "Másolás",
        copied: "Másolva",
        reset: "Visszaállítás",
        nodata: "Még nincs adat: a jelentés a könyvjelzők frissítésével töltődik.",
        ok: "Minden rendben: {n} modellből {u} állapot nélküli.",
        warn: "{n} modellből {u} esetében ismeretlen az állapot (küszöb: {p}%). A webhely valószínűleg megváltoztatta a válaszát. Kattints a „{copy}” gombra, és küldd el a JSON-t a BC_SCHEMA kulcstábla javításához.",
        age: "kor",
        unknown: "ismeretlen",
        diag: "Diagnosztika",
        nostatus: "állapot nélkül",
        toast: "{site} elemző: {n} modellből {u} esetében ismeretlen az állapot. Lehet, hogy megváltozott az oldal szerkezete. Kattints a jelentés megnyitásához.",
    },
    JA: {
        title: "パーサーレポート",
        copy: "コピー",
        copied: "コピーしました",
        reset: "リセット",
        nodata: "まだデータがありません。お気に入りの更新に合わせてレポートが埋まります。",
        ok: "問題ありません: {n}件中{u}件がステータスなし。",
        warn: "{n}件中{u}件のモデルでステータス不明です(しきい値 {p}%)。サイト側の応答が変わった可能性があります。「{copy}」を押して JSON を送ってください。BC_SCHEMA のキー表を修正します。",
        age: "経過",
        unknown: "不明",
        diag: "診断",
        nostatus: "ステータスなし",
        toast: "{site} パーサー: {n}件中{u}件のモデルでステータスが不明です。サイトの構造が変わった可能性があります。クリックでレポートを開きます。",
    },
    ZH: {
        title: "解析器报告",
        copy: "复制",
        copied: "已复制",
        reset: "重置",
        nodata: "暂无数据:报告会随收藏更新逐步填充。",
        ok: "一切正常:{n} 个中有 {u} 个无状态。",
        warn: "{n} 个模特中有 {u} 个状态未知(阈值 {p}%)。网站很可能更改了响应。请点击“{copy}”并发送 JSON,以便调整 BC_SCHEMA 键表。",
        age: "时长",
        unknown: "未知",
        diag: "诊断",
        nostatus: "无状态",
        toast: "{site} 解析器:{n} 个模特中有 {u} 个状态未知。网站结构可能已更改。点击打开报告。",
    },
    KO: {
        title: "파서 보고서",
        copy: "복사",
        copied: "복사됨",
        reset: "초기화",
        nodata: "아직 데이터가 없습니다: 즐겨찾기가 갱신되면서 보고서가 채워집니다.",
        ok: "정상: {n}개 중 {u}개 상태 없음.",
        warn: "{n}개 모델 중 {u}개의 상태를 알 수 없습니다(임계값 {p}%). 사이트가 응답을 변경했을 가능성이 큽니다. \"{copy}\"를 눌러 JSON을 보내 주시면 BC_SCHEMA 키 표를 수정할 수 있습니다.",
        age: "경과",
        unknown: "알 수 없음",
        diag: "진단",
        nostatus: "상태 없음",
        toast: "{site} 파서: {n}개 모델 중 {u}개의 상태를 알 수 없습니다. 사이트 구조가 바뀌었을 수 있습니다. 클릭하면 보고서를 엽니다.",
    },
    HE: {
        title: "דוח מנתח",
        copy: "העתק",
        copied: "הועתק",
        reset: "איפוס",
        nodata: "אין עדיין נתונים: הדוח מתמלא עם רענון המועדפים.",
        ok: "הכול תקין: {u} מתוך {n} ללא סטטוס.",
        warn: "הסטטוס לא ידוע עבור {u} מתוך {n} מודלים (סף {p}%). כנראה האתר שינה את התשובה. לחצו על \"{copy}\" ושלחו את ה-JSON כדי לתקן את טבלת המפתחות BC_SCHEMA.",
        age: "גיל",
        unknown: "לא ידוע",
        diag: "אבחון",
        nostatus: "ללא סטטוס",
        toast: "מנתח {site}: הסטטוס לא ידוע עבור {u} מתוך {n} מודלים. ייתכן שמבנה האתר השתנה. לחצו כדי לפתוח את הדוח.",
    },
    AR: {
        title: "تقرير المحلّل",
        copy: "نسخ",
        copied: "تم النسخ",
        reset: "إعادة تعيين",
        nodata: "لا توجد بيانات بعد: يمتلئ التقرير مع تحديث المفضلة.",
        ok: "كل شيء على ما يرام: {u} من {n} بلا حالة.",
        warn: "الحالة غير معروفة لدى {u} من {n} نماذج (الحد {p}%). على الأرجح غيّر الموقع استجابته. اضغط «{copy}» وأرسل JSON لتعديل جدول المفاتيح BC_SCHEMA.",
        age: "العمر",
        unknown: "غير معروف",
        diag: "التشخيص",
        nostatus: "بلا حالة",
        toast: "محلّل {site}: الحالة غير معروفة لدى {u} من {n} نماذج. ربما تغيّرت بنية الموقع. انقر لفتح التقرير.",
    },
    HI: {
        title: "पार्सर रिपोर्ट",
        copy: "कॉपी करें",
        copied: "कॉपी हो गया",
        reset: "रीसेट करें",
        nodata: "अभी कोई डेटा नहीं: पसंदीदा रिफ्रेश होने पर रिपोर्ट भरती जाती है।",
        ok: "सब ठीक है: {n} में से {u} बिना स्थिति के।",
        warn: "{n} में से {u} मॉडल की स्थिति अज्ञात है (सीमा {p}%)। संभवतः साइट ने अपना उत्तर बदल दिया है। \"{copy}\" दबाएँ और BC_SCHEMA कुंजी तालिका ठीक करने के लिए JSON भेजें।",
        age: "आयु",
        unknown: "अज्ञात",
        diag: "निदान",
        nostatus: "बिना स्थिति",
        toast: "{site} पार्सर: {n} में से {u} मॉडल की स्थिति अज्ञात है। हो सकता है साइट की संरचना बदल गई हो। रिपोर्ट खोलने के लिए क्लिक करें।",
    },
    ID: {
        title: "Laporan parser",
        copy: "Salin",
        copied: "Tersalin",
        reset: "Atur ulang",
        nodata: "Belum ada data: laporan terisi seiring favorit diperbarui.",
        ok: "Semua baik: {u} dari {n} tanpa status.",
        warn: "Status tidak diketahui pada {u} dari {n} model (ambang {p}%). Kemungkinan situs mengubah responsnya. Tekan \"{copy}\" lalu kirim JSON untuk menyesuaikan tabel kunci BC_SCHEMA.",
        age: "usia",
        unknown: "tidak diketahui",
        diag: "Diagnostik",
        nostatus: "tanpa status",
        toast: "Parser {site}: status tidak diketahui pada {u} dari {n} model. Struktur situs mungkin berubah. Klik untuk membuka laporan.",
    },
    VI: {
        title: "Báo cáo trình phân tích",
        copy: "Sao chép",
        copied: "Đã sao chép",
        reset: "Đặt lại",
        nodata: "Chưa có dữ liệu: báo cáo được điền dần khi mục yêu thích được làm mới.",
        ok: "Mọi thứ ổn: {u}/{n} không có trạng thái.",
        warn: "Không xác định được trạng thái của {u}/{n} model (ngưỡng {p}%). Nhiều khả năng trang đã đổi phản hồi. Nhấn “{copy}” và gửi JSON để chỉnh bảng khóa BC_SCHEMA.",
        age: "tuổi",
        unknown: "không rõ",
        diag: "Chẩn đoán",
        nostatus: "không có trạng thái",
        toast: "Trình phân tích {site}: không xác định được trạng thái của {u}/{n} model. Cấu trúc trang có thể đã thay đổi. Nhấp để mở báo cáo.",
    },
    TH: {
        title: "รายงานตัวแยกวิเคราะห์",
        copy: "คัดลอก",
        copied: "คัดลอกแล้ว",
        reset: "รีเซ็ต",
        nodata: "ยังไม่มีข้อมูล: รายงานจะเติมเมื่อรายการโปรดรีเฟรช",
        ok: "ปกติดี: {u} จาก {n} ไม่มีสถานะ",
        warn: "ไม่ทราบสถานะของ {u} จาก {n} โมเดล (เกณฑ์ {p}%) เป็นไปได้ว่าเว็บไซต์เปลี่ยนการตอบกลับ กด \"{copy}\" แล้วส่ง JSON เพื่อปรับตารางคีย์ BC_SCHEMA",
        age: "อายุ",
        unknown: "ไม่ทราบ",
        diag: "การวินิจฉัย",
        nostatus: "ไม่มีสถานะ",
        toast: "ตัวแยกวิเคราะห์ {site}: ไม่ทราบสถานะของ {u} จาก {n} โมเดล โครงสร้างเว็บไซต์อาจเปลี่ยนไป คลิกเพื่อเปิดรายงาน",
    },
};

const init = () => {

    if (SITE === 'cb') { try { cbHoverPreview(); } catch (err) { console.error('[cb-hover-preview]', err); } }
    try { new App(); } catch (err) { console.error('[cams-unchained] App init failed', err); }
};
document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', init) : init();

})();