diff --git a/lib/API/musify.dart b/lib/API/musify.dart index 1b3fe153..2cf0d0a5 100644 --- a/lib/API/musify.dart +++ b/lib/API/musify.dart @@ -35,6 +35,7 @@ import 'package:musify/services/lyrics_manager.dart'; import 'package:musify/services/settings_manager.dart'; import 'package:musify/utilities/flutter_toast.dart'; import 'package:musify/utilities/formatter.dart'; +import 'package:musify/utilities/parsing_helper.dart'; import 'package:path_provider/path_provider.dart'; import 'package:youtube_explode_dart/youtube_explode_dart.dart'; @@ -63,11 +64,7 @@ Map activePlaylist = { 'list': [], }; -List userChosenClients = [ - YoutubeApiClient.tv, - YoutubeApiClient.androidVr, - YoutubeApiClient.safari, -]; +List userChosenClients = []; dynamic nextRecommendedSong; @@ -635,7 +632,7 @@ Future getSongManifest(String songId) async { try { final manifest = await _yt.videos.streams.getManifest( songId, - ytClients: userChosenClients, + ytClients: userChosenClients.isEmpty ? [iosNew] : userChosenClients, ); final audioStream = manifest.audioOnly.withHighestBitrate(); return audioStream; diff --git a/lib/utilities/parsing_helper.dart b/lib/utilities/parsing_helper.dart new file mode 100644 index 00000000..2a7132d9 --- /dev/null +++ b/lib/utilities/parsing_helper.dart @@ -0,0 +1,106 @@ +import 'dart:convert'; +import 'dart:math'; + +import 'package:youtube_explode_dart/youtube_explode_dart.dart'; + +var _visitorData = YoutubeParsingHelper.getRandomVisitorData(); + +class YoutubeParsingHelper { + static const String _contentPlaybackNonceAlphabet = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; + + static String getRandomVisitorData() { + final random = Random(); + final pbE2 = ProtoBuilder() + ..string(2, '') + ..varint(4, random.nextInt(255) + 1); + + final pbE = ProtoBuilder() + ..string(1, 'US') + ..bytes(2, pbE2.toBytes()); + + final pb = ProtoBuilder() + ..string( + 1, + _generateRandomStringFromAlphabet( + _contentPlaybackNonceAlphabet, + 11, + random, + ), + ) + ..varint( + 5, + DateTime.now().millisecondsSinceEpoch ~/ 1000 - random.nextInt(600000), + ) + ..bytes(6, pbE.toBytes()); + return pb.toUrlencodedBase64(); + } + + static String _generateRandomStringFromAlphabet( + String alphabet, + int length, + Random random, + ) { + final sb = StringBuffer(); + for (var i = 0; i < length; i++) { + sb.write(alphabet[random.nextInt(alphabet.length)]); + } + return sb.toString(); + } +} + +class ProtoBuilder { + final Map _fields = {}; + + void string(int fieldNumber, String value) { + _fields[fieldNumber] = value; + } + + void varint(int fieldNumber, int value) { + _fields[fieldNumber] = value; + } + + void bytes(int fieldNumber, List value) { + _fields[fieldNumber] = value; + } + + List toBytes() { + final bytes = []; + _fields.forEach((key, value) { + if (value is String) { + bytes.addAll(utf8.encode(value)); + } else if (value is int) { + bytes.add(value); + } else if (value is List) { + bytes.addAll(value); + } + }); + return bytes; + } + + String toUrlencodedBase64() { + final bytes = toBytes(); + return base64Url.encode(bytes); + } +} + +var iosNew = YoutubeApiClient( + { + 'context': { + 'client': { + 'clientName': 'IOS', + 'clientVersion': '19.45.4', + 'deviceMake': 'Apple', + 'deviceModel': 'iPhone16,2', + 'hl': 'en', + 'platform': 'MOBILE', + 'osName': 'IOS', + 'osVersion': '18.1.0.22B83', + 'visitorData': _visitorData, + }, + 'gl': 'US', + 'utcOffsetMinutes': 0, + }, + }, + 'https://www.youtube.com/youtubei/v1/player?key=AIzaSyB-63vPrdThhKuerbB2N_l7Kwwcxj6yUAc&prettyPrint=false', +);