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