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

233 lines
5.8 KiB
Dart

import 'dart:convert';
import 'package:http_parser/http_parser.dart';
import 'package:youtube_explode_dart/src/reverse_engineering/responses/generated/player_response.g.dart';
import '../../extensions/helpers_extension.dart';
import 'stream_info_provider.dart';
///
class PlayerResponse {
// Json parsed class
PlayerResponseJson _root;
/// Json parsed map
final Map<String, dynamic> _rawJson;
Iterable<StreamInfoProvider> _muxedStreams;
Iterable<StreamInfoProvider> _adaptiveStreams;
List<StreamInfoProvider> _streams;
Iterable<ClosedCaptionTrack> _closedCaptionTrack;
String _videoPlayabilityError;
///
String get playabilityStatus => _root.playabilityStatus.status;
///
bool get isVideoAvailable => playabilityStatus.toLowerCase() != 'error';
///
bool get isVideoPlayable => playabilityStatus.toLowerCase() == 'ok';
///
String get videoTitle => _root.videoDetails.title;
///
String get videoAuthor => _root.videoDetails.author;
///
DateTime get videoUploadDate =>
_root.microformat.playerMicroformatRenderer.uploadDate;
///
String get videoChannelId => _root.videoDetails.channelId;
///
Duration get videoDuration =>
Duration(seconds: int.parse(_root.videoDetails.lengthSeconds));
///
List<String> get videoKeywords => _root.videoDetails.keywords ?? const [];
///
String get videoDescription => _root.videoDetails.shortDescription;
///
int get videoViewCount => int.parse(_root.videoDetails.viewCount);
//TODO: Get these types
///
// Can be null
String get previewVideoId =>
_rawJson
.get('playabilityStatus')
?.get('errorScreen')
?.get('playerLegacyDesktopYpcTrailerRenderer')
?.getValue('trailerVideoId') ??
Uri.splitQueryString(_rawJson
.get('playabilityStatus')
?.get('errorScreen')
?.get('')
?.get('ypcTrailerRenderer')
?.getValue('playerVars') ??
'')['video_id'];
///
bool get isLive => _root.videoDetails.isLive ?? false;
///
// Can be null
String get hlsManifestUrl => _root.streamingData?.hlsManifestUrl;
///
// Can be null
String get dashManifestUrl => _root.streamingData?.dashManifestUrl;
///
List<StreamInfoProvider> get muxedStreams =>
_muxedStreams ??= _root.streamingData?.formats
?.map((e) => _StreamInfo(e))
?.cast<StreamInfoProvider>()
?.toList() ??
const <StreamInfoProvider>[];
///
List<StreamInfoProvider> get adaptiveStreams =>
_adaptiveStreams ??= _root.streamingData?.adaptiveFormats
?.map((e) => _StreamInfo(e))
?.cast<StreamInfoProvider>()
?.toList() ??
const [];
///
List<StreamInfoProvider> get streams =>
_streams ??= [...muxedStreams, ...adaptiveStreams];
///
List<ClosedCaptionTrack> get closedCaptionTrack => _closedCaptionTrack ??=
_root.captions?.playerCaptionsTracklistRenderer?.captionTracks
?.map((e) => ClosedCaptionTrack(e))
?.cast<ClosedCaptionTrack>()
?.toList() ??
const [];
/// Can be null
String getVideoPlayabilityError() =>
_videoPlayabilityError ??= _root.playabilityStatus.reason;
///
PlayerResponse.parse(String raw) : _rawJson = json.decode(raw) {
_root = PlayerResponseJson.fromJson(_rawJson);
}
}
///
class ClosedCaptionTrack {
// Json parsed class
final CaptionTrack _root;
///
String get url => _root.baseUrl;
///
String get languageCode => _root.languageCode;
///
String get languageName => _root.name.simpleText;
///
bool get autoGenerated => _root.vssId.toLowerCase().startsWith('a.');
///
ClosedCaptionTrack(this._root);
}
class _StreamInfo extends StreamInfoProvider {
static final _contentLenExp = RegExp(r'[\?&]clen=(\d+)');
// Json parsed class
final Format _root;
int _bitrate;
String _container;
int _contentLength;
int _framerate;
String _signature;
String _signatureParameter;
int _tag;
String _url;
@override
int get bitrate => _bitrate ??= _root.bitrate;
@override
String get container => _container ??= mimeType.subtype;
@override
int get contentLength =>
_contentLength ??= int.tryParse(_root.contentLength ?? '') ??
_contentLenExp.firstMatch(url)?.group(1);
@override
int get framerate => _framerate ??= _root.fps;
@override
String get signature =>
_signature ??= Uri.splitQueryString(_root.signatureCipher ?? '')['s'];
@override
String get signatureParameter =>
_signatureParameter ??= Uri.splitQueryString(_root.cipher ?? '')['sp'] ??
Uri.splitQueryString(_root.signatureCipher ?? '')['sp'];
@override
int get tag => _tag ??= _root.itag;
@override
String get url => _url ??= _getUrl();
String _getUrl() {
var url = _root.url;
url ??= Uri.splitQueryString(_root.cipher ?? '')['url'];
url ??= Uri.splitQueryString(_root.signatureCipher ?? '')['url'];
return url;
}
bool _isAudioOnly;
MediaType _mimeType;
String _codecs;
@override
String get videoCodec =>
isAudioOnly ? null : codecs.split(',').first.trim().nullIfWhitespace;
@override
int get videoHeight => _root.height;
@override
String get videoQualityLabel => _root.qualityLabel;
@override
int get videoWidth => _root.width;
bool get isAudioOnly => _isAudioOnly ??= mimeType.type == 'audio';
MediaType get mimeType => _mimeType ??= MediaType.parse(_root.mimeType);
String get codecs =>
_codecs ??= mimeType?.parameters['codecs']?.toLowerCase();
@override
String get audioCodec =>
isAudioOnly ? codecs : _getAudioCodec(codecs.split(','))?.trim();
String _getAudioCodec(List<String> codecs) {
if (codecs.length == 1) {
return null;
}
return codecs.last;
}
_StreamInfo(this._root);
}