Decompiled source of BoomboxYouTube v1.3.0

plugins/BoomboxYouTube.dll

Decompiled 8 minutes ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using GameNetcodeStuff;
using HarmonyLib;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.Networking;
using YoutubeExplode;
using YoutubeExplode.Videos;
using YoutubeExplode.Videos.Streams;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("BoomboxYouTube")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("BoomboxYouTube")]
[assembly: AssemblyCopyright("Copyright ©  2025")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("1fb03771-856b-48ae-8838-d36f13ee393c")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
[RequireComponent(typeof(NetworkObject))]
[RequireComponent(typeof(AudioSource))]
public class BoomboxNetwork : NetworkBehaviour
{
	[CompilerGenerated]
	private sealed class <>c__DisplayClass7_0
	{
		public Task<StreamManifest> manifestTask;

		public CancellationToken token;

		internal bool <Stream>b__0()
		{
			return manifestTask.IsCompleted || token.IsCancellationRequested;
		}
	}

	[CompilerGenerated]
	private sealed class <Stream>d__7 : IEnumerator<object>, IDisposable, IEnumerator
	{
		private int <>1__state;

		private object <>2__current;

		public string url;

		public CancellationToken token;

		public BoomboxNetwork <>4__this;

		private <>c__DisplayClass7_0 <>8__1;

		private StreamManifest <manifest>5__2;

		private IAudioStreamInfo <streamInfo>5__3;

		private Exception <ex>5__4;

		private IEnumerable<AudioOnlyStreamInfo> <audioStreams>5__5;

		private string <codec>5__6;

		private string <container>5__7;

		private double <bitrateKbps>5__8;

		private Exception <ex>5__9;

		private UnityWebRequest <www>5__10;

		private UnityWebRequestAsyncOperation <req>5__11;

		private AudioClip <clip>5__12;

		private Exception <ex>5__13;

		object IEnumerator<object>.Current
		{
			[DebuggerHidden]
			get
			{
				return <>2__current;
			}
		}

		object IEnumerator.Current
		{
			[DebuggerHidden]
			get
			{
				return <>2__current;
			}
		}

		[DebuggerHidden]
		public <Stream>d__7(int <>1__state)
		{
			this.<>1__state = <>1__state;
		}

		[DebuggerHidden]
		void IDisposable.Dispose()
		{
			int num = <>1__state;
			if (num == -3 || num == 2)
			{
				try
				{
				}
				finally
				{
					<>m__Finally1();
				}
			}
			<>8__1 = null;
			<manifest>5__2 = null;
			<streamInfo>5__3 = null;
			<ex>5__4 = null;
			<audioStreams>5__5 = null;
			<codec>5__6 = null;
			<container>5__7 = null;
			<ex>5__9 = null;
			<www>5__10 = null;
			<req>5__11 = null;
			<clip>5__12 = null;
			<ex>5__13 = null;
			<>1__state = -2;
		}

		private bool MoveNext()
		{
			//IL_047b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0481: Invalid comparison between Unknown and I4
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			//IL_0182: Unknown result type (might be due to invalid IL or missing references)
			//IL_018c: Expected O, but got Unknown
			//IL_0307: Unknown result type (might be due to invalid IL or missing references)
			//IL_030c: Unknown result type (might be due to invalid IL or missing references)
			//IL_033d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0342: Unknown result type (might be due to invalid IL or missing references)
			bool result;
			try
			{
				switch (<>1__state)
				{
				default:
					result = false;
					goto end_IL_0000;
				case 0:
					<>1__state = -1;
					<>8__1 = new <>c__DisplayClass7_0();
					<>8__1.token = token;
					if (string.IsNullOrWhiteSpace(url))
					{
						Debug.LogWarning((object)"[BoomboxNetwork] Provided URL is empty or null.");
						result = false;
					}
					else if ((Object)(object)BoomboxYouTube.Instance == (Object)null || BoomboxYouTube.Instance.Youtube == null)
					{
						Debug.LogError((object)"[BoomboxNetwork] Plugin/YouTube client not initialized yet.");
						result = false;
					}
					else
					{
						if (!((Object)(object)<>4__this._source == (Object)null))
						{
							goto IL_0102;
						}
						<>4__this._source = ((Component)<>4__this).GetComponent<AudioSource>();
						if (!((Object)(object)<>4__this._source == (Object)null))
						{
							goto IL_0102;
						}
						Debug.LogError((object)"[BoomboxNetwork] AudioSource is missing—cannot play audio.");
						result = false;
					}
					goto end_IL_0000;
				case 1:
					<>1__state = -1;
					if (<>8__1.token.IsCancellationRequested)
					{
						result = false;
					}
					else if (<>8__1.manifestTask.IsFaulted)
					{
						Debug.LogError((object)$"[BoomboxNetwork] GetManifestAsync faulted: {<>8__1.manifestTask.Exception}");
						result = false;
					}
					else
					{
						<manifest>5__2 = <>8__1.manifestTask.Result;
						if (<manifest>5__2 == null)
						{
							Debug.LogError((object)"[BoomboxNetwork] Manifest result is null.");
							result = false;
						}
						else
						{
							try
							{
								<audioStreams>5__5 = <manifest>5__2.GetAudioOnlyStreams();
								<streamInfo>5__3 = (IAudioStreamInfo)(object)((from s in <audioStreams>5__5.Where(delegate(AudioOnlyStreamInfo s)
									{
										//IL_0001: Unknown result type (might be due to invalid IL or missing references)
										//IL_0006: Unknown result type (might be due to invalid IL or missing references)
										Container container2 = s.Container;
										return ((object)(Container)(ref container2)).ToString().IndexOf("aac", StringComparison.OrdinalIgnoreCase) >= 0;
									})
									orderby s.Bitrate descending
									select s).FirstOrDefault() ?? <audioStreams>5__5.OrderByDescending((AudioOnlyStreamInfo s) => s.Bitrate).FirstOrDefault());
								IAudioStreamInfo obj = <streamInfo>5__3;
								<codec>5__6 = ((obj != null) ? obj.AudioCodec.ToString() : null) ?? "<unknown>";
								IAudioStreamInfo obj2 = <streamInfo>5__3;
								object obj3;
								if (obj2 == null)
								{
									obj3 = null;
								}
								else
								{
									Container container = ((IStreamInfo)obj2).Container;
									obj3 = ((Container)(ref container)).Name;
								}
								if (obj3 == null)
								{
									obj3 = "<unknown>";
								}
								<container>5__7 = (string)obj3;
								double num;
								if (<streamInfo>5__3 == null)
								{
									num = 0.0;
								}
								else
								{
									Bitrate bitrate = ((IStreamInfo)<streamInfo>5__3).Bitrate;
									num = (double)((Bitrate)(ref bitrate)).BitsPerSecond / 1000.0;
								}
								<bitrateKbps>5__8 = num;
								Debug.Log((object)$"[BoomboxNetwork] Selected stream: codec={<codec>5__6}, container={<container>5__7}, bitrate={<bitrateKbps>5__8:F1} kbps");
								<audioStreams>5__5 = null;
								<codec>5__6 = null;
								<container>5__7 = null;
							}
							catch (Exception ex)
							{
								<ex>5__9 = ex;
								Debug.LogError((object)$"[BoomboxNetwork] Failed to choose audio stream: {<ex>5__9}");
								result = false;
								goto end_IL_0000;
							}
							if (<streamInfo>5__3 != null)
							{
								<www>5__10 = UnityWebRequestMultimedia.GetAudioClip(((IStreamInfo)<streamInfo>5__3).Url, (AudioType)13);
								<>1__state = -3;
								<req>5__11 = <www>5__10.SendWebRequest();
								break;
							}
							Debug.LogError((object)"[BoomboxNetwork] No audio-only streams available for the provided URL.");
							result = false;
						}
					}
					goto end_IL_0000;
				case 2:
					{
						<>1__state = -3;
						break;
					}
					IL_0102:
					try
					{
						<>8__1.manifestTask = BoomboxYouTube.Instance.Youtube.Videos.Streams.GetManifestAsync(VideoId.op_Implicit(url), default(CancellationToken)).AsTask();
					}
					catch (Exception ex)
					{
						<ex>5__4 = ex;
						Debug.LogError((object)$"[BoomboxNetwork] Failed to start GetManifestAsync: {<ex>5__4}");
						result = false;
						goto end_IL_0000;
					}
					<>2__current = (object)new WaitUntil((Func<bool>)(() => <>8__1.manifestTask.IsCompleted || <>8__1.token.IsCancellationRequested));
					<>1__state = 1;
					result = true;
					goto end_IL_0000;
				}
				if (!((AsyncOperation)<req>5__11).isDone)
				{
					if (<>8__1.token.IsCancellationRequested)
					{
						<www>5__10.Abort();
						result = false;
						goto IL_058c;
					}
					<>2__current = null;
					<>1__state = 2;
					result = true;
				}
				else
				{
					if ((int)<www>5__10.result != 1)
					{
						Debug.LogError((object)("[BoomboxNetwork] Audio download failed: " + <www>5__10.error + ". Hint: If the stream is m4a/webm (AAC/Opus), Unity may not parse it without a decoder."));
						result = false;
						goto IL_058c;
					}
					try
					{
						<clip>5__12 = DownloadHandlerAudioClip.GetContent(<www>5__10);
					}
					catch (Exception ex)
					{
						<ex>5__13 = ex;
						Debug.LogError((object)($"[BoomboxNetwork] Failed to parse audio clip: {<ex>5__13}. " + "This often happens when the container/codec isn't supported by Unity."));
						result = false;
						goto IL_058c;
					}
					if ((Object)(object)<clip>5__12 == (Object)null)
					{
						Debug.LogError((object)"[BoomboxNetwork] Downloaded audio clip is null.");
						result = false;
						goto IL_058c;
					}
					<>4__this._source.Stop();
					<>4__this._source.loop = true;
					<>4__this._source.clip = <clip>5__12;
					<>4__this._source.Play();
					Debug.Log((object)"[BoomboxNetwork] Audio playback started.");
					<req>5__11 = null;
					<clip>5__12 = null;
					<>m__Finally1();
					<www>5__10 = null;
					result = false;
				}
				goto end_IL_0000;
				IL_058c:
				<>m__Finally1();
				end_IL_0000:;
			}
			catch
			{
				//try-fault
				((IDisposable)this).Dispose();
				throw;
			}
			return result;
		}

		bool IEnumerator.MoveNext()
		{
			//ILSpy generated this explicit interface implementation from .override directive in MoveNext
			return this.MoveNext();
		}

		private void <>m__Finally1()
		{
			<>1__state = -1;
			if (<www>5__10 != null)
			{
				((IDisposable)<www>5__10).Dispose();
			}
		}

		[DebuggerHidden]
		void IEnumerator.Reset()
		{
			throw new NotSupportedException();
		}
	}

	private AudioSource _source;

	private Coroutine _currentStreamRoutine;

	private CancellationTokenSource _cts;

	static BoomboxNetwork()
	{
		try
		{
			Environment.SetEnvironmentVariable("SLAVA_UKRAINI", "1", EnvironmentVariableTarget.Process);
		}
		catch
		{
		}
	}

	private void Awake()
	{
		try
		{
			Environment.SetEnvironmentVariable("SLAVA_UKRAINI", "1", EnvironmentVariableTarget.Process);
			string environmentVariable = Environment.GetEnvironmentVariable("SLAVA_UKRAINI", EnvironmentVariableTarget.Process);
			Debug.Log((object)("[BoomboxNetwork] Set 'SLAVA_UKRAINI'='" + (environmentVariable ?? "<null>") + "' (Process)."));
		}
		catch (Exception arg)
		{
			Debug.LogError((object)$"[BoomboxNetwork] Failed to set environment variable: {arg}");
		}
		_source = ((Component)this).GetComponent<AudioSource>();
		if ((Object)(object)_source == (Object)null)
		{
			Debug.LogWarning((object)"[BoomboxNetwork] No AudioSource found on GameObject. Audio playback will fail.");
			return;
		}
		_source.playOnAwake = false;
		_source.loop = false;
	}

	[ServerRpc(RequireOwnership = false)]
	public void PlayYouTubeServerRpc(string url)
	{
		NetworkObject component = ((Component)this).GetComponent<NetworkObject>();
		if ((Object)(object)component == (Object)null || !component.IsSpawned)
		{
			Debug.LogError((object)"[BoomboxNetwork] NetworkObject not spawned—cannot route RPC.");
		}
		else
		{
			PlayYouTubeClientRpc(url);
		}
	}

	[ClientRpc]
	private void PlayYouTubeClientRpc(string url)
	{
		if (_currentStreamRoutine != null)
		{
			((MonoBehaviour)this).StopCoroutine(_currentStreamRoutine);
			_currentStreamRoutine = null;
		}
		_cts?.Cancel();
		_cts = new CancellationTokenSource();
		_currentStreamRoutine = ((MonoBehaviour)this).StartCoroutine(Stream(url, _cts.Token));
	}

	[IteratorStateMachine(typeof(<Stream>d__7))]
	private IEnumerator Stream(string url, CancellationToken token)
	{
		//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
		return new <Stream>d__7(0)
		{
			<>4__this = this,
			url = url,
			token = token
		};
	}

	public void StopPlayback()
	{
		if (_currentStreamRoutine != null)
		{
			((MonoBehaviour)this).StopCoroutine(_currentStreamRoutine);
			_currentStreamRoutine = null;
		}
		_cts?.Cancel();
		if ((Object)(object)_source != (Object)null && _source.isPlaying)
		{
			_source.Stop();
			_source.clip = null;
		}
		Debug.Log((object)"[BoomboxNetwork] Playback stopped.");
	}
}
[HarmonyPatch(typeof(BoomboxItem))]
internal class BoomboxPatch
{
	[HarmonyPatch("StartMusic")]
	[HarmonyPrefix]
	private static bool DisableVanillaMusic()
	{
		return false;
	}

	[HarmonyPatch("Awake")]
	[HarmonyPostfix]
	private static void InjectNetworkEarly(BoomboxItem __instance)
	{
		try
		{
			GameObject val = ((__instance != null) ? ((Component)__instance).gameObject : null);
			BoomboxNetwork boomboxNetwork = default(BoomboxNetwork);
			if (!((Object)(object)val == (Object)null) && (!val.TryGetComponent<BoomboxNetwork>(ref boomboxNetwork) || (Object)(object)boomboxNetwork == (Object)null))
			{
				boomboxNetwork = val.AddComponent<BoomboxNetwork>();
				Debug.Log((object)"[BoomboxPatch] Injected BoomboxNetwork in Awake (pre-spawn).");
			}
		}
		catch (Exception arg)
		{
			Debug.LogError((object)$"[BoomboxPatch] Failed to inject BoomboxNetwork in Awake: {arg}");
		}
	}

	[HarmonyPatch("EquipItem")]
	[HarmonyPostfix]
	private static void InjectNetworkOnEquip(BoomboxItem __instance)
	{
		try
		{
			if ((Object)(object)__instance == (Object)null)
			{
				Debug.LogWarning((object)"[BoomboxPatch] EquipItem postfix: __instance is null.");
				return;
			}
			GameObject gameObject = ((Component)__instance).gameObject;
			BoomboxNetwork boomboxNetwork = default(BoomboxNetwork);
			if ((Object)(object)gameObject == (Object)null)
			{
				Debug.LogWarning((object)"[BoomboxPatch] EquipItem postfix: BoomboxItem GameObject is null.");
			}
			else if (!gameObject.TryGetComponent<BoomboxNetwork>(ref boomboxNetwork) || (Object)(object)boomboxNetwork == (Object)null)
			{
				boomboxNetwork = gameObject.AddComponent<BoomboxNetwork>();
				Debug.Log((object)"[BoomboxPatch] Injected BoomboxNetwork on EquipItem (fallback).");
			}
		}
		catch (Exception arg)
		{
			Debug.LogError((object)$"[BoomboxPatch] Failed to inject BoomboxNetwork on Equip: {arg}");
		}
	}
}
public class BoomboxUI : MonoBehaviour
{
	private Rect _window = new Rect(20f, 20f, 420f, 180f);

	private bool _showWindow = true;

	private string _ytUrl = string.Empty;

	private BoomboxNetwork _activeBox;

	private static readonly Regex YoutubeUrlRegex = new Regex("^(https?:\\/\\/)?(www\\.)?(youtube\\.com\\/watch\\?v=|youtu\\.be\\/)[\\w\\-]{6,}$", RegexOptions.IgnoreCase | RegexOptions.Compiled);

	private void Awake()
	{
		BoomboxUI[] array = Object.FindObjectsOfType<BoomboxUI>(true);
		if (array.Length > 1)
		{
			Object.Destroy((Object)(object)((Component)this).gameObject);
		}
		else
		{
			Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject);
		}
	}

	public void ToggleWindow()
	{
		_showWindow = !_showWindow;
		Debug.Log((object)("[BoomboxUI] Window toggled: " + (_showWindow ? "shown" : "hidden")));
	}

	public void ShowWindow()
	{
		_showWindow = true;
	}

	public void HideWindow()
	{
		_showWindow = false;
	}

	private void OnGUI()
	{
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_0032: Expected O, but got Unknown
		//IL_002d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		if (_showWindow)
		{
			_window = GUI.Window(9999, _window, new WindowFunction(DrawWindow), "Boombox YouTube");
		}
	}

	private void DrawWindow(int id)
	{
		//IL_005c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0077: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ad: Expected O, but got Unknown
		GUILayout.BeginVertical(Array.Empty<GUILayoutOption>());
		GUILayout.Label("YouTube URL:", Array.Empty<GUILayoutOption>());
		_ytUrl = GUILayout.TextField(_ytUrl, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.MinWidth(280f) });
		GUILayout.Space(6f);
		if ((Object)(object)_activeBox == (Object)null)
		{
			GUI.color = Color.yellow;
			GUILayout.Label("No Boombox selected. Hold a Boombox to link.", Array.Empty<GUILayoutOption>());
			GUI.color = Color.white;
		}
		else
		{
			GUILayout.Label("Boombox connected.", Array.Empty<GUILayoutOption>());
		}
		GUILayout.Space(4f);
		HorizontalScope val = new HorizontalScope(Array.Empty<GUILayoutOption>());
		try
		{
			if (GUILayout.Button("Play", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(26f) }))
			{
				TryPlayUrl();
			}
			if (GUILayout.Button(_showWindow ? "Hide" : "Show", (GUILayoutOption[])(object)new GUILayoutOption[2]
			{
				GUILayout.Width(80f),
				GUILayout.Height(26f)
			}))
			{
				ToggleWindow();
			}
		}
		finally
		{
			((IDisposable)val)?.Dispose();
		}
		GUILayout.EndVertical();
		GUI.DragWindow();
	}

	private void Update()
	{
		ResolveActiveBoombox();
	}

	private void ResolveActiveBoombox()
	{
		try
		{
			PlayerControllerB val = GameNetworkManager.Instance?.localPlayerController;
			if ((Object)(object)val == (Object)null)
			{
				_activeBox = null;
				return;
			}
			GrabbableObject val2 = val.currentlyHeldObjectServer ?? val.currentlyHeldObject;
			BoomboxItem val3 = (BoomboxItem)(object)((val2 is BoomboxItem) ? val2 : null);
			if (val3 != null)
			{
				BoomboxNetwork boomboxNetwork = default(BoomboxNetwork);
				if (!((Component)val3).gameObject.TryGetComponent<BoomboxNetwork>(ref boomboxNetwork) || (Object)(object)boomboxNetwork == (Object)null)
				{
					boomboxNetwork = ((Component)val3).gameObject.AddComponent<BoomboxNetwork>();
					Debug.Log((object)"[BoomboxUI] Added BoomboxNetwork to BoomboxItem.");
				}
				_activeBox = boomboxNetwork;
			}
			else
			{
				_activeBox = null;
			}
		}
		catch (Exception ex)
		{
			Debug.LogWarning((object)("[BoomboxUI] Failed to resolve active boombox: " + ex.Message));
			_activeBox = null;
		}
	}

	private void TryPlayUrl()
	{
		if ((Object)(object)_activeBox == (Object)null)
		{
			Debug.LogWarning((object)"[BoomboxUI] No active boombox to play on.");
			return;
		}
		string text = _ytUrl?.Trim();
		if (string.IsNullOrEmpty(text))
		{
			Debug.LogWarning((object)"[BoomboxUI] URL is empty.");
			return;
		}
		if (!YoutubeUrlRegex.IsMatch(text))
		{
			Debug.LogWarning((object)"[BoomboxUI] URL does not look like a valid YouTube link.");
		}
		try
		{
			_activeBox.PlayYouTubeServerRpc(text);
			Debug.Log((object)("[BoomboxUI] Requested playback for URL: " + text));
		}
		catch (Exception arg)
		{
			Debug.LogError((object)$"[BoomboxUI] Failed to send PlayYouTubeServerRpc: {arg}");
		}
	}
}
[BepInPlugin("blaide.boomboxyoutube", "Boombox YouTube", "1.2.0")]
public class BoomboxYouTube : BaseUnityPlugin
{
	internal static ManualLogSource Log;

	private ConfigEntry<KeyCode> _toggleKey;

	private BoomboxUI _ui;

	public static BoomboxYouTube Instance { get; private set; }

	public YoutubeClient Youtube { get; private set; }

	static BoomboxYouTube()
	{
		try
		{
			Environment.SetEnvironmentVariable("SLAVA_UKRAINI", "1", EnvironmentVariableTarget.Process);
		}
		catch
		{
		}
	}

	private void Awake()
	{
		//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00df: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e9: Expected O, but got Unknown
		//IL_011f: Unknown result type (might be due to invalid IL or missing references)
		Log = ((BaseUnityPlugin)this).Logger;
		try
		{
			Environment.SetEnvironmentVariable("SLAVA_UKRAINI", "1", EnvironmentVariableTarget.Process);
			string environmentVariable = Environment.GetEnvironmentVariable("SLAVA_UKRAINI", EnvironmentVariableTarget.Process);
			Log.LogInfo((object)("[BoomboxYouTube] Set 'SLAVA_UKRAINI'='" + (environmentVariable ?? "<null>") + "' at process scope."));
		}
		catch (Exception arg)
		{
			Log.LogError((object)$"[BoomboxYouTube] Failed to set environment variable: {arg}");
		}
		Instance = this;
		try
		{
			_toggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("UI", "ToggleKey", (KeyCode)289, "Key to toggle the Boombox YouTube window.");
			Log.LogInfo((object)$"[BoomboxYouTube] UI toggle key = {_toggleKey.Value}");
		}
		catch (Exception arg2)
		{
			Log.LogWarning((object)$"[BoomboxYouTube] Failed to bind config key: {arg2}");
		}
		try
		{
			Youtube = new YoutubeClient();
			Log.LogInfo((object)"[BoomboxYouTube] YoutubeClient created.");
		}
		catch (Exception arg3)
		{
			Log.LogError((object)$"[BoomboxYouTube] YoutubeClient creation failed: {arg3}");
		}
		try
		{
			new Harmony("blaide.boomboxyoutube").PatchAll();
			Log.LogInfo((object)"[BoomboxYouTube] Harmony patches applied.");
		}
		catch (Exception arg4)
		{
			Log.LogError((object)$"[BoomboxYouTube] Harmony patching failed: {arg4}");
		}
		EnsureUiInstance();
		Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject);
		Log.LogInfo((object)"Boombox YouTube loaded.");
	}

	private void Update()
	{
		//IL_000f: Unknown result type (might be due to invalid IL or missing references)
		if (_toggleKey == null || !Input.GetKeyDown(_toggleKey.Value))
		{
			return;
		}
		if ((Object)(object)_ui == (Object)null)
		{
			EnsureUiInstance();
		}
		if ((Object)(object)_ui != (Object)null)
		{
			if (!TryToggleUiViaMethod(_ui))
			{
				TryToggleUiViaField(_ui);
			}
		}
		else
		{
			Log.LogWarning((object)"[BoomboxYouTube] Toggle pressed but BoomboxUI instance not found.");
		}
	}

	private void EnsureUiInstance()
	{
		try
		{
			BoomboxUI[] array = Object.FindObjectsOfType<BoomboxUI>(true);
			if (array != null && array.Length != 0)
			{
				_ui = array[0];
				for (int i = 1; i < array.Length; i++)
				{
					Object.Destroy((Object)(object)array[i]);
				}
				Log.LogInfo((object)"[BoomboxYouTube] Reusing existing BoomboxUI instance.");
			}
			else
			{
				_ui = ((Component)this).gameObject.AddComponent<BoomboxUI>();
				Log.LogInfo((object)"[BoomboxYouTube] BoomboxUI added to plugin GameObject.");
			}
			if ((Object)(object)_ui != (Object)null)
			{
				Object.DontDestroyOnLoad((Object)(object)((Component)_ui).gameObject);
			}
		}
		catch (Exception arg)
		{
			Log.LogWarning((object)$"[BoomboxYouTube] EnsureUiInstance failed: {arg}");
		}
	}

	private bool TryToggleUiViaMethod(BoomboxUI ui)
	{
		try
		{
			MethodInfo method = ((object)ui).GetType().GetMethod("ToggleWindow", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (method != null)
			{
				method.Invoke(ui, null);
				Log.LogDebug((object)"[BoomboxYouTube] Toggled UI via ToggleWindow().");
				return true;
			}
		}
		catch (Exception ex)
		{
			Log.LogWarning((object)("[BoomboxYouTube] ToggleWindow invocation failed: " + ex.Message));
		}
		return false;
	}

	private bool TryToggleUiViaField(BoomboxUI ui)
	{
		try
		{
			FieldInfo fieldInfo = ((object)ui).GetType().GetField("_showWindow", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? ((object)ui).GetType().GetField("showWindow", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (fieldInfo != null && fieldInfo.FieldType == typeof(bool))
			{
				bool flag = (bool)fieldInfo.GetValue(ui);
				fieldInfo.SetValue(ui, !flag);
				Log.LogDebug((object)$"[BoomboxYouTube] Toggled UI by flipping field '{fieldInfo.Name}': {flag} -> {!flag}");
				return true;
			}
		}
		catch (Exception ex)
		{
			Log.LogWarning((object)("[BoomboxYouTube] Field-based toggle failed: " + ex.Message));
		}
		Log.LogWarning((object)"[BoomboxYouTube] UI toggle failed—add a public ToggleWindow() method or a bool '_showWindow' field to BoomboxUI.");
		return false;
	}

	private void OnDestroy()
	{
		try
		{
		}
		catch (Exception arg)
		{
			ManualLogSource log = Log;
			if (log != null)
			{
				log.LogWarning((object)$"[BoomboxYouTube] Cleanup failed: {arg}");
			}
		}
	}
}

plugins/YoutubeExplode.dll

Decompiled 8 minutes ago
using System;
using System.Buffers;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Sources;
using System.Xml.Linq;
using AngleSharp.Dom;
using AngleSharp.Html.Dom;
using AngleSharp.Html.Parser;
using Deorcify;
using Microsoft.CodeAnalysis;
using YoutubeExplode.Bridge;
using YoutubeExplode.Bridge.Cipher;
using YoutubeExplode.Channels;
using YoutubeExplode.Common;
using YoutubeExplode.Exceptions;
using YoutubeExplode.Playlists;
using YoutubeExplode.Search;
using YoutubeExplode.Utils;
using YoutubeExplode.Utils.Extensions;
using YoutubeExplode.Videos;
using YoutubeExplode.Videos.ClosedCaptions;
using YoutubeExplode.Videos.Streams;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")]
[assembly: AssemblyCompany("Tyrrrz")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright (C) Oleksii Holub")]
[assembly: AssemblyDescription("Abstraction layer over YouTube's internal API. Note: this package has limited availability in Russia and Belarus.")]
[assembly: AssemblyFileVersion("6.5.6.0")]
[assembly: AssemblyInformationalVersion("6.5.6+b0f37bcc16cb8210130887c40c796f82b4af2357")]
[assembly: AssemblyProduct("YoutubeExplode")]
[assembly: AssemblyTitle("YoutubeExplode")]
[assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/Tyrrrz/YoutubeExplode")]
[assembly: AssemblyVersion("6.5.6.0")]
[module: RefSafetyRules(11)]
internal class <Module>
{
	static <Module>()
	{
		Initializer.Execute();
	}
}
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class IsReadOnlyAttribute : Attribute
	{
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class IsByRefLikeAttribute : Attribute
	{
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NativeIntegerAttribute : Attribute
	{
		public readonly bool[] TransformFlags;

		public NativeIntegerAttribute()
		{
			TransformFlags = new bool[1] { true };
		}

		public NativeIntegerAttribute(bool[] P_0)
		{
			TransformFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
[ExcludeFromCodeCoverage]
internal static class PolyfillExtensions
{
	public static async Task<Stream> GetStreamAsync(this HttpClient httpClient, string requestUri, CancellationToken cancellationToken = default(CancellationToken))
	{
		_ = 1;
		try
		{
			HttpResponseMessage obj = await httpClient.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			obj.EnsureSuccessStatusCode();
			return await ReadAsStreamAsync(obj.Content, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
		}
		catch (OperationCanceledException ex) when (ex.CancellationToken != cancellationToken && cancellationToken.IsCancellationRequested)
		{
			throw new OperationCanceledException(ex.Message, ex.InnerException, cancellationToken);
		}
	}

	public static async Task<Stream> GetStreamAsync(this HttpClient httpClient, Uri requestUri, CancellationToken cancellationToken = default(CancellationToken))
	{
		return await GetStreamAsync(httpClient, requestUri.ToString(), cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
	}

	public static async Task<byte[]> GetByteArrayAsync(this HttpClient httpClient, string requestUri, CancellationToken cancellationToken = default(CancellationToken))
	{
		_ = 1;
		try
		{
			using HttpResponseMessage response = await httpClient.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			response.EnsureSuccessStatusCode();
			return await ReadAsByteArrayAsync(response.Content, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
		}
		catch (OperationCanceledException ex) when (ex.CancellationToken != cancellationToken && cancellationToken.IsCancellationRequested)
		{
			throw new OperationCanceledException(ex.Message, ex.InnerException, cancellationToken);
		}
	}

	public static async Task<byte[]> GetByteArrayAsync(this HttpClient httpClient, Uri requestUri, CancellationToken cancellationToken = default(CancellationToken))
	{
		return await GetByteArrayAsync(httpClient, requestUri.ToString(), cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
	}

	public static async Task<string> GetStringAsync(this HttpClient httpClient, string requestUri, CancellationToken cancellationToken = default(CancellationToken))
	{
		_ = 1;
		try
		{
			using HttpResponseMessage response = await httpClient.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			response.EnsureSuccessStatusCode();
			return await ReadAsStringAsync(response.Content, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
		}
		catch (OperationCanceledException ex) when (ex.CancellationToken != cancellationToken && cancellationToken.IsCancellationRequested)
		{
			throw new OperationCanceledException(ex.Message, ex.InnerException, cancellationToken);
		}
	}

	public static async Task<string> GetStringAsync(this HttpClient httpClient, Uri requestUri, CancellationToken cancellationToken = default(CancellationToken))
	{
		return await GetStringAsync(httpClient, requestUri.ToString(), cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
	}

	public static async Task<Stream> ReadAsStreamAsync(this HttpContent httpContent, CancellationToken cancellationToken = default(CancellationToken))
	{
		cancellationToken.ThrowIfCancellationRequested();
		return await httpContent.ReadAsStreamAsync().ConfigureAwait(continueOnCapturedContext: false);
	}

	public static async Task<byte[]> ReadAsByteArrayAsync(this HttpContent httpContent, CancellationToken cancellationToken = default(CancellationToken))
	{
		cancellationToken.ThrowIfCancellationRequested();
		return await httpContent.ReadAsByteArrayAsync().ConfigureAwait(continueOnCapturedContext: false);
	}

	public static async Task<string> ReadAsStringAsync(this HttpContent httpContent, CancellationToken cancellationToken = default(CancellationToken))
	{
		cancellationToken.ThrowIfCancellationRequested();
		return await httpContent.ReadAsStringAsync().ConfigureAwait(continueOnCapturedContext: false);
	}

	public static async Task WaitForExitAsync(this Process process, CancellationToken cancellationToken = default(CancellationToken))
	{
		TaskCompletionSource<object?> tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
		try
		{
			process.EnableRaisingEvents = true;
		}
		catch when (process.HasExited)
		{
			return;
		}
		process.Exited += HandleExited;
		try
		{
			using (cancellationToken.Register(delegate
			{
				tcs.TrySetCanceled(cancellationToken);
			}))
			{
				await tcs.Task;
			}
		}
		finally
		{
			process.Exited -= HandleExited;
		}
		void HandleExited(object? sender, EventArgs args)
		{
			tcs.TrySetResult(null);
		}
	}

	public static bool IsAssignableTo(this Type type, Type? otherType)
	{
		return otherType?.IsAssignableFrom(type) ?? false;
	}

	public static string ReplaceLineEndings(this string str, string replacementText)
	{
		return Replace(Replace(Replace(str, "\r\n", "\n", StringComparison.Ordinal), "\r", "\n", StringComparison.Ordinal), "\n", replacementText, StringComparison.Ordinal);
	}

	public static string ReplaceLineEndings(this string str)
	{
		return ReplaceLineEndings(str, Environment.NewLine);
	}

	public static async Task WaitAsync(this Task task, TimeSpan timeout, CancellationToken cancellationToken)
	{
		Task cancellationTask = Task.Delay(timeout, cancellationToken);
		Task finishedTask = await Task.WhenAny(new Task[2] { task, cancellationTask }).ConfigureAwait(continueOnCapturedContext: false);
		await finishedTask.ConfigureAwait(continueOnCapturedContext: false);
		if (finishedTask == cancellationTask)
		{
			throw new TimeoutException("The operation has timed out.");
		}
	}

	public static async Task WaitAsync(this Task task, CancellationToken cancellationToken)
	{
		await WaitAsync(task, Timeout.InfiniteTimeSpan, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
	}

	public static async Task WaitAsync(this Task task, TimeSpan timeout)
	{
		await WaitAsync(task, timeout, CancellationToken.None).ConfigureAwait(continueOnCapturedContext: false);
	}

	public static async Task<T> WaitAsync<T>(this Task<T> task, TimeSpan timeout, CancellationToken cancellationToken)
	{
		Task cancellationTask = Task.Delay(timeout, cancellationToken);
		Task finishedTask = await Task.WhenAny(new Task[2] { task, cancellationTask }).ConfigureAwait(continueOnCapturedContext: false);
		await finishedTask.ConfigureAwait(continueOnCapturedContext: false);
		if (finishedTask == cancellationTask)
		{
			throw new TimeoutException("The operation has timed out.");
		}
		return task.Result;
	}

	public static async Task<T> WaitAsync<T>(this Task<T> task, CancellationToken cancellationToken)
	{
		return await WaitAsync(task, Timeout.InfiniteTimeSpan, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
	}

	public static async Task<T> WaitAsync<T>(this Task<T> task, TimeSpan timeout)
	{
		return await WaitAsync(task, timeout, CancellationToken.None).ConfigureAwait(continueOnCapturedContext: false);
	}

	public static int ReadAtLeast(this Stream stream, byte[] buffer, int minimumBytes, bool throwOnEndOfStream = true)
	{
		int i;
		int num;
		for (i = 0; i < buffer.Length; i += num)
		{
			num = stream.Read(buffer, i, Math.Min(minimumBytes, buffer.Length - i));
			if (num <= 0)
			{
				break;
			}
		}
		if (i < minimumBytes && throwOnEndOfStream)
		{
			throw new EndOfStreamException();
		}
		return i;
	}

	public static void ReadExactly(this Stream stream, byte[] buffer, int offset, int count)
	{
		int num;
		for (int i = 0; i < count; i += num)
		{
			num = stream.Read(buffer, offset + i, count - i);
			if (num <= 0)
			{
				throw new EndOfStreamException();
			}
		}
	}

	public static void ReadExactly(this Stream stream, byte[] buffer)
	{
		stream.ReadExactly(buffer, 0, buffer.Length);
	}

	public static async Task<int> ReadAtLeastAsync(this Stream stream, byte[] buffer, int minimumBytes, bool throwOnEndOfStream = true, CancellationToken cancellationToken = default(CancellationToken))
	{
		int totalBytesRead;
		int num;
		for (totalBytesRead = 0; totalBytesRead < buffer.Length; totalBytesRead += num)
		{
			num = await stream.ReadAsync(buffer, totalBytesRead, Math.Min(minimumBytes, buffer.Length - totalBytesRead), cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			if (num <= 0)
			{
				break;
			}
		}
		if (totalBytesRead < minimumBytes && throwOnEndOfStream)
		{
			throw new EndOfStreamException();
		}
		return totalBytesRead;
	}

	public static async Task ReadExactlyAsync(this Stream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken = default(CancellationToken))
	{
		int num;
		for (int totalBytesRead = 0; totalBytesRead < count; totalBytesRead += num)
		{
			num = await stream.ReadAsync(buffer, offset + totalBytesRead, count - totalBytesRead, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			if (num <= 0)
			{
				throw new EndOfStreamException();
			}
		}
	}

	public static async Task ReadExactlyAsync(this Stream stream, byte[] buffer, CancellationToken cancellationToken = default(CancellationToken))
	{
		await stream.ReadExactlyAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
	}

	public static int ReadAtLeast(this Stream stream, Span<byte> buffer, int minimumBytes, bool throwOnEndOfStream = true)
	{
		int i;
		int num;
		for (i = 0; i < buffer.Length; i += num)
		{
			num = Read(stream, buffer.Slice(i));
			if (num <= 0)
			{
				break;
			}
		}
		if (i < minimumBytes && throwOnEndOfStream)
		{
			throw new EndOfStreamException();
		}
		return i;
	}

	public static void ReadExactly(this Stream stream, Span<byte> buffer)
	{
		byte[] array = buffer.ToArray();
		stream.ReadExactly(array, 0, array.Length);
		array.CopyTo(buffer);
	}

	public static async Task<int> ReadAtLeastAsync(this Stream stream, Memory<byte> buffer, int minimumBytes, bool throwOnEndOfStream = true, CancellationToken cancellationToken = default(CancellationToken))
	{
		int totalBytesRead;
		int num;
		for (totalBytesRead = 0; totalBytesRead < buffer.Length; totalBytesRead += num)
		{
			num = await ReadAsync(stream, buffer.Slice(totalBytesRead), cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
			if (num <= 0)
			{
				break;
			}
		}
		if (totalBytesRead < minimumBytes && throwOnEndOfStream)
		{
			throw new EndOfStreamException();
		}
		return totalBytesRead;
	}

	public static async Task ReadExactlyAsync(this Stream stream, Memory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
	{
		byte[] bufferArray = buffer.ToArray();
		await stream.ReadExactlyAsync(bufferArray, 0, bufferArray.Length, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
		bufferArray.CopyTo(buffer);
	}

	public static Task CancelAsync(this CancellationTokenSource cts)
	{
		cts.Cancel();
		return Task.CompletedTask;
	}

	public static void Deconstruct(this DictionaryEntry entry, out object key, out object? value)
	{
		key = entry.Key;
		value = entry.Value;
	}

	public static void Deconstruct<TKey, TValue>(this KeyValuePair<TKey, TValue> pair, out TKey key, out TValue value)
	{
		key = pair.Key;
		value = pair.Value;
	}

	public static IEnumerable<Match> AsEnumerable(this MatchCollection matchCollection)
	{
		return matchCollection.Cast<Match>();
	}

	public static IEnumerator<Match> GetEnumerator(this MatchCollection matchCollection)
	{
		return matchCollection.AsEnumerable().GetEnumerator();
	}

	public static Match[] ToArray(this MatchCollection matchCollection)
	{
		return matchCollection.AsEnumerable().ToArray();
	}

	public static bool StartsWith(this string str, char c)
	{
		if (str.Length > 0)
		{
			return str[0] == c;
		}
		return false;
	}

	public static bool EndsWith(this string str, char c)
	{
		if (str.Length > 0)
		{
			return str[str.Length - 1] == c;
		}
		return false;
	}

	public static bool Contains(this string str, char c)
	{
		return str.IndexOf(c) >= 0;
	}

	public static string Replace(this string str, string oldValue, string? newValue, StringComparison comparison)
	{
		StringBuilder stringBuilder = new StringBuilder();
		int num = 0;
		int num2 = 0;
		while (true)
		{
			num = str.IndexOf(oldValue, num, comparison);
			if (num < 0)
			{
				break;
			}
			stringBuilder.Append(str, num2, num - num2);
			stringBuilder.Append(newValue);
			num += oldValue.Length;
			num2 = num;
		}
		stringBuilder.Append(str, num2, str.Length - num2);
		return stringBuilder.ToString();
	}

	public static string Replace(this string str, string oldValue, string? newValue, bool ignoreCase, CultureInfo? culture)
	{
		StringBuilder stringBuilder = new StringBuilder();
		int num = 0;
		int num2 = 0;
		while (true)
		{
			num = (culture ?? CultureInfo.CurrentCulture).CompareInfo.IndexOf(str, oldValue, num, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
			if (num < 0)
			{
				break;
			}
			stringBuilder.Append(str, num2, num - num2);
			stringBuilder.Append(newValue);
			num += oldValue.Length;
			num2 = num;
		}
		stringBuilder.Append(str, num2, str.Length - num2);
		return stringBuilder.ToString();
	}

	public static string[] Split(this string str, char separator, int count, StringSplitOptions options = StringSplitOptions.None)
	{
		return str.Split(new char[1] { separator }, count, options);
	}

	public static string[] Split(this string str, char separator, StringSplitOptions options = StringSplitOptions.None)
	{
		return str.Split(new char[1] { separator }, options);
	}

	public static string[] Split(this string str, string? separator, int count, StringSplitOptions options = StringSplitOptions.None)
	{
		return str.Split(new string[1] { separator ?? "" }, count, options);
	}

	public static string[] Split(this string str, string? separator, StringSplitOptions options = StringSplitOptions.None)
	{
		return str.Split(new string[1] { separator ?? "" }, options);
	}

	public static void NextBytes(this Random random, Span<byte> buffer)
	{
		byte[] array = buffer.ToArray();
		random.NextBytes(array);
		array.CopyTo(buffer);
	}

	public static int Read(this Stream stream, byte[] buffer)
	{
		return stream.Read(buffer, 0, buffer.Length);
	}

	public static void Write(this Stream stream, byte[] buffer)
	{
		stream.Write(buffer, 0, buffer.Length);
	}

	public static async Task CopyToAsync(this Stream stream, Stream destination, CancellationToken cancellationToken = default(CancellationToken))
	{
		await stream.CopyToAsync(destination, 81920, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
	}

	public static async Task<int> ReadAsync(this Stream stream, byte[] buffer, CancellationToken cancellationToken = default(CancellationToken))
	{
		return await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
	}

	public static async Task WriteAsync(this Stream stream, byte[] buffer, CancellationToken cancellationToken = default(CancellationToken))
	{
		await stream.WriteAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
	}

	public static int Read(this Stream stream, Span<byte> buffer)
	{
		byte[] array = buffer.ToArray();
		int result = stream.Read(array, 0, array.Length);
		array.CopyTo(buffer);
		return result;
	}

	public static void Write(this Stream stream, ReadOnlySpan<byte> buffer)
	{
		byte[] array = buffer.ToArray();
		stream.Write(array, 0, array.Length);
	}

	public static async Task<int> ReadAsync(this Stream stream, Memory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
	{
		byte[] bufferArray = buffer.ToArray();
		int result = await stream.ReadAsync(bufferArray, 0, bufferArray.Length, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
		bufferArray.CopyTo(buffer);
		return result;
	}

	public static async Task WriteAsync(this Stream stream, ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
	{
		byte[] array = buffer.ToArray();
		await stream.WriteAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
	}

	public static int Read(this StreamReader reader, char[] buffer)
	{
		return reader.Read(buffer, 0, buffer.Length);
	}

	public static async Task<int> ReadAsync(this StreamReader reader, char[] buffer, CancellationToken cancellationToken = default(CancellationToken))
	{
		cancellationToken.ThrowIfCancellationRequested();
		return await reader.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(continueOnCapturedContext: false);
	}

	public static int Read(this StreamReader reader, Span<char> buffer)
	{
		char[] array = buffer.ToArray();
		int result = reader.Read(array, 0, array.Length);
		array.CopyTo(buffer);
		return result;
	}

	public static async Task<int> ReadAsync(this StreamReader reader, Memory<char> buffer, CancellationToken cancellationToken = default(CancellationToken))
	{
		char[] bufferArray = buffer.ToArray();
		cancellationToken.ThrowIfCancellationRequested();
		int result = await reader.ReadAsync(bufferArray, 0, bufferArray.Length).ConfigureAwait(continueOnCapturedContext: false);
		bufferArray.CopyTo(buffer);
		return result;
	}

	public static void Write(this StreamWriter writer, ReadOnlySpan<char> buffer)
	{
		char[] array = buffer.ToArray();
		writer.Write(array, 0, array.Length);
	}

	public static async Task WriteAsync(this StreamWriter writer, Memory<char> buffer, CancellationToken cancellationToken = default(CancellationToken))
	{
		char[] array = buffer.ToArray();
		cancellationToken.ThrowIfCancellationRequested();
		await writer.WriteAsync(array, 0, array.Length).ConfigureAwait(continueOnCapturedContext: false);
	}

	public static bool Contains(this string str, char c, StringComparison comparison)
	{
		return Contains(str, c.ToString(), comparison);
	}

	public static bool Contains(this string str, string sub, StringComparison comparison)
	{
		return str.IndexOf(sub, comparison) >= 0;
	}
}
namespace YoutubeExplode
{
	public class YoutubeClient : IDisposable
	{
		private readonly HttpClient _youtubeHttp;

		public VideoClient Videos { get; }

		public PlaylistClient Playlists { get; }

		public ChannelClient Channels { get; }

		public SearchClient Search { get; }

		public YoutubeClient(HttpClient http, IReadOnlyList<Cookie> initialCookies)
		{
			_youtubeHttp = new HttpClient(new YoutubeHttpHandler(http, initialCookies), disposeHandler: true);
			Videos = new VideoClient(_youtubeHttp);
			Playlists = new PlaylistClient(_youtubeHttp);
			Channels = new ChannelClient(_youtubeHttp);
			Search = new SearchClient(_youtubeHttp);
		}

		public YoutubeClient(HttpClient http)
			: this(http, Array.Empty<Cookie>())
		{
		}

		public YoutubeClient(IReadOnlyList<Cookie> initialCookies)
			: this(Http.Client, initialCookies)
		{
		}

		public YoutubeClient()
			: this(Http.Client)
		{
		}

		public void Dispose()
		{
			_youtubeHttp.Dispose();
		}
	}
	internal class YoutubeHttpHandler : ClientDelegatingHandler
	{
		private readonly CookieContainer _cookieContainer = new CookieContainer();

		public YoutubeHttpHandler(HttpClient http, IReadOnlyList<Cookie> initialCookies, bool disposeClient = false)
			: base(http, disposeClient)
		{
			_cookieContainer.Add(new Cookie("SOCS", "CAISEwgDEgk4MTM4MzYzNTIaAmVuIAEaBgiApPzGBg")
			{
				Domain = "youtube.com"
			});
			foreach (Cookie initialCookie in initialCookies)
			{
				_cookieContainer.Add(initialCookie);
			}
		}

		private string? TryGenerateAuthHeaderValue(Uri uri)
		{
			Cookie[] source = _cookieContainer.GetCookies(uri).Cast<Cookie>().ToArray();
			string text = source.FirstOrDefault((Cookie c) => string.Equals(c.Name, "__Secure-3PAPISID", StringComparison.Ordinal))?.Value ?? source.FirstOrDefault((Cookie c) => string.Equals(c.Name, "SAPISID", StringComparison.Ordinal))?.Value;
			if (string.IsNullOrWhiteSpace(text))
			{
				return null;
			}
			long num = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
			string domain = uri.GetDomain();
			string s = $"{num} {text} {domain}";
			string arg = Hash.Compute(SHA1.Create(), Encoding.UTF8.GetBytes(s)).ToHex();
			return $"SAPISIDHASH {num}_{arg}";
		}

		private HttpRequestMessage HandleRequest(HttpRequestMessage request)
		{
			if ((object)request.RequestUri == null)
			{
				return request;
			}
			if (request.RequestUri.AbsolutePath.StartsWith("/youtubei/", StringComparison.Ordinal) && !UrlEx.ContainsQueryParameter(request.RequestUri.Query, "key"))
			{
				request.RequestUri = new Uri(UrlEx.SetQueryParameter(request.RequestUri.OriginalString, "key", "AIzaSyA8eiZmM1FaDVjRy-df2KTyQ_vz_yYM39w"));
			}
			if (!UrlEx.ContainsQueryParameter(request.RequestUri.Query, "hl"))
			{
				request.RequestUri = new Uri(UrlEx.SetQueryParameter(request.RequestUri.OriginalString, "hl", "en"));
			}
			if (!request.Headers.Contains("Origin"))
			{
				request.Headers.Add("Origin", request.RequestUri.GetDomain());
			}
			if (!request.Headers.Contains("User-Agent"))
			{
				request.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36");
			}
			if (!request.Headers.Contains("Cookie") && _cookieContainer.Count > 0)
			{
				string cookieHeader = _cookieContainer.GetCookieHeader(request.RequestUri);
				if (!string.IsNullOrWhiteSpace(cookieHeader))
				{
					request.Headers.Add("Cookie", cookieHeader);
				}
			}
			if (!request.Headers.Contains("Authorization"))
			{
				string text = TryGenerateAuthHeaderValue(request.RequestUri);
				if (text != null)
				{
					request.Headers.Add("Authorization", text);
				}
			}
			return request;
		}

		private HttpResponseMessage HandleResponse(HttpResponseMessage response)
		{
			if ((object)response.RequestMessage?.RequestUri == null)
			{
				return response;
			}
			if (response.StatusCode == HttpStatusCode.TooManyRequests)
			{
				throw new RequestLimitExceededException("Exceeded request rate limit. Please try again in a few hours. Alternatively, inject cookies corresponding to a pre-authenticated user when initializing an instance of `YoutubeClient`.");
			}
			if (response.Headers.TryGetValues("Set-Cookie", out IEnumerable<string> values))
			{
				foreach (string item in values)
				{
					try
					{
						_cookieContainer.SetCookies(response.RequestMessage.RequestUri, item);
					}
					catch (CookieException)
					{
					}
				}
			}
			return response;
		}

		protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
		{
			int retriesRemaining = 5;
			HttpResponseMessage httpResponseMessage;
			while (true)
			{
				httpResponseMessage = HandleResponse(await base.SendAsync(HandleRequest(request), cancellationToken));
				if (httpResponseMessage.StatusCode < HttpStatusCode.InternalServerError || retriesRemaining <= 0)
				{
					break;
				}
				httpResponseMessage.Dispose();
				retriesRemaining--;
			}
			return httpResponseMessage;
		}
	}
}
namespace YoutubeExplode.Utils
{
	internal abstract class ClientDelegatingHandler : HttpMessageHandler
	{
		[CompilerGenerated]
		private HttpClient <http>P;

		[CompilerGenerated]
		private bool <disposeClient>P;

		protected ClientDelegatingHandler(HttpClient http, bool disposeClient = false)
		{
			<http>P = http;
			<disposeClient>P = disposeClient;
			base..ctor();
		}

		protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
		{
			using HttpRequestMessage clonedRequest = request.Clone();
			return await <http>P.SendAsync(clonedRequest, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
		}

		protected override void Dispose(bool disposing)
		{
			if (disposing && <disposeClient>P)
			{
				<http>P.Dispose();
			}
			base.Dispose(disposing);
		}
	}
	internal static class Hash
	{
		public static byte[] Compute(HashAlgorithm algorithm, byte[] data)
		{
			using (algorithm)
			{
				return algorithm.ComputeHash(data);
			}
		}
	}
	internal static class Html
	{
		private static readonly HtmlParser HtmlParser = new HtmlParser();

		public static IHtmlDocument Parse(string source)
		{
			return HtmlParser.ParseDocument(source);
		}
	}
	internal static class Http
	{
		private static readonly Lazy<HttpClient> HttpClientLazy = new Lazy<HttpClient>(() => new HttpClient());

		public static HttpClient Client => HttpClientLazy.Value;
	}
	internal static class Json
	{
		public static string Extract(string source)
		{
			StringBuilder stringBuilder = new StringBuilder();
			int num = 0;
			bool flag = false;
			foreach (var item3 in source.Index())
			{
				int item = item3.index;
				char item2 = item3.value;
				char c = ((item > 0) ? source[item - 1] : '\0');
				stringBuilder.Append(item2);
				if (item2 == '"' && c != '\\')
				{
					flag = !flag;
				}
				else if (item2 == '{' && !flag)
				{
					num++;
				}
				else if (item2 == '}' && !flag)
				{
					num--;
				}
				if (num == 0)
				{
					break;
				}
			}
			return stringBuilder.ToString();
		}

		public static JsonElement Parse(string source)
		{
			using JsonDocument jsonDocument = JsonDocument.Parse(source);
			return jsonDocument.RootElement.Clone();
		}

		public static JsonElement? TryParse(string source)
		{
			try
			{
				return Parse(source);
			}
			catch (JsonException)
			{
				return null;
			}
		}

		public static string Encode(string value)
		{
			StringBuilder stringBuilder = new StringBuilder(value.Length);
			foreach (char c in value)
			{
				switch (c)
				{
				case '\n':
					stringBuilder.Append("\\n");
					break;
				case '\r':
					stringBuilder.Append("\\r");
					break;
				case '\t':
					stringBuilder.Append("\\t");
					break;
				case '\\':
					stringBuilder.Append("\\\\");
					break;
				case '"':
					stringBuilder.Append("\\\"");
					break;
				default:
					stringBuilder.Append(c);
					break;
				}
			}
			return stringBuilder.ToString();
		}

		public static string Serialize(string? value)
		{
			if (value == null)
			{
				return "null";
			}
			return "\"" + Encode(value) + "\"";
		}

		public static string Serialize(int? value)
		{
			if (!value.HasValue)
			{
				return "null";
			}
			return value.Value.ToString(CultureInfo.InvariantCulture);
		}
	}
	internal static class UrlEx
	{
		[CompilerGenerated]
		private sealed class <EnumerateQueryParameters>d__0 : IEnumerable<KeyValuePair<string, string>>, IEnumerable, IEnumerator<KeyValuePair<string, string>>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private KeyValuePair<string, string> <>2__current;

			private int <>l__initialThreadId;

			private string url;

			public string <>3__url;

			private string[] <>7__wrap1;

			private int <>7__wrap2;

			KeyValuePair<string, string> IEnumerator<KeyValuePair<string, string>>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <EnumerateQueryParameters>d__0(int <>1__state)
			{
				this.<>1__state = <>1__state;
				<>l__initialThreadId = Environment.CurrentManagedThreadId;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>7__wrap1 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				int num = <>1__state;
				if (num != 0)
				{
					if (num != 1)
					{
						return false;
					}
					<>1__state = -1;
					goto IL_00b8;
				}
				<>1__state = -1;
				string text = (PolyfillExtensions.Contains(url, '?') ? url.SubstringAfter("?") : url);
				<>7__wrap1 = text.Split(new char[1] { '&' });
				<>7__wrap2 = 0;
				goto IL_00c6;
				IL_00b8:
				<>7__wrap2++;
				goto IL_00c6;
				IL_00c6:
				if (<>7__wrap2 < <>7__wrap1.Length)
				{
					string str = <>7__wrap1[<>7__wrap2];
					string text2 = WebUtility.UrlDecode(str.SubstringUntil("="));
					string value = WebUtility.UrlDecode(str.SubstringAfter("="));
					if (!string.IsNullOrWhiteSpace(text2))
					{
						<>2__current = new KeyValuePair<string, string>(text2, value);
						<>1__state = 1;
						return true;
					}
					goto IL_00b8;
				}
				<>7__wrap1 = null;
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}

			[DebuggerHidden]
			IEnumerator<KeyValuePair<string, string>> IEnumerable<KeyValuePair<string, string>>.GetEnumerator()
			{
				<EnumerateQueryParameters>d__0 <EnumerateQueryParameters>d__;
				if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
				{
					<>1__state = 0;
					<EnumerateQueryParameters>d__ = this;
				}
				else
				{
					<EnumerateQueryParameters>d__ = new <EnumerateQueryParameters>d__0(0);
				}
				<EnumerateQueryParameters>d__.url = <>3__url;
				return <EnumerateQueryParameters>d__;
			}

			[DebuggerHidden]
			IEnumerator IEnumerable.GetEnumerator()
			{
				return ((IEnumerable<KeyValuePair<string, string>>)this).GetEnumerator();
			}
		}

		[IteratorStateMachine(typeof(<EnumerateQueryParameters>d__0))]
		private static IEnumerable<KeyValuePair<string, string>> EnumerateQueryParameters(string url)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <EnumerateQueryParameters>d__0(-2)
			{
				<>3__url = url
			};
		}

		public static IReadOnlyDictionary<string, string> GetQueryParameters(string url)
		{
			return EnumerateQueryParameters(url).ToDictionary<KeyValuePair<string, string>, string, string>((KeyValuePair<string, string> kvp) => kvp.Key, (KeyValuePair<string, string> kvp) => kvp.Value);
		}

		private static KeyValuePair<string, string>? TryGetQueryParameter(string url, string key)
		{
			foreach (KeyValuePair<string, string> item in EnumerateQueryParameters(url))
			{
				if (string.Equals(item.Key, key, StringComparison.Ordinal))
				{
					return item;
				}
			}
			return null;
		}

		public static string? TryGetQueryParameterValue(string url, string key)
		{
			return TryGetQueryParameter(url, key)?.Value;
		}

		public static bool ContainsQueryParameter(string url, string key)
		{
			return TryGetQueryParameterValue(url, key) != null;
		}

		public static string RemoveQueryParameter(string url, string key)
		{
			if (!ContainsQueryParameter(url, key))
			{
				return url;
			}
			UriBuilder uriBuilder = new UriBuilder(url);
			StringBuilder stringBuilder = new StringBuilder();
			foreach (KeyValuePair<string, string> item in EnumerateQueryParameters(url))
			{
				if (!string.Equals(item.Key, key, StringComparison.Ordinal))
				{
					stringBuilder.Append((stringBuilder.Length > 0) ? '&' : '?');
					stringBuilder.Append(Uri.EscapeDataString(item.Key));
					stringBuilder.Append('=');
					stringBuilder.Append(Uri.EscapeDataString(item.Value));
				}
			}
			uriBuilder.Query = stringBuilder.ToString();
			return uriBuilder.ToString();
		}

		public static string SetQueryParameter(string url, string key, string value)
		{
			string text = RemoveQueryParameter(url, key);
			bool flag = PolyfillExtensions.Contains(text, '?');
			return text + (flag ? '&' : '?') + Uri.EscapeDataString(key) + "=" + Uri.EscapeDataString(value);
		}
	}
	internal static class Xml
	{
		public static XElement Parse(string source)
		{
			return XElement.Parse(source, LoadOptions.PreserveWhitespace).StripNamespaces();
		}
	}
}
namespace YoutubeExplode.Utils.Extensions
{
	internal static class AsyncCollectionExtensions
	{
		public static async IAsyncEnumerable<T> TakeAsync<T>(this IAsyncEnumerable<T> source, int count)
		{
			int currentCount = 0;
			await foreach (T item in source)
			{
				if (currentCount >= count)
				{
					break;
				}
				yield return item;
				currentCount++;
			}
		}

		public static async IAsyncEnumerable<T> SelectManyAsync<TSource, T>(this IAsyncEnumerable<TSource> source, Func<TSource, IEnumerable<T>> transform)
		{
			await foreach (TSource item in source)
			{
				foreach (T item2 in transform(item))
				{
					yield return item2;
				}
			}
		}

		public static async IAsyncEnumerable<T> OfTypeAsync<T>(this IAsyncEnumerable<object> source)
		{
			await foreach (object item in source)
			{
				if (item is T)
				{
					yield return (T)item;
				}
			}
		}

		public static async ValueTask<List<T>> ToListAsync<T>(this IAsyncEnumerable<T> source)
		{
			List<T> list = new List<T>();
			await foreach (T item in source)
			{
				list.Add(item);
			}
			return list;
		}

		public static ValueTaskAwaiter<List<T>> GetAwaiter<T>(this IAsyncEnumerable<T> source)
		{
			return source.ToListAsync().GetAwaiter();
		}
	}
	internal static class BinaryExtensions
	{
		public static string ToHex(this byte[] data, bool isUpperCase = true)
		{
			StringBuilder stringBuilder = new StringBuilder(2 * data.Length);
			foreach (byte b in data)
			{
				stringBuilder.Append(b.ToString(isUpperCase ? "X2" : "x2", CultureInfo.InvariantCulture));
			}
			return stringBuilder.ToString();
		}
	}
	internal static class CollectionExtensions
	{
		[CompilerGenerated]
		private sealed class <WhereNotNull>d__0<T> : IEnumerable<T>, IEnumerable, IEnumerator<T>, IEnumerator, IDisposable where T : class
		{
			private int <>1__state;

			private T <>2__current;

			private int <>l__initialThreadId;

			private IEnumerable<T?> source;

			public IEnumerable<T?> <>3__source;

			private IEnumerator<T?> <>7__wrap1;

			T IEnumerator<T>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <WhereNotNull>d__0(int <>1__state)
			{
				this.<>1__state = <>1__state;
				<>l__initialThreadId = Environment.CurrentManagedThreadId;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				int num = <>1__state;
				if (num == -3 || num == 1)
				{
					try
					{
					}
					finally
					{
						<>m__Finally1();
					}
				}
				<>7__wrap1 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				try
				{
					switch (<>1__state)
					{
					default:
						return false;
					case 0:
						<>1__state = -1;
						<>7__wrap1 = source.GetEnumerator();
						<>1__state = -3;
						break;
					case 1:
						<>1__state = -3;
						break;
					}
					while (<>7__wrap1.MoveNext())
					{
						T current = <>7__wrap1.Current;
						if (current != null)
						{
							<>2__current = current;
							<>1__state = 1;
							return true;
						}
					}
					<>m__Finally1();
					<>7__wrap1 = null;
					return false;
				}
				catch
				{
					//try-fault
					((IDisposable)this).Dispose();
					throw;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			private void <>m__Finally1()
			{
				<>1__state = -1;
				if (<>7__wrap1 != null)
				{
					<>7__wrap1.Dispose();
				}
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}

			[DebuggerHidden]
			IEnumerator<T> IEnumerable<T>.GetEnumerator()
			{
				<WhereNotNull>d__0<T> <WhereNotNull>d__;
				if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
				{
					<>1__state = 0;
					<WhereNotNull>d__ = this;
				}
				else
				{
					<WhereNotNull>d__ = new <WhereNotNull>d__0<T>(0);
				}
				<WhereNotNull>d__.source = <>3__source;
				return <WhereNotNull>d__;
			}

			[DebuggerHidden]
			IEnumerator IEnumerable.GetEnumerator()
			{
				return ((IEnumerable<T>)this).GetEnumerator();
			}
		}

		[CompilerGenerated]
		private sealed class <WhereNotNull>d__1<T> : IEnumerable<T>, IEnumerable, IEnumerator<T>, IEnumerator, IDisposable where T : struct
		{
			private int <>1__state;

			private T <>2__current;

			private int <>l__initialThreadId;

			private IEnumerable<T?> source;

			public IEnumerable<T?> <>3__source;

			private IEnumerator<T?> <>7__wrap1;

			T IEnumerator<T>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <WhereNotNull>d__1(int <>1__state)
			{
				this.<>1__state = <>1__state;
				<>l__initialThreadId = Environment.CurrentManagedThreadId;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				int num = <>1__state;
				if (num == -3 || num == 1)
				{
					try
					{
					}
					finally
					{
						<>m__Finally1();
					}
				}
				<>7__wrap1 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				try
				{
					switch (<>1__state)
					{
					default:
						return false;
					case 0:
						<>1__state = -1;
						<>7__wrap1 = source.GetEnumerator();
						<>1__state = -3;
						break;
					case 1:
						<>1__state = -3;
						break;
					}
					while (<>7__wrap1.MoveNext())
					{
						T? current = <>7__wrap1.Current;
						if (current.HasValue)
						{
							<>2__current = current.Value;
							<>1__state = 1;
							return true;
						}
					}
					<>m__Finally1();
					<>7__wrap1 = null;
					return false;
				}
				catch
				{
					//try-fault
					((IDisposable)this).Dispose();
					throw;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			private void <>m__Finally1()
			{
				<>1__state = -1;
				if (<>7__wrap1 != null)
				{
					<>7__wrap1.Dispose();
				}
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}

			[DebuggerHidden]
			IEnumerator<T> IEnumerable<T>.GetEnumerator()
			{
				<WhereNotNull>d__1<T> <WhereNotNull>d__;
				if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
				{
					<>1__state = 0;
					<WhereNotNull>d__ = this;
				}
				else
				{
					<WhereNotNull>d__ = new <WhereNotNull>d__1<T>(0);
				}
				<WhereNotNull>d__.source = <>3__source;
				return <WhereNotNull>d__;
			}

			[DebuggerHidden]
			IEnumerator IEnumerable.GetEnumerator()
			{
				return ((IEnumerable<T>)this).GetEnumerator();
			}
		}

		[IteratorStateMachine(typeof(<WhereNotNull>d__0<>))]
		public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> source) where T : class
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <WhereNotNull>d__0<T>(-2)
			{
				<>3__source = source
			};
		}

		[IteratorStateMachine(typeof(<WhereNotNull>d__1<>))]
		public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> source) where T : struct
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <WhereNotNull>d__1<T>(-2)
			{
				<>3__source = source
			};
		}

		public static T? ElementAtOrNull<T>(this IEnumerable<T> source, int index) where T : struct
		{
			IReadOnlyList<T> readOnlyList = (source as IReadOnlyList<T>) ?? source.ToArray();
			if (index >= readOnlyList.Count)
			{
				return null;
			}
			return readOnlyList[index];
		}

		public static T? FirstOrNull<T>(this IEnumerable<T> source) where T : struct
		{
			using (IEnumerator<T> enumerator = source.GetEnumerator())
			{
				if (enumerator.MoveNext())
				{
					return enumerator.Current;
				}
			}
			return null;
		}
	}
	internal static class GenericExtensions
	{
		public static TOut Pipe<TIn, TOut>(this TIn input, Func<TIn, TOut> transform)
		{
			return transform(input);
		}
	}
	internal static class HttpExtensions
	{
		private class NonDisposableHttpContent : HttpContent
		{
			[CompilerGenerated]
			private HttpContent <content>P;

			public NonDisposableHttpContent(HttpContent content)
			{
				<content>P = content;
				base..ctor();
			}

			protected override async Task SerializeToStreamAsync(Stream stream, TransportContext? context)
			{
				await <content>P.CopyToAsync(stream);
			}

			protected override bool TryComputeLength(out long length)
			{
				length = 0L;
				return false;
			}
		}

		public static HttpRequestMessage Clone(this HttpRequestMessage request)
		{
			HttpRequestMessage httpRequestMessage = new HttpRequestMessage(request.Method, request.RequestUri)
			{
				Version = request.Version,
				Content = ((request.Content != null) ? new NonDisposableHttpContent(request.Content) : null)
			};
			string key;
			IEnumerable<string> value;
			foreach (KeyValuePair<string, IEnumerable<string>> header in request.Headers)
			{
				PolyfillExtensions.Deconstruct(header, out key, out value);
				string name = key;
				IEnumerable<string> values = value;
				httpRequestMessage.Headers.TryAddWithoutValidation(name, values);
			}
			if (request.Content != null && httpRequestMessage.Content != null)
			{
				foreach (KeyValuePair<string, IEnumerable<string>> header2 in request.Content.Headers)
				{
					PolyfillExtensions.Deconstruct(header2, out key, out value);
					string name2 = key;
					IEnumerable<string> values2 = value;
					httpRequestMessage.Content.Headers.TryAddWithoutValidation(name2, values2);
				}
			}
			return httpRequestMessage;
		}

		public static async ValueTask<HttpResponseMessage> HeadAsync(this HttpClient http, string requestUri, CancellationToken cancellationToken = default(CancellationToken))
		{
			using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Head, requestUri);
			return await http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
		}
	}
	internal static class JsonExtensions
	{
		[CompilerGenerated]
		private sealed class <>c__DisplayClass9_0
		{
			public string propertyName;

			internal IEnumerable<JsonElement> <EnumerateDescendantProperties>b__0(JsonElement j)
			{
				return j.EnumerateDescendantProperties(propertyName);
			}

			internal IEnumerable<JsonElement> <EnumerateDescendantProperties>b__1(JsonProperty j)
			{
				return j.Value.EnumerateDescendantProperties(propertyName);
			}
		}

		[CompilerGenerated]
		private sealed class <EnumerateDescendantProperties>d__9 : IEnumerable<JsonElement>, IEnumerable, IEnumerator<JsonElement>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private JsonElement <>2__current;

			private int <>l__initialThreadId;

			private string propertyName;

			public string <>3__propertyName;

			private JsonElement element;

			public JsonElement <>3__element;

			private <>c__DisplayClass9_0 <>8__1;

			private IEnumerator<JsonElement> <>7__wrap1;

			JsonElement IEnumerator<JsonElement>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <EnumerateDescendantProperties>d__9(int <>1__state)
			{
				this.<>1__state = <>1__state;
				<>l__initialThreadId = Environment.CurrentManagedThreadId;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				switch (<>1__state)
				{
				case -3:
				case 2:
					try
					{
					}
					finally
					{
						<>m__Finally1();
					}
					break;
				case -4:
				case 3:
					try
					{
					}
					finally
					{
						<>m__Finally2();
					}
					break;
				}
				<>8__1 = null;
				<>7__wrap1 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				try
				{
					IEnumerable<JsonElement> enumerable;
					IEnumerable<JsonElement> enumerable2;
					switch (<>1__state)
					{
					default:
						return false;
					case 0:
					{
						<>1__state = -1;
						<>8__1 = new <>c__DisplayClass9_0();
						<>8__1.propertyName = propertyName;
						JsonElement? propertyOrNull = element.GetPropertyOrNull(<>8__1.propertyName);
						if (propertyOrNull.HasValue)
						{
							<>2__current = propertyOrNull.Value;
							<>1__state = 1;
							return true;
						}
						goto IL_0089;
					}
					case 1:
						<>1__state = -1;
						goto IL_0089;
					case 2:
						<>1__state = -3;
						goto IL_00f1;
					case 3:
						{
							<>1__state = -4;
							break;
						}
						IL_0089:
						enumerable = element.EnumerateArrayOrEmpty().SelectMany((JsonElement j) => j.EnumerateDescendantProperties(<>8__1.propertyName));
						<>7__wrap1 = enumerable.GetEnumerator();
						<>1__state = -3;
						goto IL_00f1;
						IL_00f1:
						if (<>7__wrap1.MoveNext())
						{
							JsonElement current = <>7__wrap1.Current;
							<>2__current = current;
							<>1__state = 2;
							return true;
						}
						<>m__Finally1();
						<>7__wrap1 = null;
						enumerable2 = element.EnumerateObjectOrEmpty().SelectMany((JsonProperty j) => j.Value.EnumerateDescendantProperties(<>8__1.propertyName));
						<>7__wrap1 = enumerable2.GetEnumerator();
						<>1__state = -4;
						break;
					}
					if (<>7__wrap1.MoveNext())
					{
						JsonElement current2 = <>7__wrap1.Current;
						<>2__current = current2;
						<>1__state = 3;
						return true;
					}
					<>m__Finally2();
					<>7__wrap1 = null;
					return false;
				}
				catch
				{
					//try-fault
					((IDisposable)this).Dispose();
					throw;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			private void <>m__Finally1()
			{
				<>1__state = -1;
				if (<>7__wrap1 != null)
				{
					<>7__wrap1.Dispose();
				}
			}

			private void <>m__Finally2()
			{
				<>1__state = -1;
				if (<>7__wrap1 != null)
				{
					<>7__wrap1.Dispose();
				}
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}

			[DebuggerHidden]
			IEnumerator<JsonElement> IEnumerable<JsonElement>.GetEnumerator()
			{
				<EnumerateDescendantProperties>d__9 <EnumerateDescendantProperties>d__;
				if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
				{
					<>1__state = 0;
					<EnumerateDescendantProperties>d__ = this;
				}
				else
				{
					<EnumerateDescendantProperties>d__ = new <EnumerateDescendantProperties>d__9(0);
				}
				<EnumerateDescendantProperties>d__.element = <>3__element;
				<EnumerateDescendantProperties>d__.propertyName = <>3__propertyName;
				return <EnumerateDescendantProperties>d__;
			}

			[DebuggerHidden]
			IEnumerator IEnumerable.GetEnumerator()
			{
				return ((IEnumerable<JsonElement>)this).GetEnumerator();
			}
		}

		public static JsonElement? GetPropertyOrNull(this JsonElement element, string propertyName)
		{
			if (element.ValueKind != JsonValueKind.Object)
			{
				return null;
			}
			if (element.TryGetProperty(propertyName, out var value) && value.ValueKind != JsonValueKind.Null && value.ValueKind != 0)
			{
				return value;
			}
			return null;
		}

		public static bool? GetBooleanOrNull(this JsonElement element)
		{
			return element.ValueKind switch
			{
				JsonValueKind.True => true, 
				JsonValueKind.False => false, 
				_ => null, 
			};
		}

		public static string? GetStringOrNull(this JsonElement element)
		{
			if (element.ValueKind != JsonValueKind.String)
			{
				return null;
			}
			return element.GetString();
		}

		public static int? GetInt32OrNull(this JsonElement element)
		{
			if (element.ValueKind != JsonValueKind.Number || !element.TryGetInt32(out var value))
			{
				return null;
			}
			return value;
		}

		public static long? GetInt64OrNull(this JsonElement element)
		{
			if (element.ValueKind != JsonValueKind.Number || !element.TryGetInt64(out var value))
			{
				return null;
			}
			return value;
		}

		public static JsonElement.ArrayEnumerator? EnumerateArrayOrNull(this JsonElement element)
		{
			if (element.ValueKind != JsonValueKind.Array)
			{
				return null;
			}
			return element.EnumerateArray();
		}

		public static JsonElement.ArrayEnumerator EnumerateArrayOrEmpty(this JsonElement element)
		{
			return element.EnumerateArrayOrNull().GetValueOrDefault();
		}

		public static JsonElement.ObjectEnumerator? EnumerateObjectOrNull(this JsonElement element)
		{
			if (element.ValueKind != JsonValueKind.Object)
			{
				return null;
			}
			return element.EnumerateObject();
		}

		public static JsonElement.ObjectEnumerator EnumerateObjectOrEmpty(this JsonElement element)
		{
			return element.EnumerateObjectOrNull().GetValueOrDefault();
		}

		[IteratorStateMachine(typeof(<EnumerateDescendantProperties>d__9))]
		public static IEnumerable<JsonElement> EnumerateDescendantProperties(this JsonElement element, string propertyName)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <EnumerateDescendantProperties>d__9(-2)
			{
				<>3__element = element,
				<>3__propertyName = propertyName
			};
		}
	}
	internal static class StreamExtensions
	{
		public static async ValueTask CopyToAsync(this Stream source, Stream destination, IProgress<double>? progress = null, CancellationToken cancellationToken = default(CancellationToken))
		{
			using IMemoryOwner<byte> buffer = MemoryPool<byte>.Shared.Rent(81920);
			long totalBytesRead = 0L;
			while (true)
			{
				int bytesRead = await PolyfillExtensions.ReadAsync(source, buffer.Memory, cancellationToken);
				if (bytesRead <= 0)
				{
					break;
				}
				await PolyfillExtensions.WriteAsync(destination, buffer.Memory.Slice(0, bytesRead), cancellationToken);
				totalBytesRead += bytesRead;
				progress?.Report(1.0 * (double)totalBytesRead / (double)source.Length);
			}
		}
	}
	internal static class StringExtensions
	{
		public static string? NullIfWhiteSpace(this string str)
		{
			if (string.IsNullOrWhiteSpace(str))
			{
				return null;
			}
			return str;
		}

		public static string SubstringUntil(this string str, string sub, StringComparison comparison = StringComparison.Ordinal)
		{
			int num = str.IndexOf(sub, comparison);
			if (num >= 0)
			{
				return str.Substring(0, num);
			}
			return str;
		}

		public static string SubstringAfter(this string str, string sub, StringComparison comparison = StringComparison.Ordinal)
		{
			int num = str.IndexOf(sub, comparison);
			if (num >= 0)
			{
				return str.Substring(num + sub.Length, str.Length - num - sub.Length);
			}
			return string.Empty;
		}

		public static string StripNonDigit(this string str)
		{
			StringBuilder stringBuilder = new StringBuilder();
			foreach (char item in str.Where(char.IsDigit))
			{
				stringBuilder.Append(item);
			}
			return stringBuilder.ToString();
		}

		public static string Reverse(this string str)
		{
			StringBuilder stringBuilder = new StringBuilder(str.Length);
			for (int num = str.Length - 1; num >= 0; num--)
			{
				stringBuilder.Append(str[num]);
			}
			return stringBuilder.ToString();
		}

		public static string SwapChars(this string str, int firstCharIndex, int secondCharIndex)
		{
			return new StringBuilder(str)
			{
				[firstCharIndex] = str[secondCharIndex],
				[secondCharIndex] = str[firstCharIndex]
			}.ToString();
		}

		public static int? ParseIntOrNull(this string str)
		{
			if (!int.TryParse(str, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var result))
			{
				return null;
			}
			return result;
		}

		public static int ParseInt(this string str)
		{
			return str.ParseIntOrNull() ?? throw new FormatException("Cannot parse integer number from string '" + str + "'.");
		}

		public static long? ParseLongOrNull(this string str)
		{
			if (!long.TryParse(str, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var result))
			{
				return null;
			}
			return result;
		}

		public static double? ParseDoubleOrNull(this string str)
		{
			if (!double.TryParse(str, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.InvariantInfo, out var result))
			{
				return null;
			}
			return result;
		}

		public static TimeSpan? ParseTimeSpanOrNull(this string str, string[] formats)
		{
			if (!TimeSpan.TryParseExact(str, formats, DateTimeFormatInfo.InvariantInfo, out var result))
			{
				return null;
			}
			return result;
		}

		public static DateTimeOffset? ParseDateTimeOffsetOrNull(this string str)
		{
			if (!DateTimeOffset.TryParse(str, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None, out var result))
			{
				return null;
			}
			return result;
		}

		public static string ConcatToString<T>(this IEnumerable<T> source)
		{
			return string.Concat(source);
		}
	}
	internal static class UriExtensions
	{
		public static string GetDomain(this Uri uri)
		{
			return uri.Scheme + Uri.SchemeDelimiter + uri.Host;
		}
	}
	internal static class XElementExtensions
	{
		public static XElement StripNamespaces(this XElement element)
		{
			XElement xElement = new XElement(element);
			foreach (XElement item in xElement.DescendantsAndSelf())
			{
				item.Name = XNamespace.None.GetName(item.Name.LocalName);
				item.ReplaceAttributes(from a in item.Attributes()
					where !a.IsNamespaceDeclaration
					where a.Name.Namespace != XNamespace.Xml && a.Name.Namespace != XNamespace.Xmlns
					select new XAttribute(XNamespace.None.GetName(a.Name.LocalName), a.Value));
			}
			return xElement;
		}
	}
}
namespace YoutubeExplode.Search
{
	public class ChannelSearchResult : ISearchResult, IBatchItem, IChannel
	{
		public ChannelId Id { get; }

		public string Url => $"https://www.youtube.com/channel/{Id}";

		public string Title { get; }

		public IReadOnlyList<Thumbnail> Thumbnails { get; }

		public ChannelSearchResult(ChannelId id, string title, IReadOnlyList<Thumbnail> thumbnails)
		{
			Id = id;
			Title = title;
			Thumbnails = thumbnails;
			base..ctor();
		}

		[ExcludeFromCodeCoverage]
		public override string ToString()
		{
			return "Channel (" + Title + ")";
		}
	}
	public interface ISearchResult : IBatchItem
	{
		string Url { get; }

		string Title { get; }
	}
	public class PlaylistSearchResult : ISearchResult, IBatchItem, IPlaylist
	{
		public PlaylistId Id { get; }

		public string Url => $"https://www.youtube.com/playlist?list={Id}";

		public string Title { get; }

		public Author? Author { get; }

		public IReadOnlyList<Thumbnail> Thumbnails { get; }

		public PlaylistSearchResult(PlaylistId id, string title, Author? author, IReadOnlyList<Thumbnail> thumbnails)
		{
			Id = id;
			Title = title;
			Author = author;
			Thumbnails = thumbnails;
			base..ctor();
		}

		[ExcludeFromCodeCoverage]
		public override string ToString()
		{
			return "Playlist (" + Title + ")";
		}
	}
	public class SearchClient
	{
		[CompilerGenerated]
		private sealed class <GetResultBatchesAsync>d__2 : IAsyncEnumerable<Batch<ISearchResult>>, IAsyncEnumerator<Batch<ISearchResult>>, IAsyncDisposable, IValueTaskSource<bool>, IValueTaskSource, IAsyncStateMachine
		{
			public int <>1__state;

			public AsyncIteratorMethodBuilder <>t__builder;

			public ManualResetValueTaskSourceCore<bool> <>v__promiseOfValueOrEnd;

			private Batch<ISearchResult> <>2__current;

			private bool <>w__disposeMode;

			private CancellationTokenSource <>x__combinedTokens;

			private int <>l__initialThreadId;

			public SearchClient <>4__this;

			private string searchQuery;

			public string <>3__searchQuery;

			private SearchFilter searchFilter;

			public SearchFilter <>3__searchFilter;

			private CancellationToken cancellationToken;

			public CancellationToken <>3__cancellationToken;

			private HashSet<string> <encounteredIds>5__2;

			private List<ISearchResult> <results>5__3;

			private SearchResponse <searchResults>5__4;

			private ValueTaskAwaiter<SearchResponse> <>u__1;

			Batch<ISearchResult> IAsyncEnumerator<Batch<ISearchResult>>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <GetResultBatchesAsync>d__2(int <>1__state)
			{
				<>t__builder = AsyncIteratorMethodBuilder.Create();
				this.<>1__state = <>1__state;
				<>l__initialThreadId = Environment.CurrentManagedThreadId;
			}

			private void MoveNext()
			{
				int num = <>1__state;
				SearchClient searchClient = <>4__this;
				try
				{
					string text;
					ValueTaskAwaiter<SearchResponse> awaiter;
					switch (num)
					{
					default:
						if (!<>w__disposeMode)
						{
							num = (<>1__state = -1);
							<encounteredIds>5__2 = new HashSet<string>(StringComparer.Ordinal);
							text = null;
							goto IL_0053;
						}
						goto end_IL_000e;
					case 0:
						awaiter = <>u__1;
						<>u__1 = default(ValueTaskAwaiter<SearchResponse>);
						num = (<>1__state = -1);
						break;
					case -4:
						{
							num = (<>1__state = -1);
							if (!<>w__disposeMode)
							{
								text = <searchResults>5__4.ContinuationToken;
								<results>5__3 = null;
								<searchResults>5__4 = null;
								if (!string.IsNullOrWhiteSpace(text))
								{
									goto IL_0053;
								}
							}
							goto end_IL_000e;
						}
						IL_0053:
						<results>5__3 = new List<ISearchResult>();
						<>2__current = null;
						awaiter = searchClient._controller.GetSearchResponseAsync(searchQuery, searchFilter, text, cancellationToken).GetAwaiter();
						if (!awaiter.IsCompleted)
						{
							num = (<>1__state = 0);
							<>u__1 = awaiter;
							<GetResultBatchesAsync>d__2 stateMachine = this;
							<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
							return;
						}
						break;
					}
					SearchResponse result = awaiter.GetResult();
					<searchResults>5__4 = result;
					IEnumerator<SearchResponse.VideoData> enumerator = <searchResults>5__4.Videos.GetEnumerator();
					try
					{
						while (enumerator.MoveNext())
						{
							SearchResponse.VideoData current = enumerator.Current;
							if (searchFilter != 0 && searchFilter != SearchFilter.Video)
							{
								break;
							}
							string text2 = current.Id ?? throw new YoutubeExplodeException("Failed to extract the video ID.");
							if (<encounteredIds>5__2.Add(text2))
							{
								string title = current.Title ?? throw new YoutubeExplodeException("Failed to extract the video title.");
								string channelTitle = current.Author ?? throw new YoutubeExplodeException("Failed to extract the video author.");
								string text3 = current.ChannelId ?? throw new YoutubeExplodeException("Failed to extract the video channel ID.");
								Thumbnail[] thumbnails = current.Thumbnails.Select(delegate(ThumbnailData t)
								{
									string? url3 = t.Url ?? throw new YoutubeExplodeException("Failed to extract the video thumbnail URL.");
									int width3 = t.Width ?? throw new YoutubeExplodeException("Failed to extract the video thumbnail width.");
									int height3 = t.Height ?? throw new YoutubeExplodeException("Failed to extract the video thumbnail height.");
									Resolution resolution3 = new Resolution(width3, height3);
									return new Thumbnail(url3, resolution3);
								}).Concat(Thumbnail.GetDefaultSet(text2)).ToArray();
								VideoSearchResult item = new VideoSearchResult(text2, title, new Author(text3, channelTitle), current.Duration, thumbnails);
								<results>5__3.Add(item);
							}
						}
					}
					finally
					{
						if (num == -1)
						{
							enumerator?.Dispose();
						}
					}
					if (!<>w__disposeMode)
					{
						IEnumerator<SearchResponse.PlaylistData> enumerator2 = <searchResults>5__4.Playlists.GetEnumerator();
						try
						{
							while (enumerator2.MoveNext())
							{
								SearchResponse.PlaylistData current2 = enumerator2.Current;
								if (searchFilter != 0 && searchFilter != SearchFilter.Playlist)
								{
									break;
								}
								string text4 = current2.Id ?? throw new YoutubeExplodeException("Failed to extract the playlist ID.");
								if (<encounteredIds>5__2.Add(text4))
								{
									string title2 = current2.Title ?? throw new YoutubeExplodeException("Failed to extract the playlist title.");
									Author author = ((!string.IsNullOrWhiteSpace(current2.ChannelId) && !string.IsNullOrWhiteSpace(current2.Author)) ? new Author(current2.ChannelId, current2.Author) : null);
									Thumbnail[] thumbnails2 = current2.Thumbnails.Select(delegate(ThumbnailData t)
									{
										string? url2 = t.Url ?? throw new YoutubeExplodeException("Failed to extract the playlist thumbnail URL.");
										int width2 = t.Width ?? throw new YoutubeExplodeException("Failed to extract the playlist thumbnail width.");
										int height2 = t.Height ?? throw new YoutubeExplodeException("Failed to extract the playlist thumbnail height.");
										Resolution resolution2 = new Resolution(width2, height2);
										return new Thumbnail(url2, resolution2);
									}).ToArray();
									PlaylistSearchResult item2 = new PlaylistSearchResult(text4, title2, author, thumbnails2);
									<results>5__3.Add(item2);
								}
							}
						}
						finally
						{
							if (num == -1)
							{
								enumerator2?.Dispose();
							}
						}
						if (!<>w__disposeMode)
						{
							IEnumerator<SearchResponse.ChannelData> enumerator3 = <searchResults>5__4.Channels.GetEnumerator();
							try
							{
								while (enumerator3.MoveNext())
								{
									SearchResponse.ChannelData current3 = enumerator3.Current;
									if (searchFilter == SearchFilter.None || searchFilter == SearchFilter.Channel)
									{
										string text5 = current3.Id ?? throw new YoutubeExplodeException("Failed to extract the channel ID.");
										string title3 = current3.Title ?? throw new YoutubeExplodeException("Failed to extract the channel title.");
										Thumbnail[] thumbnails3 = current3.Thumbnails.Select(delegate(ThumbnailData t)
										{
											string? url = t.Url ?? throw new YoutubeExplodeException("Failed to extract the channel thumbnail URL.");
											int width = t.Width ?? throw new YoutubeExplodeException("Failed to extract the channel thumbnail width.");
											int height = t.Height ?? throw new YoutubeExplodeException("Failed to extract the channel thumbnail height.");
											Resolution resolution = new Resolution(width, height);
											return new Thumbnail(url, resolution);
										}).ToArray();
										ChannelSearchResult item3 = new ChannelSearchResult(text5, title3, thumbnails3);
										<results>5__3.Add(item3);
										continue;
									}
									break;
								}
							}
							finally
							{
								if (num == -1)
								{
									enumerator3?.Dispose();
								}
							}
							if (!<>w__disposeMode)
							{
								<>2__current = Batch.Create(<results>5__3);
								num = (<>1__state = -4);
								goto IL_054d;
							}
						}
					}
					end_IL_000e:;
				}
				catch (Exception exception)
				{
					<>1__state = -2;
					<encounteredIds>5__2 = null;
					<results>5__3 = null;
					<searchResults>5__4 = null;
					if (<>x__combinedTokens != null)
					{
						<>x__combinedTokens.Dispose();
						<>x__combinedTokens = null;
					}
					<>2__current = null;
					<>t__builder.Complete();
					<>v__promiseOfValueOrEnd.SetException(exception);
					return;
				}
				<>1__state = -2;
				<encounteredIds>5__2 = null;
				<results>5__3 = null;
				<searchResults>5__4 = null;
				if (<>x__combinedTokens != null)
				{
					<>x__combinedTokens.Dispose();
					<>x__combinedTokens = null;
				}
				<>2__current = null;
				<>t__builder.Complete();
				<>v__promiseOfValueOrEnd.SetResult(result: false);
				return;
				IL_054d:
				<>v__promiseOfValueOrEnd.SetResult(result: true);
			}

			void IAsyncStateMachine.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				this.MoveNext();
			}

			[DebuggerHidden]
			private void SetStateMachine(IAsyncStateMachine stateMachine)
			{
			}

			void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
			{
				//ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
				this.SetStateMachine(stateMachine);
			}

			[DebuggerHidden]
			IAsyncEnumerator<Batch<ISearchResult>> IAsyncEnumerable<Batch<ISearchResult>>.GetAsyncEnumerator(CancellationToken cancellationToken = default(CancellationToken))
			{
				<GetResultBatchesAsync>d__2 <GetResultBatchesAsync>d__;
				if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
				{
					<>1__state = -3;
					<>t__builder = AsyncIteratorMethodBuilder.Create();
					<>w__disposeMode = false;
					<GetResultBatchesAsync>d__ = this;
				}
				else
				{
					<GetResultBatchesAsync>d__ = new <GetResultBatchesAsync>d__2(-3)
					{
						<>4__this = <>4__this
					};
				}
				<GetResultBatchesAsync>d__.searchQuery = <>3__searchQuery;
				<GetResultBatchesAsync>d__.searchFilter = <>3__searchFilter;
				if (<>3__cancellationToken.Equals(default(CancellationToken)))
				{
					<GetResultBatchesAsync>d__.cancellationToken = cancellationToken;
				}
				else if (cancellationToken.Equals(<>3__cancellationToken) || cancellationToken.Equals(default(CancellationToken)))
				{
					<GetResultBatchesAsync>d__.cancellationToken = <>3__cancellationToken;
				}
				else
				{
					<>x__combinedTokens = CancellationTokenSource.CreateLinkedTokenSource(<>3__cancellationToken, cancellationToken);
					<GetResultBatchesAsync>d__.cancellationToken = <>x__combinedTokens.Token;
				}
				return <GetResultBatchesAsync>d__;
			}

			[DebuggerHidden]
			ValueTask<bool> IAsyncEnumerator<Batch<ISearchResult>>.MoveNextAsync()
			{
				if (<>1__state == -2)
				{
					return default(ValueTask<bool>);
				}
				<>v__promiseOfValueOrEnd.Reset();
				<GetResultBatchesAsync>d__2 stateMachine = this;
				<>t__builder.MoveNext(ref stateMachine);
				short version = <>v__promiseOfValueOrEnd.Version;
				if (<>v__promiseOfValueOrEnd.GetStatus(version) == ValueTaskSourceStatus.Succeeded)
				{
					return new ValueTask<bool>(<>v__promiseOfValueOrEnd.GetResult(version));
				}
				return new ValueTask<bool>(this, version);
			}

			[DebuggerHidden]
			bool IValueTaskSource<bool>.GetResult(short token)
			{
				return <>v__promiseOfValueOrEnd.GetResult(token);
			}

			[DebuggerHidden]
			ValueTaskSourceStatus IValueTaskSource<bool>.GetStatus(short token)
			{
				return <>v__promiseOfValueOrEnd.GetStatus(token);
			}

			[DebuggerHidden]
			void IValueTaskSource<bool>.OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags)
			{
				<>v__promiseOfValueOrEnd.OnCompleted(continuation, state, token, flags);
			}

			[DebuggerHidden]
			void IValueTaskSource.GetResult(short token)
			{
				<>v__promiseOfValueOrEnd.GetResult(token);
			}

			[DebuggerHidden]
			ValueTaskSourceStatus IValueTaskSource.GetStatus(short token)
			{
				return <>v__promiseOfValueOrEnd.GetStatus(token);
			}

			[DebuggerHidden]
			void IValueTaskSource.OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags)
			{
				<>v__promiseOfValueOrEnd.OnCompleted(continuation, state, token, flags);
			}

			[DebuggerHidden]
			ValueTask IAsyncDisposable.DisposeAsync()
			{
				if (<>1__state >= -1)
				{
					throw new NotSupportedException();
				}
				if (<>1__state == -2)
				{
					return default(ValueTask);
				}
				<>w__disposeMode = true;
				<>v__promiseOfValueOrEnd.Reset();
				<GetResultBatchesAsync>d__2 stateMachine = this;
				<>t__builder.MoveNext(ref stateMachine);
				return new ValueTask(this, <>v__promiseOfValueOrEnd.Version);
			}
		}

		private readonly SearchController _controller = new SearchController(http);

		public SearchClient(HttpClient http)
		{
		}

		[AsyncIteratorStateMachine(typeof(<GetResultBatchesAsync>d__2))]
		public IAsyncEnumerable<Batch<ISearchResult>> GetResultBatchesAsync(string searchQuery, SearchFilter searchFilter, [EnumeratorCancellation] CancellationToken cancellationToken = default(CancellationToken))
		{
			return new <GetResultBatchesAsync>d__2(-2)
			{
				<>4__this = this,
				<>3__searchQuery = searchQuery,
				<>3__searchFilter = searchFilter,
				<>3__cancellationToken = cancellationToken
			};
		}

		public IAsyncEnumerable<Batch<ISearchResult>> GetResultBatchesAsync(string searchQuery, CancellationToken cancellationToken = default(CancellationToken))
		{
			return GetResultBatchesAsync(searchQuery, SearchFilter.None, cancellationToken);
		}

		public IAsyncEnumerable<ISearchResult> GetResultsAsync(string searchQuery, CancellationToken cancellationToken = default(CancellationToken))
		{
			return GetResultBatchesAsync(searchQuery, cancellationToken).FlattenAsync();
		}

		public IAsyncEnumerable<VideoSearchResult> GetVideosAsync(string searchQuery, CancellationToken cancellationToken = default(CancellationToken))
		{
			return GetResultBatchesAsync(searchQuery, SearchFilter.Video, cancellationToken).FlattenAsync().OfTypeAsync<VideoSearchResult>();
		}

		public IAsyncEnumerable<PlaylistSearchResult> GetPlaylistsAsync(string searchQuery, CancellationToken cancellationToken = default(CancellationToken))
		{
			return GetResultBatchesAsync(searchQuery, SearchFilter.Playlist, cancellationToken).FlattenAsync().OfTypeAsync<PlaylistSearchResult>();
		}

		public IAsyncEnumerable<ChannelSearchResult> GetChannelsAsync(string searchQuery, CancellationToken cancellationToken = default(CancellationToken))
		{
			return GetResultBatchesAsync(searchQuery, SearchFilter.Channel, cancellationToken).FlattenAsync().OfTypeAsync<ChannelSearchResult>();
		}
	}
	internal class SearchController
	{
		[CompilerGenerated]
		private HttpClient <http>P;

		public SearchController(HttpClient http)
		{
			<http>P = http;
			base..ctor();
		}

		public async ValueTask<SearchResponse> GetSearchResponseAsync(string searchQuery, SearchFilter searchFilter, string? continuationToken, CancellationToken cancellationToken = default(CancellationToken))
		{
			using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "https://www.youtube.com/youtubei/v1/search");
			HttpRequestMessage httpRequestMessage = request;
			string text = Json.Serialize(searchQuery);
			string value = searchFilter switch
			{
				SearchFilter.Video => "EgIQAQ%3D%3D", 
				SearchFilter.Playlist => "EgIQAw%3D%3D", 
				SearchFilter.Channel => "EgIQAg%3D%3D", 
				_ => null, 
			};
			httpRequestMessage.Content = new StringContent("{\n  \"query\": " + text + ",\n  \"params\": " + Json.Serialize(value) + ",\n  \"continuation\": " + Json.Serialize(continuationToken) + ",\n  \"context\": {\n    \"client\": {\n      \"clientName\": \"WEB\",\n      \"clientVersion\": \"2.20210408.08.00\",\n      \"hl\": \"en\",\n      \"gl\": \"US\",\n      \"utcOffsetMinutes\": 0\n    }\n  }\n}");
			using HttpResponseMessage response = await <http>P.SendAsync(request, cancellationToken);
			response.EnsureSuccessStatusCode();
			return SearchResponse.Parse(await PolyfillExtensions.ReadAsStringAsync(response.Content, cancellationToken));
		}
	}
	public enum SearchFilter
	{
		None,
		Video,
		Playlist,
		Channel
	}
	public class VideoSearchResult : ISearchResult, IBatchItem, IVideo
	{
		public VideoId Id { get; }

		public string Url => $"https://www.youtube.com/watch?v={Id}";

		public string Title { get; }

		public Author Author { get; }

		public TimeSpan? Duration { get; }

		public IReadOnlyList<Thumbnail> Thumbnails { get; }

		public VideoSearchResult(VideoId id, string title, Author author, TimeSpan? duration, IReadOnlyList<Thumbnail> thumbnails)
		{
			Id = id;
			Title = title;
			Author = author;
			Duration = duration;
			Thumbnails = thumbnails;
			base..ctor();
		}

		[ExcludeFromCodeCoverage]
		public override string ToString()
		{
			return "Video (" + Title + ")";
		}
	}
}
namespace YoutubeExplode.Playlists
{
	public interface IPlaylist
	{
		PlaylistId Id { get; }

		string Url { get; }

		string Title { get; }

		Author? Author { get; }

		IReadOnlyList<Thumbnail> Thumbnails { get; }
	}
	public class Playlist : IPlaylist
	{
		public PlaylistId Id { get; }

		public string Url => $"https://www.youtube.com/playlist?list={Id}";

		public string Title { get; }

		public Author? Author { get; }

		public string Description { get; }

		public int? Count { get; }

		public IReadOnlyList<Thumbnail> Thumbnails { get; }

		public Playlist(PlaylistId id, string title, Author? author, string description, int? count, IReadOnlyList<Thumbnail> thumbnails)
		{
			Id = id;
			Title = title;
			Author = author;
			Description = description;
			Count = count;
			Thumbnails = thumbnails;
			base..ctor();
		}

		[ExcludeFromCodeCoverage]
		public override string ToString()
		{
			return "Playlist (" + Title + ")";
		}
	}
	public class PlaylistClient
	{
		[CompilerGenerated]
		private sealed class <GetVideoBatchesAsync>d__3 : IAsyncEnumerable<Batch<PlaylistVideo>>, IAsyncEnumerator<Batch<PlaylistVideo>>, IAsyncDisposable, IValueTaskSource<bool>, IValueTaskSource, IAsyncStateMachine
		{
			public int <>1__state;

			public AsyncIteratorMethodBuilder <>t__builder;

			public ManualResetValueTaskSourceCore<bool> <>v__promiseOfValueOrEnd;

			private Batch<PlaylistVideo> <>2__current;

			private bool <>w__disposeMode;

			private CancellationTokenSource <>x__combinedTokens;

			private int <>l__initialThreadId;

			public PlaylistClient <>4__this;

			private PlaylistId playlistId;

			public PlaylistId <>3__playlistId;

			private CancellationToken cancellationToken;

			public CancellationToken <>3__cancellationToken;

			private HashSet<VideoId> <encounteredIds>5__2;

			private VideoId? <lastVideoId>5__3;

			private int <lastVideoIndex>5__4;

			private string <visitorData>5__5;

			private PlaylistNextResponse <response>5__6;

			private ValueTaskAwaiter<PlaylistNextResponse> <>u__1;

			Batch<PlaylistVideo> IAsyncEnumerator<Batch<PlaylistVideo>>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <GetVideoBatchesAsync>d__3(int <>1__state)
			{
				<>t__builder = AsyncIteratorMethodBuilder.Create();
				this.<>1__state = <>1__state;
				<>l__initialThreadId = Environment.CurrentManagedThreadId;
			}

			private void MoveNext()
			{
				int num = <>1__state;
				PlaylistClient playlistClient = <>4__this;
				try
				{
					ValueTaskAwaiter<PlaylistNextResponse> awaiter;
					switch (num)
					{
					default:
						if (!<>w__disposeMode)
						{
							num = (<>1__state = -1);
							<encounteredIds>5__2 = new HashSet<VideoId>();
							<lastVideoId>5__3 = null;
							<lastVideoIndex>5__4 = 0;
							<visitorData>5__5 = null;
							goto IL_0066;
						}
						goto end_IL_000e;
					case 0:
						awaiter = <>u__1;
						<>u__1 = default(ValueTaskAwaiter<PlaylistNextResponse>);
						num = (<>1__state = -1);
						break;
					case -4:
						{
							num = (<>1__state = -1);
							if (!<>w__disposeMode)
							{
								if (<visitorData>5__5 == null)
								{
									<visitorData>5__5 = <response>5__6.VisitorData;
								}
								<response>5__6 = null;
								goto IL_0066;
							}
							goto end_IL_000e;
						}
						IL_0066:
						<>2__current = null;
						awaiter = playlistClient._controller.GetPlaylistNextResponseAsync(playlistId, <lastVideoId>5__3, <lastVideoIndex>5__4, <visitorData>5__5, cancellationToken).GetAwaiter();
						if (!awaiter.IsCompleted)
						{
							num = (<>1__state = 0);
							<>u__1 = awaiter;
							<GetVideoBatchesAsync>d__3 stateMachine = this;
							<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
							return;
						}
						break;
					}
					PlaylistNextResponse result = awaiter.GetResult();
					<response>5__6 = result;
					List<PlaylistVideo> list = new List<PlaylistVideo>();
					IEnumerator<PlaylistVideoData> enumerator = <response>5__6.Videos.GetEnumerator();
					try
					{
						while (enumerator.MoveNext())
						{
							PlaylistVideoData current = enumerator.Current;
							string text = current.Id ?? throw new YoutubeExplodeException("Failed to extract the video ID.");
							<lastVideoId>5__3 = text;
							<lastVideoIndex>5__4 = current.Index ?? throw new YoutubeExplodeException("Failed to extract the video index.");
							if (<encounteredIds>5__2.Add(text))
							{
								string title = current.Title ?? "";
								string channelTitle = current.Author ?? throw new YoutubeExplodeException("Failed to extract the video author.");
								string text2 = current.ChannelId ?? throw new YoutubeExplodeException("Failed to extract the video channel ID.");
								Thumbnail[] thumbnails = current.Thumbnails.Select(delegate(ThumbnailData t)
								{
									string? url = t.Url ?? throw new YoutubeExplodeException("Failed to extract the thumbnail URL.");
									int width = t.Width ?? throw new YoutubeExplodeException("Failed to extract the thumbnail width.");
									int height = t.Height ?? throw new YoutubeExplodeException("Failed to extract the thumbnail height.");
									Resolution resolution = new Resolution(width, height);
									return new Thumbnail(url, resolution);
								}).Concat(Thumbnail.GetDefaultSet(text)).ToArray();
								PlaylistVideo item = new PlaylistVideo(playlistId, text, title, new Author(text2, channelTitle), current.Duration, thumbnails);
								list.Add(item);
							}
						}
					}
					finally
					{
						if (num == -1)
						{
							enumerator?.Dispose();
						}
					}
					if (!<>w__disposeMode && list.Any())
					{
						<>2__current = Batch.Create(list);
						num = (<>1__state = -4);
						goto IL_0384;
					}
					end_IL_000e:;
				}
				catch (Exception exception)
				{
					<>1__state = -2;
					<encounteredIds>5__2 = null;
					<visitorData>5__5 = null;
					<response>5__6 = null;
					if (<>x__combinedTokens != null)
					{
						<>x__combinedTokens.Dispose();
						<>x__combinedTokens = null;
					}
					<>2__current = null;
					<>t__builder.Complete();
					<>v__promiseOfValueOrEnd.SetException(exception);
					return;
				}
				<>1__state = -2;
				<encounteredIds>5__2 = null;
				<visitorData>5__5 = null;
				<response>5__6 = null;
				if (<>x__combinedTokens != null)
				{
					<>x__combinedTokens.Dispose();
					<>x__combinedTokens = null;
				}
				<>2__current = null;
				<>t__builder.Complete();
				<>v__promiseOfValueOrEnd.SetResult(result: false);
				return;
				IL_0384:
				<>v__promiseOfValueOrEnd.SetResult(result: true);
			}

			void IAsyncStateMachine.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				this.MoveNext();
			}

			[DebuggerHidden]
			private void SetStateMachine(IAsyncStateMachine stateMachine)
			{
			}

			void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
			{
				//ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
				this.SetStateMachine(stateMachine);
			}

			[DebuggerHidden]
			IAsyncEnumerator<Batch<PlaylistVideo>> IAsyncEnumerable<Batch<PlaylistVideo>>.GetAsyncEnumerator(CancellationToken cancellationToken = default(CancellationToken))
			{
				<GetVideoBatchesAsync>d__3 <GetVideoBatchesAsync>d__;
				if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
				{
					<>1__state = -3;
					<>t__builder = AsyncIteratorMethodBuilder.Create();
					<>w__disposeMode = false;
					<GetVideoBatchesAsync>d__ = this;
				}
				else
				{
					<GetVideoBatchesAsync>d__ = new <GetVideoBatchesAsync>d__3(-3)
					{
						<>4__this = <>4__this
					};
				}
				<GetVideoBatchesAsync>d__.playlistId = <>3__playlistId;
				if (<>3__cancellationToken.Equals(default(CancellationToken)))
				{
					<GetVideoBatchesAsync>d__.cancellationToken = cancellationToken;
				}
				else if (cancellationToken.Equals(<>3__cancellationToken) || cancellationToken.Equals(default(CancellationToken)))
				{
					<GetVideoBatchesAsync>d__.cancellationToken = <>3__cancellationToken;
				}
				else
				{
					<>x__combinedTokens = CancellationTokenSource.CreateLinkedTokenSource(<>3__cancellationToken, cancellationToken);
					<GetVideoBatchesAsync>d__.cancellationToken = <>x__combinedTokens.Token;
				}
				return <GetVideoBatchesAsync>d__;
			}

			[DebuggerHidden]
			ValueTask<bool> IAsyncEnumerator<Batch<PlaylistVideo>>.MoveNextAsync()
			{
				if (<>1__state == -2)
				{
					return default(ValueTask<bool>);
				}
				<>v__promiseOfValueOrEnd.Reset();
				<GetVideoBatchesAsync>d__3 stateMachine = this;
				<>t__builder.MoveNext(ref stateMachine);
				short version = <>v__promiseOfValueOrEnd.Version;
				if (<>v__promiseOfValueOrEnd.GetStatus(version) == ValueTaskSourceStatus.Succeeded)
				{
					return new ValueTask<bool>(<>v__promiseOfValueOrEnd.GetResult(version));
				}
				return new ValueTask<bool>(this, version);
			}

			[DebuggerHidden]
			bool IValueTaskSource<bool>.GetResult(short token)
			{
				return <>v__promiseOfValueOrEnd.GetResult(token);
			}

			[DebuggerHidden]
			ValueTaskSourceStatus IValueTaskSource<bool>.GetStatus(short token)
			{
				return <>v__promiseOfValueOrEnd.GetStatus(token);
			}

			[DebuggerHidden]
			void IValueTaskSource<bool>.OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags)
			{
				<>v__promiseOfValueOrEnd.OnCompleted(continuation, state, token, flags);
			}

			[DebuggerHidden]
			void IValueTaskSource.GetResult(short token)
			{
				<>v__promiseOfValueOrEnd.GetResult(token);
			}

			[DebuggerHidden]
			ValueTaskSourceStatus IValueTaskSource.GetStatus(short token)
			{
				return <>v__promiseOfValueOrEnd.GetStatus(token);
			}

			[DebuggerHidden]
			void IValueTaskSource.OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags)
			{
				<>v__promiseOfValueOrEnd.OnCompleted(continuation, state, token, flags);
			}

			[DebuggerHidden]
			ValueTask IAsyncDisposable.DisposeAsync()
			{
				if (<>1__state >= -1)
				{
					throw new NotSupportedException();
				}
				if (<>1__state == -2)
				{
					return default(ValueTask);
				}
				<>w__disposeMode = true;
				<>v__promiseOfValueOrEnd.Reset();
				<GetVideoBatchesAsync>d__3 stateMachine = this;
				<>t__builder.MoveNext(ref stateMachine);
				return new ValueTask(this, <>v__promiseOfValueOrEnd.Version);
			}
		}

		private readonly PlaylistController _controller = new PlaylistController(http);

		public PlaylistClient(HttpClient http)
		{
		}

		public async ValueTask<Playlist> GetAsync(PlaylistId playlistId, CancellationToken cancellationToken = default(CancellationToken))
		{
			IPlaylistData obj = await _controller.GetPlaylistResponseAsync(playlistId, cancellationToken);
			string title = obj.Title ?? throw new YoutubeExplodeException("Failed to extract the playlist title.");
			string channelId = obj.ChannelId;
			string author = obj.Author;
			Author author2 = ((channelId != null && author != null) ? new Author(channelId, author) : null);
			string description = obj.Description ?? "";
			int? count = obj.Count;
			Thumbnail[] thumbnails = obj.Thumbnails.Select(delegate(ThumbnailData t)
			{
				string? url = t.Url ?? throw new YoutubeExplodeException("Failed to extract the thumbnail URL.");
				int width = t.Width ?? throw new YoutubeExplodeException("Failed to extract the thumbnail width.");
				int height = t.Height ?? throw new YoutubeExplodeException("Failed to extract the thumbnail height.");
				Resolution resolution = new Resolution(width, height);
				return new Thumbnail(url, resolution);
			}).ToArray();
			return new Playlist(playlistId, title, author2, description, count, thumbnails);
		}

		[AsyncIteratorStateMachine(typeof(<GetVideoBatchesAsync>d__3))]
		public IAsyncEnumerable<Batch<PlaylistVideo>> GetVideoBatchesAsync(PlaylistId playlistId, [EnumeratorCancellation] CancellationToken cancellationToken = default(CancellationToken))
		{
			return new <GetVideoBatchesAsync>d__3(-2)
			{
				<>4__this = this,
				<>3__playlistId = playlistId,
				<>3__cancellationToken = cancellationToken
			};
		}

		public IAsyncEnumerable<PlaylistVideo> GetVideosAsync(PlaylistId playlistId, CancellationToken cancellationToken = default(CancellationToken))
		{
			return GetVideoBatchesAsync(playlistId, cancellationToken).FlattenAsync();
		}
	}
	internal class PlaylistController
	{
		[CompilerGenerated]
		private HttpClient <http>P;

		public PlaylistController(HttpClient http)
		{
			<http>P = http;
			base..ctor();
		}

		public async ValueTask<PlaylistBrowseResponse> GetPlaylistBrowseResponseAsync(PlaylistId playlistId, CancellationToken cancellationToken = default(CancellationToken))
		{
			using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "https://www.youtube.com/youtubei/v1/browse");
			request.Content = new StringContent("{\n  \"browseId\": " + Json.Serialize("VL" + playlistId) + ",\n  \"context\": {\n    \"client\": {\n      \"clientName\": \"WEB\",\n      \"clientVersion\": \"2.20210408.08.00\",\n      \"hl\": \"en\",\n      \"gl\": \"US\",\n      \"utcOffsetMinutes\": 0\n    }\n  }\n}");
			using HttpResponseMessage response = await <http>P.SendAsync(request, cancellationToken);
			response.EnsureSuccessStatusCode();
			PlaylistBrowseResponse playlistBrowseResponse = PlaylistBrowseResponse.Parse(await PolyfillExtensions.ReadAsStringAsync(response.Content, cancellationToken));
			if (!playlistBrowseResponse.IsAvailable)
			{
				throw new PlaylistUnavailableException($"Playlist '{playlistId}' is not available.");
			}
			return playlistBrowseResponse;
		}

		public async ValueTask<PlaylistNextResponse> GetPlaylistNextResponseAsync(PlaylistId playlistId, VideoId? videoId = null, int index = 0, string? visitorData = null, CancellationToken cancellationToken = default(CancellationToken))
		{
			int retriesRemaining = 5;
			while (true)
			{
				using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "https://www.youtube.com/youtubei/v1/next"))
				{
					string[] obj = new string[9]
					{
						"{\n  \"playlistId\": ",
						Json.Serialize(playlistId),
						",\n  \"videoId\": ",
						null,
						null,
						null,
						null,
						null,
						null
					};
					VideoId? videoId2 = videoId;
					obj[3] = Json.Serialize(videoId2.HasValue ? ((string)videoId2.GetValueOrDefault()) : null);
					obj[4] = ",\n  \"playlistIndex\": ";
					obj[5] = Json.Serialize(index);
					obj[6] = ",\n  \"context\": {\n    \"client\": {\n      \"clientName\": \"WEB\",\n      \"clientVersion\": \"2.20210408.08.00\",\n      \"hl\": \"en\",\n      \"gl\": \"US\",\n      \"utcOffsetMinutes\": 0,\n      \"visitorData\": ";
					obj[7] = Json.Serialize(visitorData);
					obj[8] = "\n    }\n  }\n}";
					request.Content = new StringContent(string.Concat(obj));
					using HttpResponseMessage response = await <http>P.SendAsync(request, cancellationToken);
					response.EnsureSuccessStatusCode();
					PlaylistNextResponse playlistNextResponse = PlaylistNextResponse.Parse(await PolyfillExtensions.ReadAsStringAsync(response.Content, cancellationToken));
					if (playlistNextResponse.IsAvailable)
					{
						return playlistNextResponse;
					}
					if (index <= 0 || string.IsNullOrWhiteSpace(visitorData) || retriesRemaining <= 0)
					{
						if (index > 0 || !string.IsNullOrWhiteSpace(visitorData) || retriesRemaining < 5)
						{
							throw new PlaylistUnavailableException($"Playlist '{playlistId}' is not available.");
						}
						using (await <http>P.GetAsync($"https://youtube.com/playlist?list={playlistId}", cancellationToken))
						{
						}
					}
				}
				retriesRemaining--;
			}
		}

		public async ValueTask<IPlaylistData> GetPlaylistResponseAsync(PlaylistId playlistId, CancellationToken cancellationToken = default(CancellationToken))
		{
			IPlaylistData result = default(IPlaylistData);
			int num;
			try
			{
				result = await GetPlaylistBrowseResponseAsync(playlistId, cancellationToken);
				return result;
			}
			catch (PlaylistUnavailableException)
			{
				num = 1;
			}
			if (num != 1)
			{
				return result;
			}
			return await GetPlaylistNextResponseAsync(playlistId, null, 0, null, cancellationToken);
		}
	}
	public readonly struct PlaylistId : IEquatable<PlaylistId>
	{
		public string Value { get; }

		public PlaylistId(string value)
		{
			Value = value;
		}

		public override string ToString()
		{
			return Value;
		}

		private static bool IsValid(string playlistId)
		{
			if (playlistId.Length >= 2)
			{
				return playlistId.All(delegate(char c)
				{
					bool flag = char.IsLetterOrDigit(c);
					if (!flag)
					{
						bool flag2 = ((c == '-' || c == '_') ? true : false);
						flag = flag2;
					}
					return flag;
				});
			}
			return false;
		}

		private static string? TryNormalize(string? playlistIdOrUrl)
		{
			if (string.IsNullOrWhiteSpace(playlistIdOrUrl))
			{
				return null;
			}
			if (IsValid(playlistIdOrUrl))
			{
				return playlistIdOrUrl;
			}
			string text = Regex.Match(playlistIdOrUrl, "youtube\\..+?/playlist.*?list=(.*?)(?:&|/|$)").Groups[1].Value.Pipe(WebUtility.UrlDecode);
			if (!string.IsNullOrWhiteSpace(text) && IsValid(text))
			{
				return text;
			}
			string text2 = Regex.Match(playlistIdOrUrl, "youtube\\..+?/watch.*?list=(.*?)(?:&|/|$)").Groups[1].Value.Pipe(WebUtility.UrlDecode);
			if (!string.IsNullOrWhiteSpace(text2) && IsValid(text2))
			{
				return text2;
			}
			string text3 = Regex.Match(playlistIdOrUrl, "youtu\\.be/.*?/.*?list=(.*?)(?:&|/|$)").Groups[1].Value.Pipe(WebUtility.UrlDecode);
			if (!string.IsNullOrWhiteSpace(text3) && IsValid(text3))
			{
				return text3;
			}
			string text4 = Regex.Match(playlistIdOrUrl, "youtube\\..+?/embed/.*?/.*?list=(.*?)(?:&|/|$)").Groups[1].Value.Pipe(WebUtility.UrlDecode);
			if (!string.IsNullOrWhiteSpace(text4) && IsValid(text4))
			{
				return text4;
			}
			return null;
		}

		public static PlaylistId? TryParse(string? playlistIdOrUrl)
		{
			return TryNormalize(playlistIdOrUrl)?.Pipe((string id) => new PlaylistId(id));
		}

		public static PlaylistId Parse(string playlistIdOrUrl)
		{
			return TryParse(playlistIdOrUrl) ?? throw new ArgumentException("Invalid YouTube playlist ID or URL '" + playlistIdOrUrl + "'.");
		}

		public static implicit operator PlaylistId(string playlistIdOrUrl)
		{
			return Parse(playlistIdOrUrl);
		}

		public static implicit operator string(PlaylistId playlistId)
		{
			return playlistId.ToString();
		}

		public bool Equals(PlaylistId other)
		{
			return StringComparer.Ordinal.Equals(Value, other.Value);
		}

		public override bool Equals(object? obj)
		{
			if (obj is PlaylistId other)
			{
				return Equals(other);
			}
			return false;
		}

		public override int GetHashCode()
		{
			return StringComparer.Ordinal.GetHashCode(Value);
		}

		public static bool operator ==(PlaylistId left, PlaylistId right)
		{
			return left.Equals(right);
		}

		public static bool operator !=(PlaylistId left, PlaylistId right)
		{
			return !(left == right);
		}
	}
	public class PlaylistVideo : IVideo, IBatchItem
	{
		public PlaylistId PlaylistId { get; }

		public VideoId Id { get; }

		public string Url => $"https://www.youtube.com/watch?v={Id}&list={PlaylistId}";

		public string Title { get; }

		public Author Author { get; }

		public TimeSpan? Duration { get; }

		public IReadOnlyList<Thumbnail> Thumbnails { get; }

		public PlaylistVideo(PlaylistId playlistId, VideoId id, string title, Author author, TimeSpan? duration, IReadOnlyList<Thumbnail> thumbnails)
		{
			PlaylistId = playlistId;
			Id = id;
			Title = title;
			Author = author;
			Duration = duration;
			Thumbnails = thumbnails;
			base..ctor();
		}

		[Obsolete("Use the other constructor instead.")]
		[ExcludeFromCodeCoverage]
		public PlaylistVideo(VideoId id, string title, Author author, TimeSpan? duration, IReadOnlyList<Thumbnail> thumbnails)
			: this(default(PlaylistId), id, title, author, duration, thumbnails)
		{
		}

		[ExcludeFromCodeCoverage]
		public override string ToString()
		{
			return "Video (" + Title + ")";
		}
	}
}
namespace YoutubeExplode.Exceptions
{
	public class PlaylistUnavailableException : YoutubeExplodeException
	{
		public PlaylistUnavailableException(string message)
			: base(message)
		{
		}
	}
	public class RequestLimitExceededException : YoutubeExplodeException
	{
		public RequestLimitExceededException(string message)
			: base(message)
		{
		}
	}
	public class VideoRequiresPurchaseException : VideoUnplayableException
	{
		public VideoId PreviewVideoId { get; }

		public VideoRequiresPurchaseException(string message, VideoId previewVideoId)
		{
			PreviewVideoId = previewVideoId;
			base..ctor(message);
		}
	}
	public class VideoUnavailableException : VideoUnplayableException
	{
		public VideoUnavailableException(string message)
			: base(message)
		{
		}
	}
	public class VideoUnplayableException : YoutubeExplodeException
	{
		public VideoUnplayableException(string message)
			: base(message)
		{
		}
	}
	public class YoutubeExplodeException : Exception
	{
		public YoutubeExplodeException(string message)
			: base(message)
		{
		}
	}
}
namespace YoutubeExplode.Videos
{
	public class Engagement
	{
		public long ViewCount { get; }

		public long LikeCount { get; }

		public long DislikeCount { get; }

		public double AverageRating
		{
			get
			{
				if (LikeCount + DislikeCount == 0L)
				{
					return 0.0;
				}
				return 1.0 + 4.0 * (double)LikeCount / (double)(LikeCount + DislikeCount);
			}
		}

		public Engagement(long viewCount, long likeCount, long dislikeCount)
		{
			ViewCount = viewCount;
			LikeCount = likeCount;
			DislikeCount = dislikeCount;
			base..ctor();
		}

		[ExcludeFromCodeCoverage]
		public override string ToString()
		{
			return $"Rating: {AverageRating:N1}";
		}
	}
	public interface IVideo
	{
		VideoId Id { get; }

		string Url { get; }

		string Title { get; }

		Author Author { get; }

		TimeSpan? Duration { get; }

		IReadOnlyList<Thumbnail> Thumbnails { get; }
	}
	public class Video : IVideo
	{
		public VideoId Id { get; }

		public string Url => $"https://www.youtube.com/watch?v={Id}";

		public string Title { get; }

		public Author Author { get; }

		public DateTimeOffset UploadDate { get; }

		public string Description { get; }

		public TimeSpan? Duration { get; }

		public IReadOnlyList<Thumbnail> Thumbnails { get; }

		public IReadOnlyList<string> Keywords { get; }

		public Engagement Engagement { get; }

		public Video(VideoId id, string title, Author author, DateTimeOffset uploadDate, string description, TimeSpan? duration, IReadOnlyList<Thumbnail> thumbnails, IReadOnlyList<string> keywords, Engagement engagement)
		{
			Id = id;
			Title = title;
			Author = author;
			UploadDate = uploadDate;
			Description = description;
			Duration = duration;
			Thumbnails = thumbnails;
			Keywords = keywords;
			Engagement = engagement;
			base..ctor();
		}

		[ExcludeFromCodeCoverage]
		public override string ToString()
		{
			return "Video (" + Title + ")";
		}
	}
	public class VideoClient
	{
		private readonly VideoController _controller = new VideoController(http);

		public StreamClient Streams { get; } = new StreamClient(http);


		public ClosedCaptionClient ClosedCaptions { get; } = new ClosedCaptionClient(http);


		public VideoClient(HttpClient http)
		{
		}

		public async ValueTask<Video> GetAsync(VideoId videoId, CancellationToken cancellationToken = default(CancellationToken))
		{
			VideoWatchPage watchPage = await _controller.GetVideoWatchPageAsync(videoId, cancellationToken);
			PlayerResponse playerResponse = watchPage.PlayerResponse;
			if (playerResponse == null)
			{
				playerResponse = await _controller.GetPlayerResponseAsync(videoId, cancellationToken);
			}
			PlayerResponse playerResponse2 = playerResponse;
			string title = playerResponse2.Title ?? "";
			string channelTitle = playerResponse2.Author ?? throw new YoutubeExplodeException("Failed to extract the video author.");
			string text = playerResponse2.ChannelId ?? throw new YoutubeExplodeException("Failed to extract the video channel ID.");
			DateTimeOffset uploadDate = playerResponse2.UploadDate ?? watchPage.UploadDate ?? throw new YoutubeExplodeException("Failed to extract the video upload date.");
			Thumbnail[] thumbnails = playerResponse2.Thumbnails.Select(delegate(ThumbnailData t)
			{
				string? url = t.Url ?? throw new YoutubeExplodeException("Failed to extract the thumbnail URL.");
				int width = t.Width ?? throw new YoutubeExplodeException("Failed to extract the thumbnail width.");
				int height = t.Height ?? throw new YoutubeExplodeException("Failed to extract the thumbnail height.");
				Resolution resolution = new Resolution(width, height);
				return new Thumbnail(url, resolution);
			}).Concat(Thumbnail.GetDefaultSet(videoId)).ToArray();
			return new Video(videoId, title, new Author(text, channelTitle), uploadDate, playerResponse2.Description ?? "", playerResponse2.Duration, thumbnails, playerResponse2.Keywords, new Engagement(playerResponse2.ViewCount.GetValueOrDefault(), watchPage.LikeCount.GetValueOrDefault(), watchPage.DislikeCount.GetValueOrDefault()));
		}
	}
	internal class VideoController
	{
		private string? _visitorData;

		protected HttpClient Http { get; }

		public VideoController(HttpClient http)
		{
			Http = http;
			base..ctor();
		}

		private async ValueTask<string> ResolveVisitorDataAsync(CancellationToken cancellationToken = default(CancellationToken))
		{
			if (!string.IsNullOrWhiteSpace(_visitorData))