From 9a07bf87a7ae437ebacd5c476cc1e9aba67f85e5 Mon Sep 17 00:00:00 2001 From: "Claude (chzzk-bypass owner)" Date: Thu, 28 May 2026 14:33:14 +0900 Subject: [PATCH] =?UTF-8?q?v1.1.3:=20livePlaybackJson=20=EB=82=B4=EB=B6=80?= =?UTF-8?q?=20meta/live=20=EB=8F=84=20=ED=8C=A8=EC=B9=98=20(cosmetic=20UI?= =?UTF-8?q?=20only)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 발견: live-detail 의 outer boolean (timeMachineActive, timeMachinePlayback) 두 개를 true 로 만들어도 재생바가 안 뜸. 외부 자료 (jaesung9507/nvver playback.go) 확인 결과, 플레이어는 livePlaybackJson 내부의 meta.liveRewind / meta.duration / live.timeMachine 을 보고 UI 를 결정함. 이 필드들은 omitempty 라서 DVR ON 일 때만 응답에 존재. 수정: - patchLiveDetailData 에서 livePlaybackJson 을 파싱해 meta.liveRewind=true, meta.duration 보강 (1h 기본), live.timeMachine=true 주입 후 재직렬화. - 깨진 live-playback-json 외부 호출은 이전 커밋에서 이미 제거됨. 근본적 한계 (README 에 명시): - 이건 cosmetic 패치다. CDN 의 DVR window 는 스트리머가 타임머신을 켰을 때만 서버가 프로비저닝하므로, 스트리머가 꺼둔 라이브는 HLS 매니페스트에 과거 segment 자체가 없음. 재생바가 떠도 실제 seek 은 동작 안 할 가능성 높음. - live-playback-json 엔드포인트가 timeMachine-off 채널에서 404 인 것도 같은 이유. Streamlink/ChzzkDownloader 도 timeMachineActive 일 때만 부름. - 진짜 force-timemachine 은 서버 사이드(CDN) 의 결정이라 클라이언트에서 근본적으로 우회 불가. manifest 버전 1.1.2 → 1.1.3, README 한계 명시. --- README.md | 52 +++++++++++++++++++++++++++++++++++--------------- manifest.json | 2 +- timemachine.js | 27 ++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index f96251a..dcfb2b0 100644 --- a/README.md +++ b/README.md @@ -12,32 +12,54 @@ 구현: `content.js` -### 2. 타임머신 강제 활성화 (되감기 UI) +### 2. 타임머신 강제 활성화 (되감기 UI, cosmetic) -스트리머가 타임머신을 끄고 방송 중이어도 플레이어에 되감기 (seek) 바 UI 를 -띄웁니다. 가능하면 DVR 가능한 HLS 매니페스트까지 함께 갈아끼워 실제 되감기 -동작도 살립니다. +스트리머가 타임머신을 끄고 방송 중인 라이브에서도 플레이어의 되감기 (seek) 바 +UI 를 띄우려 시도합니다. 동작 방식: 1. `api.chzzk.naver.com/service/v3.2/channels/{id}/live-detail` 응답을 `window.fetch` 와 `XMLHttpRequest` 양쪽에서 가로챕니다. 치지직 React 앱은 axios 기반이라 실제 요청이 XHR 로 나가므로 XHR 후킹이 필수입니다. -2. 응답 본문의 `content.timeMachineActive` 와 `content.timeMachinePlayback` - 두 플래그를 강제로 `true` 로 만듭니다 → 플레이어 UI 가 되감기 바를 노출합니다. -3. 같은 채널의 `…/channels/{id}/live-playback-json` 을 호출해 DVR 매니페스트 - 문자열을 받아 `content.livePlaybackJson` 을 교체합니다 → 매니페스트가 실제 - seek 을 지원하게 됩니다. +2. 응답 본문의 `content.timeMachineActive`, `content.timeMachinePlayback` + 두 플래그를 `true` 로 만듭니다. +3. 내부의 `livePlaybackJson` (JSON 문자열) 을 파싱해서 `meta.liveRewind = true`, + `meta.duration` 보강, `live.timeMachine = true` 를 주입한 뒤 다시 직렬화합니다. + 이 필드들이 플레이어가 seek 바 UI 를 켤지 결정하는 트리거입니다. -이는 [Choonholic/ChzzkDownloader](https://github.com/Choonholic/ChzzkDownloader) -의 `--stream force-timemachine` 옵션이 사용하는 것과 같은 엔드포인트입니다. -관련 글: [blog.choonholic.com/archives/3216](https://blog.choonholic.com/archives/3216) +> **중요한 한계 (반드시 읽으세요)** +> +> 이건 **UI 만 켜는 cosmetic 패치** 입니다. 치지직의 DVR window 는 스트리머가 +> 타임머신을 켰을 때만 CDN 이 프로비저닝하기 때문에, 스트리머가 꺼둔 라이브의 +> HLS 매니페스트에는 과거 시점의 segment 자체가 존재하지 않습니다. 따라서: +> +> - **재생바가 떠도 실제 되감기는 동작하지 않을 가능성이 매우 높습니다** (누르면 +> live edge 로 튕기거나 segment not found 가 뜸). +> - 진짜 되감기를 살리려면 CDN 이 만들어 둔 DVR 매니페스트가 있어야 하는데, +> `service/v1/channels/{id}/live-playback-json` 엔드포인트는 스트리머가 타임머신을 +> 켜둔 채널에서만 200 을 돌려주고 꺼둔 채널에서는 404 입니다 (실측 확인). +> +> 참고: +> [Streamlink chzzk 플러그인](https://github.com/streamlink/streamlink/blob/master/src/streamlink/plugins/chzzk.py) +> 도 `timeMachineActive` 가 true 일 때만 `live-playback-json` 을 부릅니다. +> [Choonholic/ChzzkDownloader](https://github.com/Choonholic/ChzzkDownloader) +> 의 `--stream force-timemachine` 옵션도 마찬가지로 서버-사이드 (CORS 무관) +> 에서 동일 엔드포인트를 강제로 두드릴 뿐, 서버가 DVR 을 안 만들었다면 똑같이 +> 실패합니다. 구현: `timemachine.js` -> 주의: 타임머신 매니페스트가 보유한 되감기 가능 길이는 CDN 측 윈도우에 -> 의존하므로, 강제로 활성화했다 하더라도 무한정 과거로 되감을 수 있는 것은 -> 아닙니다. +#### 진단 모드 + +응답 구조를 직접 들여다보고 싶으면 devtools 콘솔에서: + +```js +localStorage.setItem('chzzk-bypass:debug', '1') +``` + +치고 새로고침하면 `[chzzk-bypass:timemachine] DEBUG live-detail content after patch: {...}` +로 패치된 content + `livePlaybackJson` 파싱 결과가 통째로 찍힙니다. ## 설치 diff --git a/manifest.json b/manifest.json index 154a95c..0255071 100644 --- a/manifest.json +++ b/manifest.json @@ -1,6 +1,6 @@ { "name": "Chzzk Bypass", - "version": "1.1.2", + "version": "1.1.3", "manifest_version": 3, "description": "치지직(CHZZK) 시청 환경 개선: 1) Mac 위장으로 그리드 없이 1080p 시청, 2) 스트리머가 타임머신을 꺼둔 라이브에서도 되감기 UI 강제 표시.", "icons": { diff --git a/timemachine.js b/timemachine.js index 6a2b654..5b6b60e 100644 --- a/timemachine.js +++ b/timemachine.js @@ -79,6 +79,33 @@ content.timeMachineActive = true; content.timeMachinePlayback = true; + // 내부 livePlaybackJson 의 meta/live 도 같이 패치한다. 플레이어는 외부 boolean + // 두 개만 보는 게 아니라, livePlaybackJson 을 파싱해 meta.liveRewind / meta.duration / + // live.timeMachine 을 확인해서 seekbar UI 여부를 결정한다. + // (참고: jaesung9507/nvver playback.go 의 Meta/Live 구조체. liveRewind/duration 은 + // omitempty 라서 DVR ON 일 때만 존재함) + // + // 한계: 이건 어디까지나 UI 만 켜는 cosmetic 패치다. CDN 의 DVR window 는 스트리머가 + // 타임머신을 켰을 때만 서버가 프로비저닝하므로, 실제 segment 가 과거 시점에 존재하지 + // 않아 seek 자체는 동작하지 않을 수 있다 (재생바가 떠도 누르면 live edge 로 튕김). + try { + const raw = content.livePlaybackJson; + const pb = typeof raw === 'string' ? JSON.parse(raw) : raw; + if (pb && typeof pb === 'object') { + pb.meta = pb.meta || {}; + pb.meta.liveRewind = true; + if (typeof pb.meta.duration !== 'number' || pb.meta.duration <= 0) { + pb.meta.duration = 3600; // 1h. 진짜 길이는 CDN 이 정함. + } + pb.live = pb.live || {}; + pb.live.timeMachine = true; + content.livePlaybackJson = typeof raw === 'string' ? JSON.stringify(pb) : pb; + log('inner livePlaybackJson meta/live patched (liveRewind, duration, timeMachine)'); + } + } catch (e) { + log('inner livePlaybackJson patch failed', e); + } + // 진단용: live-detail content 와 livePlaybackJson 디코딩 결과를 통째로 덤프. // 플레이어가 어느 필드를 보는지 모를 때 켜서 들여다보는 용도. // 활성화: devtools 콘솔에서 `localStorage.setItem('chzzk-bypass:debug','1')` 후 새로고침.