Decompiled source of YoutubeOnTV v0.2.7

BepInEx\plugins\YoutubeOnTV\YoutubeOnTV.dll

Decompiled 2 months ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using LethalNetworkAPI;
using LethalNetworkAPI.Utils;
using TerminalApi;
using TerminalApi.Events;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.Video;
using YoutubeDLSharp;
using YoutubeDLSharp.Options;
using YoutubeOnTV.Patches;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("YoutubeOnTV")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("YoutubeOnTV")]
[assembly: AssemblyCopyright("Copyright ©  2025")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("637d9e92-bc74-4646-8436-67954c0a9f24")]
[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")]
namespace YoutubeOnTV
{
	[BepInPlugin("com.roandegraaf.youtubeontv", "YoutubeOnTV", "0.2.3")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class YoutubeOnTVBase : BaseUnityPlugin
	{
		private readonly Harmony harmony = new Harmony("com.roandegraaf.youtubeontv");

		public static YoutubeOnTVBase Instance;

		internal ManualLogSource mls;

		private static bool initialized;

		private void Awake()
		{
			if ((Object)(object)Instance == (Object)null)
			{
				Instance = this;
			}
			mls = Logger.CreateLogSource("YoutubeOnTV");
			mls.LogInfo((object)"YoutubeOnTV loaded");
			harmony.PatchAll(typeof(YoutubeOnTVBase));
			harmony.PatchAll(typeof(TVScriptPatch));
			harmony.PatchAll(typeof(TerminalPatch));
			RegisterTerminalCommands();
			if (!initialized)
			{
				initialized = true;
				SceneManager.sceneLoaded += OnSceneLoaded;
			}
		}

		private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			if ((Object)(object)VideoManager.Instance == (Object)null)
			{
				mls.LogInfo((object)("Scene loaded: " + ((Scene)(ref scene)).name + " - initializing managers..."));
				GameObject val = new GameObject("YoutubeOnTVManagers");
				Object.DontDestroyOnLoad((Object)(object)val);
				mls.LogInfo((object)"Adding VideoManager...");
				val.AddComponent<VideoManager>();
				mls.LogInfo((object)"Adding NetworkHandler...");
				val.AddComponent<NetworkHandler>();
				mls.LogInfo((object)"All components added successfully!");
			}
		}

		private void RegisterTerminalCommands()
		{
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Expected O, but got Unknown
			mls.LogInfo((object)"Registering terminal commands...");
			TerminalKeyword val = TerminalApi.CreateTerminalKeyword("tv", true, (TerminalNode)null);
			TerminalKeyword val2 = TerminalApi.CreateTerminalKeyword("add", false, (TerminalNode)null);
			TerminalKeyword val3 = TerminalApi.CreateTerminalKeyword("clear", false, (TerminalNode)null);
			TerminalKeyword val4 = TerminalApi.CreateTerminalKeyword("skip", false, (TerminalNode)null);
			TerminalKeyword val5 = TerminalApi.CreateTerminalKeyword("queue", false, (TerminalNode)null);
			TerminalNode val6 = TerminalApi.CreateTerminalNode("Video added to queue.\n\n", true, "");
			TerminalNode val7 = TerminalApi.CreateTerminalNode("Queue cleared.\n\n", true, "");
			TerminalNode val8 = TerminalApi.CreateTerminalNode("Skipped to next video.\n\n", true, "");
			TerminalNode val9 = TerminalApi.CreateTerminalNode("Queue status displayed.\n\n", true, "");
			TerminalNode specialKeywordResult = TerminalApi.CreateTerminalNode("TV CONTROLS\n===========\nUsage: tv <command> [args]\n\nCommands:\n  tv add [url/query]  - Add video to queue\n  tv clear            - Clear video queue\n  tv skip             - Skip current video\n  tv queue            - Show queue status\n\n", true, "");
			val = TerminalExtenstionMethods.AddCompatibleNoun(val, val2, val6);
			val = TerminalExtenstionMethods.AddCompatibleNoun(val, val3, val7);
			val = TerminalExtenstionMethods.AddCompatibleNoun(val, val4, val8);
			val = TerminalExtenstionMethods.AddCompatibleNoun(val, val5, val9);
			val.specialKeywordResult = specialKeywordResult;
			val2.defaultVerb = val;
			val3.defaultVerb = val;
			val4.defaultVerb = val;
			val5.defaultVerb = val;
			TerminalApi.AddTerminalKeyword(val);
			TerminalApi.AddTerminalKeyword(val2);
			TerminalApi.AddTerminalKeyword(val3);
			TerminalApi.AddTerminalKeyword(val4);
			TerminalApi.AddTerminalKeyword(val5);
			Events.TerminalParsedSentence += new TerminalParseSentenceEventHandler(OnTerminalParsedSentence);
			mls.LogInfo((object)"Terminal commands registered successfully!");
		}

		private void OnTerminalParsedSentence(object sender, TerminalParseSentenceEventArgs e)
		{
			string text = e.SubmittedText.Trim().ToLower();
			if (!text.StartsWith("tv"))
			{
				return;
			}
			mls.LogInfo((object)("Processing TV command: " + e.SubmittedText));
			if (text.StartsWith("tv add"))
			{
				string text2 = "";
				if (e.SubmittedText.Length > 7)
				{
					int num = e.SubmittedText.ToLower().IndexOf("tv add");
					if (num != -1)
					{
						text2 = e.SubmittedText.Substring(num + 7).Trim();
					}
				}
				if (string.IsNullOrEmpty(text2))
				{
					e.ReturnedNode = TerminalApi.CreateTerminalNode("Usage: tv add [url or search query]\nExample: tv add dQw4w9WgXcQ\nExample: tv add https://youtube.com/watch?v=...\nExample: tv add never gonna give you up\n\n", true, "");
					return;
				}
				if ((Object)(object)NetworkHandler.Instance != (Object)null)
				{
					NetworkHandler.Instance.RequestAddVideo(text2);
				}
				e.ReturnedNode = TerminalApi.CreateTerminalNode("Added to queue: " + text2 + "\n" + $"Queue size: {VideoQueue.Count()}\n\n", true, "");
			}
			else if (text == "tv clear" || text == "clear tv")
			{
				int num2 = VideoQueue.Count();
				if ((Object)(object)NetworkHandler.Instance != (Object)null)
				{
					NetworkHandler.Instance.RequestClearQueue();
				}
				e.ReturnedNode = TerminalApi.CreateTerminalNode($"Cleared {num2} video(s) from queue.\n\n", true, "");
			}
			else if (text == "tv skip" || text == "skip tv")
			{
				if (VideoQueue.IsEmpty())
				{
					e.ReturnedNode = TerminalApi.CreateTerminalNode("Queue is empty. Nothing to skip.\n\n", true, "");
					return;
				}
				if ((Object)(object)NetworkHandler.Instance != (Object)null)
				{
					NetworkHandler.Instance.RequestSkipVideo();
				}
				e.ReturnedNode = TerminalApi.CreateTerminalNode("Skipped to next video.\n" + $"Queue size: {VideoQueue.Count()}\n\n", true, "");
			}
			else if (text == "tv queue" || text == "queue tv")
			{
				int num3 = VideoQueue.Count();
				if (num3 == 0)
				{
					e.ReturnedNode = TerminalApi.CreateTerminalNode("Queue is empty.\n\n", true, "");
				}
				else
				{
					e.ReturnedNode = TerminalApi.CreateTerminalNode($"Videos in queue: {num3}\n\n", true, "");
				}
			}
		}
	}
	public class VideoManager : MonoBehaviour
	{
		[CompilerGenerated]
		private sealed class <SetPlaybackTimeWhenReady>d__32 : IEnumerator<object>, IDisposable, IEnumerator
		{
			private int <>1__state;

			private object <>2__current;

			public float startTime;

			public VideoManager <>4__this;

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

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

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

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

			private bool MoveNext()
			{
				//IL_0029: Unknown result type (might be due to invalid IL or missing references)
				//IL_0033: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					break;
				case 1:
					<>1__state = -1;
					break;
				}
				if ((Object)(object)TVController.Instance != (Object)null && !TVController.Instance.videoPlayer.isPrepared)
				{
					<>2__current = (object)new WaitForSeconds(0.1f);
					<>1__state = 1;
					return true;
				}
				if ((Object)(object)TVController.Instance != (Object)null)
				{
					TVController.Instance.videoPlayer.time = startTime;
					<>4__this.logger.LogInfo((object)$"Set playback start time to {startTime}s");
				}
				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();
			}
		}

		private ManualLogSource logger;

		private bool skipRequested = false;

		private bool hasStartedCurrentVideo = false;

		private bool isPlayingFallback = false;

		private float lastSyncTime = 0f;

		private const float SYNC_INTERVAL = 2f;

		public static VideoManager Instance { get; private set; }

		public string CurrentVideoUrl { get; private set; }

		public bool IsLoadingVideo { get; private set; }

		private static string GetFallbackVideoPath()
		{
			return Path.Combine(Paths.PluginPath, "YoutubeOnTV", "fallback.mp4");
		}

		private void Awake()
		{
			if ((Object)(object)Instance == (Object)null)
			{
				Instance = this;
				Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject);
				logger = Logger.CreateLogSource("YoutubeOnTV");
				logger.LogInfo((object)"VideoManager initialized!");
			}
			else
			{
				Object.Destroy((Object)(object)((Component)this).gameObject);
			}
		}

		private void Start()
		{
			if (!LNetworkUtils.IsHostOrServer)
			{
				logger.LogInfo((object)"Client joining - will request TV state from host");
				((MonoBehaviour)this).Invoke("RequestTVStateFromHost", 0.5f);
			}
		}

		private void RequestTVStateFromHost()
		{
			if ((Object)(object)NetworkHandler.Instance != (Object)null)
			{
				NetworkHandler.Instance.RequestTVState();
			}
			else
			{
				logger.LogWarning((object)"NetworkHandler not found, cannot request TV state");
			}
		}

		private void Update()
		{
			if ((Object)(object)TVController.Instance == (Object)null)
			{
				return;
			}
			if (!IsTVOn())
			{
				if (TVController.Instance.IsPlaying())
				{
					TVController.Instance.Pause();
				}
				return;
			}
			if (LNetworkUtils.IsHostOrServer && !IsLoadingVideo)
			{
				if (!VideoQueue.IsEmpty())
				{
					if (isPlayingFallback)
					{
						logger.LogInfo((object)"Switching from fallback to queue video");
						TVController.Instance.Stop();
						isPlayingFallback = false;
						hasStartedCurrentVideo = false;
					}
					if (!hasStartedCurrentVideo && !TVController.Instance.IsPlaying())
					{
						PlayNextFromQueue();
					}
				}
				else if (!isPlayingFallback && !TVController.Instance.IsPlaying())
				{
					PlayFallbackVideo();
				}
			}
			if (LNetworkUtils.IsHostOrServer && TVController.Instance.IsPlaying() && !isPlayingFallback && Time.time - lastSyncTime >= 2f)
			{
				lastSyncTime = Time.time;
				float time = (float)TVController.Instance.videoPlayer.time;
				if ((Object)(object)NetworkHandler.Instance != (Object)null)
				{
					NetworkHandler.Instance.BroadcastPlaybackTime(time);
				}
			}
		}

		public void OnSkipRequested()
		{
			skipRequested = true;
			hasStartedCurrentVideo = false;
			isPlayingFallback = false;
			if ((Object)(object)TVController.Instance != (Object)null)
			{
				TVController.Instance.Stop();
			}
		}

		public void PlayNextFromQueue()
		{
			if (VideoQueue.IsEmpty())
			{
				logger.LogInfo((object)"Queue is empty, nothing to play");
				return;
			}
			if (IsLoadingVideo)
			{
				logger.LogInfo((object)"Already loading a video, please wait");
				return;
			}
			string input = VideoQueue.Next();
			logger.LogInfo((object)("Loading video from queue: " + input));
			IsLoadingVideo = true;
			hasStartedCurrentVideo = true;
			skipRequested = false;
			VideoStreamer.Instance.GetVideoUrl(input, delegate(string resolvedUrl)
			{
				IsLoadingVideo = false;
				if (string.IsNullOrEmpty(resolvedUrl))
				{
					logger.LogError((object)("Failed to resolve video URL: " + input));
					if (VideoQueue.IncrementRetry(input))
					{
						string text = VideoQueue.RemoveCurrent();
						logger.LogError((object)("Removing video from queue after max retries: " + text));
						if ((Object)(object)HUDManager.Instance != (Object)null)
						{
							HUDManager.Instance.DisplayTip("Video Error", "Failed to load video. Removed from queue.", true, false, "LC_Tip1");
						}
						hasStartedCurrentVideo = false;
						if (!VideoQueue.IsEmpty())
						{
							((MonoBehaviour)this).Invoke("PlayNextFromQueue", 1f);
						}
					}
					else
					{
						logger.LogInfo((object)"Retrying video after delay...");
						hasStartedCurrentVideo = false;
						((MonoBehaviour)this).Invoke("PlayNextFromQueue", 3f);
					}
				}
				else
				{
					VideoQueue.ResetRetry(input);
					CurrentVideoUrl = resolvedUrl;
					logger.LogInfo((object)"Video URL resolved successfully!");
					if ((Object)(object)TVController.Instance != (Object)null)
					{
						TVController.Instance.PlayVideo(resolvedUrl);
						if (LNetworkUtils.IsHostOrServer && (Object)(object)NetworkHandler.Instance != (Object)null)
						{
							NetworkHandler.Instance.BroadcastPlayVideo(resolvedUrl, 0f);
						}
					}
					else
					{
						logger.LogError((object)"TVController not found!");
					}
				}
			});
		}

		public void OnVideoFinished()
		{
			logger.LogInfo((object)"Video finished playing");
			CurrentVideoUrl = null;
			hasStartedCurrentVideo = false;
			isPlayingFallback = false;
			if (!VideoQueue.IsEmpty() && !skipRequested)
			{
				((MonoBehaviour)this).Invoke("PlayNextFromQueue", 1f);
			}
			else
			{
				logger.LogInfo((object)"Queue empty, will return to fallback video");
			}
		}

		public void OnVideoPlaybackError(string errorMessage)
		{
			logger.LogError((object)("Video playback error: " + errorMessage));
			if (VideoQueue.IsEmpty())
			{
				logger.LogInfo((object)"No videos in queue to retry");
				return;
			}
			string input = VideoQueue.Current();
			if (VideoQueue.IncrementRetry(input))
			{
				string text = VideoQueue.RemoveCurrent();
				logger.LogError((object)("Removing video from queue after playback errors: " + text));
				hasStartedCurrentVideo = false;
				isPlayingFallback = false;
				CurrentVideoUrl = null;
				IsLoadingVideo = false;
				if (!VideoQueue.IsEmpty())
				{
					((MonoBehaviour)this).Invoke("PlayNextFromQueue", 1f);
				}
			}
			else
			{
				logger.LogInfo((object)"Retrying video after playback error...");
				hasStartedCurrentVideo = false;
				isPlayingFallback = false;
				CurrentVideoUrl = null;
				IsLoadingVideo = false;
				((MonoBehaviour)this).Invoke("PlayNextFromQueue", 3f);
			}
		}

		private bool IsTVOn()
		{
			if ((Object)(object)TVController.Instance == (Object)null)
			{
				return false;
			}
			TVScript tVScript = TVController.Instance.GetTVScript();
			if ((Object)(object)tVScript == (Object)null)
			{
				return false;
			}
			return tVScript.tvOn;
		}

		private void PlayFallbackVideo()
		{
			string fallbackVideoPath = GetFallbackVideoPath();
			logger.LogInfo((object)("Playing fallback video: " + fallbackVideoPath));
			if ((Object)(object)TVController.Instance != (Object)null)
			{
				TVController.Instance.PlayLocalVideo(fallbackVideoPath);
				isPlayingFallback = true;
				if (LNetworkUtils.IsHostOrServer && (Object)(object)NetworkHandler.Instance != (Object)null)
				{
					NetworkHandler.Instance.BroadcastPlayFallback();
				}
			}
			else
			{
				logger.LogError((object)"Cannot play fallback: TVController not found!");
			}
		}

		public void OnTVPoweredOn()
		{
			logger.LogInfo((object)"TV powered on - checking what to play");
			if ((Object)(object)TVController.Instance != (Object)null && TVController.Instance.IsPaused())
			{
				logger.LogInfo((object)"Resuming paused video");
				TVController.Instance.Resume();
			}
			else if (!VideoQueue.IsEmpty())
			{
				logger.LogInfo((object)"Queue has videos, will play from queue");
				hasStartedCurrentVideo = false;
			}
			else
			{
				logger.LogInfo((object)"Queue is empty, will play fallback");
			}
		}

		public void PlayVideoFromNetwork(string url, float startTime)
		{
			logger.LogInfo((object)$"Playing video from network: {url} at {startTime}s");
			if (LNetworkUtils.IsHostOrServer)
			{
				return;
			}
			CurrentVideoUrl = url;
			hasStartedCurrentVideo = true;
			isPlayingFallback = false;
			if ((Object)(object)TVController.Instance != (Object)null)
			{
				TVController.Instance.PlayVideo(url);
				if (startTime > 0f)
				{
					((MonoBehaviour)this).StartCoroutine(SetPlaybackTimeWhenReady(startTime));
				}
			}
		}

		public void SyncPlaybackTime(float time)
		{
			if (!LNetworkUtils.IsHostOrServer && !((Object)(object)TVController.Instance == (Object)null) && TVController.Instance.IsPlaying())
			{
				float num = (float)TVController.Instance.videoPlayer.time;
				float num2 = Mathf.Abs(num - time);
				if (num2 > 1f)
				{
					logger.LogInfo((object)$"Syncing playback time: {num}s -> {time}s (diff: {num2}s)");
					TVController.Instance.videoPlayer.time = time;
				}
			}
		}

		[IteratorStateMachine(typeof(<SetPlaybackTimeWhenReady>d__32))]
		private IEnumerator SetPlaybackTimeWhenReady(float startTime)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <SetPlaybackTimeWhenReady>d__32(0)
			{
				<>4__this = this,
				startTime = startTime
			};
		}

		public void PlayFallbackFromNetwork()
		{
			logger.LogInfo((object)"Playing fallback from network");
			if (!LNetworkUtils.IsHostOrServer)
			{
				string fallbackVideoPath = GetFallbackVideoPath();
				if ((Object)(object)TVController.Instance != (Object)null)
				{
					TVController.Instance.PlayLocalVideo(fallbackVideoPath);
					isPlayingFallback = true;
					hasStartedCurrentVideo = false;
					CurrentVideoUrl = null;
				}
			}
		}

		public TVStateData GetCurrentTVState()
		{
			TVStateData tVStateData = default(TVStateData);
			tVStateData.isTVOn = IsTVOn();
			tVStateData.isPlayingFallback = isPlayingFallback;
			tVStateData.currentVideoUrl = CurrentVideoUrl;
			tVStateData.currentPlaybackTime = 0f;
			tVStateData.isPlaying = false;
			TVStateData result = tVStateData;
			if ((Object)(object)TVController.Instance != (Object)null)
			{
				result.isPlaying = TVController.Instance.IsPlaying();
				if (result.isPlaying && !isPlayingFallback)
				{
					result.currentPlaybackTime = (float)TVController.Instance.videoPlayer.time;
				}
			}
			logger.LogInfo((object)$"Gathering TV state - TVOn: {result.isTVOn}, Fallback: {result.isPlayingFallback}, Playing: {result.isPlaying}, URL: {result.currentVideoUrl}");
			return result;
		}

		public void ApplyTVStateFromNetwork(TVStateData state)
		{
			logger.LogInfo((object)$"Applying TV state - TVOn: {state.isTVOn}, Fallback: {state.isPlayingFallback}, URL: {state.currentVideoUrl}");
			if (LNetworkUtils.IsHostOrServer)
			{
				return;
			}
			if ((Object)(object)TVController.Instance == (Object)null)
			{
				logger.LogError((object)"Cannot apply TV state: TVController not found!");
				return;
			}
			TVScript tVScript = TVController.Instance.GetTVScript();
			if ((Object)(object)tVScript == (Object)null)
			{
				logger.LogError((object)"Cannot apply TV state: TVScript not found!");
				return;
			}
			if (!state.isTVOn)
			{
				logger.LogInfo((object)"TV is off, ensuring TV is off and stopping playback");
				if (tVScript.tvOn)
				{
					tVScript.TurnTVOnOff(false);
				}
				TVController.Instance.Stop();
				isPlayingFallback = false;
				hasStartedCurrentVideo = false;
				CurrentVideoUrl = null;
				return;
			}
			if (!tVScript.tvOn)
			{
				logger.LogInfo((object)"Turning on TV to sync with host");
				tVScript.TurnTVOnOff(true);
			}
			if (state.isPlayingFallback)
			{
				logger.LogInfo((object)"Syncing to fallback video");
				string fallbackVideoPath = GetFallbackVideoPath();
				TVController.Instance.PlayLocalVideo(fallbackVideoPath);
				isPlayingFallback = true;
				hasStartedCurrentVideo = false;
				CurrentVideoUrl = null;
			}
			else if (!string.IsNullOrEmpty(state.currentVideoUrl))
			{
				logger.LogInfo((object)$"Syncing to video: {state.currentVideoUrl} at {state.currentPlaybackTime}s");
				CurrentVideoUrl = state.currentVideoUrl;
				hasStartedCurrentVideo = true;
				isPlayingFallback = false;
				TVController.Instance.PlayVideo(state.currentVideoUrl);
				if (state.currentPlaybackTime > 0f)
				{
					((MonoBehaviour)this).StartCoroutine(SetPlaybackTimeWhenReady(state.currentPlaybackTime));
				}
			}
			else
			{
				logger.LogInfo((object)"TV is on but nothing is playing");
				TVController.Instance.Stop();
				isPlayingFallback = false;
				hasStartedCurrentVideo = false;
				CurrentVideoUrl = null;
			}
		}
	}
	public static class VideoQueue
	{
		private static readonly List<string> _inputs = new List<string>();

		private static readonly Dictionary<string, int> _retryCount = new Dictionary<string, int>();

		private static int _ptr = 0;

		private const int MAX_RETRIES = 2;

		public static void Add(string input)
		{
			Debug.Log((object)$"[VideoQueue] Add called with: '{input}' (length: {input.Length})");
			string text = "";
			string text2 = ExtractYouTubeVideoId(input);
			if (!string.IsNullOrEmpty(text2))
			{
				text = "https://www.youtube.com/watch?v=" + text2;
				Debug.Log((object)("[VideoQueue] Extracted video ID '" + text2 + "', constructed URL: '" + text + "'"));
			}
			else if (input.Length == 11 && !input.Contains(" ") && IsValidVideoId(input))
			{
				text = "https://www.youtube.com/watch?v=" + input;
				Debug.Log((object)("[VideoQueue] Detected as video ID, expanded to: '" + text + "'"));
			}
			else if (input.StartsWith("http://") || input.StartsWith("https://"))
			{
				text = input;
				Debug.Log((object)("[VideoQueue] Detected as URL, adding as-is: '" + text + "'"));
			}
			else
			{
				text = "ytsearch:" + input;
				Debug.Log((object)("[VideoQueue] Detected as search query, prefixed: '" + text + "'"));
			}
			_inputs.Add(text);
			Debug.Log((object)$"[VideoQueue] Successfully added. Queue size: {_inputs.Count}");
		}

		private static string ExtractYouTubeVideoId(string input)
		{
			if (string.IsNullOrEmpty(input))
			{
				return null;
			}
			Match match = Regex.Match(input, "[?&]v=([a-zA-Z0-9_-]{11})");
			if (match.Success)
			{
				return match.Groups[1].Value;
			}
			match = Regex.Match(input, "youtu\\.be/([a-zA-Z0-9_-]{11})");
			if (match.Success)
			{
				return match.Groups[1].Value;
			}
			match = Regex.Match(input, "youtube\\.com/embed/([a-zA-Z0-9_-]{11})");
			if (match.Success)
			{
				return match.Groups[1].Value;
			}
			match = Regex.Match(input, "youtube\\.com/v/([a-zA-Z0-9_-]{11})");
			if (match.Success)
			{
				return match.Groups[1].Value;
			}
			return null;
		}

		private static bool IsValidVideoId(string input)
		{
			if (string.IsNullOrEmpty(input) || input.Length != 11)
			{
				return false;
			}
			return Regex.IsMatch(input, "^[a-zA-Z0-9_-]{11}$");
		}

		public static void Clear()
		{
			_inputs.Clear();
			_retryCount.Clear();
			_ptr = 0;
		}

		public static bool IsEmpty()
		{
			return _inputs.Count == 0;
		}

		public static string Next()
		{
			if (_inputs.Count == 0)
			{
				return null;
			}
			string result = _inputs[_ptr];
			_ptr = (_ptr + 1) % _inputs.Count;
			return result;
		}

		public static string Current()
		{
			if (_inputs.Count == 0)
			{
				return null;
			}
			return _inputs[_ptr];
		}

		public static void Skip()
		{
			if (_inputs.Count > 0)
			{
				_ptr = (_ptr + 1) % _inputs.Count;
			}
		}

		public static int Count()
		{
			return _inputs.Count;
		}

		public static bool IncrementRetry(string input)
		{
			if (!_retryCount.ContainsKey(input))
			{
				_retryCount[input] = 0;
			}
			_retryCount[input]++;
			Debug.Log((object)$"[VideoQueue] Retry count for '{input}': {_retryCount[input]}/{2}");
			return _retryCount[input] >= 2;
		}

		public static string RemoveCurrent()
		{
			if (_inputs.Count == 0)
			{
				return null;
			}
			string text = _inputs[_ptr];
			_inputs.RemoveAt(_ptr);
			_retryCount.Remove(text);
			if (_inputs.Count > 0)
			{
				_ptr %= _inputs.Count;
			}
			else
			{
				_ptr = 0;
			}
			Debug.Log((object)$"[VideoQueue] Removed '{text}' from queue. Remaining: {_inputs.Count}");
			return text;
		}

		public static void ResetRetry(string input)
		{
			if (_retryCount.ContainsKey(input))
			{
				_retryCount.Remove(input);
				Debug.Log((object)("[VideoQueue] Reset retry count for '" + input + "'"));
			}
		}
	}
	public class VideoStreamer : MonoBehaviour
	{
		[CompilerGenerated]
		private sealed class <>c__DisplayClass11_0
		{
			public StringBuilder outputBuilder;

			public StringBuilder errorBuilder;

			public YoutubeDLProcess ytdlProc;

			public string input;

			public OptionSet options;

			internal void <GetVideoUrlCoroutine>b__0(object o, DataReceivedEventArgs e)
			{
				if (!string.IsNullOrEmpty(e.Data))
				{
					outputBuilder.AppendLine(e.Data);
				}
			}

			internal void <GetVideoUrlCoroutine>b__1(object o, DataReceivedEventArgs e)
			{
				if (!string.IsNullOrEmpty(e.Data))
				{
					errorBuilder.AppendLine(e.Data);
				}
			}

			internal async Task<int> <GetVideoUrlCoroutine>b__2()
			{
				return await ytdlProc.RunAsync(new string[1] { input }, options);
			}
		}

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

			private object <>2__current;

			public string input;

			public Action<string> onUrlFound;

			public VideoStreamer <>4__this;

			private <>c__DisplayClass11_0 <>8__1;

			private Task<int> <processTask>5__2;

			private int <exitCode>5__3;

			private string <output>5__4;

			private string <error>5__5;

			private Exception <ex>5__6;

			private string[] <urls>5__7;

			private string <videoUrl>5__8;

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

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

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

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>8__1 = null;
				<processTask>5__2 = null;
				<output>5__4 = null;
				<error>5__5 = null;
				<ex>5__6 = null;
				<urls>5__7 = null;
				<videoUrl>5__8 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
				//IL_00cd: Expected O, but got Unknown
				//IL_0137: Unknown result type (might be due to invalid IL or missing references)
				//IL_013c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0148: Unknown result type (might be due to invalid IL or missing references)
				//IL_0155: Expected O, but got Unknown
				//IL_0200: Unknown result type (might be due to invalid IL or missing references)
				//IL_020a: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<>8__1 = new <>c__DisplayClass11_0();
					<>8__1.input = input;
					goto IL_006e;
				case 1:
					<>1__state = -1;
					goto IL_006e;
				case 2:
					<>1__state = -1;
					goto IL_01e6;
				case 3:
					{
						<>1__state = -1;
						if (<processTask>5__2.IsFaulted)
						{
							<>4__this._logger.LogError((object)("yt-dlp process failed: " + <processTask>5__2.Exception?.GetBaseException().Message));
							onUrlFound(null);
							<>4__this._isResolving = false;
							return false;
						}
						<exitCode>5__3 = <processTask>5__2.Result;
						<output>5__4 = <>8__1.outputBuilder.ToString().Trim();
						<error>5__5 = <>8__1.errorBuilder.ToString().Trim();
						<>4__this._logger.LogInfo((object)$"yt-dlp exit code: {<exitCode>5__3}");
						<>4__this._logger.LogInfo((object)$"yt-dlp stdout length: {<output>5__4.Length}");
						if (!string.IsNullOrEmpty(<error>5__5))
						{
							<>4__this._logger.LogInfo((object)$"yt-dlp stderr length: {<error>5__5.Length}");
						}
						if (<exitCode>5__3 != 0 || string.IsNullOrEmpty(<output>5__4))
						{
							<>4__this._logger.LogError((object)$"yt-dlp failed (Exit Code: {<exitCode>5__3})");
							if (!string.IsNullOrEmpty(<error>5__5))
							{
								<>4__this._logger.LogError((object)("Error: " + <error>5__5));
							}
							onUrlFound(null);
						}
						else
						{
							<urls>5__7 = <output>5__4.Split(new char[2] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
							if (<urls>5__7.Length != 0)
							{
								<videoUrl>5__8 = <urls>5__7[0].Trim();
								<>4__this._logger.LogInfo((object)"Video URL resolved successfully!");
								<>4__this._logger.LogInfo((object)("URL: " + <videoUrl>5__8.Substring(0, Math.Min(100, <videoUrl>5__8.Length)) + "..."));
								onUrlFound(<videoUrl>5__8);
								<videoUrl>5__8 = null;
							}
							else
							{
								<>4__this._logger.LogError((object)"yt-dlp returned empty output");
								onUrlFound(null);
							}
							<urls>5__7 = null;
						}
						<>4__this._isResolving = false;
						return false;
					}
					IL_006e:
					if (!<>4__this._isInitialized)
					{
						<>2__current = null;
						<>1__state = 1;
						return true;
					}
					<>4__this._isResolving = true;
					<>4__this._logger.LogInfo((object)("Resolving video URL for: " + <>8__1.input));
					<>8__1.ytdlProc = new YoutubeDLProcess(<>4__this._ytDlpPath);
					<>8__1.outputBuilder = new StringBuilder();
					<>8__1.errorBuilder = new StringBuilder();
					<>8__1.ytdlProc.OutputReceived += delegate(object o, DataReceivedEventArgs e)
					{
						if (!string.IsNullOrEmpty(e.Data))
						{
							<>8__1.outputBuilder.AppendLine(e.Data);
						}
					};
					<>8__1.ytdlProc.ErrorReceived += delegate(object o, DataReceivedEventArgs e)
					{
						if (!string.IsNullOrEmpty(e.Data))
						{
							<>8__1.errorBuilder.AppendLine(e.Data);
						}
					};
					<>8__1.options = new OptionSet
					{
						Format = "18/22/(mp4)[height<=480]/worst",
						GetUrl = true
					};
					<processTask>5__2 = null;
					try
					{
						<processTask>5__2 = Task.Run(async () => await <>8__1.ytdlProc.RunAsync(new string[1] { <>8__1.input }, <>8__1.options));
					}
					catch (Exception ex)
					{
						<ex>5__6 = ex;
						<>4__this._logger.LogError((object)("Failed to start yt-dlp process: " + <ex>5__6.Message));
						onUrlFound(null);
						<>4__this._isResolving = false;
						return false;
					}
					goto IL_01e6;
					IL_01e6:
					if (!<processTask>5__2.IsCompleted)
					{
						<>2__current = null;
						<>1__state = 2;
						return true;
					}
					<>2__current = (object)new WaitForSeconds(0.2f);
					<>1__state = 3;
					return true;
				}
			}

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

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

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

			private object <>2__current;

			public VideoStreamer <>4__this;

			private string <dllPath>5__1;

			private Task <downloadTask>5__2;

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

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

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

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<dllPath>5__1 = null;
				<downloadTask>5__2 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				int num = <>1__state;
				if (num != 0)
				{
					if (num != 1)
					{
						return false;
					}
					<>1__state = -1;
				}
				else
				{
					<>1__state = -1;
					if (File.Exists(<>4__this._ytDlpPath))
					{
						<>4__this._logger.LogInfo((object)"yt-dlp.exe found at path.");
						goto IL_0138;
					}
					<>4__this._logger.LogInfo((object)"yt-dlp.exe not found. Downloading...");
					<dllPath>5__1 = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
					<downloadTask>5__2 = Utils.DownloadYtDlp(<dllPath>5__1);
				}
				if (!<downloadTask>5__2.IsCompleted)
				{
					<>2__current = null;
					<>1__state = 1;
					return true;
				}
				if (<downloadTask>5__2.IsFaulted)
				{
					<>4__this._logger.LogError((object)("Failed to download yt-dlp.exe: " + <downloadTask>5__2.Exception?.GetBaseException().Message));
					<>4__this._isInitialized = false;
					return false;
				}
				<>4__this._logger.LogInfo((object)"yt-dlp.exe downloaded successfully!");
				<dllPath>5__1 = null;
				<downloadTask>5__2 = null;
				goto IL_0138;
				IL_0138:
				<>4__this._isInitialized = true;
				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();
			}
		}

		private static VideoStreamer _instance;

		private string _ytDlpPath;

		private bool _isResolving;

		private ManualLogSource _logger;

		private YoutubeDL _ytdl;

		private bool _isInitialized;

		public static VideoStreamer Instance
		{
			get
			{
				//IL_0016: Unknown result type (might be due to invalid IL or missing references)
				//IL_001c: Expected O, but got Unknown
				if ((Object)(object)_instance == (Object)null)
				{
					GameObject val = new GameObject("VideoStreamer");
					_instance = val.AddComponent<VideoStreamer>();
					Object.DontDestroyOnLoad((Object)(object)val);
				}
				return _instance;
			}
		}

		private void Awake()
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Expected O, but got Unknown
			_logger = Logger.CreateLogSource("YoutubeOnTV");
			string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
			_ytDlpPath = Path.Combine(directoryName, "yt-dlp.exe");
			_logger.LogInfo((object)("VideoStreamer initialized. yt-dlp path: " + _ytDlpPath));
			_ytdl = new YoutubeDL((byte)4);
			_ytdl.YoutubeDLPath = _ytDlpPath;
			_ytdl.OutputFolder = Path.Combine(directoryName, "temp");
			((MonoBehaviour)this).StartCoroutine(InitializeYtDlp());
		}

		[IteratorStateMachine(typeof(<InitializeYtDlp>d__9))]
		private IEnumerator InitializeYtDlp()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <InitializeYtDlp>d__9(0)
			{
				<>4__this = this
			};
		}

		public void GetVideoUrl(string input, Action<string> onUrlFound)
		{
			((MonoBehaviour)this).StartCoroutine(GetVideoUrlCoroutine(input, onUrlFound));
		}

		[IteratorStateMachine(typeof(<GetVideoUrlCoroutine>d__11))]
		private IEnumerator GetVideoUrlCoroutine(string input, Action<string> onUrlFound)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <GetVideoUrlCoroutine>d__11(0)
			{
				<>4__this = this,
				input = input,
				onUrlFound = onUrlFound
			};
		}

		public bool IsResolving()
		{
			return _isResolving;
		}
	}
	public class NetworkHandler : MonoBehaviour
	{
		private LNetworkMessage<string> addVideoMessage;

		private LNetworkEvent skipVideoEvent;

		private LNetworkEvent clearQueueEvent;

		private LNetworkMessage<VideoPlayData> playVideoMessage;

		private LNetworkMessage<float> syncPlaybackMessage;

		private LNetworkEvent playFallbackEvent;

		private LNetworkEvent requestTVStateEvent;

		private LNetworkMessage<TVStateData> syncTVStateMessage;

		public static NetworkHandler Instance { get; private set; }

		private void Awake()
		{
			if ((Object)(object)Instance == (Object)null)
			{
				Instance = this;
				Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject);
				InitializeNetworkMessages();
				YoutubeOnTVBase.Instance.mls.LogInfo((object)"NetworkHandler initialized with LethalNetworkAPI!");
			}
			else
			{
				Object.Destroy((Object)(object)((Component)this).gameObject);
			}
		}

		private void InitializeNetworkMessages()
		{
			addVideoMessage = LNetworkMessage<string>.Connect("YoutubeOnTV_AddVideo", (Action<string, ulong>)OnServerReceivedAddVideo, (Action<string>)OnClientReceivedAddVideo, (Action<string, ulong>)null);
			skipVideoEvent = LNetworkEvent.Connect("YoutubeOnTV_Skip", (Action<ulong>)OnServerReceivedSkip, (Action)OnClientReceivedSkip, (Action<ulong>)null);
			clearQueueEvent = LNetworkEvent.Connect("YoutubeOnTV_Clear", (Action<ulong>)OnServerReceivedClear, (Action)OnClientReceivedClear, (Action<ulong>)null);
			playVideoMessage = LNetworkMessage<VideoPlayData>.Connect("YoutubeOnTV_PlayVideo", (Action<VideoPlayData, ulong>)null, (Action<VideoPlayData>)OnClientReceivedPlayVideo, (Action<VideoPlayData, ulong>)null);
			syncPlaybackMessage = LNetworkMessage<float>.Connect("YoutubeOnTV_SyncPlayback", (Action<float, ulong>)null, (Action<float>)OnClientReceivedSyncPlayback, (Action<float, ulong>)null);
			playFallbackEvent = LNetworkEvent.Connect("YoutubeOnTV_PlayFallback", (Action<ulong>)null, (Action)OnClientReceivedPlayFallback, (Action<ulong>)null);
			requestTVStateEvent = LNetworkEvent.Connect("YoutubeOnTV_RequestTVState", (Action<ulong>)OnServerReceivedRequestTVState, (Action)null, (Action<ulong>)null);
			syncTVStateMessage = LNetworkMessage<TVStateData>.Connect("YoutubeOnTV_SyncTVState", (Action<TVStateData, ulong>)null, (Action<TVStateData>)OnClientReceivedSyncTVState, (Action<TVStateData, ulong>)null);
			YoutubeOnTVBase.Instance.mls.LogInfo((object)"Network messages initialized!");
		}

		public void RequestAddVideo(string input)
		{
			if (LNetworkUtils.IsHostOrServer)
			{
				OnServerReceivedAddVideo(input, 0uL);
			}
			else
			{
				addVideoMessage.SendServer(input);
			}
		}

		public void RequestSkipVideo()
		{
			if (LNetworkUtils.IsHostOrServer)
			{
				OnServerReceivedSkip(0uL);
			}
			else
			{
				skipVideoEvent.InvokeServer();
			}
		}

		public void RequestClearQueue()
		{
			if (LNetworkUtils.IsHostOrServer)
			{
				OnServerReceivedClear(0uL);
			}
			else
			{
				clearQueueEvent.InvokeServer();
			}
		}

		public void BroadcastPlayVideo(string url, float startTime)
		{
			if (!LNetworkUtils.IsHostOrServer)
			{
				YoutubeOnTVBase.Instance.mls.LogWarning((object)"Only host can broadcast play video!");
				return;
			}
			VideoPlayData videoPlayData = default(VideoPlayData);
			videoPlayData.url = url;
			videoPlayData.startTime = startTime;
			VideoPlayData videoPlayData2 = videoPlayData;
			playVideoMessage.SendClients(videoPlayData2);
			YoutubeOnTVBase.Instance.mls.LogInfo((object)$"Broadcasting play video: {url} at {startTime}s");
		}

		public void BroadcastPlaybackTime(float time)
		{
			if (LNetworkUtils.IsHostOrServer)
			{
				syncPlaybackMessage.SendClients(time);
			}
		}

		public void BroadcastPlayFallback()
		{
			if (!LNetworkUtils.IsHostOrServer)
			{
				YoutubeOnTVBase.Instance.mls.LogWarning((object)"Only host can broadcast play fallback!");
				return;
			}
			playFallbackEvent.InvokeClients();
			YoutubeOnTVBase.Instance.mls.LogInfo((object)"Broadcasting play fallback");
		}

		public void RequestTVState()
		{
			if (LNetworkUtils.IsHostOrServer)
			{
				YoutubeOnTVBase.Instance.mls.LogWarning((object)"Host doesn't need to request TV state!");
				return;
			}
			requestTVStateEvent.InvokeServer();
			YoutubeOnTVBase.Instance.mls.LogInfo((object)"Requesting TV state from host");
		}

		public void BroadcastTVState(TVStateData state)
		{
			if (!LNetworkUtils.IsHostOrServer)
			{
				YoutubeOnTVBase.Instance.mls.LogWarning((object)"Only host can broadcast TV state!");
				return;
			}
			syncTVStateMessage.SendClients(state);
			YoutubeOnTVBase.Instance.mls.LogInfo((object)$"Broadcasting TV state - TVOn: {state.isTVOn}, Fallback: {state.isPlayingFallback}, URL: {state.currentVideoUrl}");
		}

		private void OnServerReceivedAddVideo(string input, ulong clientId)
		{
			YoutubeOnTVBase.Instance.mls.LogInfo((object)("[Host] Received add video request: " + input));
			addVideoMessage.SendClients(input);
		}

		private void OnServerReceivedSkip(ulong clientId)
		{
			YoutubeOnTVBase.Instance.mls.LogInfo((object)"[Host] Received skip request");
			skipVideoEvent.InvokeClients();
		}

		private void OnServerReceivedClear(ulong clientId)
		{
			YoutubeOnTVBase.Instance.mls.LogInfo((object)"[Host] Received clear queue request");
			clearQueueEvent.InvokeClients();
		}

		private void OnServerReceivedRequestTVState(ulong clientId)
		{
			YoutubeOnTVBase.Instance.mls.LogInfo((object)$"[Host] Received TV state request from client {clientId}");
			if ((Object)(object)VideoManager.Instance != (Object)null)
			{
				TVStateData currentTVState = VideoManager.Instance.GetCurrentTVState();
				BroadcastTVState(currentTVState);
			}
		}

		private void OnClientReceivedAddVideo(string input)
		{
			YoutubeOnTVBase.Instance.mls.LogInfo((object)("[Client] Adding video to queue: " + input));
			VideoQueue.Add(input);
		}

		private void OnClientReceivedSkip()
		{
			YoutubeOnTVBase.Instance.mls.LogInfo((object)"[Client] Skipping video");
			VideoQueue.Skip();
			if ((Object)(object)VideoManager.Instance != (Object)null)
			{
				VideoManager.Instance.OnSkipRequested();
			}
		}

		private void OnClientReceivedClear()
		{
			YoutubeOnTVBase.Instance.mls.LogInfo((object)"[Client] Clearing queue");
			VideoQueue.Clear();
			if ((Object)(object)VideoManager.Instance != (Object)null)
			{
				VideoManager.Instance.OnSkipRequested();
			}
		}

		private void OnClientReceivedPlayVideo(VideoPlayData data)
		{
			YoutubeOnTVBase.Instance.mls.LogInfo((object)$"[Client] Received play video: {data.url} at {data.startTime}s");
			if ((Object)(object)VideoManager.Instance != (Object)null)
			{
				VideoManager.Instance.PlayVideoFromNetwork(data.url, data.startTime);
			}
		}

		private void OnClientReceivedSyncPlayback(float time)
		{
			if ((Object)(object)VideoManager.Instance != (Object)null)
			{
				VideoManager.Instance.SyncPlaybackTime(time);
			}
		}

		private void OnClientReceivedPlayFallback()
		{
			YoutubeOnTVBase.Instance.mls.LogInfo((object)"[Client] Received play fallback command");
			if ((Object)(object)VideoManager.Instance != (Object)null)
			{
				VideoManager.Instance.PlayFallbackFromNetwork();
			}
		}

		private void OnClientReceivedSyncTVState(TVStateData state)
		{
			YoutubeOnTVBase.Instance.mls.LogInfo((object)$"[Client] Received TV state - TVOn: {state.isTVOn}, Fallback: {state.isPlayingFallback}, URL: {state.currentVideoUrl}");
			if ((Object)(object)VideoManager.Instance != (Object)null)
			{
				VideoManager.Instance.ApplyTVStateFromNetwork(state);
			}
		}
	}
	[Serializable]
	public struct VideoPlayData
	{
		public string url;

		public float startTime;
	}
	[Serializable]
	public struct TVStateData
	{
		public bool isTVOn;

		public bool isPlayingFallback;

		public string currentVideoUrl;

		public float currentPlaybackTime;

		public bool isPlaying;
	}
	public class TVController : MonoBehaviour
	{
		public VideoPlayer videoPlayer;

		private VideoPlayer vanillaVideoPlayer;

		private RenderTexture renderTexture;

		private AudioSource tvAudioSource;

		private TVScript tvScript;

		private ManualLogSource logger;

		public static TVController Instance { get; private set; }

		private void Awake()
		{
			//IL_0200: Unknown result type (might be due to invalid IL or missing references)
			//IL_020a: Expected O, but got Unknown
			//IL_0218: Unknown result type (might be due to invalid IL or missing references)
			//IL_0222: Expected O, but got Unknown
			//IL_0230: Unknown result type (might be due to invalid IL or missing references)
			//IL_023a: Expected O, but got Unknown
			//IL_0248: Unknown result type (might be due to invalid IL or missing references)
			//IL_0252: Expected O, but got Unknown
			Instance = this;
			logger = Logger.CreateLogSource("YoutubeOnTV");
			tvScript = ((Component)this).gameObject.GetComponent<TVScript>();
			if ((Object)(object)tvScript == (Object)null)
			{
				logger.LogError((object)"TVScript not found on this GameObject!");
				return;
			}
			tvAudioSource = tvScript.tvSFX;
			logger.LogInfo((object)("Found TV AudioSource: " + ((Object)tvAudioSource).name));
			vanillaVideoPlayer = tvScript.video;
			if ((Object)(object)vanillaVideoPlayer != (Object)null)
			{
				renderTexture = vanillaVideoPlayer.targetTexture;
				logger.LogInfo((object)$"Captured render texture: {((Texture)renderTexture).width}x{((Texture)renderTexture).height}");
				vanillaVideoPlayer.Stop();
				((Behaviour)vanillaVideoPlayer).enabled = false;
				logger.LogInfo((object)"Disabled vanilla VideoPlayer");
			}
			else
			{
				logger.LogError((object)"Vanilla VideoPlayer not found!");
			}
			videoPlayer = ((Component)this).gameObject.AddComponent<VideoPlayer>();
			logger.LogInfo((object)"Created custom VideoPlayer");
			videoPlayer.playOnAwake = false;
			videoPlayer.isLooping = false;
			videoPlayer.source = (VideoSource)1;
			videoPlayer.skipOnDrop = true;
			videoPlayer.controlledAudioTrackCount = 1;
			videoPlayer.audioOutputMode = (VideoAudioOutputMode)1;
			videoPlayer.SetTargetAudioSource((ushort)0, tvAudioSource);
			videoPlayer.targetTexture = renderTexture;
			logger.LogInfo((object)"Configured VideoPlayer with TV's render texture");
			tvScript.video = videoPlayer;
			logger.LogInfo((object)"Replaced TVScript.video with custom VideoPlayer");
			videoPlayer.loopPointReached -= new EventHandler(OnVideoEnd);
			videoPlayer.loopPointReached += new EventHandler(OnVideoEnd);
			videoPlayer.errorReceived -= new ErrorEventHandler(OnVideoError);
			videoPlayer.errorReceived += new ErrorEventHandler(OnVideoError);
			logger.LogInfo((object)"TVController initialized successfully!");
		}

		private void OnDestroy()
		{
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
		}

		public void PlayVideo(string url)
		{
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Expected O, but got Unknown
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Expected O, but got Unknown
			if (string.IsNullOrEmpty(url))
			{
				logger.LogError((object)"Cannot play video: URL is null or empty");
				return;
			}
			logger.LogInfo((object)("Playing video: " + url.Substring(0, Math.Min(100, url.Length)) + "..."));
			videoPlayer.url = url;
			videoPlayer.prepareCompleted -= new EventHandler(OnVideoPrepared);
			videoPlayer.prepareCompleted += new EventHandler(OnVideoPrepared);
			videoPlayer.Prepare();
		}

		private void OnVideoPrepared(VideoPlayer vp)
		{
			logger.LogInfo((object)$"Video prepared! Audio tracks: {vp.audioTrackCount}");
			if (vp.audioTrackCount > 0)
			{
				logger.LogInfo((object)$"Audio channels: {vp.GetAudioChannelCount((ushort)0)}");
			}
			else
			{
				logger.LogWarning((object)"Video has NO audio tracks!");
			}
			logger.LogInfo((object)$"TV AudioSource volume: {tvAudioSource.volume}");
			vp.Play();
			logger.LogInfo((object)"Video playback started!");
		}

		public void Stop()
		{
			if (videoPlayer.isPlaying)
			{
				videoPlayer.Stop();
				logger.LogInfo((object)"Video stopped");
			}
		}

		public void Pause()
		{
			if (videoPlayer.isPlaying)
			{
				videoPlayer.Pause();
				logger.LogInfo((object)"Video paused");
			}
		}

		public void Resume()
		{
			if (!videoPlayer.isPlaying && !string.IsNullOrEmpty(videoPlayer.url))
			{
				videoPlayer.Play();
				logger.LogInfo((object)"Video resumed");
			}
		}

		public bool IsPaused()
		{
			return !videoPlayer.isPlaying && !string.IsNullOrEmpty(videoPlayer.url) && videoPlayer.isPrepared;
		}

		public bool IsPlaying()
		{
			return videoPlayer.isPlaying;
		}

		public void SetLooping(bool shouldLoop)
		{
			videoPlayer.isLooping = shouldLoop;
			logger.LogInfo((object)$"Video looping set to: {shouldLoop}");
		}

		public void PlayLocalVideo(string filePath, bool shouldLoop = false)
		{
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Expected O, but got Unknown
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Expected O, but got Unknown
			if (string.IsNullOrEmpty(filePath))
			{
				logger.LogError((object)"Cannot play local video: file path is null or empty");
				return;
			}
			if (!File.Exists(filePath))
			{
				logger.LogError((object)("Cannot play local video: file not found at " + filePath));
				return;
			}
			logger.LogInfo((object)("Playing local video: " + filePath));
			videoPlayer.isLooping = shouldLoop;
			videoPlayer.url = "file://" + filePath;
			videoPlayer.prepareCompleted -= new EventHandler(OnVideoPrepared);
			videoPlayer.prepareCompleted += new EventHandler(OnVideoPrepared);
			videoPlayer.Prepare();
		}

		public TVScript GetTVScript()
		{
			return tvScript;
		}

		private void OnVideoEnd(VideoPlayer vp)
		{
			logger.LogInfo((object)"Video playback completed");
			if (!vp.isLooping && (Object)(object)VideoManager.Instance != (Object)null)
			{
				VideoManager.Instance.OnVideoFinished();
			}
		}

		private void OnVideoError(VideoPlayer vp, string message)
		{
			logger.LogError((object)("Video playback error: " + message));
			if ((Object)(object)VideoManager.Instance != (Object)null)
			{
				VideoManager.Instance.OnVideoPlaybackError(message);
			}
			if ((Object)(object)HUDManager.Instance != (Object)null)
			{
				HUDManager.Instance.DisplayTip("Video Playback Error", "Failed to play video. Trying next...", true, false, "LC_Tip1");
			}
		}
	}
	public static class PluginInfo
	{
		public const string PLUGIN_GUID = "YoutubeOnTV";

		public const string PLUGIN_NAME = "";

		public const string PLUGIN_VERSION = "1.0.0.0";
	}
}
namespace YoutubeOnTV.Patches
{
	[HarmonyPatch(typeof(TVScript))]
	internal class TVScriptPatch
	{
		[HarmonyPatch("Update")]
		[HarmonyPrefix]
		private static bool UpdatePatch(TVScript __instance)
		{
			if ((Object)(object)((Component)__instance).gameObject.GetComponent<TVController>() == (Object)null)
			{
				((Component)__instance).gameObject.AddComponent<TVController>();
				Debug.Log((object)"Attached TVController to TVScript object.");
			}
			return false;
		}

		[HarmonyPatch("TurnTVOnOff")]
		[HarmonyPrefix]
		private static bool TurnTVOnOffPatch(TVScript __instance, bool on)
		{
			__instance.tvOn = on;
			if (on)
			{
				Debug.Log((object)"TV turned ON - triggering mod video playback");
				__instance.tvSFX.PlayOneShot(__instance.switchTVOn);
				WalkieTalkie.TransmitOneShotAudio(__instance.tvSFX, __instance.switchTVOn, 1f);
				if ((Object)(object)VideoManager.Instance != (Object)null)
				{
					VideoManager.Instance.OnTVPoweredOn();
				}
			}
			else
			{
				Debug.Log((object)"TV turned OFF");
				__instance.tvSFX.PlayOneShot(__instance.switchTVOff);
				WalkieTalkie.TransmitOneShotAudio(__instance.tvSFX, __instance.switchTVOff, 1f);
			}
			typeof(TVScript).GetMethod("SetTVScreenMaterial", BindingFlags.Instance | BindingFlags.NonPublic)?.Invoke(__instance, new object[1] { on });
			return false;
		}
	}
	[HarmonyPatch(typeof(Terminal))]
	internal class TerminalPatch
	{
		[HarmonyPatch("TextChanged")]
		[HarmonyPrefix]
		private static bool TextChangedPatch(Terminal __instance, string newText, ref int ___textAdded, ref string ___currentText, ref bool ___modifyingText)
		{
			if ((Object)(object)__instance.currentNode == (Object)null)
			{
				return false;
			}
			if (___modifyingText)
			{
				___modifyingText = false;
				return false;
			}
			___textAdded += newText.Length - ___currentText.Length;
			if (___textAdded < 0)
			{
				__instance.screenText.text = ___currentText;
				___textAdded = 0;
				return false;
			}
			int num = 500;
			if (___textAdded > num)
			{
				__instance.screenText.text = ___currentText;
				___textAdded = num;
				return false;
			}
			___currentText = newText;
			return false;
		}
	}
}