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

232 lines
6.6 KiB
Dart
Raw Normal View History

2020-05-31 23:36:23 +02:00
import 'dart:convert';
import 'package:http_parser/http_parser.dart';
2020-06-05 16:17:08 +02:00
import '../../extensions/helpers_extension.dart';
import 'stream_info_provider.dart';
2020-05-31 23:36:23 +02:00
class PlayerResponse {
// Json parsed map
final Map<String, dynamic> _root;
2020-06-22 17:40:57 +02:00
String _playerabilityStatus;
bool _isVideoAvailable;
bool _isVideoPlayable;
String _videoTitle;
String _videoAuthor;
DateTime _videoUploadDate;
String _videoChannelId;
Duration _videoDuration;
Iterable<String> _videoKeywords;
String _videoDescription;
int _videoViewCount;
String _previewVideoId;
bool _isLive;
String _hlsManifestUrl;
String _dashManifestUrl;
Iterable<StreamInfoProvider> _muxedStreams;
Iterable<StreamInfoProvider> _adaptiveStreams;
List<StreamInfoProvider> _streams;
Iterable<ClosedCaptionTrack> _closedCaptionTrack;
String _videoPlayabilityError;
String get playabilityStatus =>
_playerabilityStatus ??= _root['playabilityStatus']['status'];
bool get isVideoAvailable =>
_isVideoAvailable ??= playabilityStatus.toLowerCase() != 'error';
bool get isVideoPlayable =>
_isVideoAvailable ??= playabilityStatus.toLowerCase() == 'ok';
String get videoTitle => _videoTitle ??= _root['videoDetails']['title'];
String get videoAuthor => _videoAuthor ??= _root['videoDetails']['author'];
DateTime get videoUploadDate => _videoUploadDate ??= DateTime.parse(
2020-06-03 23:02:21 +02:00
_root['microformat']['playerMicroformatRenderer']['uploadDate']);
2020-05-31 23:36:23 +02:00
2020-06-22 17:40:57 +02:00
String get videoChannelId =>
_videoChannelId ??= _root['videoDetails']['channelId'];
2020-05-31 23:36:23 +02:00
2020-06-22 17:40:57 +02:00
Duration get videoDuration => _videoDuration ??=
2020-06-03 23:02:21 +02:00
Duration(seconds: int.parse(_root['videoDetails']['lengthSeconds']));
2020-05-31 23:36:23 +02:00
2020-06-22 17:40:57 +02:00
Iterable<String> get videoKeywords => _videoKeywords ??=
2020-06-05 16:17:08 +02:00
_root['videoDetails']['keywords']?.cast<String>() ?? const [];
2020-05-31 23:36:23 +02:00
2020-06-22 17:40:57 +02:00
String get videoDescription =>
_videoDescription ??= _root['videoDetails']['shortDescription'];
2020-05-31 23:36:23 +02:00
2020-06-22 17:40:57 +02:00
int get videoViewCount =>
_videoViewCount ??= int.parse(_root['videoDetails']['viewCount']);
2020-05-31 23:36:23 +02:00
// Can be null
2020-06-22 17:40:57 +02:00
String get previewVideoId => _previewVideoId ??= _root
2020-05-31 23:36:23 +02:00
.get('playabilityStatus')
?.get('errorScreen')
?.get('playerLegacyDesktopYpcTrailerRenderer')
2020-06-05 16:17:08 +02:00
?.getValue('trailerVideoId') ??
2020-05-31 23:36:23 +02:00
Uri.splitQueryString(_root
.get('playabilityStatus')
?.get('errorScreen')
?.get('')
?.get('ypcTrailerRenderer')
2020-06-05 16:17:08 +02:00
?.getValue('playerVars') ??
2020-05-31 23:36:23 +02:00
'')['video_id'];
2020-06-22 17:40:57 +02:00
bool get isLive =>
_isLive ??= _root.get('videoDetails')?.getValue('isLive') ?? false;
2020-05-31 23:36:23 +02:00
2020-06-03 13:18:37 +02:00
// Can be null
2020-06-22 17:40:57 +02:00
String get hlsManifestUrl => _hlsManifestUrl ??=
2020-06-05 16:17:08 +02:00
_root.get('streamingData')?.getValue('hlsManifestUrl');
2020-06-03 13:18:37 +02:00
2020-05-31 23:36:23 +02:00
// Can be null
2020-06-22 17:40:57 +02:00
String get dashManifestUrl => _dashManifestUrl ??=
2020-06-05 16:17:08 +02:00
_root.get('streamingData')?.getValue('dashManifestUrl');
2020-05-31 23:36:23 +02:00
2020-06-22 17:40:57 +02:00
Iterable<StreamInfoProvider> get muxedStreams => _muxedStreams ??= _root
2020-06-05 16:17:08 +02:00
?.get('streamingData')
?.getValue('formats')
?.map((e) => _StreamInfo(e))
?.cast<StreamInfoProvider>() ??
const <StreamInfoProvider>[];
2020-05-31 23:36:23 +02:00
2020-06-22 17:40:57 +02:00
Iterable<StreamInfoProvider> get adaptiveStreams => _adaptiveStreams ??= _root
2020-05-31 23:36:23 +02:00
?.get('streamingData')
2020-06-05 16:17:08 +02:00
?.getValue('adaptiveFormats')
?.map((e) => _StreamInfo(e))
?.cast<StreamInfoProvider>() ??
const <StreamInfoProvider>[];
2020-05-31 23:36:23 +02:00
2020-06-22 17:40:57 +02:00
List<StreamInfoProvider> get streams =>
_streams ??= [...muxedStreams, ...adaptiveStreams];
2020-05-31 23:36:23 +02:00
Iterable<ClosedCaptionTrack> get closedCaptionTrack =>
2020-06-22 17:40:57 +02:00
_closedCaptionTrack ??= _root
.get('captions')
?.get('playerCaptionsTracklistRenderer')
?.getValue('captionTracks')
?.map((e) => ClosedCaptionTrack(e))
?.cast<ClosedCaptionTrack>() ??
const [];
PlayerResponse(this._root);
String getVideoPlayabilityError() => _videoPlayabilityError ??=
2020-06-05 16:17:08 +02:00
_root.get('playabilityStatus')?.getValue('reason');
2020-06-03 13:18:37 +02:00
2020-05-31 23:36:23 +02:00
PlayerResponse.parse(String raw) : _root = json.decode(raw);
}
class ClosedCaptionTrack {
// Json parsed map
final Map<String, dynamic> _root;
2020-06-22 17:40:57 +02:00
String _url;
String _languageCode;
String _languageName;
bool _autoGenerated;
2020-05-31 23:36:23 +02:00
2020-06-22 17:40:57 +02:00
String get url => _url ??= _root['baseUrl'];
2020-05-31 23:36:23 +02:00
2020-06-22 17:40:57 +02:00
String get languageCode => _languageCode ??= _root['languageCode'];
2020-06-03 23:02:21 +02:00
2020-06-22 17:40:57 +02:00
String get languageName => _languageName ??= _root['name']['simpleText'];
2020-05-31 23:36:23 +02:00
2020-06-22 17:40:57 +02:00
bool get autoGenerated =>
_autoGenerated ??= _root['vssId'].toLowerCase().startsWith("a.");
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+)');
2020-05-31 23:36:23 +02:00
// Json parsed map
final Map<String, dynamic> _root;
2020-06-22 17:40:57 +02:00
int _bitrate;
String _container;
int _contentLength;
int _framerate;
String _signature;
String _signatureParameter;
int _tag;
String _url;
2020-05-31 23:36:23 +02:00
@override
2020-06-22 17:40:57 +02:00
int get bitrate => _bitrate ??= _root['bitrate'];
2020-05-31 23:36:23 +02:00
@override
2020-06-22 17:40:57 +02:00
String get container => _container ??= mimeType.subtype;
2020-06-05 16:17:08 +02:00
2020-05-31 23:36:23 +02:00
@override
int get contentLength =>
2020-06-22 17:40:57 +02:00
_contentLength ??= int.tryParse(_root['contentLength'] ?? '') ??
_contentLenExp.firstMatch(url)?.group(1);
2020-05-31 23:36:23 +02:00
@override
2020-06-22 17:40:57 +02:00
int get framerate => _framerate ??= _root['fps'];
2020-05-31 23:36:23 +02:00
@override
2020-06-05 16:17:08 +02:00
String get signature =>
2020-06-22 17:40:57 +02:00
_signature ??= Uri.splitQueryString(_root['signatureCipher'] ?? '')['s'];
2020-05-31 23:36:23 +02:00
@override
2020-06-22 17:40:57 +02:00
String get signatureParameter => _signatureParameter ??=
2020-06-05 16:17:08 +02:00
Uri.splitQueryString(_root['cipher'] ?? '')['sp'] ??
2020-06-22 17:40:57 +02:00
Uri.splitQueryString(_root['signatureCipher'] ?? '')['sp'];
2020-05-31 23:36:23 +02:00
@override
2020-06-22 17:40:57 +02:00
int get tag => _tag ??= _root['itag'];
2020-05-31 23:36:23 +02:00
@override
2020-06-22 17:40:57 +02:00
String get url => _url ??= _getUrl();
2020-06-05 16:17:08 +02:00
String _getUrl() {
var url = _root['url'];
url ??= Uri.splitQueryString(_root['cipher'] ?? '')['url'];
url ??= Uri.splitQueryString(_root['signatureCipher'] ?? '')['url'];
return url;
}
2020-05-31 23:36:23 +02:00
2020-06-22 17:40:57 +02:00
bool _isAudioOnly;
MediaType _mimeType;
String _codecs;
2020-05-31 23:36:23 +02:00
@override
2020-06-05 16:17:08 +02:00
String get videoCodec =>
isAudioOnly ? null : codecs.split(',').first.trim().nullIfWhitespace;
2020-05-31 23:36:23 +02:00
@override
int get videoHeight => _root['height'];
@override
String get videoQualityLabel => _root['qualityLabel'];
@override
int get videoWidth => _root['width'];
2020-06-22 17:40:57 +02:00
bool get isAudioOnly => _isAudioOnly ??= mimeType.type == 'audio';
2020-05-31 23:36:23 +02:00
2020-06-22 17:40:57 +02:00
MediaType get mimeType => _mimeType ??= MediaType.parse(_root['mimeType']);
2020-05-31 23:36:23 +02:00
2020-06-22 17:40:57 +02:00
String get codecs =>
_codecs ??= mimeType?.parameters['codecs']?.toLowerCase();
2020-05-31 23:36:23 +02:00
@override
2020-06-05 16:17:08 +02:00
String get audioCodec =>
2020-06-05 20:08:04 +02:00
isAudioOnly ? codecs : _getAudioCodec(codecs.split(','))?.trim();
String _getAudioCodec(List<String> codecs) {
if (codecs.length == 1) {
return null;
}
return codecs.last;
}
2020-06-22 17:40:57 +02:00
_StreamInfo(this._root);
2020-05-31 23:36:23 +02:00
}