CHZZK Initial Highest Quality (Internal API)

치지직(chzzk) 라이브/다시보기 초기 화질을 최고화질로 고정합니다.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

Advertisement:

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

Advertisement:

// ==UserScript==
// @name         CHZZK Initial Highest Quality (Internal API)
// @name:ko      치지직 초기화질을 최고화질로 고정하는 스크립트 (내부 API사용)
// @namespace    local/scriptcat/chzzk-initial-highest-quality-internal
// @version      0.2.2
// @description  치지직(chzzk) 라이브/다시보기 초기 화질을 최고화질로 고정합니다.
// @author       떱_
// @match        https://chzzk.naver.com/*
// @match        https://*.chzzk.naver.com/*
// @match        https://chzzk.naver.com/live/*
// @match        https://chzzk.naver.com/video/*
// @match        https://chzzk.naver.com/?multiview=true
// @match        https://chzzk.naver.com/lives
// @run-at       document-start
// @grant        none
// @sandbox      raw
// @inject-into  page
// @license      MIT
// @noframes
// ==/UserScript==

(() => {
  'use strict';

  if (window.__CHZZK_INITIAL_HIGHEST_QUALITY_INTERNAL_V22__) return;
  window.__CHZZK_INITIAL_HIGHEST_QUALITY_INTERNAL_V22__ = true;

  const NAME = 'CHZZK Initial Highest Quality (Internal API)';
  const VERSION = '0.2.2';

  const CONFIG = {
    maxWaitMs: 30000,
    pollIntervalMs: 180,
    selectionVerifyMs: 3000,
    preSwitchStableMs: 600,
    preSwitchStableTimeoutMs: 1400,
    passiveResumeWaitMs: 400,
    resumeVerifyMs: 1500,
    maxApplyAttempts: 2,
    routeCheckMs: 1000,
    targetStabilityPolls: 2,
    radioConfirmMs: 300,
    videoRouteSettleMs: 1200,
    returnRepairMinBackgroundMs: 15000,
    returnRepairDelayMs: 700,
    returnRepairCooldownMs: 10000,
    maxTimeoutRetryOnVisible: 1,
    playbackStartedResourceFallbackMs: 2500,

    requireFreshChannelResource: true,
    freshResourceSettleMs: 100,
    acceptOpaqueStreamResource: true,
    bypassRadioOnlyPlayback: true,
    respectTrustedUserSelection: true,

    /* 초기 저화질 프레임 노출을 줄이고, 실패 시 자동으로 열어 둡니다. */
    visualMaskEnabled: true,
    initialVisualMaskEnabled: true,
    visualMaskColor: '#000',
    loadingMessage: '최고화질 자동 적용 중',
    visualMaskFadeMs: 160,
    stableFrameWaitMs: 1800,
    revealDelayMs: 40,
    initialMaskMaxMs: 6000,
    switchMaskMaxMs: 3000,

    /* Greasy Fork 배포본 기본값. 문제 확인 시 true로 바꾸면 됩니다. */
    debug: false
  };

  const state = {
    version: VERSION,
    routeKind: '',
    contentId: '',
    channelId: '',
    generation: 0,
    startedAt: 0,
    resetPerfNow: 0,
    deadlineAt: 0,

    freshPlaybackSeen: false,
    freshResourceAtPerf: 0,
    freshResourceKind: '',
    resourceQuality: '',
    targetResourceAtPerf: 0,

    controllerFound: false,
    controllerCandidateCount: 0,
    selectedControllerTrackCount: 0,
    targetStableKey: '',
    targetStableCount: 0,
    staleHighestIgnored: 0,
    resourceTargetMismatchDetected: false,

    playbackMode: 'unknown',
    radioOnlyDetected: false,
    radioCandidateSince: 0,
    radioEvidenceCount: 0,
    routeReadyAtPerf: 0,
    waitingForPlayback: false,

    videoFound: false,
    playbackStarted: false,
    decodedVideoWidth: 0,
    decodedVideoHeight: 0,

    source: '',
    availableFixedQualities: [],
    target: null,
    selectedBefore: null,
    selectedAfter: null,

    applyAttempts: 0,
    done: false,
    doneReason: '',
    userSelectedManually: false,



    videoWasPlayingBeforeSwitch: false,
    videoPausedAfterSwitch: false,
    resumeAttempts: 0,
    resumeResult: '',
    preSwitchStableResult: '',
    wasPlayingBeforeHidden: false,
    wasPlayingBeforeBlur: false,
    hiddenStartedAt: 0,
    blurStartedAt: 0,
    returnRepairAttempts: 0,
    returnRepairScheduled: false,
    returnRepairCooldownUntil: 0,
    returnRepairLastReason: '',
    returnRepairLastResult: '',
    timeoutRetryAttempts: 0,
    timeoutRetryLastReason: '',

    visualMaskArmed: false,
    visualMaskReleased: false,
    visualMaskReason: '',
    visualMaskArmedAt: 0,
    visualMaskReleasedAt: 0,

    switchStartedAtPerf: 0,
    targetSelectedAtPerf: 0,
    targetFrameReadyAtPerf: 0,

    lastError: ''
  };

  let pollTimer = null;
  let routeTimer = null;
  let perfObserver = null;
  let maskFailOpenTimer = null;
  let applying = false;
  let polling = false;
  let pollQueued = false;
  let clickListenerInstalled = false;
  let returnRepairTimer = null;

  const observedVideos = new WeakSet();
  const MASK_ATTR = 'data-chzzk-initial-quality-mask';
  const MASK_STYLE_ID = 'chzzk-initial-quality-mask-style';

  let wasPlayingBeforeHidden = false;

  function log(...args) {
    if (CONFIG.debug) console.log(`🟢 [${NAME}]`, ...args);
  }

  function warn(...args) {
    console.warn(`🟡 [${NAME}]`, ...args);
  }

  function recordError(error) {
    state.lastError = String(error && (error.stack || error.message) || error);
  }

  function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }

  const PLAYER_ROOT_SELECTORS = [
    '#live_player_layout',
    '#vod_player_layout',
    '#video_player_layout',
    '[id*="player_layout"]',
    '.chzzk_player'
  ];

  function playerRootSelector(suffix = '') {
    return PLAYER_ROOT_SELECTORS.map((selector) => `${selector}${suffix}`).join(',');
  }

  function getRouteInfo() {
    const live = location.pathname.match(/^\/live\/([^/?#]+)/)?.[1] || '';
    if (live) return { kind: 'live', id: live };

    const video = location.pathname.match(/^\/video\/(\d+)/)?.[1] || '';
    if (video) return { kind: 'video', id: video };

    return { kind: '', id: '' };
  }

  function getChannelId() {
    const info = getRouteInfo();
    return info.kind && info.id ? `${info.kind}:${info.id}` : '';
  }

  function isLivePath() {
    return !!getRouteInfo().kind;
  }

  function isVideoRoute() {
    return state.routeKind === 'video';
  }

  function textOf(el) {
    try {
      return String(el?.innerText ?? el?.textContent ?? '')
        .replace(/\s+/g, ' ')
        .trim();
    } catch (_) {
      return '';
    }
  }

  function schedulePoll() {
    if (pollQueued || state.done || !isLivePath()) return;
    pollQueued = true;

    queueMicrotask(() => {
      pollQueued = false;
      void poll();
    });
  }

  function ensureVisualMaskStyle() {
    if (!CONFIG.visualMaskEnabled) return;
    if (document.getElementById(MASK_STYLE_ID)) return;

    const style = document.createElement('style');
    style.id = MASK_STYLE_ID;
    style.textContent = `
      ${playerRootSelector()},
      ${playerRootSelector(' .pzp')} {
        background: ${CONFIG.visualMaskColor} !important;
      }

      html[${MASK_ATTR}="pending"] ${playerRootSelector()},
      html[${MASK_ATTR}="pending"] ${playerRootSelector(' .pzp')},
      html[${MASK_ATTR}="revealing"] ${playerRootSelector()},
      html[${MASK_ATTR}="revealing"] ${playerRootSelector(' .pzp')} {
        position: relative !important;
        isolation: isolate !important;
      }

      html[${MASK_ATTR}="pending"] ${playerRootSelector(' .pzp-pc__video')},
      html[${MASK_ATTR}="pending"] ${playerRootSelector(' video.webplayer-internal-video')},
      html[${MASK_ATTR}="pending"] ${playerRootSelector(' video')} {
        opacity: 1 !important;
      }

      html[${MASK_ATTR}="pending"] ${playerRootSelector(' .pzp-pc__video')},
      html[${MASK_ATTR}="pending"] ${playerRootSelector(' video.webplayer-internal-video')},
      html[${MASK_ATTR}="pending"] ${playerRootSelector(' video')},
      html[${MASK_ATTR}="revealing"] ${playerRootSelector(' .pzp-pc__video')},
      html[${MASK_ATTR}="revealing"] ${playerRootSelector(' video.webplayer-internal-video')},
      html[${MASK_ATTR}="revealing"] ${playerRootSelector(' video')} {
        transition: opacity ${CONFIG.visualMaskFadeMs}ms ease-out !important;
      }

      html[${MASK_ATTR}="revealing"] ${playerRootSelector(' .pzp-pc__video')},
      html[${MASK_ATTR}="revealing"] ${playerRootSelector(' video.webplayer-internal-video')},
      html[${MASK_ATTR}="revealing"] ${playerRootSelector(' video')} {
        opacity: 1 !important;
      }

      html[${MASK_ATTR}="pending"] ${playerRootSelector('::before')},
      html[${MASK_ATTR}="revealing"] ${playerRootSelector('::before')} {
        content: "${CONFIG.loadingMessage}";
        position: absolute !important;
        inset: 0 !important;
        z-index: 2147483646 !important;
        display: flex !important;
        align-items: center !important;
        justify-content: center !important;
        padding-top: 72px !important;
        box-sizing: border-box !important;
        background: rgba(0, 0, 0, .62) !important;
        color: rgba(255, 255, 255, .96) !important;
        font: 600 14px/1.4 -apple-system, BlinkMacSystemFont, "Segoe UI", "Apple SD Gothic Neo", "Noto Sans KR", sans-serif !important;
        letter-spacing: 0 !important;
        text-shadow: 0 1px 2px rgba(0, 0, 0, .45) !important;
        pointer-events: none !important;
        opacity: 1 !important;
        transition: opacity ${CONFIG.visualMaskFadeMs}ms ease-out, background ${CONFIG.visualMaskFadeMs}ms ease-out !important;
      }

      html[${MASK_ATTR}="pending"] ${playerRootSelector('::after')},
      html[${MASK_ATTR}="revealing"] ${playerRootSelector('::after')} {
        content: "" !important;
        position: absolute !important;
        left: 50% !important;
        top: 50% !important;
        z-index: 2147483647 !important;
        width: 36px !important;
        height: 36px !important;
        margin: -28px 0 0 -18px !important;
        border: 3px solid rgba(255, 255, 255, .32) !important;
        border-top-color: rgba(255, 255, 255, .98) !important;
        border-radius: 999px !important;
        box-sizing: border-box !important;
        pointer-events: none !important;
        opacity: 1 !important;
        animation: chzzkQualitySpin .82s linear infinite !important;
        transition: opacity ${CONFIG.visualMaskFadeMs}ms ease-out !important;
      }

      html[${MASK_ATTR}="revealing"] ${playerRootSelector('::before')},
      html[${MASK_ATTR}="revealing"] ${playerRootSelector('::after')} {
        opacity: 0 !important;
      }

      @keyframes chzzkQualitySpin {
        to { transform: rotate(360deg); }
      }
    `;

    const append = () => {
      if (style.isConnected) return true;
      const root = document.head || document.documentElement;
      if (!root) return false;
      root.appendChild(style);
      return true;
    };

    if (!append()) {
      const observer = new MutationObserver(() => {
        if (append()) observer.disconnect();
      });
      observer.observe(document, { childList: true, subtree: true });
    }
  }

  function forceClearVisualMask(reason = 'force-clear') {
    if (maskFailOpenTimer) {
      clearTimeout(maskFailOpenTimer);
      maskFailOpenTimer = null;
    }

    document.documentElement?.removeAttribute(MASK_ATTR);

    if (state.visualMaskArmed && !state.visualMaskReleased) {
      state.visualMaskReleased = true;
      state.visualMaskReason = reason;
      state.visualMaskReleasedAt = Date.now();
    }
  }

  function armVisualMask(reason, generation = state.generation, maxMs = CONFIG.switchMaskMaxMs) {
    if (!CONFIG.visualMaskEnabled) return;

    ensureVisualMaskStyle();
    const html = document.documentElement;
    if (!html) return;

    if (maskFailOpenTimer) clearTimeout(maskFailOpenTimer);

    html.setAttribute(MASK_ATTR, 'pending');
    state.visualMaskArmed = true;
    state.visualMaskReleased = false;
    state.visualMaskReason = reason;
    state.visualMaskArmedAt = Date.now();
    state.visualMaskReleasedAt = 0;

    maskFailOpenTimer = setTimeout(() => {
      if (
        generation === state.generation &&
        state.visualMaskArmed &&
        !state.visualMaskReleased
      ) {
        releaseVisualMask('switch-mask-timeout', true);
      }
    }, maxMs);
  }

  function releaseVisualMask(reason, immediate = false) {
    if (!CONFIG.visualMaskEnabled) return;
    if (!state.visualMaskArmed || state.visualMaskReleased) return;

    state.visualMaskReleased = true;
    state.visualMaskReason = reason;
    state.visualMaskReleasedAt = Date.now();

    if (maskFailOpenTimer) {
      clearTimeout(maskFailOpenTimer);
      maskFailOpenTimer = null;
    }

    const html = document.documentElement;
    if (!html) return;

    if (immediate || CONFIG.visualMaskFadeMs <= 0) {
      html.removeAttribute(MASK_ATTR);
      return;
    }

    html.setAttribute(MASK_ATTR, 'revealing');
    setTimeout(() => {
      if (document.documentElement?.getAttribute(MASK_ATTR) === 'revealing') {
        document.documentElement.removeAttribute(MASK_ATTR);
      }
    }, CONFIG.visualMaskFadeMs + 80);
  }

  function parseQuality(value) {
    const match = String(value || '').match(/\b(\d{3,4})p\b/i);
    if (!match) return null;
    const height = Number(match[1]);
    if (!Number.isFinite(height)) return null;
    return { label: `${height}p`, height };
  }

  function isAbrLike(value) {
    const text = String(value || '').trim().toLowerCase();
    return text === 'abr' || text.includes('자동');
  }

  function summarizeTrack(track) {
    if (!track || typeof track !== 'object') return null;

    return {
      id: track.id ?? null,
      label: track.label ?? '',
      quality: track.quality ?? '',
      width: Number(track.width ?? track.videoWidth ?? 0) || 0,
      height: Number(track.height ?? track.videoHeight ?? 0) || 0,
      videoBitRate: Number(
        track.videoBitRate ?? track.bitrate ?? track.bandwidth ?? 0
      ) || 0,
      selected: !!track.selected,
      checked: !!track.checked,
      avoidReencoding: !!track.dataset?.avoidReencoding || !!track.avoidReencoding,
      audioOnly: !!track.audioOnly
    };
  }

  function isFixedNumericTrack(item) {
    if (!item || item.id == null || item.audioOnly) return false;
    if (isAbrLike(item.id) || isAbrLike(item.label) || isAbrLike(item.quality)) {
      return false;
    }

    const parsed = parseQuality(item.quality) || parseQuality(item.label);
    if (!parsed && !item.height) return false;

    return !/audio|audioonly|radio/i.test(
      `${item.id || ''} ${item.label || ''} ${item.quality || ''}`
    );
  }

  function getCandidateScore(item) {
    const parsed = parseQuality(item.quality) || parseQuality(item.label);
    const height = Number(item.height || parsed?.height || 0);
    const width = Number(item.width || 0);
    const bitrate = Number(item.videoBitRate || 0);

    return (
      height * 1_000_000_000 +
      width * 1_000_000 +
      bitrate +
      (item.avoidReencoding ? 100_000 : 0)
    );
  }

  function findQualityControllers() {
    const controllers = [];
    const seen = new WeakSet();

    const add = (vm) => {
      if (!vm || typeof vm !== 'object' || seen.has(vm)) return;
      if (vm.$el instanceof Element && !vm.$el.isConnected) return;

      if (
        typeof vm.getVideoTracksList === 'function' ||
        typeof vm.formattedVideoTracks === 'function' ||
        typeof vm.selectVideoTrack === 'function'
      ) {
        seen.add(vm);
        controllers.push(vm);
      }
    };

    for (const el of document.querySelectorAll([
      playerRootSelector(' .pzp-setting-quality-pane'),
      playerRootSelector(' li.pzp-ui-setting-quality-item'),
      playerRootSelector(' .pzp-setting-intro-quality'),
      '.pzp-setting-quality-pane',
      'li.pzp-ui-setting-quality-item',
      '.pzp-setting-intro-quality'
    ].join(','))) {
      let vm = el.__vue__;
      for (let depth = 0; vm && depth < 8; depth++, vm = vm.$parent) add(vm);
    }

    const rootVm = document.querySelector(`${playerRootSelector(' .pzp')},.pzp`)?.__vue__;
    if (rootVm) {
      const queue = [rootVm];
      const traversed = new WeakSet();
      let visited = 0;

      while (queue.length && visited < 120) {
        const vm = queue.shift();
        if (!vm || typeof vm !== 'object' || traversed.has(vm)) continue;
        traversed.add(vm);
        visited++;
        add(vm);
        for (const child of vm.$children || []) queue.push(child);
      }
    }

    return controllers;
  }

  function getTrackModels(controller) {
    const rawMap = new Map();
    const rows = [];

    try {
      const raw = controller?.getVideoTracksList?.();
      if (Array.isArray(raw)) {
        for (const track of raw) {
          const summary = summarizeTrack(track);
          if (summary?.id != null) rawMap.set(String(summary.id), summary);
        }
      }
    } catch (error) {
      recordError(error);
    }

    try {
      const formatted = controller?.formattedVideoTracks?.();
      if (Array.isArray(formatted)) {
        for (const item of formatted) {
          const id = item?.id ?? null;
          const raw = id != null ? rawMap.get(String(id)) : null;

          rows.push({
            ...(raw || {}),
            id,
            label: item?.quality ?? raw?.label ?? '',
            quality: item?.quality ?? raw?.quality ?? '',
            width: Number(raw?.width ?? item?.width ?? 0) || 0,
            height: Number(raw?.height ?? item?.height ?? 0) || 0,
            videoBitRate: Number(raw?.videoBitRate ?? item?.videoBitRate ?? 0) || 0,
            selected: !!(item?.selected || raw?.selected),
            checked: !!(item?.checked || raw?.checked),
            avoidReencoding: !!(
              raw?.avoidReencoding ||
              item?.avoidReencoding ||
              /\(원본\)|passthrough/i.test(String(item?.passthrough || ''))
            ),
            source: 'formattedVideoTracks'
          });
        }
      }
    } catch (error) {
      recordError(error);
    }

    if (!rows.length) {
      for (const raw of rawMap.values()) {
        rows.push({ ...raw, source: 'getVideoTracksList' });
      }
    }

    return rows;
  }

  function analyzeQualityControllers() {
    const candidates = findQualityControllers()
      .map((controller) => {
        const rows = getTrackModels(controller);
        const fixedRows = rows.filter(isFixedNumericTrack);
        const maxFixedHeight = fixedRows.reduce((max, item) => {
          const parsed = parseQuality(item.quality) || parseQuality(item.label);
          return Math.max(max, Number(item.height || parsed?.height || 0));
        }, 0);
        const abrRows = rows.filter((item) => (
          isAbrLike(item.id) || isAbrLike(item.label) || isAbrLike(item.quality)
        ));
        const elementText = textOf(controller?.$el);
        const score =
          fixedRows.length * 1_000_000_000 +
          maxFixedHeight * 1_000_000 +
          rows.length * 10_000 +
          (/1080p|720p|480p|360p/i.test(elementText) ? 1_000 : 0);

        return { controller, rows, fixedRows, abrRows, maxFixedHeight, score };
      })
      .sort((a, b) => b.score - a.score);

    const withFixed = candidates.find((item) => (
      item.fixedRows.length > 0 &&
      typeof item.controller.selectVideoTrack === 'function'
    )) || null;

    state.controllerCandidateCount = candidates.length;
    state.selectedControllerTrackCount = withFixed?.rows.length || 0;

    return {
      candidates,
      withFixed,
      totalFixedTracks: candidates.reduce((sum, item) => sum + item.fixedRows.length, 0)
    };
  }

  function chooseHighestFixedTrack(rows) {
    const fixed = rows
      .filter(isFixedNumericTrack)
      .map((item) => ({
        ...item,
        parsed: parseQuality(item.quality) || parseQuality(item.label)
      }));

    state.availableFixedQualities = fixed
      .map((item) => ({
        id: item.id,
        label: item.parsed?.label || item.quality || item.label,
        width: item.width,
        height: item.height || item.parsed?.height || 0,
        selected: item.selected || item.checked,
        source: item.source
      }))
      .sort((a, b) => b.height - a.height || b.width - a.width);

    return fixed.sort((a, b) => getCandidateScore(b) - getCandidateScore(a))[0] || null;
  }

  function getSelectedTrack(rows) {
    return rows.find((item) => item.selected || item.checked) || null;
  }

  function summarizeSelected(track) {
    if (!track) return null;
    return {
      id: track.id,
      label:
        parseQuality(track.quality)?.label ||
        parseQuality(track.label)?.label ||
        track.quality ||
        track.label
    };
  }

  function selectedMatchesTarget(controller, target) {
    const selected = getSelectedTrack(getTrackModels(controller));
    state.selectedAfter = summarizeSelected(selected);
    return !!(selected && String(selected.id) === String(target.id));
  }

  function updateTargetStability(controller, target, rows) {
    const key = [
      state.channelId,
      target.id,
      ...rows.map((item) => item.id).sort()
    ].join('|');

    if (key === state.targetStableKey) {
      state.targetStableCount++;
    } else {
      state.targetStableKey = key;
      state.targetStableCount = 1;
    }

    return state.targetStableCount >= CONFIG.targetStabilityPolls;
  }

  function findVideo() {
    return document.querySelector([
      playerRootSelector(' video.webplayer-internal-video'),
      playerRootSelector(' video'),
      'video.webplayer-internal-video',
      'video'
    ].join(','));
  }

  function hasPlaybackStarted(video) {
    return !!(
      video instanceof HTMLVideoElement &&
      !video.paused &&
      !video.ended &&
      video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA
    );
  }

  function routeSettled() {
    return !state.routeReadyAtPerf || performance.now() >= state.routeReadyAtPerf;
  }

  function attachVideoEvents(video) {
    if (!(video instanceof HTMLVideoElement) || observedVideos.has(video)) return;
    observedVideos.add(video);

    for (const type of ['loadedmetadata', 'loadeddata', 'canplay', 'playing', 'pause', 'ended', 'resize']) {
      video.addEventListener(type, () => {
        schedulePoll();
      }, { passive: true });
    }
  }

  function getTargetLabel(target) {
    return (
      target?.parsed?.label ||
      parseQuality(target?.quality)?.label ||
      parseQuality(target?.label)?.label ||
      ''
    );
  }

  function targetFrameEvidence(controller, target, video) {
    if (!(video instanceof HTMLVideoElement)) return false;

    const targetHeight = Number(target?.height || target?.parsed?.height || 0);
    selectedMatchesTarget(controller, target);
    const decodedHeight = Number(video.videoHeight || 0);
    const decodedTarget = !!(
      targetHeight > 0 &&
      decodedHeight >= Math.max(1, targetHeight - 8)
    );
    const switchedLongEnough = !!(
      state.switchStartedAtPerf &&
      performance.now() - state.switchStartedAtPerf >= 250
    );

    return !!(
      hasPlaybackStarted(video) &&
      video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA &&
      decodedTarget &&
      switchedLongEnough
    );
  }

  async function waitForCondition(test, timeoutMs, intervalMs = 80, generation = state.generation) {
    const started = Date.now();

    while (Date.now() - started < timeoutMs) {
      if (generation !== state.generation) return false;
      try {
        if (test()) return true;
      } catch (_) {}
      await sleep(intervalMs);
    }

    return false;
  }

  async function waitForStablePlayback(video, stableMs, timeoutMs, generation) {
    const started = Date.now();
    let stableSince = 0;

    while (Date.now() - started < timeoutMs) {
      if (generation !== state.generation) return false;

      if (hasPlaybackStarted(video)) {
        if (!stableSince) stableSince = Date.now();
        if (Date.now() - stableSince >= stableMs) return true;
      } else {
        stableSince = 0;
      }

      await sleep(50);
    }

    return false;
  }

  async function waitForOneVideoFrame(video, generation) {
    if (generation !== state.generation) return;

    if (typeof video.requestVideoFrameCallback !== 'function') {
      await sleep(80);
      return;
    }

    await Promise.race([
      new Promise((resolve) => video.requestVideoFrameCallback(() => resolve())),
      sleep(350)
    ]);
  }

  async function revealWhenTargetFrameReady(controller, target, video, generation) {
    if (!state.visualMaskArmed || state.visualMaskReleased) return false;

    const ready = await waitForCondition(
      () => targetFrameEvidence(controller, target, video),
      CONFIG.stableFrameWaitMs,
      50,
      generation
    );

    if (generation !== state.generation) return false;

    if (ready) {
      await waitForOneVideoFrame(video, generation);
      if (CONFIG.revealDelayMs > 0) await sleep(CONFIG.revealDelayMs);
      state.targetFrameReadyAtPerf = performance.now();
      releaseVisualMask('target-frame-ready');
      return true;
    }

    releaseVisualMask('target-frame-wait-timeout');
    return false;
  }

  function looksLikeRadioOnlyPlayer(analysis) {
    const playerText = textOf(document.querySelector(`${playerRootSelector(' .pzp')},.pzp`));
    const explicitRadioUi =
      /라디오\s*모드로\s*재생\s*중|영상도\s*함께\s*보려면|멤버십을\s*시작|치트키\s*이용자도\s*시청\s*가능/i
        .test(playerText);

    return !!(
      CONFIG.bypassRadioOnlyPlayback &&
      state.freshPlaybackSeen &&
      state.playbackStarted &&
      analysis.totalFixedTracks === 0 &&
      (explicitRadioUi || state.freshResourceKind === 'audio-only')
    );
  }

  function confirmRadioOnly(evidence) {
    if (!evidence) {
      state.radioCandidateSince = 0;
      state.radioEvidenceCount = 0;
      return false;
    }

    state.radioEvidenceCount++;
    if (!state.radioCandidateSince) state.radioCandidateSince = Date.now();

    return !!(
      state.radioEvidenceCount >= 2 &&
      Date.now() - state.radioCandidateSince >= CONFIG.radioConfirmMs
    );
  }

  function finish(reason) {
    if (state.done) return;

    if (state.visualMaskArmed && !state.visualMaskReleased) {
      releaseVisualMask(`finish:${reason}`, !reason.startsWith('highest-fixed'));
    }

    state.done = true;
    state.doneReason = reason;

    if (pollTimer) {
      clearInterval(pollTimer);
      pollTimer = null;
    }

    try {
      perfObserver?.disconnect();
    } catch (_) {}
    perfObserver = null;

    log('완료:', reason, state.target || '');
  }

  async function restorePlaybackIfNeeded(controller, video, generation) {
    const naturallyStable = await waitForStablePlayback(
      video,
      160,
      CONFIG.passiveResumeWaitMs,
      generation
    );

    if (naturallyStable) {
      state.resumeResult = 'continued-or-resumed-naturally';
      return true;
    }

    state.videoPausedAfterSwitch = !!video.paused;

    if (!state.videoWasPlayingBeforeSwitch) {
      state.resumeResult = 'not-restored-because-playback-had-not-started';
      return false;
    }

    state.resumeAttempts++;

    try {
      await Promise.resolve(controller?.$store?.dispatch?.('play'));
    } catch (error) {
      recordError(error);
    }

    let resumed = await waitForStablePlayback(video, 140, 650, generation);

    if (!resumed) {
      state.resumeAttempts++;
      try {
        await video.play();
      } catch (error) {
        recordError(error);
      }
      resumed = await waitForStablePlayback(
        video,
        140,
        CONFIG.resumeVerifyMs,
        generation
      );
    }

    state.resumeResult = resumed ? 'restored-after-track-switch' : 'resume-failed';
    return resumed;
  }

  async function verifySelection(controller, target, video, generation) {
    return waitForCondition(() => {
      if (selectedMatchesTarget(controller, target)) {
        state.targetSelectedAtPerf ||= performance.now();
        return true;
      }

      return targetFrameEvidence(controller, target, video);
    }, CONFIG.selectionVerifyMs, 100, generation);
  }

  async function applyHighestTrack(controller, target, video, generation) {
    if (
      applying ||
      state.done ||
      state.userSelectedManually ||
      generation !== state.generation
    ) {
      return;
    }

    applying = true;

    try {
      const rows = getTrackModels(controller);
      state.selectedBefore = summarizeSelected(getSelectedTrack(rows));
      state.videoWasPlayingBeforeSwitch = hasPlaybackStarted(video);
      state.switchStartedAtPerf = performance.now();

      if (CONFIG.preSwitchStableMs > 0) {
        const stableBeforeSwitch = await waitForStablePlayback(
          video,
          CONFIG.preSwitchStableMs,
          CONFIG.preSwitchStableTimeoutMs,
          generation
        );
        if (generation !== state.generation) return;

        if (!stableBeforeSwitch) {
          state.preSwitchStableResult = 'deferred-before-switch-unstable';
          return;
        }

        state.preSwitchStableResult = 'stable-before-switch';
      }

      state.applyAttempts++;

      armVisualMask('quality-switch', generation);

      log(
        `안정 재생 확인 후 내부 API 선택 시도 ${state.applyAttempts}/${CONFIG.maxApplyAttempts}:`,
        getTargetLabel(target) || String(target.id),
        target.id
      );

      await Promise.resolve(controller.selectVideoTrack(target.id));
      if (generation !== state.generation) return;

      state.source = 'controller.selectVideoTrack';

      const selected = await verifySelection(controller, target, video, generation);
      if (generation !== state.generation) return;

      if (!selected) {
        releaseVisualMask('selection-not-confirmed', true);

        if (state.applyAttempts >= CONFIG.maxApplyAttempts) {
          finish('selection-verification-failed');
        }
        return;
      }

      const playing = await restorePlaybackIfNeeded(controller, video, generation);
      if (generation !== state.generation) return;

      const frameReady = await revealWhenTargetFrameReady(
        controller,
        target,
        video,
        generation
      );
      if (generation !== state.generation) return;

      if (!playing) {
        finish('highest-fixed-selected-but-playback-paused');
      } else if (state.resumeResult === 'restored-after-track-switch') {
        finish(frameReady
          ? 'highest-fixed-selected-playback-restored'
          : 'highest-fixed-selected-playback-restored-frame-unconfirmed');
      } else {
        finish(frameReady
          ? 'highest-fixed-selected-playing'
          : 'highest-fixed-selected-playing-frame-unconfirmed');
      }
    } catch (error) {
      if (generation !== state.generation) return;

      recordError(error);
      releaseVisualMask('internal-api-error', true);
      warn('내부 품질 API 호출 실패:', error);

      if (state.applyAttempts >= CONFIG.maxApplyAttempts) {
        finish('internal-api-error');
      }
    } finally {
      if (generation === state.generation) applying = false;
    }
  }

  async function poll() {
    if (polling || applying || state.done || state.userSelectedManually) return;
    polling = true;
    const generation = state.generation;

    try {
      if (!isLivePath()) {
        finish('left-supported-page');
        return;
      }

      if (Date.now() > state.deadlineAt) {
        if (document.visibilityState !== 'visible' || !document.hasFocus?.()) {
          state.deadlineAt = Date.now() + CONFIG.maxWaitMs;
          state.doneReason = 'timeout-deferred-unfocused';
          return;
        }

        finish('controller-or-playback-timeout');
        return;
      }

      if (!routeSettled()) return;

      const video = findVideo();
      if (video) {
        attachVideoEvents(video);
        state.videoFound = true;
        state.playbackStarted = hasPlaybackStarted(video);
        state.decodedVideoWidth = Number(video.videoWidth || 0);
        state.decodedVideoHeight = Number(video.videoHeight || 0);
      }

      const analysis = analyzeQualityControllers();
      state.controllerFound = analysis.candidates.length > 0;

      if (confirmRadioOnly(looksLikeRadioOnlyPlayer(analysis))) {
        state.radioOnlyDetected = true;
        state.playbackMode = 'radio-only';
        forceClearVisualMask('radio-only-pass-through');
        finish('radio-only-pass-through');
        return;
      }

      const candidate = analysis.withFixed;
      if (!candidate || !video) return;

      const controller = candidate.controller;
      const rows = candidate.rows;
      const target = chooseHighestFixedTrack(rows);
      if (!target) return;

      state.playbackMode = 'video';
      state.target = {
        id: target.id,
        label: getTargetLabel(target) || target.quality || target.label,
        width: target.width,
        height: target.height || target.parsed?.height || 0,
        source: target.source
      };

      if (!updateTargetStability(controller, target, rows)) return;

      const fallbackResourceReady = !!(
        CONFIG.requireFreshChannelResource &&
        !state.freshPlaybackSeen &&
        !isVideoRoute() &&
        state.playbackStarted &&
        document.visibilityState === 'visible' &&
        (typeof document.hasFocus !== 'function' || document.hasFocus()) &&
        (
          state.timeoutRetryAttempts > 0 ||
          performance.now() - (state.routeReadyAtPerf || state.resetPerfNow) >=
            CONFIG.playbackStartedResourceFallbackMs
        )
      );
      if (fallbackResourceReady) {
        state.freshPlaybackSeen = true;
        state.freshResourceAtPerf = performance.now();
        state.freshResourceKind = state.timeoutRetryAttempts > 0
          ? 'started-video-after-timeout-retry'
          : 'started-video-no-resource';
      }

      if (CONFIG.requireFreshChannelResource && !state.freshPlaybackSeen && !isVideoRoute()) return;
      if (
        state.freshResourceAtPerf &&
        performance.now() - state.freshResourceAtPerf < CONFIG.freshResourceSettleMs
      ) {
        return;
      }
      if (!state.playbackStarted) {
        if (isVideoRoute() && video) {
          state.waitingForPlayback = true;
          if (state.visualMaskArmed && !state.visualMaskReleased) {
            releaseVisualMask('vod-waiting-for-playback');
          }
        }
        return;
      }

      state.waitingForPlayback = false;

      const selected = getSelectedTrack(rows);
      const selectedIsTarget = !!(
        selected && String(selected.id) === String(target.id)
      );
      const targetLabel = getTargetLabel(target);
      const targetHeight = Number(target.height || target.parsed?.height || 0);
      const decodedTarget = !!(
        targetHeight > 0 &&
        Number(video.videoHeight || 0) >= Math.max(1, targetHeight - 8)
      );
      const numericResourceIsTarget = !!(
        targetLabel &&
        state.resourceQuality.toLowerCase() === targetLabel.toLowerCase()
      );
      if (selectedIsTarget && decodedTarget) {
        state.selectedBefore = summarizeSelected(selected);
        state.selectedAfter = summarizeSelected(selected);
        state.targetSelectedAtPerf ||= performance.now();
        state.targetFrameReadyAtPerf ||= performance.now();
        finish('already-highest-fixed');
        return;
      }

      if (numericResourceIsTarget && (!selectedIsTarget || !decodedTarget)) {
        state.resourceTargetMismatchDetected = true;
        log(
          '최고화질 리소스 관측값과 현재 선택/프레임이 일치하지 않아 완료 판정을 보류:',
          summarizeSelected(selected),
          `${Number(video.videoWidth || 0)}x${Number(video.videoHeight || 0)}`
        );
      }

      if (selectedIsTarget && !decodedTarget) {
        state.staleHighestIgnored++;
        log(
          '선택값은 최고화질이지만 실제 프레임이 확인되지 않아 내부 API를 재적용:',
          state.resourceQuality || state.freshResourceKind || 'unknown'
        );
      }

      if (state.applyAttempts < CONFIG.maxApplyAttempts) {
        void applyHighestTrack(controller, target, video, generation);
      }
    } finally {
      polling = false;
    }
  }

  function installResourceObserver() {
    if (typeof PerformanceObserver === 'undefined') return;

    const generation = state.generation;
    const resetPerfNow = state.resetPerfNow;

    try {
      perfObserver = new PerformanceObserver((list) => {
        if (generation !== state.generation) return;

        for (const entry of list.getEntries()) {
          if (Number(entry.startTime || 0) + 1 < resetPerfNow) continue;
          if (state.routeReadyAtPerf && Number(entry.startTime || 0) + 1 < state.routeReadyAtPerf) continue;

          const name = String(entry.name || '');
          const isStreamingResource =
            /\.(?:m3u8|m4s|m4v|ts)(?:\?|$)/i.test(name) &&
            /(?:livecloud|nlive|nvelop|navercdn|pstatic)/i.test(name);
          if (!isStreamingResource) continue;

          const numericMatch = name.match(
            /\/(2160p|1440p|1080p|720p|480p|360p|144p)\//i
          );
          const audioOnly = /audioOnly|audio_only|\/radio(?:\/|_|-)/i.test(name);

          if (numericMatch) {
            state.resourceQuality = numericMatch[1];
            state.freshResourceKind = 'numeric-video';
            if (
              state.target?.label &&
              state.target.label.toLowerCase() === numericMatch[1].toLowerCase()
            ) {
              state.targetResourceAtPerf ||= performance.now();
            }
          } else if (audioOnly) {
            state.resourceQuality = 'audioOnly';
            state.freshResourceKind = 'audio-only';
          } else if (CONFIG.acceptOpaqueStreamResource) {
            state.freshResourceKind = 'opaque-stream';
          } else {
            continue;
          }

          if (!state.freshPlaybackSeen) {
            state.freshPlaybackSeen = true;
            state.freshResourceAtPerf = performance.now();
          }
        }

        schedulePoll();
      });

      perfObserver.observe({ type: 'resource', buffered: true });
    } catch (error) {
      recordError(error);
    }
  }

  function installTrustedClickCancel() {
    if (clickListenerInstalled || !CONFIG.respectTrustedUserSelection) return;

    document.addEventListener('click', (event) => {
      if (!event.isTrusted || state.done) return;

      const target = event.target instanceof Element
        ? event.target.closest([
            playerRootSelector(' li.pzp-ui-setting-quality-item'),
            playerRootSelector(' .pzp-setting-quality-pane [role="menuitem"]'),
            'li.pzp-ui-setting-quality-item',
            '.pzp-setting-quality-pane [role="menuitem"]'
          ].join(','))
        : null;
      if (!target) return;

      if (/\b\d{3,4}p\b|자동|abr/i.test(textOf(target))) {
        state.userSelectedManually = true;
        forceClearVisualMask('user-selected-quality-manually');
        finish('user-selected-quality-manually');
      }
    }, true);

    clickListenerInstalled = true;
  }

  function findPlayerRoot() {
    return document.querySelector(`${playerRootSelector(' .pzp')},.pzp`) ||
      document.querySelector(playerRootSelector()) ||
      findVideo()?.closest?.('.pzp,[id*="player_layout"],.chzzk_player') ||
      null;
  }

  function dispatchPlayerActivity(root = findPlayerRoot()) {
    if (!(root instanceof Element)) return;

    const rect = root.getBoundingClientRect?.();
    const clientX = Math.round((rect?.left || 0) + Math.max(1, (rect?.width || 2) / 2));
    const clientY = Math.round((rect?.top || 0) + Math.max(1, (rect?.height || 2) / 2));
    const eventInit = {
      bubbles: true,
      cancelable: true,
      view: window,
      clientX,
      clientY
    };

    for (const type of ['pointermove', 'mousemove', 'mouseover']) {
      try {
        const EventCtor = type.startsWith('pointer') && typeof PointerEvent === 'function'
          ? PointerEvent
          : MouseEvent;
        root.dispatchEvent(new EventCtor(type, eventInit));
      } catch (_) {}
    }
  }

  function isVisibleElement(el) {
    if (!(el instanceof Element)) return false;
    const rect = el.getBoundingClientRect?.();
    if (!rect || rect.width < 4 || rect.height < 4) return false;
    const style = getComputedStyle(el);
    return !(
      style.display === 'none' ||
      style.visibility === 'hidden' ||
      Number(style.opacity || 1) <= 0.05
    );
  }

  function findVisibleNativePlayOverlays() {
    return [...document.querySelectorAll([
      playerRootSelector(' .pzp-pc__brand-playback-button'),
      playerRootSelector(' .pzp-pc__center-playback-button'),
      playerRootSelector(' [class*="brand-playback-button"]'),
      playerRootSelector(' [class*="center-playback-button"]'),
      '.pzp-pc__brand-playback-button',
      '.pzp-pc__center-playback-button',
      '[class*="brand-playback-button"]',
      '[class*="center-playback-button"]'
    ].join(','))].filter(isVisibleElement);
  }

  function clearReturnRepairFlags() {
    state.wasPlayingBeforeHidden = false;
    state.wasPlayingBeforeBlur = false;
    state.hiddenStartedAt = 0;
    state.blurStartedAt = 0;
    wasPlayingBeforeHidden = false;
  }

  async function repairPlaybackUiAfterReturn(reason, force = false) {
    if (!isLivePath()) return;

    forceClearVisualMask(`return:${reason}`);
    state.returnRepairScheduled = false;

    const video = findVideo();
    if (!(video instanceof HTMLVideoElement)) return;

    attachVideoEvents(video);
    const playing = hasPlaybackStarted(video);
    const overlays = findVisibleNativePlayOverlays();

    if (!force && !playing) {
      state.returnRepairLastReason = reason;
      state.returnRepairLastResult = 'skipped-video-not-playing';
      clearReturnRepairFlags();
      return;
    }

    if (!force && !overlays.length) {
      state.returnRepairLastReason = reason;
      state.returnRepairLastResult = 'skipped-no-visible-stale-overlay';
      clearReturnRepairFlags();
      return;
    }

    state.returnRepairAttempts++;
    state.returnRepairLastReason = reason;
    state.returnRepairCooldownUntil = Date.now() + CONFIG.returnRepairCooldownMs;

    try {
      if (force && video.paused && !video.ended) {
        const analysis = analyzeQualityControllers();
        const controller = analysis.withFixed?.controller || analysis.candidates[0]?.controller || null;
        if (controller?.$store?.dispatch) {
          await Promise.resolve(controller.$store.dispatch('play'));
        }
        try {
          await video.play();
        } catch (error) {
          recordError(error);
        }
      }

      dispatchPlayerActivity();
      state.returnRepairLastResult = force
        ? 'manual-player-ui-nudged'
        : 'visible-stale-overlay-nudged';
      schedulePoll();
    } catch (error) {
      recordError(error);
      state.returnRepairLastResult = 'error';
    } finally {
      clearReturnRepairFlags();
    }
  }

  function scheduleReturnRepair(reason, force = false) {
    if (!isLivePath()) return false;

    const now = Date.now();
    const hiddenForMs = state.hiddenStartedAt ? now - state.hiddenStartedAt : 0;
    const blurredForMs = state.blurStartedAt ? now - state.blurStartedAt : 0;
    const longBackground = (
      (state.wasPlayingBeforeHidden && hiddenForMs >= CONFIG.returnRepairMinBackgroundMs) ||
      (state.wasPlayingBeforeBlur && blurredForMs >= CONFIG.returnRepairMinBackgroundMs)
    );

    if (!force && !longBackground) {
      state.returnRepairLastReason = reason;
      state.returnRepairLastResult = 'skipped-short-background';
      clearReturnRepairFlags();
      return false;
    }

    if (!force && now < state.returnRepairCooldownUntil) {
      state.returnRepairLastReason = reason;
      state.returnRepairLastResult = 'skipped-cooldown';
      return false;
    }

    if (returnRepairTimer) {
      state.returnRepairLastReason = reason;
      state.returnRepairLastResult = 'skipped-already-scheduled';
      return false;
    }

    state.returnRepairScheduled = true;
    returnRepairTimer = setTimeout(() => {
      returnRepairTimer = null;
      void repairPlaybackUiAfterReturn(reason, force);
    }, force ? 0 : CONFIG.returnRepairDelayMs);
    return true;
  }

  function retryAfterTimeoutOnVisible(reason) {
    if (!isLivePath()) return false;
    if (!state.done || state.doneReason !== 'controller-or-playback-timeout') return false;
    if (state.timeoutRetryAttempts >= CONFIG.maxTimeoutRetryOnVisible) return false;

    const attempts = state.timeoutRetryAttempts + 1;
    state.timeoutRetryAttempts = attempts;
    state.timeoutRetryLastReason = reason;
    resetForChannel(`timeout-visible-retry:${reason}`);
    state.timeoutRetryAttempts = attempts;
    state.timeoutRetryLastReason = reason;
    return true;
  }

  function installPlaybackReturnRepair() {
    document.addEventListener('visibilitychange', () => {
      const video = findVideo();
      if (document.visibilityState === 'hidden') {
        state.hiddenStartedAt = Date.now();
        state.wasPlayingBeforeHidden = video ? hasPlaybackStarted(video) : false;
        wasPlayingBeforeHidden = state.wasPlayingBeforeHidden;
        return;
      }

      if (document.visibilityState === 'visible') {
        if (retryAfterTimeoutOnVisible('visibility-visible')) return;
        if (wasPlayingBeforeHidden) state.wasPlayingBeforeHidden = true;
        scheduleReturnRepair('visibility-visible');
      }
    });

    window.addEventListener('blur', () => {
      const video = findVideo();
      state.blurStartedAt = Date.now();
      state.wasPlayingBeforeBlur = video ? hasPlaybackStarted(video) : false;
    });

    window.addEventListener('focus', () => {
      if (retryAfterTimeoutOnVisible('window-focus')) return;
      scheduleReturnRepair('window-focus');
    });

    window.addEventListener('pageshow', () => {
      if (retryAfterTimeoutOnVisible('pageshow')) return;
      scheduleReturnRepair('pageshow');
    });
  }

  function resetForChannel(reason) {
    if (pollTimer) {
      clearInterval(pollTimer);
      pollTimer = null;
    }
    try {
      perfObserver?.disconnect();
    } catch (_) {}
    perfObserver = null;

    forceClearVisualMask('channel-reset');
    applying = false;
    polling = false;
    pollQueued = false;

    state.generation++;
    const routeInfo = getRouteInfo();
    state.routeKind = routeInfo.kind;
    state.contentId = routeInfo.id;
    state.channelId = getChannelId();
    state.startedAt = Date.now();
    state.resetPerfNow = performance.now();
    state.routeReadyAtPerf = state.resetPerfNow + (routeInfo.kind === 'video' ? CONFIG.videoRouteSettleMs : 0);
    state.deadlineAt = state.startedAt + CONFIG.maxWaitMs;

    state.freshPlaybackSeen = false;
    state.freshResourceAtPerf = 0;
    state.freshResourceKind = '';
    state.resourceQuality = '';
    state.targetResourceAtPerf = 0;

    state.controllerFound = false;
    state.controllerCandidateCount = 0;
    state.selectedControllerTrackCount = 0;
    state.targetStableKey = '';
    state.targetStableCount = 0;
    state.staleHighestIgnored = 0;
    state.resourceTargetMismatchDetected = false;

    state.playbackMode = 'unknown';
    state.radioOnlyDetected = false;
    state.radioCandidateSince = 0;
    state.radioEvidenceCount = 0;
    state.waitingForPlayback = false;

    state.videoFound = false;
    state.playbackStarted = false;
    state.decodedVideoWidth = 0;
    state.decodedVideoHeight = 0;

    state.source = '';
    state.availableFixedQualities = [];
    state.target = null;
    state.selectedBefore = null;
    state.selectedAfter = null;

    state.applyAttempts = 0;
    state.done = false;
    state.doneReason = '';
    state.userSelectedManually = false;

    state.videoWasPlayingBeforeSwitch = false;
    state.videoPausedAfterSwitch = false;
    state.resumeAttempts = 0;
    state.resumeResult = '';
    state.preSwitchStableResult = '';
    state.wasPlayingBeforeHidden = false;
    state.wasPlayingBeforeBlur = false;
    state.hiddenStartedAt = 0;
    state.blurStartedAt = 0;
    state.returnRepairAttempts = 0;
    state.returnRepairScheduled = false;
    state.returnRepairCooldownUntil = 0;
    state.returnRepairLastReason = '';
    state.returnRepairLastResult = '';
    if (!String(reason || '').startsWith('timeout-visible-retry:')) {
      state.timeoutRetryAttempts = 0;
      state.timeoutRetryLastReason = '';
    }
    wasPlayingBeforeHidden = false;
    if (returnRepairTimer) {
      clearTimeout(returnRepairTimer);
      returnRepairTimer = null;
    }

    state.visualMaskArmed = false;
    state.visualMaskReleased = false;
    state.visualMaskReason = '';
    state.visualMaskArmedAt = 0;
    state.visualMaskReleasedAt = 0;

    state.switchStartedAtPerf = 0;
    state.targetSelectedAtPerf = 0;
    state.targetFrameReadyAtPerf = 0;
    state.lastError = '';

    if (CONFIG.initialVisualMaskEnabled) {
      armVisualMask('initial-quality-wait', state.generation, CONFIG.initialMaskMaxMs);
    }

    installResourceObserver();
    pollTimer = setInterval(schedulePoll, CONFIG.pollIntervalMs);
    schedulePoll();

    log('채널 초기화:', reason, state.channelId, `generation=${state.generation}`);
  }

  function watchRouteChanges() {
    let lastPath = location.pathname;

    const check = (reason) => {
      if (location.pathname === lastPath) return;
      lastPath = location.pathname;

      if (isLivePath()) {
        resetForChannel('spa-route-change');
      } else if (!state.done) {
        forceClearVisualMask('left-supported-page');
        finish('left-supported-page');
      }
    };

    for (const methodName of ['pushState', 'replaceState']) {
      const original = history[methodName];
      if (typeof original !== 'function' || original.__chzzkQualityRouteWrapped) {
        continue;
      }

      function wrappedHistoryMethod(...args) {
        const result = original.apply(this, args);
        queueMicrotask(() => check(`history.${methodName}`));
        return result;
      }

      Object.defineProperty(wrappedHistoryMethod, '__chzzkQualityRouteWrapped', {
        value: true
      });
      history[methodName] = wrappedHistoryMethod;
    }

    window.addEventListener('popstate', () => check('popstate'));
    routeTimer = setInterval(() => check('interval'), CONFIG.routeCheckMs);
  }

  function diag() {
    const now = performance.now();

    return {
      name: NAME,
      version: VERSION,
      href: location.href,
      routeKind: state.routeKind,
      contentId: state.contentId,
      channelId: state.channelId,
      generation: state.generation,

      freshPlaybackSeen: state.freshPlaybackSeen,
      freshResourceAtPerf: state.freshResourceAtPerf,
      freshResourceKind: state.freshResourceKind,
      resourceQuality: state.resourceQuality,
      targetResourceAtPerf: state.targetResourceAtPerf,

      controllerFound: state.controllerFound,
      controllerCandidateCount: state.controllerCandidateCount,
      selectedControllerTrackCount: state.selectedControllerTrackCount,
      targetStableCount: state.targetStableCount,
      staleHighestIgnored: state.staleHighestIgnored,
      resourceTargetMismatchDetected: state.resourceTargetMismatchDetected,

      playbackMode: state.playbackMode,
      radioOnlyDetected: state.radioOnlyDetected,
      routeReadyAtPerf: state.routeReadyAtPerf,
      routeSettled: routeSettled(),
      waitingForPlayback: state.waitingForPlayback,
      videoFound: state.videoFound,
      playbackStarted: state.playbackStarted,
      decodedVideoWidth: state.decodedVideoWidth,
      decodedVideoHeight: state.decodedVideoHeight,

      source: state.source,
      availableFixedQualities: state.availableFixedQualities,
      target: state.target,
      selectedBefore: state.selectedBefore,
      selectedAfter: state.selectedAfter,

      videoWasPlayingBeforeSwitch: state.videoWasPlayingBeforeSwitch,
      videoPausedAfterSwitch: state.videoPausedAfterSwitch,
      resumeAttempts: state.resumeAttempts,
      resumeResult: state.resumeResult,
      preSwitchStableResult: state.preSwitchStableResult,
      wasPlayingBeforeHidden: state.wasPlayingBeforeHidden,
      wasPlayingBeforeBlur: state.wasPlayingBeforeBlur,
      hiddenStartedAt: state.hiddenStartedAt,
      blurStartedAt: state.blurStartedAt,
      returnRepairAttempts: state.returnRepairAttempts,
      returnRepairScheduled: state.returnRepairScheduled,
      returnRepairCooldownUntil: state.returnRepairCooldownUntil,
      returnRepairLastReason: state.returnRepairLastReason,
      returnRepairLastResult: state.returnRepairLastResult,
      timeoutRetryAttempts: state.timeoutRetryAttempts,
      timeoutRetryLastReason: state.timeoutRetryLastReason,

      visualMaskArmed: state.visualMaskArmed,
      visualMaskReleased: state.visualMaskReleased,
      visualMaskReason: state.visualMaskReason,
      visualMaskDurationMs:
        state.visualMaskArmedAt && state.visualMaskReleasedAt
          ? state.visualMaskReleasedAt - state.visualMaskArmedAt
          : 0,

      switchStartedAtPerf: state.switchStartedAtPerf,
      targetSelectedAtPerf: state.targetSelectedAtPerf,
      targetFrameReadyAtPerf: state.targetFrameReadyAtPerf,
      switchToVisibleMs:
        state.switchStartedAtPerf && state.visualMaskReleasedAt
          ? Math.max(0, state.visualMaskReleasedAt - (performance.timeOrigin + state.switchStartedAtPerf))
          : 0,

      applyAttempts: state.applyAttempts,
      done: state.done,
      doneReason: state.doneReason,
      userSelectedManually: state.userSelectedManually,
      remainingWaitMs: Math.max(0, state.deadlineAt - Date.now()),
      elapsedPerfMs: Math.round(now),
      lastError: state.lastError
    };
  }

  function init() {
    ensureVisualMaskStyle();
    installTrustedClickCancel();
    installPlaybackReturnRepair();
    watchRouteChanges();

    window.__CHZZK_INITIAL_QUALITY__ = {
      version: VERSION,
      config: CONFIG,
      diag,
      retry() {
        if (!isLivePath()) return false;
        resetForChannel('manual-retry');
        return true;
      },
      stop() {
        forceClearVisualMask('stopped-manually');
        finish('stopped-manually');
        return true;
      },
      repairUi(reason = 'manual-repair-ui') {
        scheduleReturnRepair(reason, true);
        return true;
      }
    };

    if (isLivePath()) resetForChannel('initial-load');
    log(`v${VERSION} loaded`);
  }

  init();
})();