-
-
Notifications
You must be signed in to change notification settings - Fork 147
/
Copy pathvideo_controller.dart
113 lines (103 loc) · 3.73 KB
/
video_controller.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import 'package:meta/meta.dart';
import '../../youtube_explode_dart.dart';
import '../reverse_engineering/pages/watch_page.dart';
import '../reverse_engineering/player/player_response.dart';
@internal
class VideoController {
/// Used to fetch streams without signature deciphering, but has limited streams.
static const androidTestSuiteClient = {
'context': {
'client': {
'clientName': 'ANDROID_TESTSUITE',
'clientVersion': '1.9',
'androidSdkVersion': 30,
'hl': 'en',
'gl': 'US',
'utcOffsetMinutes': 0,
},
},
};
/// Used to to fetch all the video streams (but has signature deciphering).
static const androidClient = {
'context': {
'client': {
'clientName': 'ANDROID',
'clientVersion': '19.09.37',
'androidSdkVersion': 30,
'userAgent':
'com.google.android.youtube/19.09.37 (Linux; U; Android 11) gzip',
'hl': 'en',
'timeZone': 'UTC',
'utcOffsetMinutes': 0,
},
},
};
/// Used to fetch streams for age-restricted videos without authentication.
static const tvClient = {
'context': {
'client': {
'clientName': 'TVHTML5_SIMPLY_EMBEDDED_PLAYER',
'clientVersion': '2.0',
'hl': 'en',
'gl': 'US',
'utcOffsetMinutes': 0,
},
'thirdParty': {
'embedUrl': 'https://www.youtube.com',
},
},
};
@protected
final YoutubeHttpClient httpClient;
VideoController(this.httpClient);
Future<WatchPage> getVideoWatchPage(VideoId videoId) {
return WatchPage.get(httpClient, videoId.value);
}
Future<PlayerResponse> getPlayerResponse(VideoId videoId,
Map<String, Map<String, Map<String, Object>>> client) async {
assert(client['context'] != null, 'client must contain a context');
assert(client['context']!['client'] != null,
'client must contain a context.client');
final userAgent = client['context']!['client']!['userAgent'] as String?;
// From https://github.com/Tyrrrz/YoutubeExplode:
// The most optimal client to impersonate is the Android client, because
// it doesn't require signature deciphering (for both normal and n-parameter signatures).
// However, the regular Android client has a limitation, preventing it from downloading
// multiple streams from the same manifest (or the same stream multiple times).
// As a workaround, we're using ANDROID_TESTSUITE which appears to offer the same
// functionality, but doesn't impose the aforementioned limitation.
// https://github.com/Tyrrrz/YoutubeExplode/issues/705
final content = await httpClient.postString(
'https://www.youtube.com/youtubei/v1/player?key=AIzaSyA8eiZmM1FaDVjRy-df2KTyQ_vz_yYM39w&prettyPrint=false',
body: {
...client,
'videoId': videoId.value,
},
headers: {
if (userAgent != null) 'User-Agent': userAgent,
},
);
return PlayerResponse.parse(content);
}
Future<PlayerResponse> getPlayerResponseWithSignature(
VideoId videoId,
String? signatureTimestamp,
) async {
/// The only client that can handle age-restricted videos without authentication is the
/// TVHTML5_SIMPLY_EMBEDDED_PLAYER client.
/// This client does require signature deciphering, so we only use it as a fallback.
final content = await httpClient.postString(
'https://www.youtube.com/youtubei/v1/player?key=AIzaSyA8eiZmM1FaDVjRy-df2KTyQ_vz_yYM39w&prettyPrint=false',
body: {
...tvClient,
'videoId': videoId.value,
'playbackContext': {
'contentPlaybackContext': {
'signatureTimestamp': signatureTimestamp ?? '19369',
},
},
},
);
return PlayerResponse.parse(content);
}
}