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