Skip to content

Commit 162733b

Browse files
committed
Clean up after last PR
1 parent 84e29bb commit 162733b

File tree

3 files changed

+49
-164
lines changed

3 files changed

+49
-164
lines changed

YoutubeExplode/Bridge/VisitorData.cs

Lines changed: 0 additions & 92 deletions
This file was deleted.

YoutubeExplode/Utils/ProtoBuilder.cs

Lines changed: 0 additions & 71 deletions
This file was deleted.

YoutubeExplode/Videos/VideoController.cs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,59 @@
11
using System.Net.Http;
2+
using System.Net.Http.Headers;
23
using System.Threading;
34
using System.Threading.Tasks;
45
using YoutubeExplode.Bridge;
56
using YoutubeExplode.Exceptions;
67
using YoutubeExplode.Utils;
8+
using YoutubeExplode.Utils.Extensions;
79

810
namespace YoutubeExplode.Videos;
911

1012
internal class VideoController(HttpClient http)
1113
{
14+
private string? _visitorData;
15+
1216
protected HttpClient Http { get; } = http;
1317

18+
private async ValueTask<string> ResolveVisitorDataAsync(
19+
CancellationToken cancellationToken = default
20+
)
21+
{
22+
if (!string.IsNullOrWhiteSpace(_visitorData))
23+
return _visitorData;
24+
25+
using var request = new HttpRequestMessage(
26+
HttpMethod.Get,
27+
"https://www.youtube.com/sw.js_data"
28+
);
29+
30+
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
31+
32+
request.Headers.Add(
33+
"User-Agent",
34+
"com.google.ios.youtube/19.45.4 (iPhone16,2; U; CPU iOS 18_1_0 like Mac OS X; US)"
35+
);
36+
37+
using var response = await Http.SendAsync(request, cancellationToken);
38+
response.EnsureSuccessStatusCode();
39+
40+
// TODO: move this to a bridge wrapper
41+
var jsonString = await response.Content.ReadAsStringAsync(cancellationToken);
42+
if (jsonString.StartsWith(")]}'"))
43+
jsonString = jsonString[4..];
44+
45+
var json = Json.Parse(jsonString);
46+
47+
// This is just an ordered (but unstructured) blob of data
48+
var value = json[0][2][0][0][13].GetStringOrNull();
49+
if (string.IsNullOrWhiteSpace(value))
50+
{
51+
throw new YoutubeExplodeException("Failed to resolve visitor data.");
52+
}
53+
54+
return _visitorData = value;
55+
}
56+
1457
public async ValueTask<VideoWatchPage> GetVideoWatchPageAsync(
1558
VideoId videoId,
1659
CancellationToken cancellationToken = default
@@ -47,6 +90,8 @@ public async ValueTask<PlayerResponse> GetPlayerResponseAsync(
4790
CancellationToken cancellationToken = default
4891
)
4992
{
93+
var visitorData = await ResolveVisitorDataAsync(cancellationToken);
94+
5095
// The most optimal client to impersonate is any mobile client, because they
5196
// don't require signature deciphering (for both normal and n-parameter signatures).
5297
// However, we can't use the ANDROID client because it has a limitation, preventing it
@@ -76,7 +121,7 @@ public async ValueTask<PlayerResponse> GetPlayerResponseAsync(
76121
"platform": "MOBILE",
77122
"osName": "IOS",
78123
"osVersion": "18.1.0.22B83",
79-
"visitorData": {{Json.Serialize(await VisitorData.ExtractFromYoutube(Http))}},
124+
"visitorData": {{Json.Serialize(visitorData)}},
80125
"hl": "en",
81126
"gl": "US",
82127
"utcOffsetMinutes": 0
@@ -112,6 +157,8 @@ public async ValueTask<PlayerResponse> GetPlayerResponseAsync(
112157
CancellationToken cancellationToken = default
113158
)
114159
{
160+
var visitorData = await ResolveVisitorDataAsync(cancellationToken);
161+
115162
// The only client that can handle age-restricted videos without authentication is the
116163
// TVHTML5_SIMPLY_EMBEDDED_PLAYER client.
117164
// This client does require signature deciphering, so we only use it as a fallback.
@@ -129,6 +176,7 @@ public async ValueTask<PlayerResponse> GetPlayerResponseAsync(
129176
"client": {
130177
"clientName": "TVHTML5_SIMPLY_EMBEDDED_PLAYER",
131178
"clientVersion": "2.0",
179+
"visitorData": {{Json.Serialize(visitorData)}},
132180
"hl": "en",
133181
"gl": "US",
134182
"utcOffsetMinutes": 0

0 commit comments

Comments
 (0)