o2o-site-AEO/solution/site/scripts/mockup/AUTOPLAY.md
Mina Choi 5e2def200b [chore] site/mockup: 번들 갱신·문서 정리 · 2안(사진) 빌더 스크립트 추가
- vendor/ 옛 해시 번들을 retired/ 로 옮기고 새 해시로 교체
- build_photo6.py 신설 — /s/stay6 "2안(사진)" 판, 슬러그별로 다시 구울 수 있다
  (python3 build_photo6.py <slug>)
- build_pension.py·build_reading.py·build_stay6.py·patch_stay.py 정리
- AUTOPLAY.md·README.md·audit-all.mjs 갱신
2026-09-23 13:18:51 +09:00

8.0 KiB

자동재생 — 안 된다. 왜 안 되는지와, 그래서 뭘 했는지

2026-09-22 — 자동재생은 켠다. 끄지 않는다. 대표 지시("내가 키라고 몇번을 말해"). inject.js:600autoplay(); 다. void autoplay; 로 되돌리는 변경은 하지 않는다. (09-15 에 "에러로 보이니 꺼놔" 로 한 번 껐던 것을 되돌린 것이다 — 09-11 빼 → 09-14 켜 → 09-15 끔 → 09-22 켜.) 아래는 왜 첫 방문에 소리가 안 나는가의 기록이지, 호출을 빼라는 근거가 아니다 — 브라우저가 거절해도 호출은 건다.

/s/stay 배경음악을 새로고침 직후 바로 나게 하려고 시도한 것 전부와 실측값이다. 같은 얘기가 다시 나오면 이 파일부터 본다.

측정: 2026-09-14 · 킹서버 https://web4ai.o2osolution.ai/s/stay · Chrome(정식 채널)


결론

브라우저에서 첫 방문에 소리가 저절로 나게 하는 방법은 없다. 사람 손 입력(isTrusted: true) 없이는 크롬이 play()NotAllowedError 로 거절한다. 자바스크립트로 그 검사를 통과시키는 길은 없고, 그건 우회 대상이 아니라 그 우회를 막으려고 만들어진 검사다.

지금 동작: 손님이 화면에 처음 닿으면(클릭·탭·키) 켜진다. 그 이상은 어떤 사이트도 못 한다.

해 본 것과 결과

시도 결과
로드 즉시 audio.play() NotAllowedError
무음(muted=true)으로 시작 → 나중에 unmute NotAllowedError (무음도 거절된다)
<audio muted playsinline> 태그로 두고 play() NotAllowedError
가짜 이벤트 강제 발생 — dispatchEventpointerdown·mousedown·pointerup·mouseup·click·keydown 6종 navigator.userActivation false 그대로, 거절
재생 단추를 코드로 누르기 — btn.click(), 0.7초 간격 3회 play=0
--app=URL 앱 모드로 띄우기 play=0
Web Audio APInew AudioContext() state: "suspended"
ctx.resume() 약속이 영원히 안 돌아온다 (제스처를 기다리며 매달린다)
mp3 를 decodeAudioData 로 풀어 BufferSource 재생 currentTime 0.000 → 0.000, 안 돈다
speechSynthesis.speak() error: not-allowed
마우스만 움직임 / 스크롤만 play=0 (act=false)
진짜 클릭·탭·키 입력 한 번 켜진다 (act=true, play=1)

dispatchEventel.click() 이 만든 이벤트는 isTrusted: false 로 찍혀 나간다. 크롬의 사용자 활성화 카운터(navigator.userActivation)는 isTrusted: true 인 입력만 세고, 그게 올라야 소리가 풀린다.

브라우저에서 소리를 낼 수 있는 API 는 셋뿐이고 셋 다 같은 게이트 뒤에 있다 — HTMLMediaElement(<audio>) · Web Audio(AudioContext) · speechSynthesis. 우회로가 없는 이유다.

활성화를 준다: mousedown · pointerdown · pointerup · touchend · keydown(Esc 제외) · click 안 준다: mousemove · pointermove · touchmove · wheel · scroll


안 한 것과 그 이유

크롬 문서(developer.chrome.com/blog/autoplay)에 예외가 넷 있지만 전부 손님 쪽 설정이라 제품에서는 쓸 수 없다. 한 번 만들었다가 되돌렸다.

예외 왜 안 쓰나
PWA 설치본 펜션 사이트 보러 온 손님이 앱을 설치할 리 없다. 매니페스트까지 붙였다가 걷어냈다
MEI (같은 출처에서 7초 이상 여러 번) 손님이 쌓는 것이라 첫 방문에는 없다
--autoplay-policy=no-user-gesture-required 브라우저를 그 플래그로 띄워야 한다
기업정책 AutoplayAllowlist 기계 관리자가 넣는 것이다

강제 클릭 코드도 뺐다. 넣어서 배포까지 해 봤지만 기본 크롬에서 play=0 이고, 동작하지 않는 코드가 남으면 다음 사람이 "이미 처리돼 있네" 로 읽는다.


지금 코드가 하는 일 (inject.js startPlayer)

  1. 로드 즉시 play() 를 건다. (거절되지만, 자동재생이 허락된 환경이면 여기서 켜진다)
  2. 거절되면 첫 사람 손길을 기다린다. 창 전체에 걸고, 플레이어 자기 단추만 빼고 어디를 눌러도 걸린다.
    • STRONG(활성화를 주는 입력): 스로틀 없이 매번 시도
    • WEAK(안 주는 입력): 1초에 한 번만 — 초당 수십 번 오는 mousemove 로 단추가 깜빡이는 것을 막는다
  3. 탭이 다시 앞으로 나올 때(visibilitychange) 한 번 더 건다.

여기서 밟았던 함정 둘

  • 가짜 pointerdown 이 대기를 태웠다. tameRails() 가 레일 자동 넘김을 세우려고 레일마다 pointerdown 을 쏘는데(정상 동작), 그게 첫 손길 대기를 먼저 먹었다. 그 뒤로는 손님이 아무 데나 눌러도 안 켜졌다. → onFirstTouchevent.isTrusted 를 먼저 본다.
  • 스로틀이 클릭을 걸렀다. 입력을 안 가리고 1초 스로틀을 걸었더니 클릭 직전의 mousemove 가 그 1초를 먹어 정작 활성화를 주는 mousedown 이 걸러졌다(실측: act=true 인데 play=0). → STRONG/WEAK 로 갈라 STRONG 에는 스로틀을 안 건다.

"트리플픽은 되던데?" — 실측 결과

트리플픽도 안 된다. 똑같이 거절당한다.

https://triplepick.o2o.kr/match/MLB_BAL_TOR_20260914?song=BAL (공유 링크 = 자동 로드 경로)에 깨끗한 크롬으로 들어가 HTMLMediaElement.prototype.play 를 가로채 찍은 값이다.

[T] play() 거절 NotAllowedError  .kr/api/songs/audio/815/0?v=89a0fcd6

코드도 우리와 같은 한 줄이다 — usePlayer.tsx:133 if (shouldPlay) audio.play().catch(() => {}). 게스처 감지 코드는 0줄이고, 평소 재생 경로는 MatchupHUD.tsx:55「♪ 응원가 듣기」 버튼이다. playlist.ts:19PATH_PLAYLISTS 는 비어 있어서 경로 진입 자동 로드는 실을 곡도 없다.

그럼 왜 내 브라우저에서는 나나 — MEI

크롬은 같은 출처에서 소리를 여러 번 들은 사람에게만 그 출처의 자동재생을 풀어 준다 (Media Engagement Index). 조건: 7초 초과 재생 · 음소거 아님 · 탭 활성 · (영상이면) 200x140px 초과.

트리플픽을 자주 쓰면서 응원가를 여러 번 들었으면 그 브라우저에는 이미 허락이 쌓여 있다. 그 브라우저에서만 자동재생된다. 손님의 새 브라우저에서는 안 된다.

확인: chrome://media-engagement/ 에서 출처별 점수와 Has high score 를 본다.

같은 일이 이 사이트에도 일어난다. 목업을 자주 열고 음악을 들으면 그 브라우저는 web4ai.o2osolution.ai 에 허락을 쌓고, 그 뒤로는 새로고침해도 바로 난다. 대표님 노트북에서 시연하면 그 상태가 된다 — 다만 손님 브라우저는 아니다.


재보고 싶을 때 — 측정 주의

Playwright 로 잴 때 page.evaluate 를 쓰면 안 된다. CDP 가 userGesture: true 로 넣어서 상태를 읽으려고 부른 그 호출이 브라우저를 "조작됨" 으로 만든다. 그 뒤 측정은 전부 거짓 양성이다 — 실제로 이것 때문에 "마우스만 움직여도 켜진다" 는 틀린 결론을 한 번 냈다.

상태는 addInitScript 로 심고 console 로만 받는다.

await page.addInitScript(() => {
  setInterval(() => {
    const el = document.getElementById('w4d-play');
    console.log('[C] play=' + (el ? el.dataset.playing : '-')
      + ' act=' + navigator.userActivation.isActive);
  }, 1000);
});
page.on('console', (m) => { /* 여기서만 읽는다 */ });

지금 무엇을 보고 있는지 가리는 법

/s/max-age=300, must-revalidate 다. "고쳤는데 안 된다" 의 상당수가 캐시된 옛 파일이었다. 콘솔 첫 줄에 빌드 시각이 찍힌다.

[w4d] 주입분 로드 · 빌드 2026-09-14T07:12:00+00:00

이 시각이 방금 구운 것과 다르면 옛 파일을 보고 있는 것이다. ⌘⇧R.