Please disclose if your mod was created primarily using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of YoutubeShortsKR v2.4.1
KRBroadcasting.dll
Decompiled 5 hours ago
The result has been truncated due to the large size, download it to view full contents!
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>();