Please disclose if any significant portion of your mod was created 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 YoutubeOnTV v0.2.7
BepInEx\plugins\YoutubeOnTV\YoutubeOnTV.dll
Decompiled 5 months agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using BepInEx; using BepInEx.Logging; using HarmonyLib; using LethalNetworkAPI; using LethalNetworkAPI.Utils; using TerminalApi; using TerminalApi.Events; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.Video; using YoutubeDLSharp; using YoutubeDLSharp.Options; using YoutubeOnTV.Patches; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("YoutubeOnTV")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("YoutubeOnTV")] [assembly: AssemblyCopyright("Copyright © 2025")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("637d9e92-bc74-4646-8436-67954c0a9f24")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.0.0.0")] namespace YoutubeOnTV { [BepInPlugin("com.roandegraaf.youtubeontv", "YoutubeOnTV", "0.2.3")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class YoutubeOnTVBase : BaseUnityPlugin { private readonly Harmony harmony = new Harmony("com.roandegraaf.youtubeontv"); public static YoutubeOnTVBase Instance; internal ManualLogSource mls; private static bool initialized; private void Awake() { if ((Object)(object)Instance == (Object)null) { Instance = this; } mls = Logger.CreateLogSource("YoutubeOnTV"); mls.LogInfo((object)"YoutubeOnTV loaded"); harmony.PatchAll(typeof(YoutubeOnTVBase)); harmony.PatchAll(typeof(TVScriptPatch)); harmony.PatchAll(typeof(TerminalPatch)); RegisterTerminalCommands(); if (!initialized) { initialized = true; SceneManager.sceneLoaded += OnSceneLoaded; } } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown if ((Object)(object)VideoManager.Instance == (Object)null) { mls.LogInfo((object)("Scene loaded: " + ((Scene)(ref scene)).name + " - initializing managers...")); GameObject val = new GameObject("YoutubeOnTVManagers"); Object.DontDestroyOnLoad((Object)(object)val); mls.LogInfo((object)"Adding VideoManager..."); val.AddComponent<VideoManager>(); mls.LogInfo((object)"Adding NetworkHandler..."); val.AddComponent<NetworkHandler>(); mls.LogInfo((object)"All components added successfully!"); } } private void RegisterTerminalCommands() { //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Expected O, but got Unknown mls.LogInfo((object)"Registering terminal commands..."); TerminalKeyword val = TerminalApi.CreateTerminalKeyword("tv", true, (TerminalNode)null); TerminalKeyword val2 = TerminalApi.CreateTerminalKeyword("add", false, (TerminalNode)null); TerminalKeyword val3 = TerminalApi.CreateTerminalKeyword("clear", false, (TerminalNode)null); TerminalKeyword val4 = TerminalApi.CreateTerminalKeyword("skip", false, (TerminalNode)null); TerminalKeyword val5 = TerminalApi.CreateTerminalKeyword("queue", false, (TerminalNode)null); TerminalNode val6 = TerminalApi.CreateTerminalNode("Video added to queue.\n\n", true, ""); TerminalNode val7 = TerminalApi.CreateTerminalNode("Queue cleared.\n\n", true, ""); TerminalNode val8 = TerminalApi.CreateTerminalNode("Skipped to next video.\n\n", true, ""); TerminalNode val9 = TerminalApi.CreateTerminalNode("Queue status displayed.\n\n", true, ""); TerminalNode specialKeywordResult = TerminalApi.CreateTerminalNode("TV CONTROLS\n===========\nUsage: tv <command> [args]\n\nCommands:\n tv add [url/query] - Add video to queue\n tv clear - Clear video queue\n tv skip - Skip current video\n tv queue - Show queue status\n\n", true, ""); val = TerminalExtenstionMethods.AddCompatibleNoun(val, val2, val6); val = TerminalExtenstionMethods.AddCompatibleNoun(val, val3, val7); val = TerminalExtenstionMethods.AddCompatibleNoun(val, val4, val8); val = TerminalExtenstionMethods.AddCompatibleNoun(val, val5, val9); val.specialKeywordResult = specialKeywordResult; val2.defaultVerb = val; val3.defaultVerb = val; val4.defaultVerb = val; val5.defaultVerb = val; TerminalApi.AddTerminalKeyword(val); TerminalApi.AddTerminalKeyword(val2); TerminalApi.AddTerminalKeyword(val3); TerminalApi.AddTerminalKeyword(val4); TerminalApi.AddTerminalKeyword(val5); Events.TerminalParsedSentence += new TerminalParseSentenceEventHandler(OnTerminalParsedSentence); mls.LogInfo((object)"Terminal commands registered successfully!"); } private void OnTerminalParsedSentence(object sender, TerminalParseSentenceEventArgs e) { string text = e.SubmittedText.Trim().ToLower(); if (!text.StartsWith("tv")) { return; } mls.LogInfo((object)("Processing TV command: " + e.SubmittedText)); if (text.StartsWith("tv add")) { string text2 = ""; if (e.SubmittedText.Length > 7) { int num = e.SubmittedText.ToLower().IndexOf("tv add"); if (num != -1) { text2 = e.SubmittedText.Substring(num + 7).Trim(); } } if (string.IsNullOrEmpty(text2)) { e.ReturnedNode = TerminalApi.CreateTerminalNode("Usage: tv add [url or search query]\nExample: tv add dQw4w9WgXcQ\nExample: tv add https://youtube.com/watch?v=...\nExample: tv add never gonna give you up\n\n", true, ""); return; } if ((Object)(object)NetworkHandler.Instance != (Object)null) { NetworkHandler.Instance.RequestAddVideo(text2); } e.ReturnedNode = TerminalApi.CreateTerminalNode("Added to queue: " + text2 + "\n" + $"Queue size: {VideoQueue.Count()}\n\n", true, ""); } else if (text == "tv clear" || text == "clear tv") { int num2 = VideoQueue.Count(); if ((Object)(object)NetworkHandler.Instance != (Object)null) { NetworkHandler.Instance.RequestClearQueue(); } e.ReturnedNode = TerminalApi.CreateTerminalNode($"Cleared {num2} video(s) from queue.\n\n", true, ""); } else if (text == "tv skip" || text == "skip tv") { if (VideoQueue.IsEmpty()) { e.ReturnedNode = TerminalApi.CreateTerminalNode("Queue is empty. Nothing to skip.\n\n", true, ""); return; } if ((Object)(object)NetworkHandler.Instance != (Object)null) { NetworkHandler.Instance.RequestSkipVideo(); } e.ReturnedNode = TerminalApi.CreateTerminalNode("Skipped to next video.\n" + $"Queue size: {VideoQueue.Count()}\n\n", true, ""); } else if (text == "tv queue" || text == "queue tv") { int num3 = VideoQueue.Count(); if (num3 == 0) { e.ReturnedNode = TerminalApi.CreateTerminalNode("Queue is empty.\n\n", true, ""); } else { e.ReturnedNode = TerminalApi.CreateTerminalNode($"Videos in queue: {num3}\n\n", true, ""); } } } } public class VideoManager : MonoBehaviour { [CompilerGenerated] private sealed class <SetPlaybackTimeWhenReady>d__32 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public float startTime; public VideoManager <>4__this; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <SetPlaybackTimeWhenReady>d__32(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; break; case 1: <>1__state = -1; break; } if ((Object)(object)TVController.Instance != (Object)null && !TVController.Instance.videoPlayer.isPrepared) { <>2__current = (object)new WaitForSeconds(0.1f); <>1__state = 1; return true; } if ((Object)(object)TVController.Instance != (Object)null) { TVController.Instance.videoPlayer.time = startTime; <>4__this.logger.LogInfo((object)$"Set playback start time to {startTime}s"); } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private ManualLogSource logger; private bool skipRequested = false; private bool hasStartedCurrentVideo = false; private bool isPlayingFallback = false; private float lastSyncTime = 0f; private const float SYNC_INTERVAL = 2f; public static VideoManager Instance { get; private set; } public string CurrentVideoUrl { get; private set; } public bool IsLoadingVideo { get; private set; } private static string GetFallbackVideoPath() { return Path.Combine(Paths.PluginPath, "YoutubeOnTV", "fallback.mp4"); } private void Awake() { if ((Object)(object)Instance == (Object)null) { Instance = this; Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); logger = Logger.CreateLogSource("YoutubeOnTV"); logger.LogInfo((object)"VideoManager initialized!"); } else { Object.Destroy((Object)(object)((Component)this).gameObject); } } private void Start() { if (!LNetworkUtils.IsHostOrServer) { logger.LogInfo((object)"Client joining - will request TV state from host"); ((MonoBehaviour)this).Invoke("RequestTVStateFromHost", 0.5f); } } private void RequestTVStateFromHost() { if ((Object)(object)NetworkHandler.Instance != (Object)null) { NetworkHandler.Instance.RequestTVState(); } else { logger.LogWarning((object)"NetworkHandler not found, cannot request TV state"); } } private void Update() { if ((Object)(object)TVController.Instance == (Object)null) { return; } if (!IsTVOn()) { if (TVController.Instance.IsPlaying()) { TVController.Instance.Pause(); } return; } if (LNetworkUtils.IsHostOrServer && !IsLoadingVideo) { if (!VideoQueue.IsEmpty()) { if (isPlayingFallback) { logger.LogInfo((object)"Switching from fallback to queue video"); TVController.Instance.Stop(); isPlayingFallback = false; hasStartedCurrentVideo = false; } if (!hasStartedCurrentVideo && !TVController.Instance.IsPlaying()) { PlayNextFromQueue(); } } else if (!isPlayingFallback && !TVController.Instance.IsPlaying()) { PlayFallbackVideo(); } } if (LNetworkUtils.IsHostOrServer && TVController.Instance.IsPlaying() && !isPlayingFallback && Time.time - lastSyncTime >= 2f) { lastSyncTime = Time.time; float time = (float)TVController.Instance.videoPlayer.time; if ((Object)(object)NetworkHandler.Instance != (Object)null) { NetworkHandler.Instance.BroadcastPlaybackTime(time); } } } public void OnSkipRequested() { skipRequested = true; hasStartedCurrentVideo = false; isPlayingFallback = false; if ((Object)(object)TVController.Instance != (Object)null) { TVController.Instance.Stop(); } } public void PlayNextFromQueue() { if (VideoQueue.IsEmpty()) { logger.LogInfo((object)"Queue is empty, nothing to play"); return; } if (IsLoadingVideo) { logger.LogInfo((object)"Already loading a video, please wait"); return; } string input = VideoQueue.Next(); logger.LogInfo((object)("Loading video from queue: " + input)); IsLoadingVideo = true; hasStartedCurrentVideo = true; skipRequested = false; VideoStreamer.Instance.GetVideoUrl(input, delegate(string resolvedUrl) { IsLoadingVideo = false; if (string.IsNullOrEmpty(resolvedUrl)) { logger.LogError((object)("Failed to resolve video URL: " + input)); if (VideoQueue.IncrementRetry(input)) { string text = VideoQueue.RemoveCurrent(); logger.LogError((object)("Removing video from queue after max retries: " + text)); if ((Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.DisplayTip("Video Error", "Failed to load video. Removed from queue.", true, false, "LC_Tip1"); } hasStartedCurrentVideo = false; if (!VideoQueue.IsEmpty()) { ((MonoBehaviour)this).Invoke("PlayNextFromQueue", 1f); } } else { logger.LogInfo((object)"Retrying video after delay..."); hasStartedCurrentVideo = false; ((MonoBehaviour)this).Invoke("PlayNextFromQueue", 3f); } } else { VideoQueue.ResetRetry(input); CurrentVideoUrl = resolvedUrl; logger.LogInfo((object)"Video URL resolved successfully!"); if ((Object)(object)TVController.Instance != (Object)null) { TVController.Instance.PlayVideo(resolvedUrl); if (LNetworkUtils.IsHostOrServer && (Object)(object)NetworkHandler.Instance != (Object)null) { NetworkHandler.Instance.BroadcastPlayVideo(resolvedUrl, 0f); } } else { logger.LogError((object)"TVController not found!"); } } }); } public void OnVideoFinished() { logger.LogInfo((object)"Video finished playing"); CurrentVideoUrl = null; hasStartedCurrentVideo = false; isPlayingFallback = false; if (!VideoQueue.IsEmpty() && !skipRequested) { ((MonoBehaviour)this).Invoke("PlayNextFromQueue", 1f); } else { logger.LogInfo((object)"Queue empty, will return to fallback video"); } } public void OnVideoPlaybackError(string errorMessage) { logger.LogError((object)("Video playback error: " + errorMessage)); if (VideoQueue.IsEmpty()) { logger.LogInfo((object)"No videos in queue to retry"); return; } string input = VideoQueue.Current(); if (VideoQueue.IncrementRetry(input)) { string text = VideoQueue.RemoveCurrent(); logger.LogError((object)("Removing video from queue after playback errors: " + text)); hasStartedCurrentVideo = false; isPlayingFallback = false; CurrentVideoUrl = null; IsLoadingVideo = false; if (!VideoQueue.IsEmpty()) { ((MonoBehaviour)this).Invoke("PlayNextFromQueue", 1f); } } else { logger.LogInfo((object)"Retrying video after playback error..."); hasStartedCurrentVideo = false; isPlayingFallback = false; CurrentVideoUrl = null; IsLoadingVideo = false; ((MonoBehaviour)this).Invoke("PlayNextFromQueue", 3f); } } private bool IsTVOn() { if ((Object)(object)TVController.Instance == (Object)null) { return false; } TVScript tVScript = TVController.Instance.GetTVScript(); if ((Object)(object)tVScript == (Object)null) { return false; } return tVScript.tvOn; } private void PlayFallbackVideo() { string fallbackVideoPath = GetFallbackVideoPath(); logger.LogInfo((object)("Playing fallback video: " + fallbackVideoPath)); if ((Object)(object)TVController.Instance != (Object)null) { TVController.Instance.PlayLocalVideo(fallbackVideoPath); isPlayingFallback = true; if (LNetworkUtils.IsHostOrServer && (Object)(object)NetworkHandler.Instance != (Object)null) { NetworkHandler.Instance.BroadcastPlayFallback(); } } else { logger.LogError((object)"Cannot play fallback: TVController not found!"); } } public void OnTVPoweredOn() { logger.LogInfo((object)"TV powered on - checking what to play"); if ((Object)(object)TVController.Instance != (Object)null && TVController.Instance.IsPaused()) { logger.LogInfo((object)"Resuming paused video"); TVController.Instance.Resume(); } else if (!VideoQueue.IsEmpty()) { logger.LogInfo((object)"Queue has videos, will play from queue"); hasStartedCurrentVideo = false; } else { logger.LogInfo((object)"Queue is empty, will play fallback"); } } public void PlayVideoFromNetwork(string url, float startTime) { logger.LogInfo((object)$"Playing video from network: {url} at {startTime}s"); if (LNetworkUtils.IsHostOrServer) { return; } CurrentVideoUrl = url; hasStartedCurrentVideo = true; isPlayingFallback = false; if ((Object)(object)TVController.Instance != (Object)null) { TVController.Instance.PlayVideo(url); if (startTime > 0f) { ((MonoBehaviour)this).StartCoroutine(SetPlaybackTimeWhenReady(startTime)); } } } public void SyncPlaybackTime(float time) { if (!LNetworkUtils.IsHostOrServer && !((Object)(object)TVController.Instance == (Object)null) && TVController.Instance.IsPlaying()) { float num = (float)TVController.Instance.videoPlayer.time; float num2 = Mathf.Abs(num - time); if (num2 > 1f) { logger.LogInfo((object)$"Syncing playback time: {num}s -> {time}s (diff: {num2}s)"); TVController.Instance.videoPlayer.time = time; } } } [IteratorStateMachine(typeof(<SetPlaybackTimeWhenReady>d__32))] private IEnumerator SetPlaybackTimeWhenReady(float startTime) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <SetPlaybackTimeWhenReady>d__32(0) { <>4__this = this, startTime = startTime }; } public void PlayFallbackFromNetwork() { logger.LogInfo((object)"Playing fallback from network"); if (!LNetworkUtils.IsHostOrServer) { string fallbackVideoPath = GetFallbackVideoPath(); if ((Object)(object)TVController.Instance != (Object)null) { TVController.Instance.PlayLocalVideo(fallbackVideoPath); isPlayingFallback = true; hasStartedCurrentVideo = false; CurrentVideoUrl = null; } } } public TVStateData GetCurrentTVState() { TVStateData tVStateData = default(TVStateData); tVStateData.isTVOn = IsTVOn(); tVStateData.isPlayingFallback = isPlayingFallback; tVStateData.currentVideoUrl = CurrentVideoUrl; tVStateData.currentPlaybackTime = 0f; tVStateData.isPlaying = false; TVStateData result = tVStateData; if ((Object)(object)TVController.Instance != (Object)null) { result.isPlaying = TVController.Instance.IsPlaying(); if (result.isPlaying && !isPlayingFallback) { result.currentPlaybackTime = (float)TVController.Instance.videoPlayer.time; } } logger.LogInfo((object)$"Gathering TV state - TVOn: {result.isTVOn}, Fallback: {result.isPlayingFallback}, Playing: {result.isPlaying}, URL: {result.currentVideoUrl}"); return result; } public void ApplyTVStateFromNetwork(TVStateData state) { logger.LogInfo((object)$"Applying TV state - TVOn: {state.isTVOn}, Fallback: {state.isPlayingFallback}, URL: {state.currentVideoUrl}"); if (LNetworkUtils.IsHostOrServer) { return; } if ((Object)(object)TVController.Instance == (Object)null) { logger.LogError((object)"Cannot apply TV state: TVController not found!"); return; } TVScript tVScript = TVController.Instance.GetTVScript(); if ((Object)(object)tVScript == (Object)null) { logger.LogError((object)"Cannot apply TV state: TVScript not found!"); return; } if (!state.isTVOn) { logger.LogInfo((object)"TV is off, ensuring TV is off and stopping playback"); if (tVScript.tvOn) { tVScript.TurnTVOnOff(false); } TVController.Instance.Stop(); isPlayingFallback = false; hasStartedCurrentVideo = false; CurrentVideoUrl = null; return; } if (!tVScript.tvOn) { logger.LogInfo((object)"Turning on TV to sync with host"); tVScript.TurnTVOnOff(true); } if (state.isPlayingFallback) { logger.LogInfo((object)"Syncing to fallback video"); string fallbackVideoPath = GetFallbackVideoPath(); TVController.Instance.PlayLocalVideo(fallbackVideoPath); isPlayingFallback = true; hasStartedCurrentVideo = false; CurrentVideoUrl = null; } else if (!string.IsNullOrEmpty(state.currentVideoUrl)) { logger.LogInfo((object)$"Syncing to video: {state.currentVideoUrl} at {state.currentPlaybackTime}s"); CurrentVideoUrl = state.currentVideoUrl; hasStartedCurrentVideo = true; isPlayingFallback = false; TVController.Instance.PlayVideo(state.currentVideoUrl); if (state.currentPlaybackTime > 0f) { ((MonoBehaviour)this).StartCoroutine(SetPlaybackTimeWhenReady(state.currentPlaybackTime)); } } else { logger.LogInfo((object)"TV is on but nothing is playing"); TVController.Instance.Stop(); isPlayingFallback = false; hasStartedCurrentVideo = false; CurrentVideoUrl = null; } } } public static class VideoQueue { private static readonly List<string> _inputs = new List<string>(); private static readonly Dictionary<string, int> _retryCount = new Dictionary<string, int>(); private static int _ptr = 0; private const int MAX_RETRIES = 2; public static void Add(string input) { Debug.Log((object)$"[VideoQueue] Add called with: '{input}' (length: {input.Length})"); string text = ""; string text2 = ExtractYouTubeVideoId(input); if (!string.IsNullOrEmpty(text2)) { text = "https://www.youtube.com/watch?v=" + text2; Debug.Log((object)("[VideoQueue] Extracted video ID '" + text2 + "', constructed URL: '" + text + "'")); } else if (input.Length == 11 && !input.Contains(" ") && IsValidVideoId(input)) { text = "https://www.youtube.com/watch?v=" + input; Debug.Log((object)("[VideoQueue] Detected as video ID, expanded to: '" + text + "'")); } else if (input.StartsWith("http://") || input.StartsWith("https://")) { text = input; Debug.Log((object)("[VideoQueue] Detected as URL, adding as-is: '" + text + "'")); } else { text = "ytsearch:" + input; Debug.Log((object)("[VideoQueue] Detected as search query, prefixed: '" + text + "'")); } _inputs.Add(text); Debug.Log((object)$"[VideoQueue] Successfully added. Queue size: {_inputs.Count}"); } private static string ExtractYouTubeVideoId(string input) { if (string.IsNullOrEmpty(input)) { return null; } Match match = Regex.Match(input, "[?&]v=([a-zA-Z0-9_-]{11})"); if (match.Success) { return match.Groups[1].Value; } match = Regex.Match(input, "youtu\\.be/([a-zA-Z0-9_-]{11})"); if (match.Success) { return match.Groups[1].Value; } match = Regex.Match(input, "youtube\\.com/embed/([a-zA-Z0-9_-]{11})"); if (match.Success) { return match.Groups[1].Value; } match = Regex.Match(input, "youtube\\.com/v/([a-zA-Z0-9_-]{11})"); if (match.Success) { return match.Groups[1].Value; } return null; } private static bool IsValidVideoId(string input) { if (string.IsNullOrEmpty(input) || input.Length != 11) { return false; } return Regex.IsMatch(input, "^[a-zA-Z0-9_-]{11}$"); } public static void Clear() { _inputs.Clear(); _retryCount.Clear(); _ptr = 0; } public static bool IsEmpty() { return _inputs.Count == 0; } public static string Next() { if (_inputs.Count == 0) { return null; } string result = _inputs[_ptr]; _ptr = (_ptr + 1) % _inputs.Count; return result; } public static string Current() { if (_inputs.Count == 0) { return null; } return _inputs[_ptr]; } public static void Skip() { if (_inputs.Count > 0) { _ptr = (_ptr + 1) % _inputs.Count; } } public static int Count() { return _inputs.Count; } public static bool IncrementRetry(string input) { if (!_retryCount.ContainsKey(input)) { _retryCount[input] = 0; } _retryCount[input]++; Debug.Log((object)$"[VideoQueue] Retry count for '{input}': {_retryCount[input]}/{2}"); return _retryCount[input] >= 2; } public static string RemoveCurrent() { if (_inputs.Count == 0) { return null; } string text = _inputs[_ptr]; _inputs.RemoveAt(_ptr); _retryCount.Remove(text); if (_inputs.Count > 0) { _ptr %= _inputs.Count; } else { _ptr = 0; } Debug.Log((object)$"[VideoQueue] Removed '{text}' from queue. Remaining: {_inputs.Count}"); return text; } public static void ResetRetry(string input) { if (_retryCount.ContainsKey(input)) { _retryCount.Remove(input); Debug.Log((object)("[VideoQueue] Reset retry count for '" + input + "'")); } } } public class VideoStreamer : MonoBehaviour { [CompilerGenerated] private sealed class <>c__DisplayClass11_0 { public StringBuilder outputBuilder; public StringBuilder errorBuilder; public YoutubeDLProcess ytdlProc; public string input; public OptionSet options; internal void <GetVideoUrlCoroutine>b__0(object o, DataReceivedEventArgs e) { if (!string.IsNullOrEmpty(e.Data)) { outputBuilder.AppendLine(e.Data); } } internal void <GetVideoUrlCoroutine>b__1(object o, DataReceivedEventArgs e) { if (!string.IsNullOrEmpty(e.Data)) { errorBuilder.AppendLine(e.Data); } } internal async Task<int> <GetVideoUrlCoroutine>b__2() { return await ytdlProc.RunAsync(new string[1] { input }, options); } } [CompilerGenerated] private sealed class <GetVideoUrlCoroutine>d__11 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public string input; public Action<string> onUrlFound; public VideoStreamer <>4__this; private <>c__DisplayClass11_0 <>8__1; private Task<int> <processTask>5__2; private int <exitCode>5__3; private string <output>5__4; private string <error>5__5; private Exception <ex>5__6; private string[] <urls>5__7; private string <videoUrl>5__8; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <GetVideoUrlCoroutine>d__11(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>8__1 = null; <processTask>5__2 = null; <output>5__4 = null; <error>5__5 = null; <ex>5__6 = null; <urls>5__7 = null; <videoUrl>5__8 = null; <>1__state = -2; } private bool MoveNext() { //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Expected O, but got Unknown //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Expected O, but got Unknown //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>8__1 = new <>c__DisplayClass11_0(); <>8__1.input = input; goto IL_006e; case 1: <>1__state = -1; goto IL_006e; case 2: <>1__state = -1; goto IL_01e6; case 3: { <>1__state = -1; if (<processTask>5__2.IsFaulted) { <>4__this._logger.LogError((object)("yt-dlp process failed: " + <processTask>5__2.Exception?.GetBaseException().Message)); onUrlFound(null); <>4__this._isResolving = false; return false; } <exitCode>5__3 = <processTask>5__2.Result; <output>5__4 = <>8__1.outputBuilder.ToString().Trim(); <error>5__5 = <>8__1.errorBuilder.ToString().Trim(); <>4__this._logger.LogInfo((object)$"yt-dlp exit code: {<exitCode>5__3}"); <>4__this._logger.LogInfo((object)$"yt-dlp stdout length: {<output>5__4.Length}"); if (!string.IsNullOrEmpty(<error>5__5)) { <>4__this._logger.LogInfo((object)$"yt-dlp stderr length: {<error>5__5.Length}"); } if (<exitCode>5__3 != 0 || string.IsNullOrEmpty(<output>5__4)) { <>4__this._logger.LogError((object)$"yt-dlp failed (Exit Code: {<exitCode>5__3})"); if (!string.IsNullOrEmpty(<error>5__5)) { <>4__this._logger.LogError((object)("Error: " + <error>5__5)); } onUrlFound(null); } else { <urls>5__7 = <output>5__4.Split(new char[2] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); if (<urls>5__7.Length != 0) { <videoUrl>5__8 = <urls>5__7[0].Trim(); <>4__this._logger.LogInfo((object)"Video URL resolved successfully!"); <>4__this._logger.LogInfo((object)("URL: " + <videoUrl>5__8.Substring(0, Math.Min(100, <videoUrl>5__8.Length)) + "...")); onUrlFound(<videoUrl>5__8); <videoUrl>5__8 = null; } else { <>4__this._logger.LogError((object)"yt-dlp returned empty output"); onUrlFound(null); } <urls>5__7 = null; } <>4__this._isResolving = false; return false; } IL_006e: if (!<>4__this._isInitialized) { <>2__current = null; <>1__state = 1; return true; } <>4__this._isResolving = true; <>4__this._logger.LogInfo((object)("Resolving video URL for: " + <>8__1.input)); <>8__1.ytdlProc = new YoutubeDLProcess(<>4__this._ytDlpPath); <>8__1.outputBuilder = new StringBuilder(); <>8__1.errorBuilder = new StringBuilder(); <>8__1.ytdlProc.OutputReceived += delegate(object o, DataReceivedEventArgs e) { if (!string.IsNullOrEmpty(e.Data)) { <>8__1.outputBuilder.AppendLine(e.Data); } }; <>8__1.ytdlProc.ErrorReceived += delegate(object o, DataReceivedEventArgs e) { if (!string.IsNullOrEmpty(e.Data)) { <>8__1.errorBuilder.AppendLine(e.Data); } }; <>8__1.options = new OptionSet { Format = "18/22/(mp4)[height<=480]/worst", GetUrl = true }; <processTask>5__2 = null; try { <processTask>5__2 = Task.Run(async () => await <>8__1.ytdlProc.RunAsync(new string[1] { <>8__1.input }, <>8__1.options)); } catch (Exception ex) { <ex>5__6 = ex; <>4__this._logger.LogError((object)("Failed to start yt-dlp process: " + <ex>5__6.Message)); onUrlFound(null); <>4__this._isResolving = false; return false; } goto IL_01e6; IL_01e6: if (!<processTask>5__2.IsCompleted) { <>2__current = null; <>1__state = 2; return true; } <>2__current = (object)new WaitForSeconds(0.2f); <>1__state = 3; return true; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <InitializeYtDlp>d__9 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public VideoStreamer <>4__this; private string <dllPath>5__1; private Task <downloadTask>5__2; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <InitializeYtDlp>d__9(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <dllPath>5__1 = null; <downloadTask>5__2 = null; <>1__state = -2; } private bool MoveNext() { int num = <>1__state; if (num != 0) { if (num != 1) { return false; } <>1__state = -1; } else { <>1__state = -1; if (File.Exists(<>4__this._ytDlpPath)) { <>4__this._logger.LogInfo((object)"yt-dlp.exe found at path."); goto IL_0138; } <>4__this._logger.LogInfo((object)"yt-dlp.exe not found. Downloading..."); <dllPath>5__1 = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); <downloadTask>5__2 = Utils.DownloadYtDlp(<dllPath>5__1); } if (!<downloadTask>5__2.IsCompleted) { <>2__current = null; <>1__state = 1; return true; } if (<downloadTask>5__2.IsFaulted) { <>4__this._logger.LogError((object)("Failed to download yt-dlp.exe: " + <downloadTask>5__2.Exception?.GetBaseException().Message)); <>4__this._isInitialized = false; return false; } <>4__this._logger.LogInfo((object)"yt-dlp.exe downloaded successfully!"); <dllPath>5__1 = null; <downloadTask>5__2 = null; goto IL_0138; IL_0138: <>4__this._isInitialized = true; return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private static VideoStreamer _instance; private string _ytDlpPath; private bool _isResolving; private ManualLogSource _logger; private YoutubeDL _ytdl; private bool _isInitialized; public static VideoStreamer Instance { get { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown if ((Object)(object)_instance == (Object)null) { GameObject val = new GameObject("VideoStreamer"); _instance = val.AddComponent<VideoStreamer>(); Object.DontDestroyOnLoad((Object)(object)val); } return _instance; } } private void Awake() { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown _logger = Logger.CreateLogSource("YoutubeOnTV"); string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); _ytDlpPath = Path.Combine(directoryName, "yt-dlp.exe"); _logger.LogInfo((object)("VideoStreamer initialized. yt-dlp path: " + _ytDlpPath)); _ytdl = new YoutubeDL((byte)4); _ytdl.YoutubeDLPath = _ytDlpPath; _ytdl.OutputFolder = Path.Combine(directoryName, "temp"); ((MonoBehaviour)this).StartCoroutine(InitializeYtDlp()); } [IteratorStateMachine(typeof(<InitializeYtDlp>d__9))] private IEnumerator InitializeYtDlp() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <InitializeYtDlp>d__9(0) { <>4__this = this }; } public void GetVideoUrl(string input, Action<string> onUrlFound) { ((MonoBehaviour)this).StartCoroutine(GetVideoUrlCoroutine(input, onUrlFound)); } [IteratorStateMachine(typeof(<GetVideoUrlCoroutine>d__11))] private IEnumerator GetVideoUrlCoroutine(string input, Action<string> onUrlFound) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <GetVideoUrlCoroutine>d__11(0) { <>4__this = this, input = input, onUrlFound = onUrlFound }; } public bool IsResolving() { return _isResolving; } } public class NetworkHandler : MonoBehaviour { private LNetworkMessage<string> addVideoMessage; private LNetworkEvent skipVideoEvent; private LNetworkEvent clearQueueEvent; private LNetworkMessage<VideoPlayData> playVideoMessage; private LNetworkMessage<float> syncPlaybackMessage; private LNetworkEvent playFallbackEvent; private LNetworkEvent requestTVStateEvent; private LNetworkMessage<TVStateData> syncTVStateMessage; public static NetworkHandler Instance { get; private set; } private void Awake() { if ((Object)(object)Instance == (Object)null) { Instance = this; Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); InitializeNetworkMessages(); YoutubeOnTVBase.Instance.mls.LogInfo((object)"NetworkHandler initialized with LethalNetworkAPI!"); } else { Object.Destroy((Object)(object)((Component)this).gameObject); } } private void InitializeNetworkMessages() { addVideoMessage = LNetworkMessage<string>.Connect("YoutubeOnTV_AddVideo", (Action<string, ulong>)OnServerReceivedAddVideo, (Action<string>)OnClientReceivedAddVideo, (Action<string, ulong>)null); skipVideoEvent = LNetworkEvent.Connect("YoutubeOnTV_Skip", (Action<ulong>)OnServerReceivedSkip, (Action)OnClientReceivedSkip, (Action<ulong>)null); clearQueueEvent = LNetworkEvent.Connect("YoutubeOnTV_Clear", (Action<ulong>)OnServerReceivedClear, (Action)OnClientReceivedClear, (Action<ulong>)null); playVideoMessage = LNetworkMessage<VideoPlayData>.Connect("YoutubeOnTV_PlayVideo", (Action<VideoPlayData, ulong>)null, (Action<VideoPlayData>)OnClientReceivedPlayVideo, (Action<VideoPlayData, ulong>)null); syncPlaybackMessage = LNetworkMessage<float>.Connect("YoutubeOnTV_SyncPlayback", (Action<float, ulong>)null, (Action<float>)OnClientReceivedSyncPlayback, (Action<float, ulong>)null); playFallbackEvent = LNetworkEvent.Connect("YoutubeOnTV_PlayFallback", (Action<ulong>)null, (Action)OnClientReceivedPlayFallback, (Action<ulong>)null); requestTVStateEvent = LNetworkEvent.Connect("YoutubeOnTV_RequestTVState", (Action<ulong>)OnServerReceivedRequestTVState, (Action)null, (Action<ulong>)null); syncTVStateMessage = LNetworkMessage<TVStateData>.Connect("YoutubeOnTV_SyncTVState", (Action<TVStateData, ulong>)null, (Action<TVStateData>)OnClientReceivedSyncTVState, (Action<TVStateData, ulong>)null); YoutubeOnTVBase.Instance.mls.LogInfo((object)"Network messages initialized!"); } public void RequestAddVideo(string input) { if (LNetworkUtils.IsHostOrServer) { OnServerReceivedAddVideo(input, 0uL); } else { addVideoMessage.SendServer(input); } } public void RequestSkipVideo() { if (LNetworkUtils.IsHostOrServer) { OnServerReceivedSkip(0uL); } else { skipVideoEvent.InvokeServer(); } } public void RequestClearQueue() { if (LNetworkUtils.IsHostOrServer) { OnServerReceivedClear(0uL); } else { clearQueueEvent.InvokeServer(); } } public void BroadcastPlayVideo(string url, float startTime) { if (!LNetworkUtils.IsHostOrServer) { YoutubeOnTVBase.Instance.mls.LogWarning((object)"Only host can broadcast play video!"); return; } VideoPlayData videoPlayData = default(VideoPlayData); videoPlayData.url = url; videoPlayData.startTime = startTime; VideoPlayData videoPlayData2 = videoPlayData; playVideoMessage.SendClients(videoPlayData2); YoutubeOnTVBase.Instance.mls.LogInfo((object)$"Broadcasting play video: {url} at {startTime}s"); } public void BroadcastPlaybackTime(float time) { if (LNetworkUtils.IsHostOrServer) { syncPlaybackMessage.SendClients(time); } } public void BroadcastPlayFallback() { if (!LNetworkUtils.IsHostOrServer) { YoutubeOnTVBase.Instance.mls.LogWarning((object)"Only host can broadcast play fallback!"); return; } playFallbackEvent.InvokeClients(); YoutubeOnTVBase.Instance.mls.LogInfo((object)"Broadcasting play fallback"); } public void RequestTVState() { if (LNetworkUtils.IsHostOrServer) { YoutubeOnTVBase.Instance.mls.LogWarning((object)"Host doesn't need to request TV state!"); return; } requestTVStateEvent.InvokeServer(); YoutubeOnTVBase.Instance.mls.LogInfo((object)"Requesting TV state from host"); } public void BroadcastTVState(TVStateData state) { if (!LNetworkUtils.IsHostOrServer) { YoutubeOnTVBase.Instance.mls.LogWarning((object)"Only host can broadcast TV state!"); return; } syncTVStateMessage.SendClients(state); YoutubeOnTVBase.Instance.mls.LogInfo((object)$"Broadcasting TV state - TVOn: {state.isTVOn}, Fallback: {state.isPlayingFallback}, URL: {state.currentVideoUrl}"); } private void OnServerReceivedAddVideo(string input, ulong clientId) { YoutubeOnTVBase.Instance.mls.LogInfo((object)("[Host] Received add video request: " + input)); addVideoMessage.SendClients(input); } private void OnServerReceivedSkip(ulong clientId) { YoutubeOnTVBase.Instance.mls.LogInfo((object)"[Host] Received skip request"); skipVideoEvent.InvokeClients(); } private void OnServerReceivedClear(ulong clientId) { YoutubeOnTVBase.Instance.mls.LogInfo((object)"[Host] Received clear queue request"); clearQueueEvent.InvokeClients(); } private void OnServerReceivedRequestTVState(ulong clientId) { YoutubeOnTVBase.Instance.mls.LogInfo((object)$"[Host] Received TV state request from client {clientId}"); if ((Object)(object)VideoManager.Instance != (Object)null) { TVStateData currentTVState = VideoManager.Instance.GetCurrentTVState(); BroadcastTVState(currentTVState); } } private void OnClientReceivedAddVideo(string input) { YoutubeOnTVBase.Instance.mls.LogInfo((object)("[Client] Adding video to queue: " + input)); VideoQueue.Add(input); } private void OnClientReceivedSkip() { YoutubeOnTVBase.Instance.mls.LogInfo((object)"[Client] Skipping video"); VideoQueue.Skip(); if ((Object)(object)VideoManager.Instance != (Object)null) { VideoManager.Instance.OnSkipRequested(); } } private void OnClientReceivedClear() { YoutubeOnTVBase.Instance.mls.LogInfo((object)"[Client] Clearing queue"); VideoQueue.Clear(); if ((Object)(object)VideoManager.Instance != (Object)null) { VideoManager.Instance.OnSkipRequested(); } } private void OnClientReceivedPlayVideo(VideoPlayData data) { YoutubeOnTVBase.Instance.mls.LogInfo((object)$"[Client] Received play video: {data.url} at {data.startTime}s"); if ((Object)(object)VideoManager.Instance != (Object)null) { VideoManager.Instance.PlayVideoFromNetwork(data.url, data.startTime); } } private void OnClientReceivedSyncPlayback(float time) { if ((Object)(object)VideoManager.Instance != (Object)null) { VideoManager.Instance.SyncPlaybackTime(time); } } private void OnClientReceivedPlayFallback() { YoutubeOnTVBase.Instance.mls.LogInfo((object)"[Client] Received play fallback command"); if ((Object)(object)VideoManager.Instance != (Object)null) { VideoManager.Instance.PlayFallbackFromNetwork(); } } private void OnClientReceivedSyncTVState(TVStateData state) { YoutubeOnTVBase.Instance.mls.LogInfo((object)$"[Client] Received TV state - TVOn: {state.isTVOn}, Fallback: {state.isPlayingFallback}, URL: {state.currentVideoUrl}"); if ((Object)(object)VideoManager.Instance != (Object)null) { VideoManager.Instance.ApplyTVStateFromNetwork(state); } } } [Serializable] public struct VideoPlayData { public string url; public float startTime; } [Serializable] public struct TVStateData { public bool isTVOn; public bool isPlayingFallback; public string currentVideoUrl; public float currentPlaybackTime; public bool isPlaying; } public class TVController : MonoBehaviour { public VideoPlayer videoPlayer; private VideoPlayer vanillaVideoPlayer; private RenderTexture renderTexture; private AudioSource tvAudioSource; private TVScript tvScript; private ManualLogSource logger; public static TVController Instance { get; private set; } private void Awake() { //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Expected O, but got Unknown //IL_0218: Unknown result type (might be due to invalid IL or missing references) //IL_0222: Expected O, but got Unknown //IL_0230: Unknown result type (might be due to invalid IL or missing references) //IL_023a: Expected O, but got Unknown //IL_0248: Unknown result type (might be due to invalid IL or missing references) //IL_0252: Expected O, but got Unknown Instance = this; logger = Logger.CreateLogSource("YoutubeOnTV"); tvScript = ((Component)this).gameObject.GetComponent<TVScript>(); if ((Object)(object)tvScript == (Object)null) { logger.LogError((object)"TVScript not found on this GameObject!"); return; } tvAudioSource = tvScript.tvSFX; logger.LogInfo((object)("Found TV AudioSource: " + ((Object)tvAudioSource).name)); vanillaVideoPlayer = tvScript.video; if ((Object)(object)vanillaVideoPlayer != (Object)null) { renderTexture = vanillaVideoPlayer.targetTexture; logger.LogInfo((object)$"Captured render texture: {((Texture)renderTexture).width}x{((Texture)renderTexture).height}"); vanillaVideoPlayer.Stop(); ((Behaviour)vanillaVideoPlayer).enabled = false; logger.LogInfo((object)"Disabled vanilla VideoPlayer"); } else { logger.LogError((object)"Vanilla VideoPlayer not found!"); } videoPlayer = ((Component)this).gameObject.AddComponent<VideoPlayer>(); logger.LogInfo((object)"Created custom VideoPlayer"); videoPlayer.playOnAwake = false; videoPlayer.isLooping = false; videoPlayer.source = (VideoSource)1; videoPlayer.skipOnDrop = true; videoPlayer.controlledAudioTrackCount = 1; videoPlayer.audioOutputMode = (VideoAudioOutputMode)1; videoPlayer.SetTargetAudioSource((ushort)0, tvAudioSource); videoPlayer.targetTexture = renderTexture; logger.LogInfo((object)"Configured VideoPlayer with TV's render texture"); tvScript.video = videoPlayer; logger.LogInfo((object)"Replaced TVScript.video with custom VideoPlayer"); videoPlayer.loopPointReached -= new EventHandler(OnVideoEnd); videoPlayer.loopPointReached += new EventHandler(OnVideoEnd); videoPlayer.errorReceived -= new ErrorEventHandler(OnVideoError); videoPlayer.errorReceived += new ErrorEventHandler(OnVideoError); logger.LogInfo((object)"TVController initialized successfully!"); } private void OnDestroy() { if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } } public void PlayVideo(string url) { //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Expected O, but got Unknown //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Expected O, but got Unknown if (string.IsNullOrEmpty(url)) { logger.LogError((object)"Cannot play video: URL is null or empty"); return; } logger.LogInfo((object)("Playing video: " + url.Substring(0, Math.Min(100, url.Length)) + "...")); videoPlayer.url = url; videoPlayer.prepareCompleted -= new EventHandler(OnVideoPrepared); videoPlayer.prepareCompleted += new EventHandler(OnVideoPrepared); videoPlayer.Prepare(); } private void OnVideoPrepared(VideoPlayer vp) { logger.LogInfo((object)$"Video prepared! Audio tracks: {vp.audioTrackCount}"); if (vp.audioTrackCount > 0) { logger.LogInfo((object)$"Audio channels: {vp.GetAudioChannelCount((ushort)0)}"); } else { logger.LogWarning((object)"Video has NO audio tracks!"); } logger.LogInfo((object)$"TV AudioSource volume: {tvAudioSource.volume}"); vp.Play(); logger.LogInfo((object)"Video playback started!"); } public void Stop() { if (videoPlayer.isPlaying) { videoPlayer.Stop(); logger.LogInfo((object)"Video stopped"); } } public void Pause() { if (videoPlayer.isPlaying) { videoPlayer.Pause(); logger.LogInfo((object)"Video paused"); } } public void Resume() { if (!videoPlayer.isPlaying && !string.IsNullOrEmpty(videoPlayer.url)) { videoPlayer.Play(); logger.LogInfo((object)"Video resumed"); } } public bool IsPaused() { return !videoPlayer.isPlaying && !string.IsNullOrEmpty(videoPlayer.url) && videoPlayer.isPrepared; } public bool IsPlaying() { return videoPlayer.isPlaying; } public void SetLooping(bool shouldLoop) { videoPlayer.isLooping = shouldLoop; logger.LogInfo((object)$"Video looping set to: {shouldLoop}"); } public void PlayLocalVideo(string filePath, bool shouldLoop = false) { //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Expected O, but got Unknown if (string.IsNullOrEmpty(filePath)) { logger.LogError((object)"Cannot play local video: file path is null or empty"); return; } if (!File.Exists(filePath)) { logger.LogError((object)("Cannot play local video: file not found at " + filePath)); return; } logger.LogInfo((object)("Playing local video: " + filePath)); videoPlayer.isLooping = shouldLoop; videoPlayer.url = "file://" + filePath; videoPlayer.prepareCompleted -= new EventHandler(OnVideoPrepared); videoPlayer.prepareCompleted += new EventHandler(OnVideoPrepared); videoPlayer.Prepare(); } public TVScript GetTVScript() { return tvScript; } private void OnVideoEnd(VideoPlayer vp) { logger.LogInfo((object)"Video playback completed"); if (!vp.isLooping && (Object)(object)VideoManager.Instance != (Object)null) { VideoManager.Instance.OnVideoFinished(); } } private void OnVideoError(VideoPlayer vp, string message) { logger.LogError((object)("Video playback error: " + message)); if ((Object)(object)VideoManager.Instance != (Object)null) { VideoManager.Instance.OnVideoPlaybackError(message); } if ((Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.DisplayTip("Video Playback Error", "Failed to play video. Trying next...", true, false, "LC_Tip1"); } } } public static class PluginInfo { public const string PLUGIN_GUID = "YoutubeOnTV"; public const string PLUGIN_NAME = ""; public const string PLUGIN_VERSION = "1.0.0.0"; } } namespace YoutubeOnTV.Patches { [HarmonyPatch(typeof(TVScript))] internal class TVScriptPatch { [HarmonyPatch("Update")] [HarmonyPrefix] private static bool UpdatePatch(TVScript __instance) { if ((Object)(object)((Component)__instance).gameObject.GetComponent<TVController>() == (Object)null) { ((Component)__instance).gameObject.AddComponent<TVController>(); Debug.Log((object)"Attached TVController to TVScript object."); } return false; } [HarmonyPatch("TurnTVOnOff")] [HarmonyPrefix] private static bool TurnTVOnOffPatch(TVScript __instance, bool on) { __instance.tvOn = on; if (on) { Debug.Log((object)"TV turned ON - triggering mod video playback"); __instance.tvSFX.PlayOneShot(__instance.switchTVOn); WalkieTalkie.TransmitOneShotAudio(__instance.tvSFX, __instance.switchTVOn, 1f); if ((Object)(object)VideoManager.Instance != (Object)null) { VideoManager.Instance.OnTVPoweredOn(); } } else { Debug.Log((object)"TV turned OFF"); __instance.tvSFX.PlayOneShot(__instance.switchTVOff); WalkieTalkie.TransmitOneShotAudio(__instance.tvSFX, __instance.switchTVOff, 1f); } typeof(TVScript).GetMethod("SetTVScreenMaterial", BindingFlags.Instance | BindingFlags.NonPublic)?.Invoke(__instance, new object[1] { on }); return false; } } [HarmonyPatch(typeof(Terminal))] internal class TerminalPatch { [HarmonyPatch("TextChanged")] [HarmonyPrefix] private static bool TextChangedPatch(Terminal __instance, string newText, ref int ___textAdded, ref string ___currentText, ref bool ___modifyingText) { if ((Object)(object)__instance.currentNode == (Object)null) { return false; } if (___modifyingText) { ___modifyingText = false; return false; } ___textAdded += newText.Length - ___currentText.Length; if (___textAdded < 0) { __instance.screenText.text = ___currentText; ___textAdded = 0; return false; } int num = 500; if (___textAdded > num) { __instance.screenText.text = ___currentText; ___textAdded = num; return false; } ___currentText = newText; return false; } } }