트위치에서 1080p 재생이 안 되는 증상에 대한 분석
Posted 2019. 5. 15. 20:28, Filed under: 개발/JavaScript19-05-16 코멘트 추가:
현재 이 문제는 해결되었습니다. 따라서 아래의 방법을 사용할 필요는 없습니다. 참고: https://nomo.asia/406
출처 없이 자유롭게 인용, 수정, 배포하셔도 됩니다.
2019년 5월 15일 현재, 가끔 트위치에서 1080p 으로 재생하는 옵션이 없어지는 문제가 발생하고 있다.
결론부터 말하자면 비용을 절약하기 위해 한국에서만 실행되는 정책으로 추정된다. 이것은 추정이며 이 글에서는 이것이 옳다, 나쁘다를 논하지 않는다. 본 문서에서는 그 원인과 해결 방법에 대해 정리해보았다.
해결방법
1. 다음의 링크에 접속한다. https://nomomo.github.io/TwitchUniqueIdGenerator/
2. 버튼을 클릭하고 값이 생성되면 아래의 방법을 이용하여 해당 값을 적용한다.
1. 원하는 트위치 페이지 접속 후 F12 키를 눌러서 개발자 도구를 연다.
2. 저장소 또는 Application 탭을 연다. (엣지의 경우 디버거, debugger 탭)
3. 쿠키(Cookies) - twitch.tv 또는 player.twitch.tv 를 찾는다.
4. unique_id 라는 이름의 쿠키를 찾아 값(value)를 더블클릭한 후, 내용을 새롭게 생성된 unique_id 값으로 변경한다.
5. 새로고침 한다.
혹시라도 문제가 생기면 twitch.tv 의 쿠키와 캐시를 초기화하면 될 것이다.
위 방법을 사용하지 않더라도 쿠키 삭제를 반복하다보면 어느 순간 1080p 를 선택할 수 있게 된다. 포츈 쿠키의 확률은 3%로 추정되며, 이 때 10번 시도해서 한 번이라도 당첨될 확률은 26%, 20번 시도는 45%, 50번 시도는 78%, 100번 시도는 95% 이다. 당첨되면 다시 쿠키를 초기화하기 전까지는 계속 유효할 것이다.
아래는 문제의 원인과 해결 과정이므로 관심 있는 사람만 읽자.
스트림의 재생주소를 받아오는 과정
최대 화질이 720p인 재생주소와 1080p인 재생주소를 받아오는 과정에는 어떤 차이가 있을까?
일단 각 스트림의 재생주소를 받아오는 과정부터 이해해야한다.
1. OAuth 토큰 발급받기
일단 로그인하지 않은 경우에는 테스트해보지 않아서 잘 모르겠다.
로그인 한 경우 자연스럽게 로그인 때 발급받은 token이 있을거고 그 토큰 값을 계속 사용한다.
API를 이용해 직접 발급받을 수도 있다. https://dev.twitch.tv/docs/authentication/getting-tokens-oauth/ 의 방법을 그대로 사용하면 된다.
https://id.twitch.tv/oauth2/authorize?client_id=<your client ID>&redirect_uri=<your registered redirect URI>&response_type=code&scope=<space-separated list of scopes>
위 결과의 응답은 아래와 같은 토큰값으로 나온다(예시). 이 토큰 값은 다음 단계에서 사용된다.
5ehuvq4u2iroxdldfepxz3pynic8fq
2. 채널 토큰 발급받기
트위치는 각 채널을 재생하기 전에도 토큰을 발급받아야 한다. 뭐라고 부르는지는 모르겠지만 일단 채널 토큰이라고 부르겠다.
아래와 같은 주소를 요청하여 해당 채널에 대한 토큰을 발급받을 수 있다.
https://api.twitch.tv/api/channels/채널명/access_token?need_https=true&oauth_token=위에서발급받은내로그인토큰값&platform=web&player_backend=mediaplayer&player_type=embed
player_backend 값의 종류로 내가 확인한 것은 mediaplayer, flash 가 있고 player_type 은 embed, site, popout, twitch_everywhere 가 있다. 더 있을 수도 있다.
위 호출의 응답은 아래와 같이 나온다. 이 채널 토큰 값과 sig 값은 다음 단계에서 사용된다.
sig: a3228b133a6e073cf748f1bab1232b9cbf6f6c3e
token: {"adblock":true,"authorization":{"forbidden":false,"reason":""},"blackout_enabled":false,"channel":"채널명","channel_id":채널아이디,"chansub":{"restricted_bitrates":[],"view_until":1924905600},"ci_gb":false,"geoblock_reason":"","device_id":"디바이스id","expires":1557908277,"game":"게임명","hide_ads":false,"https_required":true,"mature":false,"partner":false,"platform":"web","player_type":"site","private":{"allowed_to_view":true},"privileged":false,"server_ads":false,"show_ads":true,"subscriber":false,"turbo":false,"user_id":유저아디,"user_ip":"내아이피","version":2}
3. 채널 토큰을 이용해 마스터 플레이리스트 받기
일단 1080p가 안 되는, embed 플레이어에서 기본적으로 호출하는 경우는 아래와 같이 호출한다.
https://usher.ttvnw.net/api/channel/hls/채널명.m3u8?baking_bread=true&baking_brownies=true&baking_brownies_timeout=1050&fast_bread=true&p=5288583&player_backend=mediaplayer&playlist_include_framerate=true&reassignments_supported=true&rtqos=control&sig=cfbc1b3456f630f82dbcf1406ca7e6a6a8fb35d&token={"adblock":true,"authorization":{"forbidden":false,"reason":""},"blackout_enabled":false,"channel":"채널명","channel_id":채널아이디,"chansub":{"restricted_bitrates":[],"view_until":1924905600},"ci_gb":false,"geoblock_reason":"","device_id":"디바이스id","expires":1557908277,"game":"게임명","hide_ads":false,"https_required":true,"mature":false,"partner":false,"platform":"web","player_type":"site","private":{"allowed_to_view":true},"privileged":false,"server_ads":false,"show_ads":true,"subscriber":false,"turbo":false,"user_id":유저아디,"user_ip":"내아이피","version":2}&preferred_codecs=avc1&cdm=wv
위 호출의 응답으로 각 화질별 플레이리스트를 받아올 수 있는데 720p가 max 이다. 즉 source 가 없다.
참고로 여기서 받아온 플레이리스트는 팟플레이어 등에서 주소 열기(Ctrl+U)로 바로 재생가능한 주소를 포함하고 있다.
1080p 의 플레이리스트가 포함된 경우는 아래와 같이 호출하며, allow_source=true 가 추가된다. 그 외에 다른 인자들도 무슨 역할을 하는 것 같기는 한데, sig 값과 token 만 있으면 일단 호출된다.
https://usher.ttvnw.net/api/channel/hls/채널명.m3u8?allow_source=true&sig=cfbc1b3456f630f82dbcf1406ca7e6a6a8fb35d&token={"adblock":true,"authorization":{"forbidden":false,"reason":""},"blackout_enabled":false,"channel":"채널명","channel_id":채널아이디,"chansub":{"restricted_bitrates":[],"view_until":1924905600},"ci_gb":false,"geoblock_reason":"","device_id":"디바이스id","expires":1557908277,"game":"게임명","hide_ads":false,"https_required":true,"mature":false,"partner":false,"platform":"web","player_type":"site","private":{"allowed_to_view":true},"privileged":false,"server_ads":false,"show_ads":true,"subscriber":false,"turbo":false,"user_id":유저아디,"user_ip":"내아이피","version":2}&preferred_codecs=avc1&cdm=wv
정리
플레이리스트를 호출하는 옵션에 allow_source=true 가 있으면 1080p 에 해당하는 플레이리스트를 받아올 수 있다.
문제의 원인
그럼 저 allow_source=true 가 포함되는 경우와 그렇지 않은 경우는 어떤 차이가 있을까?
2019년 5월 15일 기준, 다음의 링크에서 아래의 내용을 확인할 수 있다. https://player.twitch.tv/js/player.bcb83a3db13d2d2927b0.js
allow_source 로 검색하다보면 아래와 같은 코드를 볼 수 있다.
allowCostSaving 이라는 변수가 true 이고 지역이 "KR" 인 경우, 플레이리스트를 호출하는 url 에서 "allow_source=true&" 라는 문자열을 "" 로 바꾸도록 하는 코드이다. 변수 이름에서 추정하자면 비용 절약을 위해서 지역이 한국인 경우 의도적으로 1080p 재생을 막도록 한 것으로 추정된다. VPN을 사용하면 정상적으로 1080p가 노출된다는 점에서 지역제한이 걸려있다고 추정은 했지만 코드에 이렇게 적혀있을 줄은 몰랐다.
하지만 VPN을 사용하지 않아도 1080p가 노출되는 유저가 분명 존재하고 있다.
그 이유는 위에서 allowCostSaving 값이 false 인 경우 1080p 가 노출될 것이기 때문이다.
allowCostSaving 값은 아래와 같은 메커니즘으로 결정된다.
- 각 유저가 최초 접속 시 deviceId 라는 값이 부여된다.
예를 들어, 내 deviceId 값이 XXXXXXXXSPorwMCUlYQQWhFCjMKXbUB 라고 하자. (중복될까봐 일부분을 가림) - deviceId 값의 hash 값을 Sha1 으로 계산한다.
예제의 경우 hash 값은 25c6566bc334e4e42d00c384ede9fe9dc25a0776 가 된다. - hash 값의 끝 4자리를 자른다.
예제의 경우에는 0776 이 된다. - 자른 문자열을 16진수로 보고 10진수로 변환한다. 이 값은 0~65535 의 값을 가질것이다.
예제의 경우인 0776을 변환하면 1296이 된다. - 위에서 변환된 값을 65536 로 나눈 값을 계산한다. 이 값은 0~1 의 값을 가질 것이다.
예제의 경우 1296 / 65536 = 0.019 이다. - 위에서 계산된 값이 0.03 보다 크면 allowCostSaving 값은 true 가 되어 지역코드가 "KR" 인 경우 source(1080p) 값이 노출되지 않는다.
따라서 예제의 경우에는 0.019 < 0.03 이므로 1080p 가 노출된다.
0~1의 범위를 가지는 임의 값이 0.03 보다 작은 경우에만 노출되므로, 모든 유저 중 약 3%는 1080p 를 볼 수 있도록 되어있다.
저 deviceId 라는 값은 임의의 값으로 생성되어 unique_id 라는 이름의 쿠키에 저장되고, 쿠키가 지워지기 전까지는 해당 값을 계속 이용한다. 쿠키를 계속 초기화하다보면 되는 이유가 바로 3% 가챠에 걸렸기 때문이다.
그래서 쿠키를 지울거라면 다 지울 필요 없이 unique_id 라는 이름의 쿠키만 지워주면 된다.
테스트를 해보니 쿠키를 지울 필요 없이, 위 방식으로 계산된 값이 0.03 보다 작도록 직접 unique_id 값을 직접 만들어서 갈아치우면 정상적으로 1080p 옵션이 노출되는 것을 확인했다. 그래서 임의로 32자리 문자열을 생성하는 것을 계산된 값이 0.03 미만이 될 때까지 반복하도록 만들었다. 이렇게 만든 것이 맨 위의 해결 방법에 있는 링크이다.
※ 만약 아무 작업을 하지 않았는데도 1080p 옵션이 잘 보인다면 3% 가챠에 당첨되었거나, 브라우저가 오프라인 캐시로부터 이전 player를 불러오고 있어서 일 수 있다. 내가 이런 경우인지 확인을 원한다면 브라우저 캐시와 쿠키를 초기화해주면 된다. 그러면 97%의 확률로 최대 720p만 노출될 것이다.