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))
|
|
|
|
.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))
|
|
|
|
.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
|
|
|
|
2021-03-18 22:22:34 +01: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 {
|
2020-09-11 13:42:21 +02:00
|
|
|
// 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
|
|
|
///
|
2021-07-23 12:54:29 +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-03-11 14:20:10 +01:00
|
|
|
late final String? container = mimeType?.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-03-11 14:20:10 +01:00
|
|
|
late final String? videoQualityLabel = root.getT<String>('qualityLabel');
|
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-03-11 14:20:10 +01:00
|
|
|
late final bool isAudioOnly = mimeType?.type == 'audio';
|
2020-05-31 23:36:23 +02:00
|
|
|
|
2021-03-11 14:20:10 +01:00
|
|
|
late final MediaType? mimeType = _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);
|
|
|
|
}
|
|
|
|
|
|
|
|
late final String? codecs = mimeType?.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
|
|
|
|
2021-03-11 14:20:10 +01:00
|
|
|
_StreamInfo(this.root);
|
2020-05-31 23:36:23 +02:00
|
|
|
}
|