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.2")]
[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.2";
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>();