youtube_explode/lib/src/reverse_engineering/player/player_response.dart

252 lines
6.3 KiB
Dart
Raw Normal View History

2020-05-31 23:36:23 +02:00
import 'dart:convert';
2021-03-11 14:20:10 +01:00
import 'package:collection/collection.dart';
2020-05-31 23:36:23 +02:00
import 'package:http_parser/http_parser.dart';
2020-06-05 16:17:08 +02:00
import '../../extensions/helpers_extension.dart';
2021-07-21 02:06:02 +02:00
import '../models/stream_info_provider.dart';
2020-05-31 23:36:23 +02:00
2020-07-16 20:02:54 +02:00
///
2020-05-31 23:36:23 +02:00
class PlayerResponse {
2021-03-11 14:20:10 +01:00
// Json parsed map
2021-07-21 02:06:02 +02:00
JsonMap root;
2020-06-22 17:40:57 +02:00
2020-07-16 20:02:54 +02:00
///
2021-03-11 14:20:10 +01:00
late final String playabilityStatus =
root.get('playabilityStatus')!.getT<String>('status')!;
2020-06-22 17:40:57 +02:00
2020-07-16 20:02:54 +02:00
///
2020-06-23 10:12:08 +02:00
bool get isVideoAvailable => playabilityStatus.toLowerCase() != 'error';
2020-06-22 17:40:57 +02:00
2020-07-16 20:02:54 +02:00
///
2020-06-23 10:12:08 +02:00
bool get isVideoPlayable => playabilityStatus.toLowerCase() == 'ok';
2020-06-22 17:40:57 +02:00
2020-07-16 20:02:54 +02:00
///
2021-03-11 14:20:10 +01:00
String get videoTitle => root.get('videoDetails')!.getT<String>('title')!;
2020-06-22 17:40:57 +02:00
2020-07-16 20:02:54 +02:00
///
2021-03-11 14:20:10 +01:00
String get videoAuthor => root.get('videoDetails')!.getT<String>('author')!;
2020-06-22 17:40:57 +02:00
2020-07-16 20:02:54 +02:00
///
2021-06-18 17:14:55 +02:00
DateTime? get videoUploadDate => root
.get('microformat')
?.get('playerMicroformatRenderer')
?.getT<String>('uploadDate')
?.parseDateTime();
///
DateTime? get videoPublishDate => root
.get('microformat')
?.get('playerMicroformatRenderer')
?.getT<String>('publishDate')
?.parseDateTime();
2021-03-24 06:17:49 +01:00
2020-07-16 20:02:54 +02:00
///
2021-03-11 14:20:10 +01:00
String get videoChannelId =>
root.get('videoDetails')!.getT<String>('channelId')!;
2020-05-31 23:36:23 +02:00
2020-07-16 20:02:54 +02:00
///
2021-03-11 14:20:10 +01:00
Duration get videoDuration => Duration(
seconds:
int.parse(root.get('videoDetails')!.getT<String>('lengthSeconds')!));
2020-05-31 23:36:23 +02:00
2020-07-16 20:02:54 +02:00
///
2021-03-11 14:20:10 +01:00
List<String> get videoKeywords =>
root
.get('videoDetails')
?.getT<List<dynamic>>('keywords')
?.cast<String>() ??
const [];
2020-05-31 23:36:23 +02:00
2020-07-16 20:02:54 +02:00
///
2021-03-11 14:20:10 +01:00
String get videoDescription =>
root.get('videoDetails')!.getT<String>('shortDescription')!;
2020-05-31 23:36:23 +02:00
2020-07-16 20:02:54 +02:00
///
2021-03-11 14:20:10 +01:00
int get videoViewCount =>
int.parse(root.get('videoDetails')!.getT<String>('viewCount')!);
2020-05-31 23:36:23 +02:00
2020-07-16 20:02:54 +02:00
///
2021-03-11 14:20:10 +01:00
String? get previewVideoId =>
root
2020-05-31 23:36:23 +02:00
.get('playabilityStatus')
?.get('errorScreen')
?.get('playerLegacyDesktopYpcTrailerRenderer')
2021-06-24 15:23:35 +02:00
?.getT('trailerVideoId') ??
2021-03-11 14:20:10 +01:00
Uri.splitQueryString(root
2020-05-31 23:36:23 +02:00
.get('playabilityStatus')
?.get('errorScreen')
?.get('')
?.get('ypcTrailerRenderer')
2021-06-24 15:23:35 +02:00
?.getT('playerVars') ??
2020-05-31 23:36:23 +02:00
'')['video_id'];
2020-07-16 20:02:54 +02:00
///
2021-03-11 14:20:10 +01:00
bool get isLive => root.get('videoDetails')?.getT<bool>('isLive') ?? false;
2020-05-31 23:36:23 +02:00
2020-07-16 20:02:54 +02:00
///
2021-03-11 14:20:10 +01:00
String? get hlsManifestUrl =>
root.get('streamingData')?.getT<String>('hlsManifestUrl');
2020-06-03 13:18:37 +02:00
2020-07-16 20:02:54 +02:00
///
2021-03-11 14:20:10 +01:00
String? get dashManifestUrl =>
root.get('streamingData')?.getT<String>('dashManifestUrl');
2020-05-31 23:36:23 +02:00
2020-07-16 20:02:54 +02:00
///
2021-03-11 14:20:10 +01:00
late final List<StreamInfoProvider> muxedStreams = root
.get('streamingData')
?.getList('formats')
?.map((e) => _StreamInfo(e, StreamSource.muxed))
2021-03-11 14:20:10 +01:00
.cast<StreamInfoProvider>()
.toList() ??
const <StreamInfoProvider>[];
2020-05-31 23:36:23 +02:00
2020-07-16 20:02:54 +02:00
///
2021-03-11 14:20:10 +01:00
late final List<StreamInfoProvider> adaptiveStreams = root
.get('streamingData')
?.getList('adaptiveFormats')
?.map((e) => _StreamInfo(e, StreamSource.adaptive))
2021-03-11 14:20:10 +01:00
.cast<StreamInfoProvider>()
.toList() ??
const [];
2020-05-31 23:36:23 +02:00
2020-07-16 20:02:54 +02:00
///
2021-03-11 14:20:10 +01:00
late final List<StreamInfoProvider> streams = [
...muxedStreams,
...adaptiveStreams
];
2020-05-31 23:36:23 +02:00
2020-07-16 20:02:54 +02:00
///
2021-03-11 14:20:10 +01:00
late final List<ClosedCaptionTrack> closedCaptionTrack = root
.get('captions')
?.get('playerCaptionsTracklistRenderer')
?.getList('captionTracks')
?.map((e) => ClosedCaptionTrack(e))
.cast<ClosedCaptionTrack>()
.toList() ??
const [];
2020-06-22 17:40:57 +02:00
2021-03-11 14:20:10 +01:00
///
late final String? videoPlayabilityError =
root.get('playabilityStatus')?.getT<String>('reason');
2020-06-03 13:18:37 +02:00
PlayerResponse(this.root);
2020-07-16 20:02:54 +02:00
///
2021-03-11 14:20:10 +01:00
PlayerResponse.parse(String raw) : root = json.decode(raw);
2020-05-31 23:36:23 +02:00
}
2020-07-16 20:02:54 +02:00
///
2020-05-31 23:36:23 +02:00
class ClosedCaptionTrack {
// Json parsed class
2021-07-21 02:06:02 +02:00
final JsonMap root;
2020-05-31 23:36:23 +02:00
2020-07-16 20:02:54 +02:00
///
2021-03-11 14:20:10 +01:00
String get url => root.getT<String>('baseUrl')!;
2020-05-31 23:36:23 +02:00
2020-07-16 20:02:54 +02:00
///
2021-03-11 14:20:10 +01:00
String get languageCode => root.getT<String>('languageCode')!;
2020-05-31 23:36:23 +02:00
2020-07-16 20:02:54 +02:00
///
String? get languageName => root.get('name')!.getT<String>('simpleText');
2020-06-03 23:02:21 +02:00
2020-07-16 20:02:54 +02:00
///
2021-03-11 14:20:10 +01:00
bool get autoGenerated =>
root.getT<String>('vssId')!.toLowerCase().startsWith('a.');
2020-06-22 17:40:57 +02:00
2020-07-16 20:02:54 +02:00
///
2021-03-11 14:20:10 +01:00
ClosedCaptionTrack(this.root);
2020-05-31 23:36:23 +02:00
}
class _StreamInfo extends StreamInfoProvider {
2020-06-22 17:40:57 +02:00
static final _contentLenExp = RegExp(r'[\?&]clen=(\d+)');
2021-03-11 14:20:10 +01:00
/// Json parsed map
2021-07-21 02:06:02 +02:00
final JsonMap root;
2020-05-31 23:36:23 +02:00
@override
2021-03-11 14:20:10 +01:00
late final int? bitrate = root.getT<int>('bitrate');
2020-05-31 23:36:23 +02:00
@override
2021-10-04 13:00:22 +02:00
late final String container = codec.subtype;
2020-06-05 16:17:08 +02:00
2020-05-31 23:36:23 +02:00
@override
2021-03-11 14:20:10 +01:00
late final int? contentLength = int.tryParse(
root.getT<String>('contentLength') ??
_contentLenExp.firstMatch(url)?.group(1) ??
'');
2020-05-31 23:36:23 +02:00
@override
2021-03-11 14:20:10 +01:00
late final int? framerate = root.getT<int>('fps');
2020-05-31 23:36:23 +02:00
@override
2021-03-11 14:20:10 +01:00
late final String? signature =
Uri.splitQueryString(root.getT<String>('signatureCipher') ?? '')['s'];
2020-05-31 23:36:23 +02:00
@override
2021-03-11 14:20:10 +01:00
late final String? signatureParameter = Uri.splitQueryString(
root.getT<String>('cipher') ?? '')['sp'] ??
Uri.splitQueryString(root.getT<String>('signatureCipher') ?? '')['sp'];
2020-05-31 23:36:23 +02:00
@override
2021-03-11 14:20:10 +01:00
late final int tag = root.getT<int>('itag')!;
2020-05-31 23:36:23 +02:00
@override
2021-03-11 14:20:10 +01:00
late final String url = root.getT<String>('url') ??
Uri.splitQueryString(root.getT<String>('cipher') ?? '')['url'] ??
Uri.splitQueryString(root.getT<String>('signatureCipher') ?? '')['url']!;
2020-06-22 17:40:57 +02:00
2020-05-31 23:36:23 +02:00
@override
2021-03-11 14:20:10 +01:00
late final String? videoCodec = isAudioOnly
? null
: codecs?.split(',').firstOrNull?.trim().nullIfWhitespace;
2020-05-31 23:36:23 +02:00
@override
2021-03-11 14:20:10 +01:00
late final int? videoHeight = root.getT<int>('height');
2020-05-31 23:36:23 +02:00
@override
2021-10-04 13:00:22 +02:00
@Deprecated('Use qualityLabel')
String get videoQualityLabel => qualityLabel;
@override
late final String qualityLabel = root.getT<String>('qualityLabel') ??
'tiny'; // Not sure if 'tiny' is the correct placeholder.
2020-05-31 23:36:23 +02:00
@override
2021-03-11 14:20:10 +01:00
late final int? videoWidth = root.getT<int>('width');
2020-05-31 23:36:23 +02:00
2021-10-04 13:00:22 +02:00
late final bool isAudioOnly = codec.type == 'audio';
2020-05-31 23:36:23 +02:00
2021-10-04 13:00:22 +02:00
@override
late final MediaType codec = _getMimeType()!;
2020-05-31 23:36:23 +02:00
2021-03-11 14:20:10 +01:00
MediaType? _getMimeType() {
var mime = root.getT<String>('mimeType');
if (mime == null) {
return null;
}
return MediaType.parse(mime);
}
2021-10-04 13:00:22 +02:00
late final String? codecs = codec.parameters['codecs']?.toLowerCase();
2020-05-31 23:36:23 +02:00
@override
2021-03-11 14:20:10 +01:00
late final String? audioCodec =
isAudioOnly ? codecs : _getAudioCodec(codecs?.split(','))?.trim();
2020-06-05 20:08:04 +02:00
2021-03-11 14:20:10 +01:00
String? _getAudioCodec(List<String>? codecs) {
if (codecs == null) {
return null;
}
2020-06-05 20:08:04 +02:00
if (codecs.length == 1) {
return null;
}
return codecs.last;
}
2020-06-22 17:40:57 +02:00
@override
final StreamSource source;
_StreamInfo(this.root, this.source);
2020-05-31 23:36:23 +02:00
}