Decompiled source of YoutubeShortsKR v2.4.1

KRBroadcasting.dll

Decompiled 5 hours ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using GameNetcodeStuff;
using HarmonyLib;
using KRBroadcasting.Patches;
using LethalCompanyInputUtils.Api;
using LethalNetworkAPI;
using LethalNetworkAPI.Utils;
using Microsoft.CodeAnalysis;
using TMPro;
using TerminalApi;
using TerminalApi.Classes;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.InputSystem.Utilities;
using UnityEngine.SceneManagement;
using UnityEngine.Video;
using YoutubeDLSharp;
using YoutubeDLSharp.Options;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[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;
		}
	}
}
namespace KRBroadcasting
{
	[BepInPlugin("com.mine9289.krbroadcasting", "KRBroadcasting", "2.4.1")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class KRBroadcastingPlugin : BaseUnityPlugin
	{
		public const string PLUGIN_GUID = "com.mine9289.krbroadcasting";

		public const string PLUGIN_NAME = "KRBroadcasting";

		public const string PLUGIN_VERSION = "2.4.1";

		private readonly Harmony harmony = new Harmony("com.mine9289.krbroadcasting");

		public static KRBroadcastingPlugin Instance;

		public static ManualLogSource Log;

		private static bool initialized = false;

		public static Dictionary<string, string> TitleCache = new Dictionary<string, string>();

		public static HashSet<string> FetchingTitles = new HashSet<string>();

		public static ConfigEntry<int> ConfigVolume;

		private void Awake()
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Expected O, but got Unknown
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Expected O, but got Unknown
			if ((Object)(object)Instance == (Object)null)
			{
				Instance = this;
			}
			Log = Logger.CreateLogSource("KRBroadcasting");
			ConfigVolume = ((BaseUnityPlugin)this).Config.Bind<int>("TV Settings", "Volume", 50, new ConfigDescription("TV 볼륨 0~100. 재시작 후에도 유지.", (AcceptableValueBase)new AcceptableValueRange<int>(0, 100), Array.Empty<object>()));
			Log.LogInfo((object)"KRBroadcasting v2.4.1 loaded");
			Log.LogInfo((object)$"Loaded volume from config: {ConfigVolume.Value}%");
			TVInputActions.Initialize();
			harmony.PatchAll(typeof(KRBroadcastingPlugin));
			harmony.PatchAll(typeof(TVScriptPatch));
			harmony.PatchAll(typeof(PlayerHoverTipPatch));
			harmony.PatchAll(typeof(TerminalPatch));
			TerminalCommands.RegisterCommands();
			if (!initialized)
			{
				initialized = true;
				SceneManager.sceneLoaded += OnSceneLoaded;
			}
		}

		private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Expected O, but got Unknown
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)VideoManager.Instance == (Object)null))
			{
				return;
			}
			Log.LogInfo((object)("Scene: " + ((Scene)(ref scene)).name + " (managers)"));
			Log.LogInfo((object)"yt-dlp...");
			ManualResetEventSlim manualResetEventSlim = new ManualResetEventSlim(initialState: false);
			try
			{
				Exception ex = null;
				ThreadPool.QueueUserWorkItem(delegate
				{
					try
					{
						EnsureYtDlpOnce(Log);
					}
					catch (Exception ex2)
					{
						ex = ex2;
					}
					finally
					{
						manualResetEventSlim.Set();
					}
				});
				if (!manualResetEventSlim.Wait(180000))
				{
					Log.LogError((object)"yt-dlp: wait timeout 3m");
				}
				else if (ex != null)
				{
					Log.LogError((object)("yt-dlp: " + ex.Message));
				}
				GameObject val = new GameObject("KRBroadcastingManagers");
				Object.DontDestroyOnLoad((Object)val);
				val.AddComponent<VideoManager>();
				val.AddComponent<NetworkHandler>();
				val.AddComponent<ShortsProvider>();
				val.AddComponent<TwitterProvider>();
				val.AddComponent<TVChannelInputGUI>();
				_ = VideoStreamer.Instance;
				Log.LogInfo((object)"managers ok");
			}
			finally
			{
				if (manualResetEventSlim != null)
				{
					((IDisposable)manualResetEventSlim).Dispose();
				}
			}
		}

		public static string GetQueueListString()
		{
			try
			{
				List<string> allInputs = VideoQueue.GetAllInputs();
				int pointer = VideoQueue.GetPointer();
				StringBuilder stringBuilder = new StringBuilder();
				stringBuilder.AppendLine();
				stringBuilder.AppendLine("+==========================================+");
				stringBuilder.AppendLine("|     [ 컴퍼니 TV 편성 현황판 ]            |");
				stringBuilder.AppendLine("+==========================================+");
				if (allInputs == null || allInputs.Count == 0 || pointer >= allInputs.Count)
				{
					stringBuilder.AppendLine("|                                          |");
					stringBuilder.AppendLine("|    현재 편성된 프로그램이 없습니다      |");
					stringBuilder.AppendLine("|                                          |");
					stringBuilder.AppendLine("|    한국 인기 Shorts 자동 송출 중!       |");
					stringBuilder.AppendLine("|                                          |");
					stringBuilder.AppendLine("|    TV 앞에서 [G]를 눌러 편성 추가       |");
					stringBuilder.AppendLine("|                                          |");
					stringBuilder.AppendLine("+==========================================+");
					stringBuilder.AppendLine();
					return stringBuilder.ToString();
				}
				stringBuilder.AppendLine("|                                          |");
				int num = 1;
				int num2 = 10;
				int num3 = 0;
				for (int i = pointer; i < allInputs.Count; i++)
				{
					if (num3 >= num2)
					{
						break;
					}
					string text = GetDisplayName(allInputs[i] ?? "");
					if (text.Length > 26)
					{
						text = text.Substring(0, 23) + "...";
					}
					string arg;
					string arg2;
					if (i == pointer)
					{
						arg = "[송출중]";
						arg2 = "▶";
					}
					else
					{
						arg = "[대기]";
						arg2 = " ";
					}
					string text2 = $"{arg2} {num,2}. {text}";
					if (text2.Length > 28)
					{
						text2 = text2.Substring(0, 28);
					}
					text2 = text2.PadRight(28);
					stringBuilder.AppendLine($"|  {text2} {arg,-10} |");
					num++;
					num3++;
				}
				int num4 = allInputs.Count - pointer - num3;
				if (num4 > 0)
				{
					stringBuilder.AppendLine($"|  ... 외 {num4}개 프로그램 대기 중           |");
				}
				stringBuilder.AppendLine("|                                          |");
				stringBuilder.AppendLine("+------------------------------------------+");
				int num5 = 1;
				int num6 = Math.Max(0, allInputs.Count - pointer - 1);
				stringBuilder.AppendLine($"|  송출중 {num5}개 | 대기 {num6}개                    |");
				stringBuilder.AppendLine("+==========================================+");
				stringBuilder.AppendLine();
				return stringBuilder.ToString().Normalize(NormalizationForm.FormC);
			}
			catch (Exception ex)
			{
				Log.LogError((object)("GetQueueListString error: " + ex.Message));
				return "\n[편성표 로딩 오류]\n";
			}
		}

		public static string GetAddedAndScheduleString(string query)
		{
			StringBuilder stringBuilder = new StringBuilder();
			string text = query;
			if (text.Length > 32)
			{
				text = text.Substring(0, 29) + "...";
			}
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("+==========================================+");
			stringBuilder.AppendLine("|      [OK] 편성표에 추가됨                |");
			stringBuilder.AppendLine("+==========================================+");
			stringBuilder.AppendLine("|                                          |");
			stringBuilder.AppendLine($"|  추가됨: {text,-30} |");
			stringBuilder.AppendLine("|                                          |");
			stringBuilder.AppendLine("+==========================================+");
			stringBuilder.AppendLine();
			stringBuilder.Append(GetQueueListString());
			return stringBuilder.ToString().Normalize(NormalizationForm.FormC);
		}

		public static string GetDisplayName(string url)
		{
			if (string.IsNullOrEmpty(url))
			{
				return "[미정]";
			}
			if (TitleCache.TryGetValue(url, out var value))
			{
				return value;
			}
			string videoId = ExtractVideoId(url);
			if (!string.IsNullOrEmpty(videoId) && !FetchingTitles.Contains(url))
			{
				FetchingTitles.Add(url);
				ThreadPool.QueueUserWorkItem(delegate
				{
					FetchVideoTitle(url, videoId);
				});
			}
			if (url.StartsWith("ytsearch:"))
			{
				string text = url.Substring(9);
				if (text.Length > 22)
				{
					text = text.Substring(0, 19) + "...";
				}
				return "검색: " + text;
			}
			if (!string.IsNullOrEmpty(videoId))
			{
				if (url.Contains("/shorts/"))
				{
					return "Shorts (" + videoId + ")";
				}
				return "로딩 중... (" + videoId + ")";
			}
			if (url.Length > 28)
			{
				return url.Substring(0, 25) + "...";
			}
			return url;
		}

		public static string GetDisplayNameSync(string url, int timeoutMs = 2000)
		{
			if (string.IsNullOrEmpty(url))
			{
				return "[미정]";
			}
			if (TitleCache.TryGetValue(url, out var value))
			{
				return value;
			}
			string videoId = ExtractVideoId(url);
			if (string.IsNullOrEmpty(videoId))
			{
				if (url.StartsWith("ytsearch:"))
				{
					string text = url.Substring(9);
					if (text.Length > 22)
					{
						text = text.Substring(0, 19) + "...";
					}
					return "검색: " + text;
				}
				if (url.Length <= 28)
				{
					return url;
				}
				return url.Substring(0, 25) + "...";
			}
			string result = null;
			ManualResetEvent waitHandle = new ManualResetEvent(initialState: false);
			ThreadPool.QueueUserWorkItem(delegate
			{
				try
				{
					string address = "https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=" + videoId + "&format=json";
					using WebClient webClient = new WebClient();
					webClient.Encoding = Encoding.UTF8;
					Match match3 = Regex.Match(webClient.DownloadString(address), "\"title\"\\s*:\\s*\"([^\"]+)\"");
					if (match3.Success)
					{
						string value2 = match3.Groups[1].Value;
						value2 = value2.Replace("\\u0026", "&").Replace("\\\"", "\"").Replace("\\/", "/")
							.Replace("\\n", " ")
							.Replace("\\r", "")
							.Replace("\\t", " ");
						value2 = Regex.Replace(value2, "\\\\u([0-9A-Fa-f]{4})", (Match match2) => char.ConvertFromUtf32(Convert.ToInt32(match2.Groups[1].Value, 16)));
						value2 = value2.Normalize(NormalizationForm.FormC);
						value2 = RemoveEmojis(value2);
						TitleCache[url] = value2;
						result = value2;
					}
				}
				catch
				{
				}
				finally
				{
					waitHandle.Set();
				}
			});
			waitHandle.WaitOne(timeoutMs);
			if (!string.IsNullOrEmpty(result))
			{
				return result;
			}
			if (TitleCache.TryGetValue(url, out value))
			{
				return value;
			}
			if (!url.Contains("/shorts/"))
			{
				return "YouTube (" + videoId + ")";
			}
			return "Shorts (" + videoId + ")";
		}

		public static string RemoveEmojis(string text)
		{
			if (string.IsNullOrEmpty(text))
			{
				return text;
			}
			string pattern = "[\\u2600-\\u27BF\\uFE00-\\uFE0F]|\\uD83C[\\uDC00-\\uDFFF]|\\uD83D[\\uDC00-\\uDFFF]|\\uD83E[\\uDC00-\\uDFFF]";
			return Regex.Replace(Regex.Replace(text, pattern, ""), "\\s{2,}", " ").Trim();
		}

		public static string DecodeUnicodeEscapes(string text)
		{
			if (string.IsNullOrEmpty(text))
			{
				return text;
			}
			try
			{
				StringBuilder stringBuilder = new StringBuilder();
				int num = 0;
				while (num < text.Length)
				{
					if (num + 5 < text.Length && text[num] == '\\' && text[num + 1] == 'u' && int.TryParse(text.Substring(num + 2, 4), NumberStyles.HexNumber, null, out var result))
					{
						if (result >= 55296 && result <= 56319)
						{
							if (num + 11 < text.Length && text[num + 6] == '\\' && text[num + 7] == 'u' && int.TryParse(text.Substring(num + 8, 4), NumberStyles.HexNumber, null, out var result2) && result2 >= 56320 && result2 <= 57343)
							{
								int utf = 65536 + (result - 55296 << 10) + (result2 - 56320);
								stringBuilder.Append(char.ConvertFromUtf32(utf));
								num += 12;
							}
							else
							{
								num += 6;
							}
						}
						else if (result >= 56320 && result <= 57343)
						{
							num += 6;
						}
						else
						{
							stringBuilder.Append((char)result);
							num += 6;
						}
					}
					else
					{
						stringBuilder.Append(text[num]);
						num++;
					}
				}
				return stringBuilder.ToString();
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("DecodeUnicodeEscapes error: " + ex.Message));
				return text;
			}
		}

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

		public static void FetchVideoTitle(string url, string videoId)
		{
			try
			{
				string address = "https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=" + videoId + "&format=json";
				using WebClient webClient = new WebClient();
				webClient.Encoding = Encoding.UTF8;
				Match match = Regex.Match(webClient.DownloadString(address), "\"title\"\\s*:\\s*\"([^\"]+)\"");
				if (match.Success)
				{
					string value = match.Groups[1].Value;
					value = value.Replace("\\u0026", "&").Replace("\\\"", "\"").Replace("\\/", "/")
						.Replace("\\n", " ")
						.Replace("\\r", "")
						.Replace("\\t", " ");
					value = DecodeUnicodeEscapes(value);
					value = value.Normalize(NormalizationForm.FormC);
					value = RemoveEmojis(value);
					TitleCache[url] = value;
					Log.LogInfo((object)("Fetched title: " + value));
				}
				else
				{
					TitleCache[url] = "YouTube (" + videoId + ")";
				}
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("Failed to fetch title for " + videoId + ": " + ex.Message));
				TitleCache[url] = (url.Contains("/shorts/") ? ("Shorts (" + videoId + ")") : ("YouTube (" + videoId + ")"));
			}
			finally
			{
				FetchingTitles.Remove(url);
			}
		}

		public static void DisplayScheduleInChat(string currentTitle, bool isShorts)
		{
			try
			{
				if ((Object)(object)HUDManager.Instance != (Object)null)
				{
					string text = currentTitle.Normalize(NormalizationForm.FormC);
					string text2 = ((!isShorts) ? ("▶ TV: " + text) : ("▷ Shorts: " + text));
					HUDManager.Instance.AddTextToChatOnServer(text2.Normalize(NormalizationForm.FormC), -1);
					Log.LogInfo((object)("Chat: " + currentTitle));
				}
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("DisplayScheduleInChat error: " + ex.Message));
			}
		}

		private static string TryGetYoutubeDLSharpFolder()
		{
			try
			{
				string location = typeof(YoutubeDLProcess).Assembly.Location;
				if (string.IsNullOrEmpty(location))
				{
					return null;
				}
				return Path.GetDirectoryName(location);
			}
			catch
			{
				return null;
			}
		}

		internal static string ResolveYtDlpExe()
		{
			string text = TryGetYoutubeDLSharpFolder();
			if (!string.IsNullOrEmpty(text))
			{
				try
				{
					string path = Path.Combine(text, "yt-dlp.exe");
					if (File.Exists(path))
					{
						return Path.GetFullPath(path);
					}
				}
				catch
				{
				}
			}
			string directoryName = Path.GetDirectoryName(typeof(KRBroadcastingPlugin).Assembly.Location);
			if (string.IsNullOrEmpty(directoryName))
			{
				return null;
			}
			string[] array = new string[3]
			{
				Path.Combine(directoryName, "yt-dlp.exe"),
				Path.Combine(directoryName, "..", "yt-dlp.exe"),
				Path.Combine(directoryName, "..", "..", "yt-dlp.exe")
			};
			foreach (string path2 in array)
			{
				try
				{
					if (File.Exists(path2))
					{
						return Path.GetFullPath(path2);
					}
				}
				catch
				{
				}
			}
			string environmentVariable = Environment.GetEnvironmentVariable("PATH");
			if (string.IsNullOrEmpty(environmentVariable))
			{
				return null;
			}
			string[] array2 = environmentVariable.Split(';');
			for (int j = 0; j < array2.Length; j++)
			{
				try
				{
					string path3 = Path.Combine(array2[j].Trim(), "yt-dlp.exe");
					if (File.Exists(path3))
					{
						return Path.GetFullPath(path3);
					}
				}
				catch
				{
				}
			}
			return null;
		}

		private static void EnsureYtDlpOnce(ManualLogSource log)
		{
			string text = ResolveYtDlpExe();
			if (!string.IsNullOrEmpty(text))
			{
				log.LogInfo((object)text);
				return;
			}
			string text2 = TryGetYoutubeDLSharpFolder();
			if (string.IsNullOrEmpty(text2))
			{
				log.LogError((object)"no YoutubeDLSharp.dll (Thunderstore: Lordfirespeed-YoutubeDLSharp)");
				return;
			}
			try
			{
				log.LogInfo((object)("yt-dlp download -> " + text2));
				Utils.DownloadYtDlp(text2).ConfigureAwait(continueOnCapturedContext: false).GetAwaiter()
					.GetResult();
			}
			catch (Exception ex)
			{
				log.LogError((object)("DownloadYtDlp failed: " + ex.Message));
				return;
			}
			text = ResolveYtDlpExe();
			if (!string.IsNullOrEmpty(text))
			{
				log.LogInfo((object)text);
			}
			else
			{
				log.LogError((object)"yt-dlp.exe still missing");
			}
		}
	}
	public class NetworkHandler : MonoBehaviour
	{
		private LNetworkMessage<string> addVideoMessage;

		private LNetworkEvent skipVideoEvent;

		private LNetworkEvent clearQueueEvent;

		private LNetworkMessage<VideoPlayData> playVideoMessage;

		private LNetworkMessage<float> syncPlaybackMessage;

		private LNetworkMessage<ShortsPlayData> playShortsMessage;

		private LNetworkEvent requestTVStateEvent;

		private LNetworkMessage<TVStateData> syncTVStateMessage;

		private LNetworkMessage<PrefetchData> prefetchMessage;

		private LNetworkMessage<string> playlistVideosMessage;

		private LNetworkMessage<string> playTwitterMessage;

		private LNetworkMessage<int> channelSwitchMessage;

		private LNetworkMessage<ShortsPlayData> playTikTokMessage;

		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();
				KRBroadcastingPlugin.Log.LogInfo((object)"NetworkHandler initialized with LethalNetworkAPI!");
			}
			else
			{
				Object.Destroy((Object)(object)((Component)this).gameObject);
			}
		}

		private void InitializeNetworkMessages()
		{
			addVideoMessage = LNetworkMessage<string>.Connect("KRBroadcasting_AddVideo", (Action<string, ulong>)OnServerReceivedAddVideo, (Action<string>)OnClientReceivedAddVideo, (Action<string, ulong>)null);
			skipVideoEvent = LNetworkEvent.Connect("KRBroadcasting_Skip", (Action<ulong>)OnServerReceivedSkip, (Action)OnClientReceivedSkip, (Action<ulong>)null);
			clearQueueEvent = LNetworkEvent.Connect("KRBroadcasting_Clear", (Action<ulong>)OnServerReceivedClear, (Action)OnClientReceivedClear, (Action<ulong>)null);
			playVideoMessage = LNetworkMessage<VideoPlayData>.Connect("KRBroadcasting_PlayVideo", (Action<VideoPlayData, ulong>)null, (Action<VideoPlayData>)OnClientReceivedPlayVideo, (Action<VideoPlayData, ulong>)null);
			syncPlaybackMessage = LNetworkMessage<float>.Connect("KRBroadcasting_SyncPlayback", (Action<float, ulong>)null, (Action<float>)OnClientReceivedSyncPlayback, (Action<float, ulong>)null);
			playShortsMessage = LNetworkMessage<ShortsPlayData>.Connect("KRBroadcasting_PlayShorts", (Action<ShortsPlayData, ulong>)null, (Action<ShortsPlayData>)OnClientReceivedPlayShorts, (Action<ShortsPlayData, ulong>)null);
			KRBroadcastingPlugin.Log.LogInfo((object)"Shorts playback sync enabled!");
			requestTVStateEvent = LNetworkEvent.Connect("KRBroadcasting_RequestTVState", (Action<ulong>)OnServerReceivedRequestTVState, (Action)null, (Action<ulong>)null);
			syncTVStateMessage = LNetworkMessage<TVStateData>.Connect("KRBroadcasting_SyncTVState", (Action<TVStateData, ulong>)null, (Action<TVStateData>)OnClientReceivedSyncTVState, (Action<TVStateData, ulong>)null);
			prefetchMessage = LNetworkMessage<PrefetchData>.Connect("KRBroadcasting_Prefetch", (Action<PrefetchData, ulong>)null, (Action<PrefetchData>)OnClientReceivedPrefetch, (Action<PrefetchData, ulong>)null);
			playlistVideosMessage = LNetworkMessage<string>.Connect("KRBroadcasting_PlaylistVideos", (Action<string, ulong>)null, (Action<string>)OnClientReceivedPlaylistVideos, (Action<string, ulong>)null);
			playTwitterMessage = LNetworkMessage<string>.Connect("KRBroadcasting_PlayTwitter", (Action<string, ulong>)null, (Action<string>)OnClientReceivedPlayTwitter, (Action<string, ulong>)null);
			channelSwitchMessage = LNetworkMessage<int>.Connect("KRBroadcasting_ChannelSwitch", (Action<int, ulong>)null, (Action<int>)OnClientReceivedChannelSwitch, (Action<int, ulong>)null);
			playTikTokMessage = LNetworkMessage<ShortsPlayData>.Connect("KRBroadcasting_PlayTikTok", (Action<ShortsPlayData, ulong>)null, (Action<ShortsPlayData>)OnClientReceivedPlayTikTok, (Action<ShortsPlayData, ulong>)null);
			KRBroadcastingPlugin.Log.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, string originalInput = null)
		{
			if (!LNetworkUtils.IsHostOrServer)
			{
				KRBroadcastingPlugin.Log.LogWarning((object)"Only host can broadcast play video!");
				return;
			}
			VideoPlayData videoPlayData = default(VideoPlayData);
			videoPlayData.url = url;
			videoPlayData.originalInput = originalInput ?? url;
			videoPlayData.startTime = startTime;
			VideoPlayData videoPlayData2 = videoPlayData;
			playVideoMessage.SendClients(videoPlayData2);
			KRBroadcastingPlugin.Log.LogInfo((object)$"Broadcasting play video: {url} (original: {originalInput ?? url}) at {startTime}s");
		}

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

		public void BroadcastPlayShorts(string url, string originalInput = null)
		{
			if (!LNetworkUtils.IsHostOrServer)
			{
				KRBroadcastingPlugin.Log.LogWarning((object)"Only host can broadcast play shorts!");
				return;
			}
			ShortsPlayData shortsPlayData = default(ShortsPlayData);
			shortsPlayData.url = url;
			shortsPlayData.originalInput = originalInput ?? url;
			ShortsPlayData shortsPlayData2 = shortsPlayData;
			playShortsMessage.SendClients(shortsPlayData2);
			KRBroadcastingPlugin.Log.LogInfo((object)("Broadcasting play shorts: " + url + ", original: " + originalInput));
		}

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

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

		public void BroadcastPrefetch(string originalInput, int queueIndex, bool isShorts = false)
		{
			if (!LNetworkUtils.IsHostOrServer)
			{
				KRBroadcastingPlugin.Log.LogWarning((object)"Only host can broadcast prefetch!");
				return;
			}
			PrefetchData prefetchData = default(PrefetchData);
			prefetchData.originalInput = originalInput;
			prefetchData.queueIndex = queueIndex;
			prefetchData.isShorts = isShorts;
			PrefetchData prefetchData2 = prefetchData;
			prefetchMessage.SendClients(prefetchData2);
			KRBroadcastingPlugin.Log.LogInfo((object)$"Broadcasting prefetch: {originalInput} (index: {queueIndex}, shorts: {isShorts})");
		}

		public void BroadcastPlaylistVideos(List<string> videoIds)
		{
			if (!LNetworkUtils.IsHostOrServer)
			{
				KRBroadcastingPlugin.Log.LogWarning((object)"Only host can broadcast playlist videos!");
				return;
			}
			if (videoIds == null || videoIds.Count == 0)
			{
				KRBroadcastingPlugin.Log.LogWarning((object)"No video IDs to broadcast");
				return;
			}
			for (int i = 0; i < videoIds.Count; i += 500)
			{
				int num = Math.Min(500, videoIds.Count - i);
				List<string> range = videoIds.GetRange(i, num);
				string text = string.Join("\n", range);
				playlistVideosMessage.SendClients(text);
				KRBroadcastingPlugin.Log.LogInfo((object)$"Broadcasting playlist batch: {num} videos (batch {i / 500 + 1})");
			}
			KRBroadcastingPlugin.Log.LogInfo((object)$"Finished broadcasting {videoIds.Count} playlist videos to clients");
		}

		public void BroadcastPlayTwitter(string url)
		{
			if (!LNetworkUtils.IsHostOrServer)
			{
				KRBroadcastingPlugin.Log.LogWarning((object)"Only host can broadcast play Twitter!");
				return;
			}
			playTwitterMessage.SendClients(url);
			KRBroadcastingPlugin.Log.LogInfo((object)("Broadcasting play Twitter: " + url.Substring(0, Math.Min(80, url.Length)) + "..."));
		}

		public void BroadcastChannelSwitch(TVChannel channel)
		{
			if (!LNetworkUtils.IsHostOrServer)
			{
				KRBroadcastingPlugin.Log.LogWarning((object)"Only host can broadcast channel switch!");
				return;
			}
			channelSwitchMessage.SendClients((int)channel);
			KRBroadcastingPlugin.Log.LogInfo((object)$"Broadcasting channel switch: {channel}");
		}

		public void BroadcastPlayTikTok(string streamUrl, string originalUrl)
		{
			if (!LNetworkUtils.IsHostOrServer)
			{
				KRBroadcastingPlugin.Log.LogWarning((object)"Only host can broadcast play TikTok!");
				return;
			}
			ShortsPlayData shortsPlayData = default(ShortsPlayData);
			shortsPlayData.url = streamUrl;
			shortsPlayData.originalInput = originalUrl;
			ShortsPlayData shortsPlayData2 = shortsPlayData;
			playTikTokMessage.SendClients(shortsPlayData2);
			KRBroadcastingPlugin.Log.LogInfo((object)("Broadcasting play TikTok: " + originalUrl.Substring(0, Math.Min(60, originalUrl.Length)) + "..."));
		}

		private void OnServerReceivedAddVideo(string input, ulong clientId)
		{
			KRBroadcastingPlugin.Log.LogInfo((object)("[Host] Received add video request: " + input));
			if (VideoQueue.IsPlaylistUrl(input))
			{
				KRBroadcastingPlugin.Log.LogInfo((object)("[Host] Playlist URL detected, processing locally only: " + input));
				VideoQueue.Add(input);
			}
			else
			{
				VideoQueue.Add(input);
				KRBroadcastingPlugin.Log.LogInfo((object)("[Host] Added to own queue: " + input));
				addVideoMessage.SendClients(input);
			}
		}

		private void OnServerReceivedSkip(ulong clientId)
		{
			KRBroadcastingPlugin.Log.LogInfo((object)"[Host] Received skip request");
			VideoQueue.Skip();
			if ((Object)(object)VideoManager.Instance != (Object)null)
			{
				VideoManager.Instance.OnSkipRequested();
			}
			skipVideoEvent.InvokeClients();
		}

		private void OnServerReceivedClear(ulong clientId)
		{
			KRBroadcastingPlugin.Log.LogInfo((object)"[Host] Received clear queue request");
			VideoQueue.Clear();
			if ((Object)(object)VideoManager.Instance != (Object)null)
			{
				VideoManager.Instance.OnSkipRequested();
			}
			clearQueueEvent.InvokeClients();
		}

		private void OnServerReceivedRequestTVState(ulong clientId)
		{
			KRBroadcastingPlugin.Log.LogInfo((object)$"[Host] Received TV state request from client {clientId}");
			if (!((Object)(object)VideoManager.Instance != (Object)null))
			{
				return;
			}
			TVStateData currentTVState = VideoManager.Instance.GetCurrentTVState();
			if (currentTVState.isTVOn && currentTVState.isPlaying && !string.IsNullOrEmpty(currentTVState.originalInput))
			{
				KRBroadcastingPlugin.Log.LogInfo((object)$"[Host] Sending early prefetch for client {clientId}: {currentTVState.originalInput}");
				BroadcastPrefetch(currentTVState.originalInput, -1, currentTVState.isPlayingShorts);
			}
			if (!currentTVState.isPlayingShorts)
			{
				List<string> allInputs = VideoQueue.GetAllInputs();
				int num = VideoQueue.GetPointer() + 1;
				if (num < allInputs.Count)
				{
					string text = allInputs[num];
					KRBroadcastingPlugin.Log.LogInfo((object)$"[Host] Sending next video prefetch for client {clientId}: {text}");
					BroadcastPrefetch(text, num);
				}
			}
			BroadcastTVState(currentTVState);
		}

		private void OnClientReceivedAddVideo(string input)
		{
			if (LNetworkUtils.IsHostOrServer)
			{
				KRBroadcastingPlugin.Log.LogInfo((object)("[Host] Skipping client add (already handled in server): " + input));
				return;
			}
			KRBroadcastingPlugin.Log.LogInfo((object)("[Client] Adding video to queue: " + input));
			VideoQueue.Add(input);
		}

		private void OnClientReceivedPlaylistVideos(string batchData)
		{
			if (LNetworkUtils.IsHostOrServer)
			{
				KRBroadcastingPlugin.Log.LogInfo((object)"[Host] Skipping playlist videos (already processed locally)");
				return;
			}
			if (string.IsNullOrEmpty(batchData))
			{
				KRBroadcastingPlugin.Log.LogWarning((object)"[Client] Received empty playlist batch");
				return;
			}
			string[] array = batchData.Split('\n');
			KRBroadcastingPlugin.Log.LogInfo((object)$"[Client] Received playlist batch: {array.Length} videos");
			string[] array2 = array;
			foreach (string text in array2)
			{
				if (!string.IsNullOrEmpty(text) && text.Length == 11)
				{
					VideoQueue.AddDirect("https://www.youtube.com/watch?v=" + text.Trim());
				}
			}
			KRBroadcastingPlugin.Log.LogInfo((object)$"[Client] Playlist batch added. Queue size: {VideoQueue.Count()}");
			if ((Object)(object)TVChannelInputGUI.Instance != (Object)null)
			{
				TVChannelInputGUI.Instance.ForceUpdateQueue();
			}
		}

		private void OnClientReceivedSkip()
		{
			if (LNetworkUtils.IsHostOrServer)
			{
				KRBroadcastingPlugin.Log.LogInfo((object)"[Host] Skipping client skip (already handled in server)");
				return;
			}
			KRBroadcastingPlugin.Log.LogInfo((object)"[Client] Skipping video");
			VideoQueue.Skip();
			if ((Object)(object)VideoManager.Instance != (Object)null)
			{
				VideoManager.Instance.OnSkipRequested();
			}
		}

		private void OnClientReceivedClear()
		{
			if (LNetworkUtils.IsHostOrServer)
			{
				KRBroadcastingPlugin.Log.LogInfo((object)"[Host] Skipping client clear (already handled in server)");
				return;
			}
			KRBroadcastingPlugin.Log.LogInfo((object)"[Client] Clearing queue");
			VideoQueue.Clear();
			if ((Object)(object)VideoManager.Instance != (Object)null)
			{
				VideoManager.Instance.OnSkipRequested();
			}
		}

		private void OnClientReceivedPlayVideo(VideoPlayData data)
		{
			if (LNetworkUtils.IsHostOrServer)
			{
				KRBroadcastingPlugin.Log.LogInfo((object)("[Host] Skipping client play video (already handled locally): " + data.url));
				return;
			}
			KRBroadcastingPlugin.Log.LogInfo((object)$"[Client] Received play video: {data.url} at {data.startTime}s (original: {data.originalInput})");
			if ((Object)(object)VideoManager.Instance != (Object)null)
			{
				VideoManager.Instance.PlayVideoFromNetwork(data.url, data.startTime, data.originalInput);
			}
		}

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

		private void OnClientReceivedPlayShorts(ShortsPlayData data)
		{
			if (LNetworkUtils.IsHostOrServer)
			{
				KRBroadcastingPlugin.Log.LogInfo((object)("[Host] Skipping client play shorts (already handled locally): " + data.url));
				return;
			}
			KRBroadcastingPlugin.Log.LogInfo((object)("[Client] Received play shorts: " + data.url + ", original: " + data.originalInput));
			if ((Object)(object)VideoManager.Instance != (Object)null)
			{
				VideoManager.Instance.PlayShortsFromNetwork(data.url, data.originalInput);
			}
		}

		private void OnClientReceivedSyncTVState(TVStateData state)
		{
			KRBroadcastingPlugin.Log.LogInfo((object)$"[Client] Received TV state - TVOn: {state.isTVOn}, Shorts: {state.isPlayingShorts}, URL: {state.currentVideoUrl}");
			if ((Object)(object)VideoManager.Instance != (Object)null)
			{
				VideoManager.Instance.ApplyTVStateFromNetwork(state);
			}
		}

		private void OnClientReceivedPrefetch(PrefetchData data)
		{
			if (!LNetworkUtils.IsHostOrServer)
			{
				KRBroadcastingPlugin.Log.LogInfo((object)$"[Client] Received prefetch request: {data.originalInput} (index: {data.queueIndex}, shorts: {data.isShorts})");
				if ((Object)(object)VideoManager.Instance != (Object)null)
				{
					VideoManager.Instance.PrefetchFromNetwork(data.originalInput, data.queueIndex, data.isShorts);
				}
			}
		}

		private void OnClientReceivedPlayTwitter(string url)
		{
			if (LNetworkUtils.IsHostOrServer)
			{
				KRBroadcastingPlugin.Log.LogInfo((object)"[Host] Skipping client play Twitter (already handled locally)");
				return;
			}
			KRBroadcastingPlugin.Log.LogInfo((object)("[Client] Received play Twitter: " + url));
			if ((Object)(object)VideoManager.Instance != (Object)null)
			{
				KRBroadcastingPlugin.Log.LogWarning((object)"Twitter playback disabled; playing Shorts instead.");
				VideoManager.Instance.PlayRandomShorts();
			}
		}

		private void OnClientReceivedChannelSwitch(int channel)
		{
			if (!LNetworkUtils.IsHostOrServer)
			{
				KRBroadcastingPlugin.Log.LogInfo((object)$"[Client] Received channel switch: {(TVChannel)channel}");
				if ((Object)(object)VideoManager.Instance != (Object)null)
				{
					VideoManager.Instance.ApplyChannelFromNetwork((TVChannel)channel);
				}
			}
		}

		private void OnClientReceivedPlayTikTok(ShortsPlayData data)
		{
			if (LNetworkUtils.IsHostOrServer)
			{
				KRBroadcastingPlugin.Log.LogInfo((object)"[Host] Skipping client play TikTok (already handled locally)");
				return;
			}
			KRBroadcastingPlugin.Log.LogInfo((object)("[Client] Received play TikTok: " + data.originalInput));
			if ((Object)(object)VideoManager.Instance != (Object)null)
			{
				KRBroadcastingPlugin.Log.LogWarning((object)"TikTok playback disabled; playing Shorts instead.");
				VideoManager.Instance.PlayRandomShorts();
			}
		}
	}
	[Serializable]
	public struct PrefetchData
	{
		public string originalInput;

		public int queueIndex;

		public bool isShorts;
	}
	[Serializable]
	public struct ShortsPlayData
	{
		public string url;

		public string originalInput;
	}
	public class ShortsProvider : MonoBehaviour
	{
		private ManualLogSource _logger;

		private Queue<string> _shortsQueue = new Queue<string>();

		private HashSet<string> _playedShorts = new HashSet<string>();

		private HashSet<string> _queuedShorts = new HashSet<string>();

		private bool _isFetching;

		private float _lastFetchTime;

		private const float FETCH_COOLDOWN = 10f;

		private const int MIN_QUEUE_SIZE = 5;

		private const int FETCH_BATCH_SIZE = 20;

		private Queue<(string resolvedUrl, string originalUrl)> _prefetchedUrls = new Queue<(string, string)>();

		private bool _isPrefetching;

		private const int MAX_PREFETCH_COUNT = 3;

		private Action<string, string> _waitingForPrefetchCallback;

		private const string KOREA_TRENDING_SHORTS_URL = "https://www.youtube.com/feed/trending?bp=4gIuCAASKhIkVkxQTHg0TVRCa01qWXRZVEkyTlMwME56QmlMV0psWkRndE1EQXdNREF3TURBd01EQXc%3D&gl=KR";

		public static ShortsProvider Instance { get; private set; }

		public bool HasPrefetchedUrl => _prefetchedUrls.Count > 0;

		public int QueueCount => _shortsQueue.Count;

		public int PlayedCount => _playedShorts.Count;

		public bool IsPrefetching => _isPrefetching;

		private void Awake()
		{
			if ((Object)(object)Instance == (Object)null)
			{
				Instance = this;
				Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject);
				_logger = Logger.CreateLogSource("KRBroadcasting.Shorts");
				_logger.LogInfo((object)"shorts");
				SceneManager.sceneLoaded += OnSceneLoaded;
				((MonoBehaviour)this).Invoke("PrefetchShorts", 2f);
			}
			else
			{
				Object.Destroy((Object)(object)((Component)this).gameObject);
			}
		}

		private void OnDestroy()
		{
			SceneManager.sceneLoaded -= OnSceneLoaded;
		}

		private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			if (((Scene)(ref scene)).name == "SampleSceneRelay")
			{
				bool flag = false;
				try
				{
					flag = LNetworkUtils.IsHostOrServer;
				}
				catch
				{
					return;
				}
				if (!flag)
				{
					_logger.LogInfo((object)"[Client] Game scene loaded, clearing prefetch cache for host sync.");
					_prefetchedUrls.Clear();
					_isPrefetching = false;
					_waitingForPrefetchCallback = null;
				}
			}
		}

		private void Update()
		{
			if (_shortsQueue.Count < 5 && !_isFetching && Time.time - _lastFetchTime > 10f)
			{
				PrefetchShorts();
			}
			if (_prefetchedUrls.Count < 3 && !_isPrefetching && _shortsQueue.Count > 0)
			{
				StartPrefetchNext();
			}
		}

		public void GetNextShorts(Action<string> onShortsFound)
		{
			if (_prefetchedUrls.Count > 0)
			{
				(string, string) tuple = _prefetchedUrls.Dequeue();
				string item = ExtractVideoId(tuple.Item2);
				_queuedShorts.Remove(item);
				_logger.LogInfo((object)("Using prefetched shorts: " + tuple.Item1));
				EnsurePrefetchQueue();
				onShortsFound?.Invoke(tuple.Item1);
			}
			else if (_shortsQueue.Count > 0)
			{
				string text = _shortsQueue.Dequeue();
				string item2 = ExtractVideoId(text);
				_playedShorts.Add(item2);
				_queuedShorts.Remove(item2);
				_logger.LogInfo((object)$"Returning queued shorts: {text} (remaining: {_shortsQueue.Count})");
				EnsurePrefetchQueue();
				onShortsFound?.Invoke(text);
			}
			else if (!_isFetching)
			{
				_logger.LogInfo((object)"Queue empty, fetching Korean trending shorts...");
				FetchNewShorts(delegate(string shorts)
				{
					if (!string.IsNullOrEmpty(shorts))
					{
						onShortsFound?.Invoke(shorts);
						EnsurePrefetchQueue();
					}
					else
					{
						string text3 = "ytsearch:한국 인기 shorts";
						_logger.LogInfo((object)("Fetch failed, using fallback: " + text3));
						onShortsFound?.Invoke(text3);
					}
				});
			}
			else
			{
				string text2 = "ytsearch:한국 trending shorts";
				_logger.LogInfo((object)("Currently fetching, using temp search: " + text2));
				onShortsFound?.Invoke(text2);
			}
		}

		public void EnsurePrefetchQueue()
		{
			while (_prefetchedUrls.Count < 3 && !_isPrefetching && _shortsQueue.Count > 0)
			{
				StartPrefetchNext();
			}
			if (_shortsQueue.Count < 5 && !_isFetching)
			{
				PrefetchShorts();
			}
		}

		private void StartPrefetchNext()
		{
			if (_isPrefetching || _prefetchedUrls.Count >= 3)
			{
				return;
			}
			if (_shortsQueue.Count > 0)
			{
				string text = _shortsQueue.Dequeue();
				string text2 = ExtractVideoId(text);
				if (_playedShorts.Contains(text2))
				{
					_logger.LogInfo((object)("Skipping already played shorts: " + text2));
					_queuedShorts.Remove(text2);
					if (_shortsQueue.Count > 0)
					{
						StartPrefetchNext();
					}
					return;
				}
				_playedShorts.Add(text2);
				_logger.LogInfo((object)$"Prefetching next shorts URL... (queue: {_prefetchedUrls.Count})");
				_isPrefetching = true;
				string originalUrl = text;
				VideoStreamer.Instance.GetVideoUrl(text, delegate(string resolvedUrl)
				{
					_isPrefetching = false;
					if (!string.IsNullOrEmpty(resolvedUrl))
					{
						if (_waitingForPrefetchCallback != null)
						{
							Action<string, string> waitingForPrefetchCallback = _waitingForPrefetchCallback;
							_waitingForPrefetchCallback = null;
							_logger.LogInfo((object)("Prefetch completed, delivering to waiting callback: " + resolvedUrl.Substring(0, Math.Min(80, resolvedUrl.Length)) + "..."));
							waitingForPrefetchCallback(resolvedUrl, originalUrl);
						}
						else
						{
							_prefetchedUrls.Enqueue((resolvedUrl, originalUrl));
							_logger.LogInfo((object)$"Prefetched and resolved: {resolvedUrl.Substring(0, Math.Min(80, resolvedUrl.Length))}... (queue: {_prefetchedUrls.Count})");
						}
					}
					else
					{
						_logger.LogWarning((object)"Prefetch resolution failed (check yt-dlp).");
						if (_waitingForPrefetchCallback != null)
						{
							Action<string, string> waitingForPrefetchCallback2 = _waitingForPrefetchCallback;
							_waitingForPrefetchCallback = null;
							waitingForPrefetchCallback2(null, null);
						}
					}
					EnsurePrefetchQueue();
				});
			}
			else if (!_isFetching)
			{
				PrefetchShorts();
			}
		}

		public void PrefetchShorts()
		{
			if (!_isFetching)
			{
				_logger.LogInfo((object)"Prefetching shorts in background...");
				FetchNewShorts(null);
			}
		}

		private void FetchNewShorts(Action<string> callback)
		{
			_isFetching = true;
			_lastFetchTime = Time.time;
			_logger.LogInfo((object)"Fetching Korean trending shorts...");
			ThreadPool.QueueUserWorkItem(delegate
			{
				try
				{
					List<string> shorts = FetchKoreaTrendingShorts();
					UnityMainThreadDispatcher.Enqueue(delegate
					{
						_isFetching = false;
						int num = 0;
						foreach (string item2 in shorts)
						{
							string text = ExtractVideoId(item2);
							if (!string.IsNullOrEmpty(text) && !_playedShorts.Contains(text) && !_queuedShorts.Contains(text))
							{
								_shortsQueue.Enqueue(item2);
								_queuedShorts.Add(text);
								num++;
							}
						}
						_logger.LogInfo((object)$"Added {num} Korean trending shorts to queue (total: {_shortsQueue.Count})");
						if (callback != null)
						{
							if (_shortsQueue.Count > 0)
							{
								string text2 = _shortsQueue.Dequeue();
								string item = ExtractVideoId(text2);
								_playedShorts.Add(item);
								_queuedShorts.Remove(item);
								callback(text2);
							}
							else
							{
								callback(null);
							}
						}
						EnsurePrefetchQueue();
					});
				}
				catch (Exception ex)
				{
					_logger.LogError((object)("Failed to fetch Korean trending shorts: " + ex.Message));
					UnityMainThreadDispatcher.Enqueue(delegate
					{
						_isFetching = false;
						callback?.Invoke(null);
					});
				}
			});
		}

		private List<string> FetchKoreaTrendingShorts()
		{
			List<string> list = new List<string>();
			try
			{
				using WebClient webClient = new WebClient();
				webClient.Encoding = Encoding.UTF8;
				webClient.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
				webClient.Headers.Add("Accept-Language", "ko-KR,ko;q=0.9");
				webClient.Headers.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
				webClient.Headers.Add("Cookie", "PREF=gl=KR&hl=ko; GPS=1");
				HashSet<string> uniqueIds = new HashSet<string>();
				string[] array = new string[3] { "https://www.youtube.com/feed/trending?bp=4gIuCAASKhIkVkxQTHg0TVRCa01qWXRZVEkyTlMwME56QmlMV0psWkRndE1EQXdNREF3TURBd01EQXc%3D&gl=KR", "https://www.youtube.com/feed/trending?gl=KR", "https://www.youtube.com/shorts?gl=KR" };
				foreach (string text in array)
				{
					try
					{
						string html = webClient.DownloadString(text);
						ExtractShortsFromHtml(html, uniqueIds, list);
						if (list.Count >= 20)
						{
							break;
						}
					}
					catch (Exception ex)
					{
						_logger.LogWarning((object)("Failed to fetch from " + text + ": " + ex.Message));
					}
				}
				_logger.LogInfo((object)$"Found {list.Count} Korean trending shorts");
			}
			catch (Exception ex2)
			{
				_logger.LogError((object)("FetchKoreaTrendingShorts error: " + ex2.Message));
			}
			return list;
		}

		private void ExtractShortsFromHtml(string html, HashSet<string> uniqueIds, List<string> results)
		{
			string pattern = "/shorts/([a-zA-Z0-9_-]{11})";
			foreach (Match item in Regex.Matches(html, pattern))
			{
				string value = item.Groups[1].Value;
				if (uniqueIds.Add(value) && !_playedShorts.Contains(value))
				{
					results.Add("https://www.youtube.com/shorts/" + value);
					_logger.LogInfo((object)("Found shorts: " + value));
					if (results.Count >= 20)
					{
						break;
					}
				}
			}
		}

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

		public (string resolvedUrl, string originalUrl) GetPrefetchedUrlWithOriginal()
		{
			if (_prefetchedUrls.Count > 0)
			{
				(string, string) result = _prefetchedUrls.Dequeue();
				string item = ExtractVideoId(result.Item2);
				_queuedShorts.Remove(item);
				EnsurePrefetchQueue();
				return result;
			}
			return (null, null);
		}

		public string GetPrefetchedUrl()
		{
			if (_prefetchedUrls.Count > 0)
			{
				(string, string) tuple = _prefetchedUrls.Dequeue();
				string item = ExtractVideoId(tuple.Item2);
				_queuedShorts.Remove(item);
				EnsurePrefetchQueue();
				return tuple.Item1;
			}
			return null;
		}

		public string PeekNextOriginalUrl()
		{
			if (_prefetchedUrls.Count > 0)
			{
				return _prefetchedUrls.Peek().originalUrl;
			}
			return null;
		}

		public List<string> PeekNextOriginalUrls(int count)
		{
			List<string> list = new List<string>();
			foreach (var prefetchedUrl in _prefetchedUrls)
			{
				if (list.Count >= count)
				{
					break;
				}
				if (!string.IsNullOrEmpty(prefetchedUrl.originalUrl))
				{
					list.Add(prefetchedUrl.originalUrl);
				}
			}
			if (list.Count < count && _shortsQueue.Count > 0)
			{
				foreach (string item in _shortsQueue)
				{
					if (list.Count >= count)
					{
						break;
					}
					if (!string.IsNullOrEmpty(item) && !list.Contains(item))
					{
						list.Add(item);
					}
				}
			}
			return list;
		}

		public void ClearHistory()
		{
			_playedShorts.Clear();
			_queuedShorts.Clear();
			_shortsQueue.Clear();
			_prefetchedUrls.Clear();
			_logger.LogInfo((object)"Shorts history cleared");
		}

		public void CancelWaitingPrefetch()
		{
			if (_waitingForPrefetchCallback != null)
			{
				_logger.LogInfo((object)"Cancelling waiting prefetch callback");
				_waitingForPrefetchCallback = null;
			}
			_isPrefetching = false;
		}

		public void GetPrefetchedUrlOrWait(Action<string, string> callback)
		{
			if (_prefetchedUrls.Count > 0)
			{
				(string, string) tuple = _prefetchedUrls.Dequeue();
				string item = ExtractVideoId(tuple.Item2);
				_queuedShorts.Remove(item);
				_logger.LogInfo((object)("Using queued prefetched URL: " + tuple.Item1.Substring(0, Math.Min(80, tuple.Item1.Length)) + "..."));
				EnsurePrefetchQueue();
				callback(tuple.Item1, tuple.Item2);
			}
			else if (_isPrefetching)
			{
				_logger.LogInfo((object)"Waiting for current prefetch to complete...");
				_waitingForPrefetchCallback = callback;
			}
			else if (_shortsQueue.Count > 0)
			{
				_logger.LogInfo((object)"Starting new prefetch for waiting callback...");
				_waitingForPrefetchCallback = callback;
				StartPrefetchNext();
			}
			else
			{
				_logger.LogInfo((object)"No shorts available for prefetch");
				callback(null, null);
			}
		}
	}
	public static class TikTokProvider
	{
		private static readonly ManualLogSource Log = Logger.CreateLogSource("TikTokProvider");

		private const string GITHUB_JSON_URL = "https://raw.githubusercontent.com/moooohung/twitwi/refs/heads/main/tiktok.json";

		private static List<string> _cachedVideos = new List<string>();

		private static DateTime _lastFetchTime = DateTime.MinValue;

		private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(30.0);

		private static readonly Regex TikTokUrlPattern = new Regex("https?://(?:www\\.)?(?:tiktok\\.com/@[\\w.-]+/video/\\d+|vm\\.tiktok\\.com/\\w+|vt\\.tiktok\\.com/\\w+)", RegexOptions.IgnoreCase | RegexOptions.Compiled);

		public static async Task<List<string>> GetTrendingVideosAsync(int count = 30)
		{
			if (_cachedVideos.Count > 0 && DateTime.Now - _lastFetchTime < CacheDuration)
			{
				Log.LogInfo((object)$"Using cached TikTok videos: {_cachedVideos.Count}");
				return GetRandomSubset(_cachedVideos, count);
			}
			List<string> videos = new List<string>();
			try
			{
				videos = await FetchFromGitHubAsync();
				if (videos.Count > 0)
				{
					_cachedVideos = videos;
					_lastFetchTime = DateTime.Now;
					Log.LogInfo((object)$"Fetched {videos.Count} TikTok videos from GitHub");
					return GetRandomSubset(videos, count);
				}
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("Failed to fetch from GitHub: " + ex.Message));
			}
			if (videos.Count == 0)
			{
				videos = GetFallbackVideos();
				Log.LogInfo((object)$"Using {videos.Count} fallback TikTok videos");
			}
			return GetRandomSubset(videos, count);
		}

		private static async Task<List<string>> FetchFromGitHubAsync()
		{
			List<string> videos = new List<string>();
			using WebClient client = new WebClient();
			client.Headers.Add("User-Agent", "KRBroadcasting/1.0");
			client.Encoding = Encoding.UTF8;
			string input = await client.DownloadStringTaskAsync(new Uri("https://raw.githubusercontent.com/moooohung/twitwi/refs/heads/main/tiktok.json")).ConfigureAwait(continueOnCapturedContext: false);
			foreach (Match item in TikTokUrlPattern.Matches(input))
			{
				string value = item.Value;
				if (!videos.Contains(value))
				{
					videos.Add(value);
				}
			}
			foreach (Match item2 in new Regex("\"(?:video_url|url)\"\\s*:\\s*\"([^\"]+tiktok[^\"]+)\"").Matches(input))
			{
				string text = item2.Groups[1].Value.Replace("\\/", "/");
				if (!videos.Contains(text) && text.Contains("tiktok.com"))
				{
					videos.Add(text);
				}
			}
			return videos;
		}

		private static List<string> GetFallbackVideos()
		{
			return new List<string>
			{
				"https://www.tiktok.com/tag/fyp", "https://www.tiktok.com/tag/viral", "https://www.tiktok.com/tag/funny", "https://www.tiktok.com/tag/cute", "https://www.tiktok.com/tag/satisfying", "https://www.tiktok.com/tag/dance", "https://www.tiktok.com/tag/food", "https://www.tiktok.com/tag/pets", "https://www.tiktok.com/tag/gaming", "https://www.tiktok.com/tag/music",
				"https://www.tiktok.com/tag/comedy", "https://www.tiktok.com/tag/meme", "https://www.tiktok.com/tag/kpop", "https://www.tiktok.com/tag/korean", "https://www.tiktok.com/tag/aespa", "https://www.tiktok.com/tag/newjeans"
			};
		}

		public static async Task<string> GetRandomVideoAsync()
		{
			List<string> list = await GetTrendingVideosAsync(50);
			if (list.Count == 0)
			{
				return null;
			}
			Random random = new Random();
			return list[random.Next(list.Count)];
		}

		private static List<string> GetRandomSubset(List<string> source, int count)
		{
			if (source.Count <= count)
			{
				return new List<string>(source);
			}
			Random random = new Random();
			List<string> list = new List<string>();
			HashSet<int> hashSet = new HashSet<int>();
			while (list.Count < count && hashSet.Count < source.Count)
			{
				int num = random.Next(source.Count);
				if (hashSet.Add(num))
				{
					list.Add(source[num]);
				}
			}
			return list;
		}

		public static bool IsTikTokUrl(string url)
		{
			if (string.IsNullOrEmpty(url))
			{
				return false;
			}
			if (!url.Contains("tiktok.com") && !url.Contains("vm.tiktok.com"))
			{
				return url.Contains("vt.tiktok.com");
			}
			return true;
		}

		public static void ClearCache()
		{
			_cachedVideos.Clear();
			_lastFetchTime = DateTime.MinValue;
			Log.LogInfo((object)"TikTok cache cleared");
		}
	}
	public enum TVChannel
	{
		Shorts
	}
	public class TVChannelInputGUI : MonoBehaviour
	{
		[CompilerGenerated]
		private sealed class <DelayedQueueUpdate>d__45 : IEnumerator<object>, IDisposable, IEnumerator
		{
			private int <>1__state;

			private object <>2__current;

			public TVChannelInputGUI <>4__this;

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

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

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

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

			private bool MoveNext()
			{
				//IL_0035: Unknown result type (might be due to invalid IL or missing references)
				//IL_003f: Expected O, but got Unknown
				//IL_005b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0065: Expected O, but got Unknown
				int num = <>1__state;
				TVChannelInputGUI tVChannelInputGUI = <>4__this;
				switch (num)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					tVChannelInputGUI.UpdateQueueDisplay();
					<>2__current = (object)new WaitForSeconds(0.3f);
					<>1__state = 1;
					return true;
				case 1:
					<>1__state = -1;
					tVChannelInputGUI.UpdateQueueDisplay();
					<>2__current = (object)new WaitForSeconds(0.5f);
					<>1__state = 2;
					return true;
				case 2:
					<>1__state = -1;
					tVChannelInputGUI.UpdateQueueDisplay();
					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();
			}
		}

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

			private object <>2__current;

			public TVChannelInputGUI <>4__this;

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

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

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

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

			private bool MoveNext()
			{
				int num = <>1__state;
				TVChannelInputGUI tVChannelInputGUI = <>4__this;
				switch (num)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<>2__current = null;
					<>1__state = 1;
					return true;
				case 1:
					<>1__state = -1;
					<>2__current = null;
					<>1__state = 2;
					return true;
				case 2:
				{
					<>1__state = -1;
					QuickMenuManager val = Object.FindObjectOfType<QuickMenuManager>();
					if ((Object)(object)val != (Object)null && val.isMenuOpen)
					{
						val.CloseQuickMenu();
						KRBroadcastingPlugin.Log.LogInfo((object)"Force closed game menu that was opened by ESC");
					}
					if ((Object)(object)tVChannelInputGUI._cachedPlayer != (Object)null && tVChannelInputGUI._wasMovementDisabled)
					{
						tVChannelInputGUI._cachedPlayer.disableLookInput = false;
						tVChannelInputGUI._cachedPlayer.disableMoveInput = false;
						tVChannelInputGUI._cachedPlayer.disableInteract = false;
						tVChannelInputGUI._wasMovementDisabled = false;
					}
					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 bool _isVisible;

		private string _inputUrl = "";

		private Rect _windowRect;

		private Vector2 _scrollPosition = Vector2.zero;

		private GUIStyle _windowStyle;

		private GUIStyle _headerStyle;

		private GUIStyle _labelStyle;

		private GUIStyle _inputStyle;

		private GUIStyle _buttonStyle;

		private GUIStyle _volumeButtonStyle;

		private GUIStyle _volumeLabelStyle;

		private GUIStyle _listStyle;

		private GUIStyle _tipStyle;

		private GUIStyle _statusStyle;

		private Texture2D _bgTexture;

		private Texture2D _inputBgTexture;

		private Texture2D _buttonTexture;

		private Texture2D _buttonHoverTexture;

		private Texture2D _headerBgTexture;

		private bool _stylesInitialized;

		private Font _terminalFont;

		private bool _fontInitialized;

		private string _queueDisplay = "";

		private float _queueUpdateTime;

		private bool _wasMovementDisabled;

		private PlayerControllerB _cachedPlayer;

		public static TVChannelInputGUI Instance { get; private set; }

		public bool IsVisible => _isVisible;

		private void Awake()
		{
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)Instance == (Object)null)
			{
				Instance = this;
				Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject);
				float num = 1280f;
				float num2 = 720f;
				_windowRect = new Rect(((float)Screen.width - num) / 2f, ((float)Screen.height - num2) / 2f, num, num2);
				CreateTextures();
				KRBroadcastingPlugin.Log.LogInfo((object)"TVChannelInputGUI initialized!");
			}
			else
			{
				Object.Destroy((Object)(object)((Component)this).gameObject);
			}
		}

		private void CreateTextures()
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			_bgTexture = MakeTexture(2, 2, new Color(0.02f, 0.06f, 0.02f, 0.98f));
			_headerBgTexture = MakeTexture(2, 2, new Color(0.08f, 0.2f, 0.08f, 1f));
			_inputBgTexture = MakeTexture(2, 2, new Color(0.01f, 0.03f, 0.01f, 1f));
			_buttonTexture = MakeTexture(2, 2, new Color(0.1f, 0.3f, 0.1f, 1f));
			_buttonHoverTexture = MakeTexture(2, 2, new Color(0.15f, 0.45f, 0.15f, 1f));
		}

		private Texture2D MakeTexture(int width, int height, Color color)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: 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)
			//IL_0039: Expected O, but got Unknown
			Color[] array = (Color[])(object)new Color[width * height];
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = color;
			}
			Texture2D val = new Texture2D(width, height);
			val.SetPixels(array);
			val.Apply();
			return val;
		}

		private void InitializeStyles()
		{
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Expected O, but got Unknown
			//IL_0128: Unknown result type (might be due to invalid IL or missing references)
			//IL_0132: Expected O, but got Unknown
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_014a: Expected O, but got Unknown
			//IL_0155: Unknown result type (might be due to invalid IL or missing references)
			//IL_015f: Expected O, but got Unknown
			//IL_01bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dd: Expected O, but got Unknown
			//IL_01e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f2: Expected O, but got Unknown
			//IL_0238: Unknown result type (might be due to invalid IL or missing references)
			//IL_0259: Unknown result type (might be due to invalid IL or missing references)
			//IL_0263: Expected O, but got Unknown
			//IL_02d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_0302: Unknown result type (might be due to invalid IL or missing references)
			//IL_030c: Expected O, but got Unknown
			//IL_0327: Unknown result type (might be due to invalid IL or missing references)
			//IL_0331: Expected O, but got Unknown
			//IL_03c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_03da: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_03fc: Expected O, but got Unknown
			//IL_0417: Unknown result type (might be due to invalid IL or missing references)
			//IL_0421: Expected O, but got Unknown
			//IL_0496: Unknown result type (might be due to invalid IL or missing references)
			//IL_04ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_04dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_04e6: Expected O, but got Unknown
			//IL_0538: Unknown result type (might be due to invalid IL or missing references)
			//IL_0559: Unknown result type (might be due to invalid IL or missing references)
			//IL_0563: Expected O, but got Unknown
			//IL_05a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_05cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_05d5: Expected O, but got Unknown
			//IL_05e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_05ea: Expected O, but got Unknown
			//IL_063c: Unknown result type (might be due to invalid IL or missing references)
			//IL_065d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0667: Expected O, but got Unknown
			//IL_06b9: Unknown result type (might be due to invalid IL or missing references)
			if (_stylesInitialized)
			{
				return;
			}
			if (!_fontInitialized)
			{
				try
				{
					TMP_FontAsset[] array = Resources.FindObjectsOfTypeAll<TMP_FontAsset>();
					foreach (TMP_FontAsset val in array)
					{
						if ((Object)(object)val.sourceFontFile != (Object)null)
						{
							_terminalFont = val.sourceFontFile;
							KRBroadcastingPlugin.Log.LogInfo((object)("Found font: " + ((Object)val).name + " -> " + ((Object)_terminalFont).name));
							break;
						}
					}
					if ((Object)(object)_terminalFont == (Object)null)
					{
						_terminalFont = Font.CreateDynamicFontFromOSFont("Malgun Gothic", 24);
						KRBroadcastingPlugin.Log.LogInfo((object)"Using fallback system font: Malgun Gothic");
					}
				}
				catch (Exception ex)
				{
					KRBroadcastingPlugin.Log.LogWarning((object)("Failed to load terminal font: " + ex.Message));
					_terminalFont = Font.CreateDynamicFontFromOSFont("Malgun Gothic", 24);
				}
				_fontInitialized = true;
			}
			_windowStyle = new GUIStyle(GUI.skin.window);
			_windowStyle.normal.background = _bgTexture;
			_windowStyle.onNormal.background = _bgTexture;
			_windowStyle.border = new RectOffset(12, 12, 12, 12);
			_windowStyle.padding = new RectOffset(20, 20, 20, 20);
			_headerStyle = new GUIStyle(GUI.skin.label);
			if ((Object)(object)_terminalFont != (Object)null)
			{
				_headerStyle.font = _terminalFont;
			}
			_headerStyle.fontSize = 36;
			_headerStyle.fontStyle = (FontStyle)1;
			_headerStyle.alignment = (TextAnchor)4;
			_headerStyle.normal.textColor = new Color(0.3f, 1f, 0.3f);
			_headerStyle.padding = new RectOffset(0, 0, 20, 20);
			_labelStyle = new GUIStyle(GUI.skin.label);
			if ((Object)(object)_terminalFont != (Object)null)
			{
				_labelStyle.font = _terminalFont;
			}
			_labelStyle.fontSize = 22;
			_labelStyle.normal.textColor = new Color(0.6f, 1f, 0.6f);
			_labelStyle.wordWrap = true;
			_inputStyle = new GUIStyle(GUI.skin.textField);
			if ((Object)(object)_terminalFont != (Object)null)
			{
				_inputStyle.font = _terminalFont;
			}
			_inputStyle.fontSize = 26;
			_inputStyle.normal.background = _inputBgTexture;
			_inputStyle.focused.background = _inputBgTexture;
			_inputStyle.normal.textColor = new Color(0.3f, 1f, 0.3f);
			_inputStyle.focused.textColor = Color.white;
			_inputStyle.padding = new RectOffset(16, 16, 14, 14);
			_inputStyle.fixedHeight = 60f;
			_buttonStyle = new GUIStyle(GUI.skin.button);
			if ((Object)(object)_terminalFont != (Object)null)
			{
				_buttonStyle.font = _terminalFont;
			}
			_buttonStyle.fontSize = 22;
			_buttonStyle.fontStyle = (FontStyle)1;
			_buttonStyle.normal.background = _buttonTexture;
			_buttonStyle.hover.background = _buttonHoverTexture;
			_buttonStyle.active.background = _buttonHoverTexture;
			_buttonStyle.normal.textColor = new Color(0.7f, 1f, 0.7f);
			_buttonStyle.hover.textColor = Color.white;
			_buttonStyle.padding = new RectOffset(30, 30, 16, 16);
			_buttonStyle.fixedHeight = 60f;
			_volumeButtonStyle = new GUIStyle(GUI.skin.button);
			_volumeButtonStyle.fontSize = 36;
			_volumeButtonStyle.fontStyle = (FontStyle)1;
			_volumeButtonStyle.normal.background = _buttonTexture;
			_volumeButtonStyle.hover.background = _buttonHoverTexture;
			_volumeButtonStyle.active.background = _buttonHoverTexture;
			_volumeButtonStyle.normal.textColor = new Color(0.7f, 1f, 0.7f);
			_volumeButtonStyle.hover.textColor = Color.white;
			_volumeButtonStyle.alignment = (TextAnchor)4;
			_volumeButtonStyle.fixedHeight = 50f;
			_volumeLabelStyle = new GUIStyle(GUI.skin.label);
			if ((Object)(object)_terminalFont != (Object)null)
			{
				_volumeLabelStyle.font = _terminalFont;
			}
			_volumeLabelStyle.fontSize = 28;
			_volumeLabelStyle.fontStyle = (FontStyle)1;
			_volumeLabelStyle.normal.textColor = new Color(0.6f, 1f, 0.6f);
			_volumeLabelStyle.alignment = (TextAnchor)4;
			_listStyle = new GUIStyle(GUI.skin.label);
			if ((Object)(object)_terminalFont != (Object)null)
			{
				_listStyle.font = _terminalFont;
			}
			_listStyle.fontSize = 20;
			_listStyle.normal.textColor = new Color(0.5f, 0.9f, 0.5f);
			_listStyle.wordWrap = false;
			_listStyle.padding = new RectOffset(12, 12, 6, 6);
			_tipStyle = new GUIStyle(GUI.skin.label);
			if ((Object)(object)_terminalFont != (Object)null)
			{
				_tipStyle.font = _terminalFont;
			}
			_tipStyle.fontSize = 18;
			_tipStyle.fontStyle = (FontStyle)2;
			_tipStyle.normal.textColor = new Color(0.4f, 0.65f, 0.4f);
			_tipStyle.alignment = (TextAnchor)4;
			_statusStyle = new GUIStyle(GUI.skin.label);
			if ((Object)(object)_terminalFont != (Object)null)
			{
				_statusStyle.font = _terminalFont;
			}
			_statusStyle.fontSize = 20;
			_statusStyle.fontStyle = (FontStyle)1;
			_statusStyle.normal.textColor = new Color(1f, 0.9f, 0.3f);
			_statusStyle.alignment = (TextAnchor)4;
			_stylesInitialized = true;
		}

		public void Show()
		{
			if (!_isVisible)
			{
				_isVisible = true;
				_inputUrl = "";
				UpdateQueueDisplay();
				Cursor.visible = true;
				Cursor.lockState = (CursorLockMode)0;
				_cachedPlayer = GameNetworkManager.Instance?.localPlayerController;
				if ((Object)(object)_cachedPlayer != (Object)null)
				{
					_cachedPlayer.disableLookInput = true;
					_cachedPlayer.disableMoveInput = true;
					_cachedPlayer.disableInteract = true;
					_wasMovementDisabled = true;
				}
				KRBroadcastingPlugin.Log.LogInfo((object)"Channel input GUI opened");
			}
		}

		public void Hide()
		{
			if (_isVisible)
			{
				_isVisible = false;
				_inputUrl = "";
				Cursor.visible = false;
				Cursor.lockState = (CursorLockMode)1;
				((MonoBehaviour)this).StartCoroutine(RestoreControlsNextFrame());
				KRBroadcastingPlugin.Log.LogInfo((object)"Channel input GUI closed");
			}
		}

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

		private void Update()
		{
			if (!_isVisible)
			{
				return;
			}
			if ((Object)(object)_cachedPlayer != (Object)null)
			{
				_cachedPlayer.disableLookInput = true;
				_cachedPlayer.disableMoveInput = true;
				_cachedPlayer.disableInteract = true;
				if (_cachedPlayer.isCrouching)
				{
					_cachedPlayer.Crouch(false);
				}
			}
			if (Keyboard.current != null && ((ButtonControl)Keyboard.current.escapeKey).wasPressedThisFrame)
			{
				Hide();
				return;
			}
			if (Gamepad.current != null && Gamepad.current.buttonEast.wasPressedThisFrame)
			{
				Hide();
				return;
			}
			if (Keyboard.current != null && (((ButtonControl)Keyboard.current.enterKey).wasPressedThisFrame || ((ButtonControl)Keyboard.current.numpadEnterKey).wasPressedThisFrame))
			{
				SubmitUrl();
			}
			if (Time.time - _queueUpdateTime > 1f)
			{
				UpdateQueueDisplay();
			}
		}

		public void ForceUpdateQueue()
		{
			UpdateQueueDisplay();
			KRBroadcastingPlugin.Log.LogInfo((object)$"[GUI] Force update queue display - items: {VideoQueue.GetAllInputs()?.Count ?? 0}, ptr: {VideoQueue.GetPointer()}");
		}

		private void UpdateQueueDisplay()
		{
			_queueUpdateTime = Time.time;
			List<string> allInputs = VideoQueue.GetAllInputs();
			int pointer = VideoQueue.GetPointer();
			if (allInputs == null)
			{
				KRBroadcastingPlugin.Log.LogWarning((object)"[GUI] UpdateQueueDisplay: inputs is null");
			}
			if (allInputs == null || allInputs.Count == 0 || pointer >= allInputs.Count)
			{
				_queueDisplay = NormalizeKorean("\n    현재 편성된 프로그램이 없습니다.\n\n    한국 인기 Shorts 자동 송출 중...");
				return;
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine();
			int num = 0;
			int num2 = 4;
			for (int i = pointer; i < allInputs.Count; i++)
			{
				if (num >= num2)
				{
					break;
				}
				string url = allInputs[i] ?? "";
				string shortDisplayName = GetShortDisplayName(url);
				string text;
				string text2;
				if (i == pointer)
				{
					text = "[송출중]";
					text2 = "▶";
				}
				else
				{
					text = "[대기]";
					text2 = "  ";
				}
				int num3 = num + 1;
				stringBuilder.AppendLine($"  {text2} {num3}. {shortDisplayName}  {text}");
				num++;
			}
			int num4 = allInputs.Count - pointer - num;
			if (num4 > 0)
			{
				stringBuilder.AppendLine($"\n  ... 외 {num4}개 프로그램 대기 중");
			}
			int num5 = allInputs.Count - pointer - 1;
			if (num5 < 0)
			{
				num5 = 0;
			}
			stringBuilder.AppendLine($"\n  ■ 송출 현황: 현재 1개 송출중 / {num5}개 대기");
			_queueDisplay = NormalizeKorean(stringBuilder.ToString());
		}

		private string NormalizeKorean(string text)
		{
			if (string.IsNullOrEmpty(text))
			{
				return text;
			}
			return text.Normalize(NormalizationForm.FormC);
		}

		private string GetShortDisplayName(string url)
		{
			if (string.IsNullOrEmpty(url))
			{
				return "[미정]";
			}
			if (KRBroadcastingPlugin.TitleCache.TryGetValue(url, out var value))
			{
				return NormalizeKorean(value);
			}
			if (url.StartsWith("ytsearch:"))
			{
				string text = url.Substring(9);
				return "[검색] " + text;
			}
			Match match = Regex.Match(url, "(?:v=|youtu\\.be/|shorts/)([a-zA-Z0-9_-]{11})");
			if (match.Success)
			{
				string videoId = match.Groups[1].Value;
				if (!KRBroadcastingPlugin.FetchingTitles.Contains(url))
				{
					KRBroadcastingPlugin.FetchingTitles.Add(url);
					ThreadPool.QueueUserWorkItem(delegate
					{
						KRBroadcastingPlugin.FetchVideoTitle(url, videoId);
					});
				}
				if (url.Contains("/shorts/"))
				{
					return "[쇼츠] " + videoId;
				}
				return "[유튜브] " + videoId;
			}
			return url;
		}

		private void SubmitUrl()
		{
			string text = _inputUrl.Trim();
			if (!string.IsNullOrEmpty(text))
			{
				if ((Object)(object)NetworkHandler.Instance != (Object)null)
				{
					NetworkHandler.Instance.RequestAddVideo(text);
					KRBroadcastingPlugin.Log.LogInfo((object)("Added from GUI: " + text));
				}
				_inputUrl = "";
				((MonoBehaviour)this).StartCoroutine(DelayedQueueUpdate());
			}
		}

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

		private void OnGUI()
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Invalid comparison between Unknown and I4
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Invalid comparison between Unknown and I4
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Expected O, but got Unknown
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Invalid comparison between Unknown and I4
			if (_isVisible)
			{
				InitializeStyles();
				if ((int)Event.current.type == 4 || (int)Event.current.type == 5 || (int)Event.current.type == 0 || (int)Event.current.type == 1)
				{
					Event.current.Use();
				}
				GUI.color = new Color(0f, 0f, 0f, 0.9f);
				GUI.DrawTexture(new Rect(0f, 0f, (float)Screen.width, (float)Screen.height), (Texture)(object)Texture2D.whiteTexture);
				GUI.color = Color.white;
				DrawBorder();
				_windowRect = GUI.Window(12345, _windowRect, new WindowFunction(DrawWindow), "", _windowStyle);
			}
		}

		private void DrawBorder()
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			float num = 3f;
			GUI.color = new Color(0.2f, 0.6f, 0.2f, 1f);
			GUI.DrawTexture(new Rect(((Rect)(ref _windowRect)).x - num, ((Rect)(ref _windowRect)).y - num, ((Rect)(ref _windowRect)).width + num * 2f, num), (Texture)(object)Texture2D.whiteTexture);
			GUI.DrawTexture(new Rect(((Rect)(ref _windowRect)).x - num, ((Rect)(ref _windowRect)).y + ((Rect)(ref _windowRect)).height, ((Rect)(ref _windowRect)).width + num * 2f, num), (Texture)(object)Texture2D.whiteTexture);
			GUI.DrawTexture(new Rect(((Rect)(ref _windowRect)).x - num, ((Rect)(ref _windowRect)).y, num, ((Rect)(ref _windowRect)).height), (Texture)(object)Texture2D.whiteTexture);
			GUI.DrawTexture(new Rect(((Rect)(ref _windowRect)).x + ((Rect)(ref _windowRect)).width, ((Rect)(ref _windowRect)).y, num, ((Rect)(ref _windowRect)).height), (Texture)(object)Texture2D.whiteTexture);
			GUI.color = Color.white;
		}

		private void DrawWindow(int windowId)
		{
			//IL_0327: Unknown result type (might be due to invalid IL or missing references)
			//IL_0344: Unknown result type (might be due to invalid IL or missing references)
			//IL_0349: Unknown result type (might be due to invalid IL or missing references)
			//IL_03cc: Unknown result type (might be due to invalid IL or missing references)
			GUILayout.BeginVertical(Array.Empty<GUILayoutOption>());
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.FlexibleSpace();
			GUILayout.Label("[ 컴퍼니 TV 편성 조정실 ]", _headerStyle, Array.Empty<GUILayoutOption>());
			GUILayout.FlexibleSpace();
			GUILayout.EndHorizontal();
			DrawSeparator();
			GUILayout.Space(20f);
			GUILayout.Label("■ 프로그램 추가 (YouTube URL / 검색어 / 영상ID)", _labelStyle, Array.Empty<GUILayoutOption>());
			GUILayout.Space(12f);
			GUI.SetNextControlName("URLInput");
			_inputUrl = GUILayout.TextField(_inputUrl, 500, _inputStyle, Array.Empty<GUILayoutOption>());
			if (GUI.GetNameOfFocusedControl() == "")
			{
				GUI.FocusControl("URLInput");
			}
			GUILayout.Space(25f);
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.FlexibleSpace();
			if (GUILayout.Button("[ 편성 추가 ]", _buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(250f) }))
			{
				SubmitUrl();
			}
			GUILayout.Space(40f);
			if (GUILayout.Button("[ 채널 돌리기 ]", _buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(250f) }))
			{
				SkipCurrentVideo();
			}
			GUILayout.Space(40f);
			if (GUILayout.Button(((Object)(object)StartOfRound.Instance != (Object)null && StartOfRound.Instance.localPlayerUsingController) ? "[ 닫기 (B) ]" : "[ 닫기 (ESC) ]", _buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(220f) }))
			{
				Hide();
			}
			GUILayout.FlexibleSpace();
			GUILayout.EndHorizontal();
			GUILayout.Space(20f);
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.FlexibleSpace();
			int num = (((Object)(object)TVController.Instance != (Object)null) ? TVController.Instance.Volume : 50);
			string text = ((num == 0) ? "볼륨 : 음소거" : $"볼륨 : {num}/100");
			if (GUILayout.Button("▼", _volumeButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(50f) }) && (Object)(object)TVController.Instance != (Object)null)
			{
				TVController.Instance.VolumeDown();
			}
			GUILayout.Space(15f);
			GUILayout.Label(text, _volumeLabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(200f) });
			GUILayout.Space(15f);
			if (GUILayout.Button("▲", _volumeButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(50f) }) && (Object)(object)TVController.Instance != (Object)null)
			{
				TVController.Instance.VolumeUp();
			}
			GUILayout.FlexibleSpace();
			GUILayout.EndHorizontal();
			GUILayout.Space(25f);
			DrawSeparator();
			GUILayout.Space(20f);
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.Label("■ 현재 편성표", _labelStyle, Array.Empty<GUILayoutOption>());
			GUILayout.FlexibleSpace();
			if (GUILayout.Button("비우기", _buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(120f) }))
			{
				ClearQueue();
			}
			GUILayout.EndHorizontal();
			GUILayout.Space(12f);
			_scrollPosition = GUILayout.BeginScrollView(_scrollPosition, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(300f) });
			GUILayout.Label(_queueDisplay, _listStyle, Array.Empty<GUILayoutOption>());
			GUILayout.EndScrollView();
			GUILayout.Space(20f);
			DrawSeparator();
			GUILayout.Space(15f);
			GUILayout.Label("▶ 입력 형식: YouTube URL, 영상 ID(11자리), 검색어 모두 지원", _tipStyle, Array.Empty<GUILayoutOption>());
			GUILayout.Label("▶ 편성표가 비어있으면 한국 인기 Shorts가 자동 송출됩니다", _tipStyle, Array.Empty<GUILayoutOption>());
			GUILayout.EndVertical();
			GUI.DragWindow(new Rect(0f, 0f, ((Rect)(ref _windowRect)).width, 80f));
		}

		private void SkipCurrentVideo()
		{
			if ((Object)(object)NetworkHandler.Instance != (Object)null)
			{
				NetworkHandler.Instance.RequestSkipVideo();
				KRBroadcastingPlugin.Log.LogInfo((object)"Skip requested from GUI");
				UpdateQueueDisplay();
			}
		}

		private void ClearQueue()
		{
			if ((Object)(object)NetworkHandler.Instance != (Object)null)
			{
				NetworkHandler.Instance.RequestClearQueue();
				KRBroadcastingPlugin.Log.LogInfo((object)"Clear queue requested from GUI");
				UpdateQueueDisplay();
			}
		}

		private void DrawSeparator()
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//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)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Expected O, but got Unknown
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			GUILayout.Space(5f);
			Rect rect = GUILayoutUtility.GetRect(1f, 2f, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) });
			Color color = GUI.color;
			GUI.color = new Color(0.25f, 0.55f, 0.25f, 1f);
			GUI.DrawTexture(rect, (Texture)Texture2D.whiteTexture);
			GUI.color = color;
			GUILayout.Space(5f);
		}

		private void OnDestroy()
		{
			if (_wasMovementDisabled && (Object)(object)_cachedPlayer != (Object)null)
			{
				_cachedPlayer.disableLookInput = false;
				_cachedPlayer.disableMoveInput = false;
				_cachedPlayer.disableInteract = false;
			}
			if ((Object)(object)_bgTexture != (Object)null)
			{
				Object.Destroy((Object)(object)_bgTexture);
			}
			if ((Object)(object)_inputBgTexture != (Object)null)
			{
				Object.Destroy((Object)(object)_inputBgTexture);
			}
			if ((Object)(object)_buttonTexture != (Object)null)
			{
				Object.Destroy((Object)(object)_buttonTexture);
			}
			if ((Object)(object)_buttonHoverTexture != (Object)null)
			{
				Object.Destroy((Object)(object)_buttonHoverTexture);
			}
			if ((Object)(object)_headerBgTexture != (Object)null)
			{
				Object.Destroy((Object)(object)_headerBgTexture);
			}
		}

		private void OnDisable()
		{
			Hide();
		}
	}
	public class TVController : MonoBehaviour
	{
		public VideoPlayer videoPlayer;

		private VideoPlayer vanillaVideoPlayer;

		private RenderTexture renderTexture;

		private AudioSource tvAudioSource;

		private TVScript tvScript;

		private ManualLogSource logger;

		private int _volume = 50;

		private const int VOLUME_STEP = 5;

		private float _lastVolumeChangeTime;

		private bool _volumeNeedsSave;

		private const float VOLUME_SAVE_DELAY = 1f;

		private float videoStartTime;

		private const float MIN_VIDEO_DURATION = 5f;

		private bool isVideoReady;

		private bool endEventProcessed;

		public static TVController Instance { get; private set; }

		public int Volume => _volume;

		private void Awake()
		{
			//IL_01fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0206: Expected O, but got Unknown
			//IL_0213: Unknown result type (might be due to invalid IL or missing references)
			//IL_021d: Expected O, but got Unknown
			//IL_022a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0234: Expected O, but got Unknown
			//IL_0241: Unknown result type (might be due to invalid IL or missing references)
			//IL_024b: Expected O, but got Unknown
			Instance = this;
			logger = Logger.CreateLogSource("KRBroadcasting");
			if (KRBroadcastingPlugin.ConfigVolume != null)
			{
				_volume = KRBroadcastingPlugin.ConfigVolume.Value;
			}
			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));
			ApplyVolume();
			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)
			{
				if (_volumeNeedsSave)
				{
					SaveVolumeToConfigImmediate();
				}
				Instance = null;
			}
		}

		private void Update()
		{
			if (_volumeNeedsSave && Time.time - _lastVolumeChangeTime > 1f)
			{
				SaveVolumeToConfigImmediate();
				_volumeNeedsSave = false;
			}
		}

		public void PlayVideo(string url)
		{
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Expected O, but got Unknown
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: 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)) + "..."));
			isVideoReady = false;
			videoStartTime = 0f;
			endEventProcessed = false;
			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! Length: {vp.length:F1}s, 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}");
			videoStartTime = Time.time;
			isVideoReady = true;
			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()
		{
			if (!videoPlayer.isPlaying && !string.IsNullOrEmpty(videoPlayer.url))
			{
				return videoPlayer.isPrepared;
			}
			return false;
		}

		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_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Expected O, but got Unknown
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: 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;
		}

		public void VolumeUp()
		{
			_volume = Mathf.Min(100, _volume + 5);
			ApplyVolume();
			SaveVolumeToConfig();
		}

		public void VolumeDown()
		{
			_volume = Mathf.Max(0, _volume - 5);
			ApplyVolume();
			SaveVolumeToConfig();
		}

		public void SetVolume(int volume)
		{
			_volume = Mathf.Clamp(volume, 0, 100);
			ApplyVolume();
			SaveVolumeToConfig();
		}

		private void ApplyVolume()
		{
			if ((Object)(object)tvAudioSource != (Object)null)
			{
				tvAudioSource.volume = (float)_volume / 100f;
			}
		}

		private void SaveVolumeToConfig()
		{
			_lastVolumeChangeTime = Time.time;
			_volumeNeedsSave = true;
		}

		private void SaveVolumeToConfigImmediate()
		{
			if (KRBroadcastingPlugin.ConfigVolume == null)
			{
				return;
			}
			KRBroadcastingPlugin.ConfigVolume.Value = _volume;
			KRBroadcastingPlugin instance = KRBroadcastingPlugin.Instance;
			if ((Object)(object)instance != (Object)null)
			{
				ConfigFile config = ((BaseUnityPlugin)instance).Config;
				if (config != null)
				{
					config.Save();
				}
			}
			logger.LogDebug((object)$"Volume saved to config: {_volume}%");
		}

		private void OnVideoEnd(VideoPlayer vp)
		{
			float num = Time.time - videoStartTime;
			double length = vp.length;
			double time = vp.time;
			logger.LogInfo((object)$"OnVideoEnd called. Played: {num:F1}s, Length: {length:F1}s, Current: {time:F1}s, isReady: {isVideoReady}, processed: {endEventProcessed}");
			if (endEventProcessed)
			{
				logger.LogWarning((object)"OnVideoEnd already processed, ignoring duplicate call");
				return;
			}
			if (!isVideoReady)
			{
				logger.LogWarning((object)"OnVideoEnd called but video not ready, ignoring...");
				return;
			}
			if (num < 5f)
			{
				logger.LogWarning((object)$"OnVideoEnd called too early ({num:F1}s < {5f}s), ignoring...");
				return;
			}
			if (length > 0.0)
			{
				double num2 = length - time;
				double num3 = time / length;
				if (num2 > 3.0 && num3 < 0.9)
				{
					logger.LogWarning((object)$"OnVideoEnd called but video not at end (remaining: {num2:F1}s, played: {num3:P0}), ignoring false positive...");
					return;
				}
			}
			endEventProcessed = true;
			logger.LogInfo((object)"Video playback completed normally");
			isVideoReady = false;
			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("영상 오류", "영상 재생에 실패했습니다. 다음으로 넘어갑니다...", true, false, "LC_Tip1");
			}
		}
	}
	public class TVInputActions : LcInputActions
	{
		public static TVInputActions Instance { get; private set; }

		[InputAction("<Keyboard>/g", Name = "채널 송출", GamepadPath = "<Gamepad>/leftTrigger")]
		public InputAction ChannelEditKey { get; set; }

		[InputAction("<Keyboard>/y", Name = "채널 돌리기", GamepadPath = "<Gamepad>/rightTrigger")]
		public InputAction ChannelSkipKey { get; set; }

		[InputAction("<Keyboard>/equals", Name = "TV 볼륨 업", GamepadPath = "<Gamepad>/dpad/up")]
		public InputAction VolumeUpKey { get; set; }

		[InputAction("<Keyboard>/minus", Name = "TV 볼륨 다운", GamepadPath = "<Gamepad>/dpad/down")]
		public InputAction VolumeDownKey { get; set; }

		public static void Initialize()
		{
			if (Instance == null)
			{
				Instance = new TVInputActions();
			}
		}

		public bool IsUsingGamepad()
		{
			if (Gamepad.current != null)
			{
				if (((InputDevice)Gamepad.current).wasUpdatedThisFrame)
				{
					return true;
				}
				if (InputSystem.GetDevice<Gamepad>() != null)
				{
					Gamepad device = InputSystem.GetDevice<Gamepad>();
					if (device != null)
					{
						double lastUpdateTime = ((InputDevice)device).lastUpdateTime;
						Keyboard current = Keyboard.current;
						if (lastUpdateTime > ((current != null) ? ((InputDevice)current).lastUpdateTime : 0.0))
						{
							return true;
						}
					}
				}
			}
			return false;
		}

		public string GetChannelEditDisplayName()
		{
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: 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_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			if (ChannelEditKey == null)
			{
				return "[G]";
			}
			bool flag = IsUsingGamepad();
			Enumerator<InputBinding> enumerator = ChannelEditKey.bindings.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					InputBinding current = enumerator.Current;
					if (flag && ((InputBinding)(ref current)).effectivePath.Contains("Gamepad"))
					{
						return "[LT]";
					}
					if (!flag && (((InputBinding)(ref current)).effectivePath.Contains("Keyboard") || ((InputBinding)(ref current)).effectivePath.Contains("Mouse")))
					{
						string text = ((InputBinding)(ref current)).ToDisplayString((DisplayStringOptions)0, (InputControl)null);
						if (string.IsNullOrEmpty(text))
						{
							text = "G";
						}
						return "[" + text + "]";
					}
				}
			}
			finally
			{
				((IDisposable)enumerator).Dispose();
			}
			if (!flag)
			{
				return "[G]";
			}
			return "[LT]";
		}

		public string GetChannelSkipDisplayName()
		{
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: 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_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			if (ChannelSkipKey == null)
			{
				return "[Y]";
			}
			bool flag = IsUsingGamepad();
			Enumerator<InputBinding> enumerator = ChannelSkipKey.bindings.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					InputBinding current = enumerator.Current;
					if (flag && ((InputBinding)(ref current)).effectivePath.Contains("Gamepad"))
					{
						return "[RT]";
					}
					if (!flag && (((InputBinding)(ref current)).effectivePath.Contains("Keyboard") || ((InputBinding)(ref current)).effectivePath.Contains("Mouse")))
					{
						string text = ((InputBinding)(ref current)).ToDisplayString((DisplayStringOptions)0, (InputControl)null);
						if (string.IsNullOrEmpty(text))
						{
							text = "Y";
						}
						return "[" + text + "]";
					}
				}
			}
			finally
			{
				((IDisposable)enumerator).Dispose();
			}
			if (!flag)
			{
				return "[Y]";
			}
			return "[RT]";
		}

		public string GetVolumeKeysDisplayName()
		{
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			if (IsUsingGamepad())
			{
				return "[↑/↓]";
			}
			string text = "+";
			string text2 = "-";
			if (VolumeUpKey != null)
			{
				Enumerator<InputBinding> enumerator = VolumeUpKey.bindings.GetEnumerator();
				try
				{
					while (enumerator.MoveNext())
					{
						InputBinding current = enumerator.Current;
						if (((InputBinding)(ref current)).effectivePath.Contains("Keyboard"))
						{
							string text3 = ((InputBinding)(ref current)).ToDisplayString((DisplayStringOptions)0, (InputControl)null);
							if (!string.IsNullOrEmpty(text3))
							{
								text = text3;
							}
							break;
						}
					}
				}
				finally
				{
					((IDisposable)enumerator).Dispose();
				}
			}
			if (VolumeDownKey != null)
			{
				Enumerator<InputBinding> enumerator2 = VolumeDownKey.bindings.GetEnumerator();
				try
				{
					while (enumerator2.MoveNext())
					{
						InputBinding current2 = enumerator2.Current;
						if (((InputBinding)(ref current2)).effectivePath.Contains("Keyboard"))
						{
							string text4 = ((InputBinding)(ref current2)).ToDisplayString((DisplayStringOptions)0, (InputControl)null);
							if (!string.IsNullOrEmpty(text4))
							{
								text2 = text4;
							}
							break;
						}
					}
				}
				finally
				{
					((IDisposable)enumerator2).Dispose();
				}
			}
			return "[" + text + "/" + text2 + "]";
		}
	}
	[Serializable]
	public struct TVStateData
	{
		public bool isTVOn;

		public bool isPlayingShorts;

		public string currentVideoUrl;

		public string originalInput;

		public float currentPlaybackTime;

		public bool isPlaying;
	}
	public class TwitterProvider : MonoBehaviour
	{
		private ManualLogSource _logger;

		private Queue<string> _tweetQueue = new Queue<string>();

		private HashSet<string> _processedTweets = new HashSet<string>();

		private Queue<string> _videoQueue = new Queue<string>();