Decompiled source of SyncVideo v1.6.1

plugins/SyncVideo/SyncVideo.dll

Decompiled a week ago
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using BombRushMP.Common;
using BombRushMP.Common.Networking;
using BombRushMP.Common.Packets;
using BombRushMP.Plugin;
using BombRushMP.Plugin.Gamemodes;
using CommonAPI;
using CommonAPI.Phone;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Reptile;
using Reptile.Phone;
using SyncVideo.Model;
using SyncVideo.Phone;
using SyncVideo.Runtime;
using SyncVideo.Transport;
using SyncVideo.Transport.Packets;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.Rendering;
using UnityEngine.UI;
using UnityEngine.Video;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class IsReadOnlyAttribute : Attribute
	{
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace SyncVideo
{
	internal static class PluginInfo
	{
		public const string PLUGIN_GUID = "transrights.SyncVideo";

		public const string PLUGIN_NAME = "Sync Video";

		public const string PLUGIN_VERSION = "1.6.1";
	}
	public sealed class SyncVideoConfig
	{
		public const string DefaultLobbyName = "Sync Video Lobby";

		public const bool LogBusMessages = false;

		private const int ConfigVersion = 5;

		private const string ConfigVersionMarker = "SyncVideoConfigVersion";

		public ConfigEntry<string> TvObjectName { get; }

		public ConfigEntry<string> ScreenMaterialTextureName { get; }

		public ConfigEntry<float> HostBeaconInterval { get; }

		public ConfigEntry<bool> EnableOfflineMode { get; }

		public ConfigEntry<float> HostStateResendInterval { get; }

		public ConfigEntry<float> DriftToleranceSeconds { get; }

		public ConfigEntry<float> HardSeekThresholdSeconds { get; }

		public ConfigEntry<bool> AutoAttachToTVsOnStageLoad { get; }

		public ConfigEntry<bool> ShowScreenPositionMenu { get; }

		public ConfigEntry<bool> ShowRefreshScreensButton { get; }

		public ConfigEntry<bool> HideNativeLobbyUi { get; }

		public ConfigEntry<bool> HostAutoplay { get; }

		public ConfigEntry<int> DefaultVolume { get; }

		public ConfigEntry<string> VideoRenderResolution { get; }

		public ConfigEntry<string> YouTubeStreamResolution { get; }

		public ConfigEntry<bool> UseFFmpeg { get; }

		public ConfigEntry<bool> EnableMkvSupport { get; }

		public ConfigEntry<bool> SuppressAFK { get; }

		public ConfigEntry<bool> MuteMusicAndAmbient { get; }

		public ConfigEntry<bool> UseUnityAudioSource { get; }

		public ConfigEntry<bool> EnableMkvFfmpegConversion { get; }

		public ConfigEntry<bool> MkvTranscodeToH264 { get; }

		public ConfigEntry<float> YouTubeVolumeScale { get; }

		public ConfigEntry<float> SubtitleFontSize { get; }

		public ConfigEntry<bool> StaticTVs { get; }

		public string PluginDirectory { get; }

		public string AdvancedConfigPath { get; }

		public SyncVideoConfig(ConfigFile config, ConfigFile advancedConfig, string pluginLocation)
		{
			bool flag = Migrate(config);
			bool flag2 = Migrate(advancedConfig);
			PluginDirectory = Path.GetDirectoryName(pluginLocation) ?? string.Empty;
			AdvancedConfigPath = advancedConfig.ConfigFilePath;
			EnableOfflineMode = config.Bind<bool>("Offline Mode", "EnableOfflineMode", false, "Disable all online functionality when enabled. Create local lobbies for personal use.");
			HideNativeLobbyUi = config.Bind<bool>("ACN", "Hide Lobby UI", true, "Hide ACN's lobby UI by default.");
			SuppressAFK = config.Bind<bool>("ACN", "Suppress AFK", true, "Hide AFK animations for yourself and other players while in a Sync Video lobby.");
			HostAutoplay = config.Bind<bool>("Video", "Host Autoplay", true, "If hosting a video lobby, automatically start playback after loading your video URL.");
			VideoRenderResolution = config.Bind<string>("Video", "Video Render Resolution", "854x480", "Render resolution used for MP4 video playback. Higher resolutions will cause lag. Options: 1920x1080, 1280x720, 960x540, 854x480, 640x360, 426x240.");
			YouTubeStreamResolution = config.Bind<string>("Video", "YouTube Resolution", "1280x720", "Resolution used when streaming YouTube videos. Options: 1920x1080, 1280x720, 960x540, 854x480, 640x360, 426x240.");
			UseFFmpeg = config.Bind<bool>("Video", "Use FFmpeg", false, "Enable FFmpeg for higher quality YouTube video playback. Requires ffmpeg.exe to be placed in the plugin folder, next to SyncVideo.dll. When disabled, stream videos without downloading.");
			EnableMkvSupport = config.Bind<bool>("Video", "MKV Support", true, "Enable experimental MKV playback support. An MKV Settings menu for the host will appear in the app when an MKV file is loaded.");
			SubtitleFontSize = config.Bind<float>("Video", "Subtitle Font Size", 34f, "Font size for MKV subtitles displayed on the TV screen. Default is 34.");
			DefaultVolume = config.Bind<int>("Volume", "Default Volume", 90, "Starting volume level (0–100). Automatically rounds to increments of 10 in-game.");
			MuteMusicAndAmbient = config.Bind<bool>("Volume", "Mute Music and Ambient SFX", true, "Mute the game's music and ambient sounds while in a Sync Video lobby.");
			StaticTVs = config.Bind<bool>("World Props", "Static TVs", true, "Make TVs stay in place, so people can't kick them and disrupt your watch party.");
			ShowScreenPositionMenu = config.Bind<bool>("World Props", "Show Screen Position Menu", false, "Show the Screen Position menu in the phone app. Lets you move the screen around. Does not sync with viewers if host.");
			TvObjectName = advancedConfig.Bind<string>("Debug", "TV Object Name", "TV", "Scene object name to bind video screens to.");
			ScreenMaterialTextureName = advancedConfig.Bind<string>("Debug", "Screen Material Texture Name", "_MainTex", "Texture slot used when drawing video to a renderer.");
			AutoAttachToTVsOnStageLoad = advancedConfig.Bind<bool>("Debug", "Auto Attach To TVs On Load", true, "Automatically bind screens to matching TV objects when a map loads.");
			ShowRefreshScreensButton = advancedConfig.Bind<bool>("Debug", "Refresh Screens Button", false, "Show the Refresh Screens button in the phone app. Rebinds screens to objects.");
			YouTubeVolumeScale = advancedConfig.Bind<float>("Debug", "YouTube Volume Scale", 1f, "Volume multiplier applied to YouTube videos (0.0-1.0). Makes YouTube videos not kill your ears.");
			UseUnityAudioSource = advancedConfig.Bind<bool>("Debug", "Unity AudioSource", false, "Use Unity AudioSource output for video audio instead of Direct output. Slightly higher latency, but can prevent video/audio offset.");
			EnableMkvFfmpegConversion = advancedConfig.Bind<bool>("Experimental", "MKV To MP4 Conversion", false, "When enabled, MKV files are converted into MP4 with FFmpeg. This re-muxes the MKV to fix container issues for MKVs using the H.264 codec.");
			MkvTranscodeToH264 = advancedConfig.Bind<bool>("Experimental", "Transcode MKV to H.264", false, "Transcodes the video to H.264 instead of using the MKV's codec. Required for H.265/HEVC, VP9, AV1, and 10-bit H.264 sources. Will be very slow.");
			HostBeaconInterval = advancedConfig.Bind<float>("Networking", "Host Beacon Interval", 2f, "Seconds between lobby beacons.");
			HostStateResendInterval = advancedConfig.Bind<float>("Networking", "Host State Resend Interval", 0.5f, "Seconds between host sync state broadcasts.");
			DriftToleranceSeconds = advancedConfig.Bind<float>("Sync", "Drift Tolerance", 0.16f, "Amount of time difference between host and viewer, in seconds. If drift exceeds this, the viewer nudges toward host time.");
			HardSeekThresholdSeconds = advancedConfig.Bind<float>("Sync", "Hard Seek Threshold", 0.6f, "Maximum amount of time difference between host and viewer, in seconds. If drift exceeds this, the viewer performs a hard seek.");
			if (flag)
			{
				SaveCleanConfig(config);
			}
			else
			{
				config.Save();
			}
			if (flag2)
			{
				SaveCleanConfig(advancedConfig);
			}
			else
			{
				advancedConfig.Save();
			}
		}

		private bool Migrate(ConfigFile config)
		{
			int num = ReadConfigVersion(config.ConfigFilePath);
			if (num >= 5)
			{
				return false;
			}
			bool saveOnConfigSet = config.SaveOnConfigSet;
			config.SaveOnConfigSet = false;
			try
			{
				config.Clear();
				ClearOrphanedEntries(config);
				try
				{
					File.Delete(config.ConfigFilePath);
				}
				catch
				{
				}
			}
			finally
			{
				config.SaveOnConfigSet = saveOnConfigSet;
			}
			return true;
		}

		private int ReadConfigVersion(string path)
		{
			try
			{
				if (!File.Exists(path))
				{
					return 0;
				}
				string a = string.Empty;
				string[] array = File.ReadAllLines(path);
				foreach (string text in array)
				{
					string text2 = text.Trim();
					if (text2.StartsWith("## SyncVideoConfigVersion", StringComparison.OrdinalIgnoreCase) || text2.StartsWith("# SyncVideoConfigVersion", StringComparison.OrdinalIgnoreCase))
					{
						int num = text2.IndexOf('=');
						if (num >= 0 && int.TryParse(text2.Substring(num + 1).Trim(), out var result))
						{
							return result;
						}
					}
					if (text2.Length > 2 && text2[0] == '[' && text2[text2.Length - 1] == ']')
					{
						a = text2.Substring(1, text2.Length - 2).Trim();
					}
					else if (string.Equals(a, "Config", StringComparison.OrdinalIgnoreCase) && text2.StartsWith("Version", StringComparison.OrdinalIgnoreCase))
					{
						int num2 = text2.IndexOf('=');
						if (num2 >= 0 && int.TryParse(text2.Substring(num2 + 1).Trim(), out var result2))
						{
							return Math.Min(result2, 4);
						}
					}
				}
			}
			catch
			{
			}
			return 0;
		}

		private void SaveCleanConfig(ConfigFile config)
		{
			try
			{
				string directoryName = Path.GetDirectoryName(config.ConfigFilePath);
				if (!string.IsNullOrEmpty(directoryName))
				{
					Directory.CreateDirectory(directoryName);
				}
				List<string> list = new List<string>();
				Dictionary<string, List<ConfigEntryBase>> dictionary = new Dictionary<string, List<ConfigEntryBase>>(StringComparer.OrdinalIgnoreCase);
				foreach (ConfigDefinition key in config.Keys)
				{
					if (key == (ConfigDefinition)null)
					{
						continue;
					}
					ConfigEntryBase val = null;
					try
					{
						val = config[key];
					}
					catch
					{
					}
					if (val != null)
					{
						string text = key.Section ?? string.Empty;
						if (!dictionary.TryGetValue(text, out var value))
						{
							value = (dictionary[text] = new List<ConfigEntryBase>());
							list.Add(text);
						}
						value.Add(val);
					}
				}
				using StreamWriter streamWriter = new StreamWriter(config.ConfigFilePath, append: false, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
				streamWriter.WriteLine("## SyncVideoConfigVersion = " + 5.ToString(CultureInfo.InvariantCulture));
				streamWriter.WriteLine();
				foreach (string item in list)
				{
					streamWriter.WriteLine("[" + item + "]");
					foreach (ConfigEntryBase item2 in dictionary[item])
					{
						ConfigDescription description = item2.Description;
						string text2 = ((description != null) ? description.Description : null);
						if (!string.IsNullOrEmpty(text2))
						{
							string[] array = text2.Replace("\r\n", "\n").Split(new char[1] { '\n' });
							foreach (string text3 in array)
							{
								streamWriter.WriteLine("## " + text3);
							}
						}
						streamWriter.WriteLine(item2.Definition.Key + " = " + GetConfigValueString(item2));
						streamWriter.WriteLine();
					}
				}
			}
			catch
			{
				config.Save();
			}
		}

		private string GetConfigValueString(ConfigEntryBase entry)
		{
			try
			{
				if (entry.BoxedValue == null)
				{
					return string.Empty;
				}
				if (entry.BoxedValue is IFormattable formattable)
				{
					return formattable.ToString(null, CultureInfo.InvariantCulture);
				}
				return entry.BoxedValue.ToString();
			}
			catch
			{
				return string.Empty;
			}
		}

		private void ClearOrphanedEntries(ConfigFile config)
		{
			BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
			try
			{
				PropertyInfo[] properties = ((object)config).GetType().GetProperties(bindingAttr);
				foreach (PropertyInfo propertyInfo in properties)
				{
					if (propertyInfo.Name.IndexOf("orphan", StringComparison.OrdinalIgnoreCase) >= 0)
					{
						try
						{
							object value = propertyInfo.GetValue(config, null);
							value?.GetType().GetMethod("Clear", bindingAttr)?.Invoke(value, null);
						}
						catch
						{
						}
					}
				}
				FieldInfo[] fields = ((object)config).GetType().GetFields(bindingAttr);
				foreach (FieldInfo fieldInfo in fields)
				{
					if (fieldInfo.Name.IndexOf("orphan", StringComparison.OrdinalIgnoreCase) >= 0)
					{
						try
						{
							object value2 = fieldInfo.GetValue(config);
							value2?.GetType().GetMethod("Clear", bindingAttr)?.Invoke(value2, null);
						}
						catch
						{
						}
					}
				}
			}
			catch
			{
			}
		}
	}
	[BepInPlugin("transrights.SyncVideo", "Sync Video", "1.6.1")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class SyncVideoPlugin : BaseUnityPlugin
	{
		private Harmony _harmony;

		public static SyncVideoPlugin Instance { get; private set; }

		public static SyncVideoConfig Settings { get; private set; }

		public static VideoLobbyManager LobbyManager { get; private set; }

		public static SyncVideoController SyncController { get; private set; }

		public static VideoScreenManager ScreenManager { get; private set; }

		public static SyncVideoTransport Transport { get; private set; }

		public static LobbyUiOverrideManager LobbyUiOverride { get; private set; }

		public static event Action<bool> LobbyStateChanged;

		private void Awake()
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Expected O, but got Unknown
			Instance = this;
			string text = Path.Combine(Paths.ConfigPath, "transrights.SyncVideoAdvanced.cfg");
			ConfigFile advancedConfig = new ConfigFile(text, true);
			Settings = new SyncVideoConfig(((BaseUnityPlugin)this).Config, advancedConfig, ((BaseUnityPlugin)this).Info.Location);
			SyncVideoPacketRegistry.RegisterPackets(((BaseUnityPlugin)this).Logger);
			Transport = new SyncVideoTransport(((BaseUnityPlugin)this).Logger);
			LobbyManager = new VideoLobbyManager(((BaseUnityPlugin)this).Logger, Transport);
			LobbyManager.ActiveLobbyChanged += delegate(VideoLobby lobby)
			{
				SyncVideoPlugin.LobbyStateChanged?.Invoke(lobby != null);
			};
			SyncController = new SyncVideoController(((BaseUnityPlugin)this).Logger, LobbyManager);
			ScreenManager = new VideoScreenManager(((BaseUnityPlugin)this).Logger, SyncController);
			LobbyUiOverride = new LobbyUiOverrideManager(((BaseUnityPlugin)this).Logger, LobbyManager);
			_harmony = new Harmony("transrights.SyncVideo");
			_harmony.PatchAll();
			AppSyncVideo.Initialize();
			AppSyncVideoLobby.Initialize();
			AppSyncVideoPublicLobbies.Initialize();
			AppSyncVideoScreenOptions.Initialize();
			AppSyncVideoLobbyKick.Initialize();
			AppSyncVideoSuggestions.Initialize();
			AppSyncVideoMkvSettings.Initialize();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Sync Video loaded.");
		}

		private void Update()
		{
			LobbyManager.Tick(Time.unscaledDeltaTime);
			SyncController.Tick(Time.unscaledDeltaTime);
			ScreenManager.Tick(Time.unscaledDeltaTime);
			HudManager.Tick();
		}

		private void LateUpdate()
		{
			LobbyUiOverride?.Tick(Time.unscaledDeltaTime);
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			LobbyUiOverride?.Dispose();
			ScreenManager?.Dispose();
			SyncController?.Dispose();
			LobbyManager?.Dispose();
			Transport?.Dispose();
		}
	}
}
namespace SyncVideo.Transport
{
	public static class SyncVideoPacketRegistry
	{
		public static void RegisterPackets(ManualLogSource logger)
		{
		}
	}
	public sealed class SyncVideoTransport : IDisposable
	{
		private readonly ManualLogSource _logger;

		private bool _disposed;

		public ushort LocalPlayerId => (ushort)(((Object)(object)ClientController.Instance != (Object)null) ? ClientController.Instance.LocalID : 0);

		public bool Connected => (Object)(object)ClientController.Instance != (Object)null && ClientController.Instance.Connected;

		public event Action<SyncVideoPacketBase> SyncPacketReceived;

		public SyncVideoTransport(ManualLogSource logger)
		{
			_logger = logger;
			ClientController.RegisterCustomPacketHandler("syncvideo.state", (Action<ushort, byte[]>)OnRawState);
			ClientController.RegisterCustomPacketHandler("syncvideo.time", (Action<ushort, byte[]>)OnRawTime);
			ClientController.RegisterCustomPacketHandler("syncvideo.state_request", (Action<ushort, byte[]>)OnRawStateRequest);
			ClientController.RegisterCustomPacketHandler("syncvideo.lobby_advertise", (Action<ushort, byte[]>)OnRawLobbyAdvertise);
			ClientController.RegisterCustomPacketHandler("syncvideo.lobby_join", (Action<ushort, byte[]>)OnRawLobbyJoin);
			ClientController.RegisterCustomPacketHandler("syncvideo.lobby_leave", (Action<ushort, byte[]>)OnRawLobbyLeave);
			ClientController.RegisterCustomPacketHandler("syncvideo.lobby_members", (Action<ushort, byte[]>)OnRawLobbyMembers);
			ClientController.RegisterCustomPacketHandler("syncvideo.lobby_closed", (Action<ushort, byte[]>)OnRawLobbyClosed);
			ClientController.RegisterCustomPacketHandler("syncvideo.screen_transform", (Action<ushort, byte[]>)OnRawScreenTransform);
			ClientController.RegisterCustomPacketHandler("syncvideo.suggestion", (Action<ushort, byte[]>)OnRawSuggestion);
			ClientController.RegisterCustomPacketHandler("syncvideo.suggestions_open", (Action<ushort, byte[]>)OnRawSuggestionsOpen);
			ClientController.RegisterCustomPacketHandler("syncvideo.suggestion_ack", (Action<ushort, byte[]>)OnRawSuggestionAck);
		}

		public void Dispose()
		{
			_disposed = true;
		}

		public void BroadcastToLobby(SyncVideoPacketBase packet)
		{
			if (_disposed || !Connected || packet == null)
			{
				return;
			}
			try
			{
				ClientController.Instance.BroadcastCustomPacketToCurrentLobby(packet.Serialize(), packet.PacketId, (SendModes)2);
			}
			catch (Exception ex)
			{
				_logger.LogWarning((object)("[SyncVideo] BroadcastToLobby(" + packet.PacketId + ") failed: " + ex.Message));
			}
		}

		public void SendToPlayer(SyncVideoPacketBase packet, ushort targetPlayerId)
		{
			if (_disposed || !Connected || packet == null || targetPlayerId == 0)
			{
				return;
			}
			try
			{
				ClientController.Instance.SendCustomPacketToPlayer(packet.Serialize(), packet.PacketId, targetPlayerId, (SendModes)2);
			}
			catch (Exception ex)
			{
				_logger.LogWarning((object)("[SyncVideo] SendToPlayer(" + packet.PacketId + " → " + targetPlayerId + ") failed: " + ex.Message));
			}
		}

		private void Dispatch(SyncVideoPacketBase packet)
		{
			if (!_disposed && packet != null)
			{
				this.SyncPacketReceived?.Invoke(packet);
			}
		}

		private void OnRawState(ushort fromId, byte[] data)
		{
			try
			{
				Dispatch(SyncVideoStatePacket.Deserialize(fromId, data));
			}
			catch (Exception ex)
			{
				_logger.LogWarning((object)("[SyncVideo] Deserialise State failed: " + ex.Message));
			}
		}

		private void OnRawTime(ushort fromId, byte[] data)
		{
			try
			{
				Dispatch(SyncVideoTimePacket.Deserialize(fromId, data));
			}
			catch (Exception ex)
			{
				_logger.LogWarning((object)("[SyncVideo] Deserialise Time failed: " + ex.Message));
			}
		}

		private void OnRawStateRequest(ushort fromId, byte[] data)
		{
			try
			{
				Dispatch(SyncVideoStateRequestPacket.Deserialize(fromId, data));
			}
			catch (Exception ex)
			{
				_logger.LogWarning((object)("[SyncVideo] Deserialise StateRequest failed: " + ex.Message));
			}
		}

		private void OnRawLobbyAdvertise(ushort fromId, byte[] data)
		{
			try
			{
				Dispatch(SyncVideoLobbyAdvertisePacket.Deserialize(fromId, data));
			}
			catch (Exception ex)
			{
				_logger.LogWarning((object)("[SyncVideo] Deserialise LobbyAdvertise failed: " + ex.Message));
			}
		}

		private void OnRawLobbyJoin(ushort fromId, byte[] data)
		{
			try
			{
				Dispatch(SyncVideoLobbyJoinPacket.Deserialize(fromId, data));
			}
			catch (Exception ex)
			{
				_logger.LogWarning((object)("[SyncVideo] Deserialise LobbyJoin failed: " + ex.Message));
			}
		}

		private void OnRawLobbyLeave(ushort fromId, byte[] data)
		{
			try
			{
				Dispatch(SyncVideoLobbyLeavePacket.Deserialize(fromId, data));
			}
			catch (Exception ex)
			{
				_logger.LogWarning((object)("[SyncVideo] Deserialise LobbyLeave failed: " + ex.Message));
			}
		}

		private void OnRawLobbyMembers(ushort fromId, byte[] data)
		{
			try
			{
				Dispatch(SyncVideoLobbyMembersPacket.Deserialize(fromId, data));
			}
			catch (Exception ex)
			{
				_logger.LogWarning((object)("[SyncVideo] Deserialise LobbyMembers failed: " + ex.Message));
			}
		}

		private void OnRawLobbyClosed(ushort fromId, byte[] data)
		{
			try
			{
				Dispatch(SyncVideoLobbyClosedPacket.Deserialize(fromId, data));
			}
			catch (Exception ex)
			{
				_logger.LogWarning((object)("[SyncVideo] Deserialise LobbyClosed failed: " + ex.Message));
			}
		}

		private void OnRawScreenTransform(ushort fromId, byte[] data)
		{
			try
			{
				Dispatch(SyncVideoScreenTransformPacket.Deserialize(fromId, data));
			}
			catch (Exception ex)
			{
				_logger.LogWarning((object)("[SyncVideo] Deserialise ScreenTransform failed: " + ex.Message));
			}
		}

		private void OnRawSuggestion(ushort fromId, byte[] data)
		{
			try
			{
				Dispatch(SyncVideoSuggestionPacket.Deserialize(fromId, data));
			}
			catch (Exception ex)
			{
				_logger.LogWarning((object)("[SyncVideo] Deserialise Suggestion failed: " + ex.Message));
			}
		}

		private void OnRawSuggestionsOpen(ushort fromId, byte[] data)
		{
			try
			{
				Dispatch(SyncVideoSuggestionsOpenPacket.Deserialize(fromId, data));
			}
			catch (Exception ex)
			{
				_logger.LogWarning((object)("[SyncVideo] Deserialise SuggestionsOpen failed: " + ex.Message));
			}
		}

		private void OnRawSuggestionAck(ushort fromId, byte[] data)
		{
			try
			{
				Dispatch(SyncVideoSuggestionAckPacket.Deserialize(fromId, data));
			}
			catch (Exception ex)
			{
				_logger.LogWarning((object)("[SyncVideo] Deserialise SuggestionAck failed: " + ex.Message));
			}
		}
	}
}
namespace SyncVideo.Transport.Packets
{
	public sealed class SyncVideoLobbyAdvertisePacket : SyncVideoPacketBase
	{
		public string LobbyId = string.Empty;

		public ushort HostId;

		public string LobbyName = string.Empty;

		public int MemberCount;

		public string CurrentUrl = string.Empty;

		public string CurrentVideoId = string.Empty;

		public bool IsPlaying;

		public double MediaTimeSeconds;

		public long HostUnixMilliseconds;

		public int Revision;

		public override string PacketId => "syncvideo.lobby_advertise";

		public static SyncVideoLobbyAdvertisePacket Deserialize(ushort senderPlayerId, byte[] data)
		{
			SyncVideoLobbyAdvertisePacket syncVideoLobbyAdvertisePacket = new SyncVideoLobbyAdvertisePacket
			{
				SenderPlayerId = senderPlayerId
			};
			syncVideoLobbyAdvertisePacket.PopulateFrom(data);
			return syncVideoLobbyAdvertisePacket;
		}

		protected override void ReadPayload(BinaryReader reader)
		{
			LobbyId = SyncVideoPacketSerialization.ReadString(reader);
			HostId = reader.ReadUInt16();
			LobbyName = SyncVideoPacketSerialization.ReadString(reader);
			MemberCount = reader.ReadInt32();
			CurrentUrl = SyncVideoPacketSerialization.ReadString(reader);
			CurrentVideoId = SyncVideoPacketSerialization.ReadString(reader);
			IsPlaying = reader.ReadBoolean();
			MediaTimeSeconds = reader.ReadDouble();
			HostUnixMilliseconds = reader.ReadInt64();
			Revision = reader.ReadInt32();
		}

		protected override void WritePayload(BinaryWriter writer)
		{
			SyncVideoPacketSerialization.WriteString(writer, LobbyId);
			writer.Write(HostId);
			SyncVideoPacketSerialization.WriteString(writer, LobbyName);
			writer.Write(MemberCount);
			SyncVideoPacketSerialization.WriteString(writer, CurrentUrl);
			SyncVideoPacketSerialization.WriteString(writer, CurrentVideoId);
			writer.Write(IsPlaying);
			writer.Write(MediaTimeSeconds);
			writer.Write(HostUnixMilliseconds);
			writer.Write(Revision);
		}
	}
	public sealed class SyncVideoLobbyClosedPacket : SyncVideoPacketBase
	{
		public string LobbyId = string.Empty;

		public override string PacketId => "syncvideo.lobby_closed";

		public static SyncVideoLobbyClosedPacket Deserialize(ushort senderPlayerId, byte[] data)
		{
			SyncVideoLobbyClosedPacket syncVideoLobbyClosedPacket = new SyncVideoLobbyClosedPacket
			{
				SenderPlayerId = senderPlayerId
			};
			syncVideoLobbyClosedPacket.PopulateFrom(data);
			return syncVideoLobbyClosedPacket;
		}

		protected override void ReadPayload(BinaryReader reader)
		{
			LobbyId = SyncVideoPacketSerialization.ReadString(reader);
		}

		protected override void WritePayload(BinaryWriter writer)
		{
			SyncVideoPacketSerialization.WriteString(writer, LobbyId);
		}
	}
	public sealed class SyncVideoLobbyJoinPacket : SyncVideoPacketBase
	{
		public string LobbyId = string.Empty;

		public ushort PlayerId;

		public override string PacketId => "syncvideo.lobby_join";

		public static SyncVideoLobbyJoinPacket Deserialize(ushort senderPlayerId, byte[] data)
		{
			SyncVideoLobbyJoinPacket syncVideoLobbyJoinPacket = new SyncVideoLobbyJoinPacket
			{
				SenderPlayerId = senderPlayerId
			};
			syncVideoLobbyJoinPacket.PopulateFrom(data);
			return syncVideoLobbyJoinPacket;
		}

		protected override void ReadPayload(BinaryReader reader)
		{
			LobbyId = SyncVideoPacketSerialization.ReadString(reader);
			PlayerId = reader.ReadUInt16();
		}

		protected override void WritePayload(BinaryWriter writer)
		{
			SyncVideoPacketSerialization.WriteString(writer, LobbyId);
			writer.Write(PlayerId);
		}
	}
	public sealed class SyncVideoLobbyLeavePacket : SyncVideoPacketBase
	{
		public string LobbyId = string.Empty;

		public ushort PlayerId;

		public override string PacketId => "syncvideo.lobby_leave";

		public static SyncVideoLobbyLeavePacket Deserialize(ushort senderPlayerId, byte[] data)
		{
			SyncVideoLobbyLeavePacket syncVideoLobbyLeavePacket = new SyncVideoLobbyLeavePacket
			{
				SenderPlayerId = senderPlayerId
			};
			syncVideoLobbyLeavePacket.PopulateFrom(data);
			return syncVideoLobbyLeavePacket;
		}

		protected override void ReadPayload(BinaryReader reader)
		{
			LobbyId = SyncVideoPacketSerialization.ReadString(reader);
			PlayerId = reader.ReadUInt16();
		}

		protected override void WritePayload(BinaryWriter writer)
		{
			SyncVideoPacketSerialization.WriteString(writer, LobbyId);
			writer.Write(PlayerId);
		}
	}
	public sealed class SyncVideoLobbyMembersPacket : SyncVideoPacketBase
	{
		public string LobbyId = string.Empty;

		public ushort[] MemberIds = new ushort[0];

		public override string PacketId => "syncvideo.lobby_members";

		public static SyncVideoLobbyMembersPacket Deserialize(ushort senderPlayerId, byte[] data)
		{
			SyncVideoLobbyMembersPacket syncVideoLobbyMembersPacket = new SyncVideoLobbyMembersPacket
			{
				SenderPlayerId = senderPlayerId
			};
			syncVideoLobbyMembersPacket.PopulateFrom(data);
			return syncVideoLobbyMembersPacket;
		}

		protected override void ReadPayload(BinaryReader reader)
		{
			LobbyId = SyncVideoPacketSerialization.ReadString(reader);
			MemberIds = SyncVideoPacketSerialization.ReadUShortArray(reader);
		}

		protected override void WritePayload(BinaryWriter writer)
		{
			SyncVideoPacketSerialization.WriteString(writer, LobbyId);
			SyncVideoPacketSerialization.WriteUShortArray(writer, MemberIds);
		}
	}
	public abstract class SyncVideoPacketBase
	{
		public ushort SenderPlayerId;

		public abstract string PacketId { get; }

		public byte[] Serialize()
		{
			using MemoryStream memoryStream = new MemoryStream();
			using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8);
			WritePayload(binaryWriter);
			binaryWriter.Flush();
			return memoryStream.ToArray();
		}

		protected void PopulateFrom(byte[] data)
		{
			using MemoryStream input = new MemoryStream(data);
			using BinaryReader reader = new BinaryReader(input, Encoding.UTF8);
			ReadPayload(reader);
		}

		protected abstract void WritePayload(BinaryWriter writer);

		protected abstract void ReadPayload(BinaryReader reader);
	}
	public static class SyncVideoPacketIds
	{
		public const string LobbyAdvertise = "syncvideo.lobby_advertise";

		public const string LobbyJoin = "syncvideo.lobby_join";

		public const string LobbyLeave = "syncvideo.lobby_leave";

		public const string LobbyMembers = "syncvideo.lobby_members";

		public const string State = "syncvideo.state";

		public const string LobbyClosed = "syncvideo.lobby_closed";

		public const string StateRequest = "syncvideo.state_request";

		public const string ScreenTransform = "syncvideo.screen_transform";

		public const string Suggestion = "syncvideo.suggestion";

		public const string SuggestionsOpen = "syncvideo.suggestions_open";

		public const string SuggestionAck = "syncvideo.suggestion_ack";

		public const string Time = "syncvideo.time";
	}
	public static class SyncVideoPacketSerialization
	{
		public static void WriteString(BinaryWriter writer, string value)
		{
			writer.Write(value ?? string.Empty);
		}

		public static string ReadString(BinaryReader reader)
		{
			return reader.ReadString();
		}

		public static void WriteUShortArray(BinaryWriter writer, ushort[] values)
		{
			if (values == null)
			{
				writer.Write(0);
				return;
			}
			writer.Write(values.Length);
			for (int i = 0; i < values.Length; i++)
			{
				writer.Write(values[i]);
			}
		}

		public static ushort[] ReadUShortArray(BinaryReader reader)
		{
			int num = reader.ReadInt32();
			if (num <= 0)
			{
				return new ushort[0];
			}
			ushort[] array = new ushort[num];
			for (int i = 0; i < num; i++)
			{
				array[i] = reader.ReadUInt16();
			}
			return array;
		}
	}
	public sealed class SyncVideoScreenTransformPacket : SyncVideoPacketBase
	{
		public string LobbyId = string.Empty;

		public float PosX;

		public float PosY;

		public float PosZ;

		public float ScaleX;

		public float ScaleY;

		public int Revision;

		public override string PacketId => "syncvideo.screen_transform";

		public static SyncVideoScreenTransformPacket Deserialize(ushort senderPlayerId, byte[] data)
		{
			SyncVideoScreenTransformPacket syncVideoScreenTransformPacket = new SyncVideoScreenTransformPacket
			{
				SenderPlayerId = senderPlayerId
			};
			syncVideoScreenTransformPacket.PopulateFrom(data);
			return syncVideoScreenTransformPacket;
		}

		protected override void ReadPayload(BinaryReader reader)
		{
			LobbyId = SyncVideoPacketSerialization.ReadString(reader);
			PosX = reader.ReadSingle();
			PosY = reader.ReadSingle();
			PosZ = reader.ReadSingle();
			ScaleX = reader.ReadSingle();
			ScaleY = reader.ReadSingle();
			Revision = reader.ReadInt32();
		}

		protected override void WritePayload(BinaryWriter writer)
		{
			SyncVideoPacketSerialization.WriteString(writer, LobbyId);
			writer.Write(PosX);
			writer.Write(PosY);
			writer.Write(PosZ);
			writer.Write(ScaleX);
			writer.Write(ScaleY);
			writer.Write(Revision);
		}
	}
	public sealed class SyncVideoStatePacket : SyncVideoPacketBase
	{
		public string LobbyId = string.Empty;

		public string Url = string.Empty;

		public string VideoId = string.Empty;

		public bool IsPlaying;

		public double MediaTimeSeconds;

		public long HostUnixMilliseconds;

		public int Revision;

		public int SeekRevision;

		public bool HasEnded;

		public bool IsOpen = true;

		public bool SuggestionsOpen;

		public int SelectedAudioTrack = 0;

		public int SelectedSubtitleTrack = -1;

		public override string PacketId => "syncvideo.state";

		public static SyncVideoStatePacket Deserialize(ushort senderPlayerId, byte[] data)
		{
			SyncVideoStatePacket syncVideoStatePacket = new SyncVideoStatePacket
			{
				SenderPlayerId = senderPlayerId
			};
			syncVideoStatePacket.PopulateFrom(data);
			return syncVideoStatePacket;
		}

		protected override void ReadPayload(BinaryReader reader)
		{
			LobbyId = SyncVideoPacketSerialization.ReadString(reader);
			Url = SyncVideoPacketSerialization.ReadString(reader);
			VideoId = SyncVideoPacketSerialization.ReadString(reader);
			IsPlaying = reader.ReadBoolean();
			MediaTimeSeconds = reader.ReadDouble();
			HostUnixMilliseconds = reader.ReadInt64();
			Revision = reader.ReadInt32();
			HasEnded = reader.ReadBoolean();
			IsOpen = reader.ReadBoolean();
			SuggestionsOpen = reader.ReadBoolean();
			if (reader.BaseStream.Position < reader.BaseStream.Length)
			{
				SelectedAudioTrack = reader.ReadInt32();
			}
			if (reader.BaseStream.Position < reader.BaseStream.Length)
			{
				SelectedSubtitleTrack = reader.ReadInt32();
			}
			if (reader.BaseStream.Position < reader.BaseStream.Length)
			{
				SeekRevision = reader.ReadInt32();
			}
		}

		protected override void WritePayload(BinaryWriter writer)
		{
			SyncVideoPacketSerialization.WriteString(writer, LobbyId);
			SyncVideoPacketSerialization.WriteString(writer, Url);
			SyncVideoPacketSerialization.WriteString(writer, VideoId);
			writer.Write(IsPlaying);
			writer.Write(MediaTimeSeconds);
			writer.Write(HostUnixMilliseconds);
			writer.Write(Revision);
			writer.Write(HasEnded);
			writer.Write(IsOpen);
			writer.Write(SuggestionsOpen);
			writer.Write(SelectedAudioTrack);
			writer.Write(SelectedSubtitleTrack);
			writer.Write(SeekRevision);
		}
	}
	public sealed class SyncVideoStateRequestPacket : SyncVideoPacketBase
	{
		public string LobbyId = string.Empty;

		public override string PacketId => "syncvideo.state_request";

		public static SyncVideoStateRequestPacket Deserialize(ushort senderPlayerId, byte[] data)
		{
			SyncVideoStateRequestPacket syncVideoStateRequestPacket = new SyncVideoStateRequestPacket
			{
				SenderPlayerId = senderPlayerId
			};
			syncVideoStateRequestPacket.PopulateFrom(data);
			return syncVideoStateRequestPacket;
		}

		protected override void ReadPayload(BinaryReader reader)
		{
			LobbyId = SyncVideoPacketSerialization.ReadString(reader);
		}

		protected override void WritePayload(BinaryWriter writer)
		{
			SyncVideoPacketSerialization.WriteString(writer, LobbyId);
		}
	}
	public sealed class SyncVideoSuggestionAckPacket : SyncVideoPacketBase
	{
		public ushort RecipientPlayerId;

		public string LobbyId = string.Empty;

		public string SuggestionKey = string.Empty;

		public override string PacketId => "syncvideo.suggestion_ack";

		public static SyncVideoSuggestionAckPacket Deserialize(ushort senderPlayerId, byte[] data)
		{
			SyncVideoSuggestionAckPacket syncVideoSuggestionAckPacket = new SyncVideoSuggestionAckPacket
			{
				SenderPlayerId = senderPlayerId
			};
			syncVideoSuggestionAckPacket.PopulateFrom(data);
			return syncVideoSuggestionAckPacket;
		}

		protected override void ReadPayload(BinaryReader reader)
		{
			RecipientPlayerId = reader.ReadUInt16();
			LobbyId = SyncVideoPacketSerialization.ReadString(reader);
			SuggestionKey = SyncVideoPacketSerialization.ReadString(reader);
		}

		protected override void WritePayload(BinaryWriter writer)
		{
			writer.Write(RecipientPlayerId);
			SyncVideoPacketSerialization.WriteString(writer, LobbyId);
			SyncVideoPacketSerialization.WriteString(writer, SuggestionKey);
		}
	}
	public sealed class SyncVideoSuggestionPacket : SyncVideoPacketBase
	{
		public string LobbyId = string.Empty;

		public string Url = string.Empty;

		public string Title = string.Empty;

		public string PlayerName = string.Empty;

		public override string PacketId => "syncvideo.suggestion";

		public static SyncVideoSuggestionPacket Deserialize(ushort senderPlayerId, byte[] data)
		{
			SyncVideoSuggestionPacket syncVideoSuggestionPacket = new SyncVideoSuggestionPacket
			{
				SenderPlayerId = senderPlayerId
			};
			syncVideoSuggestionPacket.PopulateFrom(data);
			return syncVideoSuggestionPacket;
		}

		protected override void ReadPayload(BinaryReader reader)
		{
			LobbyId = SyncVideoPacketSerialization.ReadString(reader);
			Url = SyncVideoPacketSerialization.ReadString(reader);
			Title = SyncVideoPacketSerialization.ReadString(reader);
			PlayerName = ((reader.BaseStream.Position < reader.BaseStream.Length) ? SyncVideoPacketSerialization.ReadString(reader) : string.Empty);
		}

		protected override void WritePayload(BinaryWriter writer)
		{
			SyncVideoPacketSerialization.WriteString(writer, LobbyId);
			SyncVideoPacketSerialization.WriteString(writer, Url);
			SyncVideoPacketSerialization.WriteString(writer, Title);
			SyncVideoPacketSerialization.WriteString(writer, PlayerName);
		}
	}
	public sealed class SyncVideoSuggestionsOpenPacket : SyncVideoPacketBase
	{
		public string LobbyId = string.Empty;

		public bool IsOpen;

		public override string PacketId => "syncvideo.suggestions_open";

		public static SyncVideoSuggestionsOpenPacket Deserialize(ushort senderPlayerId, byte[] data)
		{
			SyncVideoSuggestionsOpenPacket syncVideoSuggestionsOpenPacket = new SyncVideoSuggestionsOpenPacket
			{
				SenderPlayerId = senderPlayerId
			};
			syncVideoSuggestionsOpenPacket.PopulateFrom(data);
			return syncVideoSuggestionsOpenPacket;
		}

		protected override void ReadPayload(BinaryReader reader)
		{
			LobbyId = SyncVideoPacketSerialization.ReadString(reader);
			IsOpen = reader.ReadBoolean();
		}

		protected override void WritePayload(BinaryWriter writer)
		{
			SyncVideoPacketSerialization.WriteString(writer, LobbyId);
			writer.Write(IsOpen);
		}
	}
	public sealed class SyncVideoTimePacket : SyncVideoPacketBase
	{
		public string LobbyId = string.Empty;

		public double MediaTimeSeconds;

		public bool IsPlaying;

		public long HostSentMilliseconds;

		public override string PacketId => "syncvideo.time";

		public static SyncVideoTimePacket Deserialize(ushort senderPlayerId, byte[] data)
		{
			SyncVideoTimePacket syncVideoTimePacket = new SyncVideoTimePacket
			{
				SenderPlayerId = senderPlayerId
			};
			syncVideoTimePacket.PopulateFrom(data);
			return syncVideoTimePacket;
		}

		protected override void ReadPayload(BinaryReader reader)
		{
			LobbyId = SyncVideoPacketSerialization.ReadString(reader);
			MediaTimeSeconds = reader.ReadDouble();
			IsPlaying = reader.ReadBoolean();
			HostSentMilliseconds = reader.ReadInt64();
		}

		protected override void WritePayload(BinaryWriter writer)
		{
			SyncVideoPacketSerialization.WriteString(writer, LobbyId);
			writer.Write(MediaTimeSeconds);
			writer.Write(IsPlaying);
			writer.Write(HostSentMilliseconds);
		}
	}
}
namespace SyncVideo.Runtime
{
	public sealed class DirectUrlVideoBackend : IDisposable, IVideoBackend
	{
		private sealed class AudioTrackInfo
		{
			public int StreamIndex;

			public int UnityTrackIndex;

			public string Language = string.Empty;

			public string Title = string.Empty;

			public string Codec = string.Empty;

			public int Channels;
		}

		private const string NoVideoLoadedMessage = "No Video Loaded!";

		private const string LoadedReadyMessage = "Video Loaded!\nPress Play!";

		private const string UrlErrorMessage = "Video URL Error!";

		private const string PausedMessage = "Paused!";

		private const string VideoEndedMessage = "Video Ended!";

		private const string ResolvingBaseMessage = "Loading Youtube URL!\nPlease wait";

		private const string DownloadingBaseMessage = "FFmpeg enabled!\nDownloading HD Video!\nPlease wait";

		private readonly ManualLogSource _logger;

		private readonly GameObject _root;

		private readonly VideoPlayer _player;

		private readonly AudioSource _separateAudioSource;

		private readonly StreamingPcmAudioFilter _separateAudioFilter;

		private AudioClip _separateAudioClip;

		private readonly RenderTexture _texture;

		private readonly int _renderWidth;

		private readonly int _renderHeight;

		private readonly SubtitleManager _subtitleManager;

		private readonly bool _useAudioSourceOutput;

		private readonly List<AudioSource> _audioSources = new List<AudioSource>();

		private string _lastOriginalUrl = string.Empty;

		private string _subtitleSourceUrl = string.Empty;

		private string _lastVideoId = string.Empty;

		private string _separateAudioUrl = string.Empty;

		private readonly object _audioTrackLock = new object();

		private readonly List<AudioTrackInfo> _audioTracks = new List<AudioTrackInfo>();

		private CancellationTokenSource _audioProbeCts;

		private int _knownAudioTrackCount = 1;

		private int _selectedAudioTrack = 0;

		private bool _isAudioProbing;

		private bool _usingSeparateAudioStream;

		private bool _separateVideoPrepared;

		private bool _separateAudioPrepared;

		private bool _separatePreparedRaised;

		private Process _separateAudioProcess;

		private Thread _separateAudioReadThread;

		private CancellationTokenSource _separateAudioCts;

		private readonly object _separateAudioBufferLock = new object();

		private readonly float[] _separateAudioBuffer = new float[192000];

		private int _separateAudioReadPosition;

		private int _separateAudioWritePosition;

		private int _separateAudioBufferedSamples;

		private double _separateAudioStartSeconds;

		private bool _separateAudioDecoderFailed;

		private bool _separateAudioErrorReported;

		private bool _separateAudioPendingResume;

		private bool _separateAudioPaused;

		private bool _separateSeekPending;

		private bool _separateSeekWasPlaying;

		private double _separateSeekTarget;

		private double _separateSeekStartedDsp;

		private double _sharedClockMediaStart;

		private double _sharedClockDspStart;

		private double _sharedClockPausedTime;

		private double _sharedClockRate = 1.0;

		private bool _sharedClockRunning;

		private bool _separateAwaitingVideoStart;

		private double _separatePendingStartMediaTime;

		private bool _isMuted;

		private float _volume;

		private double _lastKnownTimeSeconds;

		private bool _isResolving;

		private float _resolvingAnimTimer;

		private int _resolvingAnimStep;

		private string _resolvingCurrentBase = string.Empty;

		private bool _isAudioSwitching;

		private double _audioSwitchPendingSeek;

		private bool _audioSwitchPendingWasPlaying;

		private static readonly Regex _audioStreamRx = new Regex("Stream #0:(\\d+)(?:\\(([^)]+)\\))?[^:]*: Audio", RegexOptions.IgnoreCase | RegexOptions.Compiled);

		private static readonly Regex _audioTitleLineRx = new Regex("^\\s+title\\s*:\\s*(.+)$", RegexOptions.IgnoreCase | RegexOptions.Compiled);

		public string CurrentDirectUrl { get; private set; } = string.Empty;

		public bool HasPreparedUrl => !string.IsNullOrWhiteSpace(CurrentDirectUrl);

		public string StatusOverlayText { get; private set; } = "No Video Loaded!";

		public bool IsPrepared => (!_usingSeparateAudioStream) ? _player.isPrepared : (_player.isPrepared && _separateAudioPrepared);

		public bool IsPlaying => _player.isPlaying;

		public bool IsSeeking => _usingSeparateAudioStream && (_separateSeekPending || (_separateAudioPendingResume && !_separateAudioPrepared));

		public double CurrentTimeSeconds => _usingSeparateAudioStream ? GetSharedMediaTimeSeconds() : ((_player.isPrepared || _player.isPlaying) ? _player.time : _lastKnownTimeSeconds);

		public double DurationSeconds => _player.isPrepared ? _player.length : 0.0;

		public object OutputTexture => _texture;

		public float LocalVolume => _volume;

		public bool IsMuted => _isMuted;

		public bool IsCurrentMkv => UrlNormalizer.IsMkvUrl(CurrentDirectUrl) || UrlNormalizer.IsMkvUrl(_lastOriginalUrl);

		public bool ShouldShowFfmpegSyncingStatus => !string.IsNullOrEmpty(_lastVideoId) && YouTube.IsFfmpegAvailable();

		public int AudioTrackCount => Math.Max(1, _knownAudioTrackCount);

		public int SelectedAudioTrack => Mathf.Clamp(_selectedAudioTrack, 0, Math.Max(0, AudioTrackCount - 1));

		public int SubtitleTrackCount => _subtitleManager.TrackCount;

		public int SelectedSubtitleTrack => _subtitleManager.SelectedTrack;

		public bool IsSubtitleProbing => _subtitleManager.IsProbing;

		public bool IsSubtitleExtracting => _subtitleManager.IsExtracting;

		public bool IsAudioSwitching => _isAudioSwitching;

		public bool IsAudioProbing => _isAudioProbing;

		public event Action Prepared;

		public event Action Ended;

		public event Action AudioTrackSwitchCompleted;

		public event Action AudioTracksChanged;

		public DirectUrlVideoBackend()
		{
			//IL_0122: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: Expected O, but got Unknown
			//IL_0172: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Expected O, but got Unknown
			//IL_026c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0276: Expected O, but got Unknown
			//IL_0284: Unknown result type (might be due to invalid IL or missing references)
			//IL_028e: Expected O, but got Unknown
			//IL_029c: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a6: Expected O, but got Unknown
			//IL_02b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02be: Expected O, but got Unknown
			//IL_02cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d6: Expected O, but got Unknown
			_logger = Logger.CreateLogSource("SyncVideo.DirectUrlVideoBackend");
			_subtitleManager = new SubtitleManager(_logger);
			_useAudioSourceOutput = SyncVideoPlugin.Settings == null || SyncVideoPlugin.Settings.UseUnityAudioSource.Value;
			_volume = Mathf.Clamp01((float)((SyncVideoPlugin.Settings != null) ? SyncVideoPlugin.Settings.DefaultVolume.Value : 90) / 100f);
			_root = new GameObject("SyncVideoBackend");
			Object.DontDestroyOnLoad((Object)(object)_root);
			ParseRenderResolution((SyncVideoPlugin.Settings != null) ? SyncVideoPlugin.Settings.VideoRenderResolution.Value : null, out _renderWidth, out _renderHeight);
			_texture = new RenderTexture(_renderWidth, _renderHeight, 0, (RenderTextureFormat)0);
			_texture.useMipMap = false;
			_texture.autoGenerateMips = false;
			_texture.antiAliasing = 1;
			_texture.Create();
			ClearRenderTexture();
			_player = _root.AddComponent<VideoPlayer>();
			_player.playOnAwake = false;
			_player.isLooping = false;
			_player.renderMode = (VideoRenderMode)2;
			_player.targetTexture = _texture;
			_player.aspectRatio = (VideoAspectRatio)3;
			_player.audioOutputMode = (VideoAudioOutputMode)(_useAudioSourceOutput ? 1 : 2);
			if (_useAudioSourceOutput)
			{
				EnsureAudioSourceCount(1);
			}
			_player.EnableAudioTrack((ushort)0, true);
			_player.skipOnDrop = true;
			_player.waitForFirstFrame = true;
			_player.prepareCompleted += new EventHandler(OnPrepareCompleted);
			_player.errorReceived += new ErrorEventHandler(OnErrorReceived);
			_player.loopPointReached += new EventHandler(OnLoopPointReached);
			_player.seekCompleted += new EventHandler(OnSeekCompleted);
			_player.frameReady += new FrameReadyEventHandler(OnFrameReady);
			_separateAudioSource = _root.AddComponent<AudioSource>();
			_separateAudioFilter = _root.AddComponent<StreamingPcmAudioFilter>();
			_separateAudioFilter.SetReader(OnSeparateAudioRead);
			_separateAudioFilter.Active = false;
			_separateAudioSource.playOnAwake = false;
			_separateAudioSource.loop = true;
			_separateAudioSource.spatialBlend = 0f;
			_separateAudioSource.volume = 0f;
			_separateAudioClip = AudioClip.Create("SyncVideoYouTubeAudio", 48000, 2, 48000, false);
			_separateAudioSource.clip = _separateAudioClip;
			_logger.LogInfo((object)$"Using video render texture {_renderWidth}x{_renderHeight}.");
			_logger.LogInfo((object)("Using video audio output mode " + (_useAudioSourceOutput ? "AudioSource" : "Direct") + "."));
		}

		private static void ParseRenderResolution(string configuredValue, out int width, out int height)
		{
			width = 854;
			height = 480;
			switch (string.IsNullOrWhiteSpace(configuredValue) ? string.Empty : configuredValue.Trim().ToLowerInvariant().Replace('x', 'x'))
			{
			case "1920x1080":
				width = 1920;
				height = 1080;
				break;
			case "1280x720":
				width = 1280;
				height = 720;
				break;
			case "960x540":
				width = 960;
				height = 540;
				break;
			case "854x480":
				width = 854;
				height = 480;
				break;
			case "640x360":
				width = 640;
				height = 360;
				break;
			case "426x240":
				width = 426;
				height = 240;
				break;
			}
		}

		public string GetSubtitleTrackLabel(int index)
		{
			return _subtitleManager.GetTrackLabel(index);
		}

		public void SelectSubtitleTrack(int trackIndex, Action onComplete)
		{
			if (string.IsNullOrWhiteSpace(_subtitleSourceUrl))
			{
				return;
			}
			string text = SubtitleManager.FindFfmpegPath();
			if (text != null)
			{
				_subtitleManager.SelectTrack(trackIndex, _subtitleSourceUrl, text, delegate
				{
					SyncVideoPlugin.SyncController?.EnqueueMainThreadAction(onComplete);
				});
			}
		}

		public void DisableSubtitles()
		{
			_subtitleManager.DisableSubtitles();
		}

		public string GetCurrentSubtitleText(double time)
		{
			return _subtitleManager.GetActiveSubtitle(time);
		}

		public void SetResolvingStatus()
		{
			if (_player.isPlaying || _player.isPrepared)
			{
				_player.Stop();
			}
			_player.playbackSpeed = 1f;
			_lastKnownTimeSeconds = 0.0;
			CurrentDirectUrl = string.Empty;
			ClearRenderTexture();
			_isResolving = true;
			_resolvingAnimTimer = 0f;
			_resolvingAnimStep = 0;
			_resolvingCurrentBase = "Loading Youtube URL!\nPlease wait";
			SetStatusOverlay("Loading Youtube URL!\nPlease wait");
		}

		public void SetDownloadingStatus()
		{
			if (_player.isPlaying || _player.isPrepared)
			{
				_player.Stop();
			}
			_player.playbackSpeed = 1f;
			_lastKnownTimeSeconds = 0.0;
			CurrentDirectUrl = string.Empty;
			ClearRenderTexture();
			_isResolving = true;
			_resolvingAnimTimer = 0f;
			_resolvingAnimStep = 0;
			_resolvingCurrentBase = "FFmpeg enabled!\nDownloading HD Video!\nPlease wait";
			SetStatusOverlay("FFmpeg enabled!\nDownloading HD Video!\nPlease wait");
		}

		public void SetConvertingStatus()
		{
			if (_player.isPlaying || _player.isPrepared)
			{
				_player.Stop();
			}
			_player.playbackSpeed = 1f;
			_lastKnownTimeSeconds = 0.0;
			CurrentDirectUrl = string.Empty;
			ClearRenderTexture();
			_isResolving = true;
			_resolvingAnimTimer = 0f;
			_resolvingAnimStep = 0;
			_resolvingCurrentBase = "FFmpeg: Converting MKV!\nPlease wait";
			SetStatusOverlay("FFmpeg: Converting MKV!\nPlease wait");
		}

		public void SetErrorStatus(string message)
		{
			_isResolving = false;
			ClearRenderTexture();
			int num = (message ?? string.Empty).IndexOf('\n');
			string statusOverlay = ((num >= 0) ? ("Error: Video not supported!" + message.Substring(num)) : "Error: Video not supported!");
			SetStatusOverlay(statusOverlay);
		}

		public void Dispose()
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Expected O, but got Unknown
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Expected O, but got Unknown
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Expected O, but got Unknown
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Expected O, but got Unknown
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Expected O, but got Unknown
			_player.prepareCompleted -= new EventHandler(OnPrepareCompleted);
			_player.errorReceived -= new ErrorEventHandler(OnErrorReceived);
			_player.loopPointReached -= new EventHandler(OnLoopPointReached);
			_player.seekCompleted -= new EventHandler(OnSeekCompleted);
			_player.frameReady -= new FrameReadyEventHandler(OnFrameReady);
			StopSeparateAudioDecoder();
			CancelAudioProbe();
			_subtitleManager.Clear();
			if ((Object)(object)_texture != (Object)null)
			{
				_texture.Release();
			}
			if ((Object)(object)_separateAudioClip != (Object)null)
			{
				Object.Destroy((Object)(object)_separateAudioClip);
			}
			if ((Object)(object)_root != (Object)null)
			{
				Object.Destroy((Object)(object)_root);
			}
			if (_logger != null)
			{
				Logger.Sources.Remove((ILogSource)(object)_logger);
			}
		}

		public void Load(string directPlayableUrl, string originalUrl, string videoId)
		{
			_lastOriginalUrl = originalUrl ?? string.Empty;
			_subtitleSourceUrl = ResolveSubtitleSourceUrl(directPlayableUrl, _lastOriginalUrl);
			_lastVideoId = videoId ?? string.Empty;
			CurrentDirectUrl = directPlayableUrl ?? string.Empty;
			_separateAudioUrl = string.Empty;
			_lastKnownTimeSeconds = 0.0;
			ResetAudioTrackCache();
			_selectedAudioTrack = 0;
			_isAudioSwitching = false;
			_audioSwitchPendingSeek = 0.0;
			_audioSwitchPendingWasPlaying = false;
			Stop();
			if (string.IsNullOrWhiteSpace(directPlayableUrl))
			{
				SetStatusOverlay("Video URL Error!");
				return;
			}
			ProbeAudioTracksIfSupported(_subtitleSourceUrl);
			ProbeSubtitlesIfSupported(_subtitleSourceUrl);
			SetStatusOverlay(string.Empty);
			_player.source = (VideoSource)1;
			_player.url = directPlayableUrl;
			ResetAudioRouting();
			ConfigureAudioTracks();
			_player.Prepare();
		}

		public void LoadYouTubeStreams(string videoUrl, string audioUrl, string originalUrl, string videoId)
		{
			if (string.IsNullOrWhiteSpace(audioUrl))
			{
				Load(videoUrl, originalUrl, videoId);
				return;
			}
			Stop();
			_lastOriginalUrl = originalUrl ?? string.Empty;
			_subtitleSourceUrl = videoUrl ?? string.Empty;
			_lastVideoId = videoId ?? string.Empty;
			CurrentDirectUrl = videoUrl ?? string.Empty;
			_separateAudioUrl = audioUrl ?? string.Empty;
			_lastKnownTimeSeconds = 0.0;
			ResetAudioTrackCache();
			_knownAudioTrackCount = 1;
			_selectedAudioTrack = 0;
			_usingSeparateAudioStream = true;
			_separateVideoPrepared = false;
			_separateAudioPrepared = false;
			_separatePreparedRaised = false;
			if (string.IsNullOrWhiteSpace(videoUrl) || string.IsNullOrWhiteSpace(audioUrl))
			{
				SetStatusOverlay("Video URL Error!");
				return;
			}
			SetStatusOverlay(string.Empty);
			_player.source = (VideoSource)1;
			_player.url = videoUrl;
			_player.audioOutputMode = (VideoAudioOutputMode)0;
			_player.timeReference = (VideoTimeReference)2;
			ResetSharedClock(0.0, running: false);
			ApplyAudioState();
			_player.Prepare();
			StartSeparateAudioDecoder(0.0);
		}

		public void ReloadCurrent()
		{
			if (!string.IsNullOrWhiteSpace(CurrentDirectUrl))
			{
				if (_usingSeparateAudioStream && !string.IsNullOrWhiteSpace(_separateAudioUrl))
				{
					LoadYouTubeStreams(CurrentDirectUrl, _separateAudioUrl, _lastOriginalUrl, _lastVideoId);
				}
				else
				{
					Load(CurrentDirectUrl, _lastOriginalUrl, _lastVideoId);
				}
			}
		}

		public void Play()
		{
			if (_usingSeparateAudioStream && _player.isPrepared && !_separateAudioPrepared)
			{
				_separateAudioPendingResume = true;
			}
			else
			{
				if (!IsPrepared)
				{
					return;
				}
				SetStatusOverlay(string.Empty);
				if (_usingSeparateAudioStream)
				{
					double num = (_sharedClockPausedTime = (_sharedClockMediaStart = GetSharedMediaTimeSeconds()));
					_sharedClockRunning = false;
					_player.externalReferenceTime = num;
					_separateAudioSource.pitch = (float)_sharedClockRate;
					if (_separateAudioPaused)
					{
						double sharedClockDspStart = AudioSettings.dspTime + 0.03;
						_sharedClockDspStart = sharedClockDspStart;
						_sharedClockRunning = true;
						_separateAwaitingVideoStart = false;
						_separateAudioFilter.Active = true;
						_separateAudioSource.UnPause();
						_separateAudioPaused = false;
						_player.Play();
					}
					else if (!_separateAudioSource.isPlaying)
					{
						_separatePendingStartMediaTime = num;
						_separateAwaitingVideoStart = true;
						_player.sendFrameReadyEvents = true;
						_player.Play();
					}
					else
					{
						_sharedClockDspStart = AudioSettings.dspTime;
						_sharedClockRunning = true;
						_separateAudioFilter.Active = true;
						_player.Play();
					}
				}
				else
				{
					_player.Play();
				}
			}
		}

		public void Pause()
		{
			if (IsPrepared)
			{
				if (_usingSeparateAudioStream)
				{
					_sharedClockPausedTime = GetSharedMediaTimeSeconds();
					_sharedClockRunning = false;
					_separateAwaitingVideoStart = false;
					_player.sendFrameReadyEvents = false;
					_separateAudioFilter.Active = false;
					_player.externalReferenceTime = _sharedClockPausedTime;
				}
				_player.Pause();
				if (_usingSeparateAudioStream && _separateAudioSource.isPlaying)
				{
					_separateAudioSource.Pause();
					_separateAudioPaused = true;
				}
				UpdatePausedOverlay();
			}
		}

		public void Stop()
		{
			if (_player.isPlaying || _player.isPrepared)
			{
				_player.Stop();
			}
			StopSeparateAudioDecoder();
			StopAudioSources();
			try
			{
				_separateAudioSource.Stop();
			}
			catch
			{
			}
			_player.playbackSpeed = 1f;
			_separateAudioSource.pitch = 1f;
			_usingSeparateAudioStream = false;
			_sharedClockRunning = false;
			_sharedClockRate = 1.0;
			_sharedClockPausedTime = 0.0;
			_sharedClockMediaStart = 0.0;
			_sharedClockDspStart = 0.0;
			_separateAwaitingVideoStart = false;
			_separatePendingStartMediaTime = 0.0;
			_player.sendFrameReadyEvents = false;
			_separateSeekPending = false;
			_separateSeekWasPlaying = false;
			_separateSeekTarget = 0.0;
			_separateSeekStartedDsp = 0.0;
			_player.timeReference = (VideoTimeReference)0;
			_separateVideoPrepared = false;
			_separateAudioPrepared = false;
			_separatePreparedRaised = false;
			RestoreMainAudioOutputMode();
			_lastKnownTimeSeconds = 0.0;
			_isResolving = false;
			SetStatusOverlay("No Video Loaded!");
			ClearRenderTexture();
			CancelAudioProbe();
			ResetAudioTrackCache();
			_subtitleManager.Clear();
		}

		public void Seek(double seconds)
		{
			if (!_player.isPrepared)
			{
				return;
			}
			bool flag = ((!_usingSeparateAudioStream || (!_separateSeekPending && !_separateAudioPendingResume)) ? _player.isPlaying : (_separateSeekWasPlaying || _separateAudioPendingResume));
			if (_player.isPlaying)
			{
				_player.Pause();
				if (_usingSeparateAudioStream && _separateAudioSource.isPlaying)
				{
					_separateAudioSource.Pause();
				}
			}
			double num = Math.Max(0.0, seconds);
			if (_usingSeparateAudioStream)
			{
				double sharedMediaTimeSeconds = GetSharedMediaTimeSeconds();
				if (Math.Abs(num - sharedMediaTimeSeconds) <= 0.05 && !_separateSeekPending)
				{
					if (flag && _separateAudioPrepared)
					{
						Play();
					}
					else if (!flag)
					{
						UpdatePausedOverlay();
					}
					return;
				}
				if (!_separateSeekPending)
				{
					StopSeparateAudioDecoder();
				}
				ResetSharedClock(num, running: false);
				_player.externalReferenceTime = num;
				_separateAudioPendingResume = false;
				_separateSeekPending = true;
				_separateSeekWasPlaying = flag;
				_separateSeekTarget = num;
				_separateSeekStartedDsp = AudioSettings.dspTime;
				_player.time = num;
			}
			else
			{
				_player.time = num;
			}
			_lastKnownTimeSeconds = num;
			_subtitleManager.ResetSearchHint();
			if (!_usingSeparateAudioStream)
			{
				if (flag)
				{
					Play();
				}
				else
				{
					UpdatePausedOverlay();
				}
			}
			else if (!flag)
			{
				UpdatePausedOverlay();
			}
		}

		public void NudgeToward(double seconds, double driftSeconds)
		{
			if (!_player.isPrepared)
			{
				return;
			}
			if (_usingSeparateAudioStream)
			{
				double sharedMediaTimeSeconds = GetSharedMediaTimeSeconds();
				double num = seconds - sharedMediaTimeSeconds;
				if (Math.Abs(num) < 0.015 || driftSeconds <= 0.0)
				{
					SetSharedClockRate(1.0);
					return;
				}
				double num2 = Math.Max(-0.08, Math.Min(0.08, num * 0.14));
				if (Math.Abs(num2) < 0.012)
				{
					num2 = ((num > 0.0) ? 0.012 : (-0.012));
				}
				SetSharedClockRate(Math.Max(0.92, Math.Min(1.08, 1.0 + num2)));
				return;
			}
			double num3 = seconds - _player.time;
			double num4 = Math.Abs(num3);
			if (num4 < 0.015 || driftSeconds <= 0.0)
			{
				_player.playbackSpeed = 1f;
				return;
			}
			float num5 = Mathf.Clamp((float)(num3 * 0.14), -0.08f, 0.08f);
			if (Math.Abs(num5) < 0.012f)
			{
				num5 = ((num3 > 0.0) ? 0.012f : (-0.012f));
			}
			_player.playbackSpeed = Mathf.Clamp(1f + num5, 0.92f, 1.08f);
		}

		public void Tick(float deltaTime)
		{
			if (_isResolving)
			{
				_resolvingAnimTimer += deltaTime;
				if (_resolvingAnimTimer >= 0.35f)
				{
					_resolvingAnimTimer = 0f;
					_resolvingAnimStep = (_resolvingAnimStep + 1) % 4;
					switch (_resolvingAnimStep)
					{
					case 1:
						SetStatusOverlay(_resolvingCurrentBase + ".");
						break;
					case 2:
						SetStatusOverlay(_resolvingCurrentBase + "..");
						break;
					case 3:
						SetStatusOverlay(_resolvingCurrentBase + "...");
						break;
					default:
						SetStatusOverlay(_resolvingCurrentBase);
						break;
					}
				}
			}
			if (_player.isPrepared || _player.isPlaying)
			{
				_lastKnownTimeSeconds = Math.Max(0.0, _player.time);
			}
			if (_usingSeparateAudioStream)
			{
				if (_separateSeekPending && AudioSettings.dspTime - _separateSeekStartedDsp >= 0.75)
				{
					double num = (_player.isPrepared ? Math.Max(0.0, _player.time) : _separateSeekTarget);
					if (Math.Abs(num - _separateSeekTarget) <= 0.35)
					{
						CompleteSeparateSeek(num);
					}
					else
					{
						_separateSeekStartedDsp = AudioSettings.dspTime;
						_player.time = _separateSeekTarget;
					}
				}
				if (_separateAudioDecoderFailed && !_separateAudioErrorReported)
				{
					_separateAudioErrorReported = true;
					_logger.LogError((object)"SyncVideo separate audio stream failed.");
					if (_player.isPlaying || _player.isPrepared)
					{
						_player.Stop();
					}
					try
					{
						_separateAudioSource.Stop();
					}
					catch
					{
					}
					SetStatusOverlay("Video URL Error!");
					ClearRenderTexture();
				}
				if (!_separateAudioPrepared && !_separateAudioDecoderFailed && GetSeparateAudioBufferedSamples() >= 9600)
				{
					_separateAudioPrepared = true;
					ApplyAudioState();
					TryCompleteSeparatePrepare();
					if (_separateAudioPendingResume && _separateVideoPrepared)
					{
						_separateAudioPendingResume = false;
						Play();
					}
				}
				if (_player.isPrepared && _separateAudioPrepared)
				{
					double sharedMediaTimeSeconds = GetSharedMediaTimeSeconds();
					_player.externalReferenceTime = sharedMediaTimeSeconds;
					_lastKnownTimeSeconds = sharedMediaTimeSeconds;
					if (_player.isPlaying)
					{
						if (!_separateAudioSource.isPlaying && AudioSettings.dspTime >= _sharedClockDspStart && _separateAudioPaused)
						{
							_separateAudioFilter.Active = true;
							_separateAudioSource.UnPause();
							_separateAudioPaused = false;
						}
						_separateAudioSource.pitch = (float)_sharedClockRate;
					}
					else if (_separateAudioSource.isPlaying)
					{
						_separateAudioFilter.Active = false;
						_separateAudioSource.Pause();
						_separateAudioPaused = true;
					}
				}
			}
			if (!_usingSeparateAudioStream && !_player.isPlaying && Math.Abs(_player.playbackSpeed - 1f) > 0.001f)
			{
				_player.playbackSpeed = 1f;
			}
		}

		public void AdjustVolume(float delta)
		{
			_volume = Mathf.Clamp01(_volume + delta);
			ApplyAudioState();
		}

		public void ToggleMute()
		{
			_isMuted = !_isMuted;
			ApplyAudioState();
		}

		public string GetAudioTrackLabel(int trackIndex)
		{
			AudioTrackInfo cachedAudioTrack = GetCachedAudioTrack(trackIndex);
			int num = trackIndex + 1;
			if (cachedAudioTrack == null)
			{
				return _isAudioProbing ? ("Audio Track " + num + "\n<color=yellow>Scanning...</color>") : ("Audio Track " + num);
			}
			string text = BuildAudioTrackDetail(cachedAudioTrack);
			if (string.IsNullOrWhiteSpace(text))
			{
				return "Audio Track " + num;
			}
			return "Audio Track " + num + "\n(" + text + ")";
		}

		public bool SelectAudioTrack(int trackIndex)
		{
			return SelectAudioTrack(trackIndex, null, null);
		}

		public bool SelectAudioTrack(int trackIndex, double? resumeTimeOverride, bool? resumePlayingOverride)
		{
			if (string.IsNullOrWhiteSpace(CurrentDirectUrl))
			{
				return false;
			}
			int audioTrackCount = AudioTrackCount;
			if (audioTrackCount <= 0)
			{
				return false;
			}
			int num = Mathf.Clamp(trackIndex, 0, Math.Max(0, audioTrackCount - 1));
			if (num == _selectedAudioTrack)
			{
				return true;
			}
			_selectedAudioTrack = num;
			_audioSwitchPendingSeek = resumeTimeOverride ?? CurrentTimeSeconds;
			_audioSwitchPendingWasPlaying = resumePlayingOverride ?? _player.isPlaying;
			_isAudioSwitching = true;
			if (_player.isPlaying || _player.isPrepared)
			{
				_player.Stop();
			}
			_player.playbackSpeed = 1f;
			_player.source = (VideoSource)1;
			_player.url = CurrentDirectUrl;
			ResetAudioRouting();
			ConfigureAudioTracks();
			_player.Prepare();
			return true;
		}

		public void ShowEndedState(double seconds)
		{
			if (_player.isPlaying || _player.isPrepared)
			{
				_player.Stop();
			}
			StopSeparateAudioDecoder();
			StopAudioSources();
			try
			{
				_separateAudioSource.Stop();
			}
			catch
			{
			}
			_player.playbackSpeed = 1f;
			_separateAudioSource.pitch = 1f;
			_lastKnownTimeSeconds = Math.Max(_lastKnownTimeSeconds, seconds);
			ClearRenderTexture();
			SetStatusOverlay("Video Ended!");
		}

		private void ApplyAudioState()
		{
			float num = (_isMuted ? 0f : _volume);
			if (!string.IsNullOrEmpty(_lastVideoId) && SyncVideoPlugin.Settings != null)
			{
				num *= Mathf.Clamp01(SyncVideoPlugin.Settings.YouTubeVolumeScale.Value);
			}
			if (_usingSeparateAudioStream)
			{
				_separateAudioSource.volume = num;
				return;
			}
			int num2 = Math.Max(1, _knownAudioTrackCount);
			int num3 = Mathf.Clamp(_selectedAudioTrack, 0, Math.Max(0, num2 - 1));
			if (_useAudioSourceOutput)
			{
				if (HasCachedAudioTracks())
				{
					List<AudioTrackInfo> cachedAudioTracksSnapshot = GetCachedAudioTracksSnapshot();
					EnsureAudioSourceCount(cachedAudioTracksSnapshot.Count);
					for (int i = 0; i < cachedAudioTracksSnapshot.Count; i++)
					{
						int unityTrackIndex = cachedAudioTracksSnapshot[i].UnityTrackIndex;
						if (unityTrackIndex >= 0 && unityTrackIndex < _audioSources.Count)
						{
							_audioSources[unityTrackIndex].volume = ((i == num3) ? num : 0f);
						}
					}
				}
				else
				{
					EnsureAudioSourceCount(num2);
					for (int j = 0; j < num2 && j < _audioSources.Count; j++)
					{
						_audioSources[j].volume = ((j == num3) ? num : 0f);
					}
				}
				return;
			}
			if (HasCachedAudioTracks())
			{
				List<AudioTrackInfo> cachedAudioTracksSnapshot2 = GetCachedAudioTracksSnapshot();
				for (int k = 0; k < cachedAudioTracksSnapshot2.Count; k++)
				{
					ushort num4 = (ushort)cachedAudioTracksSnapshot2[k].UnityTrackIndex;
					try
					{
						_player.SetDirectAudioVolume(num4, (k == num3) ? num : 0f);
					}
					catch
					{
					}
				}
				return;
			}
			for (ushort num5 = 0; num5 < (ushort)num2; num5++)
			{
				try
				{
					_player.SetDirectAudioVolume(num5, (num5 == num3) ? num : 0f);
				}
				catch
				{
				}
			}
		}

		private void OnPrepareCompleted(VideoPlayer source)
		{
			source.playbackSpeed = 1f;
			_lastKnownTimeSeconds = 0.0;
			if (_usingSeparateAudioStream)
			{
				_separateVideoPrepared = true;
				TryCompleteSeparatePrepare();
			}
			else if (_isAudioSwitching)
			{
				int val = Math.Max(1, (int)source.audioTrackCount);
				if (!HasCachedAudioTracks())
				{
					_knownAudioTrackCount = Math.Max(_knownAudioTrackCount, val);
				}
				_selectedAudioTrack = Mathf.Clamp(_selectedAudioTrack, 0, Math.Max(0, _knownAudioTrackCount - 1));
				ResetAudioRouting();
				ConfigureAudioTracks();
				ApplyAudioState();
				_isAudioSwitching = false;
				double audioSwitchPendingSeek = _audioSwitchPendingSeek;
				bool audioSwitchPendingWasPlaying = _audioSwitchPendingWasPlaying;
				_audioSwitchPendingSeek = 0.0;
				_audioSwitchPendingWasPlaying = false;
				if (audioSwitchPendingSeek > 0.05)
				{
					source.time = Math.Max(0.0, audioSwitchPendingSeek);
					_lastKnownTimeSeconds = Math.Max(0.0, audioSwitchPendingSeek);
				}
				_subtitleManager.ResetSearchHint();
				if (audioSwitchPendingWasPlaying)
				{
					source.Play();
				}
				else
				{
					UpdatePausedOverlay();
				}
				this.AudioTrackSwitchCompleted?.Invoke();
			}
			else
			{
				if (!HasCachedAudioTracks())
				{
					_knownAudioTrackCount = Math.Max(1, (int)source.audioTrackCount);
				}
				_selectedAudioTrack = Mathf.Clamp(_selectedAudioTrack, 0, Math.Max(0, _knownAudioTrackCount - 1));
				ResetAudioRouting();
				ConfigureAudioTracks();
				ApplyAudioState();
				ClearRenderTexture();
				SetStatusOverlay("Video Loaded!\nPress Play!");
				this.Prepared?.Invoke();
			}
		}

		private void TryCompleteSeparatePrepare()
		{
			if (_usingSeparateAudioStream && _separateVideoPrepared && _separateAudioPrepared && !_separatePreparedRaised)
			{
				_separatePreparedRaised = true;
				ResetSharedClock(0.0, running: false);
				_player.externalReferenceTime = 0.0;
				_player.time = 0.0;
				_lastKnownTimeSeconds = 0.0;
				ClearRenderTexture();
				SetStatusOverlay("Video Loaded!\nPress Play!");
				this.Prepared?.Invoke();
			}
		}

		private void OnErrorReceived(VideoPlayer source, string message)
		{
			_logger.LogError((object)("SyncVideo URL Video backend error: " + message));
			source.Stop();
			StopSeparateAudioDecoder();
			StopAudioSources();
			source.playbackSpeed = 1f;
			_separateAudioSource.pitch = 1f;
			SetStatusOverlay("Video URL Error!");
			ClearRenderTexture();
		}

		private void OnFrameReady(VideoPlayer source, long frameIdx)
		{
			if (_usingSeparateAudioStream && _separateAwaitingVideoStart && _separateAudioPrepared && source.isPlaying)
			{
				double num = Math.Max(0.0, _separatePendingStartMediaTime);
				double num2 = AudioSettings.dspTime + 0.03;
				_separateAwaitingVideoStart = false;
				source.sendFrameReadyEvents = false;
				_sharedClockMediaStart = num;
				_sharedClockPausedTime = num;
				_sharedClockDspStart = num2;
				_sharedClockRunning = true;
				source.externalReferenceTime = num;
				_separateAudioSource.pitch = (float)_sharedClockRate;
				_separateAudioFilter.Active = true;
				_separateAudioSource.PlayScheduled(num2);
			}
		}

		private void OnSeekCompleted(VideoPlayer source)
		{
			if (_usingSeparateAudioStream && _separateSeekPending)
			{
				double num = Math.Max(0.0, source.time);
				if (!(Math.Abs(num - _separateSeekTarget) > 0.35))
				{
					CompleteSeparateSeek(num);
				}
			}
		}

		private void CompleteSeparateSeek(double confirmed)
		{
			if (_separateSeekPending)
			{
				_separateSeekPending = false;
				_separateSeekStartedDsp = 0.0;
				ResetSharedClock(confirmed, running: false);
				_player.externalReferenceTime = confirmed;
				_lastKnownTimeSeconds = confirmed;
				_separateAudioPendingResume = _separateSeekWasPlaying;
				_separateSeekWasPlaying = false;
				StartSeparateAudioDecoder(confirmed);
				if (!_separateAudioPendingResume)
				{
					UpdatePausedOverlay();
				}
			}
		}

		private void OnLoopPointReached(VideoPlayer source)
		{
			double num = source.time;
			if (num <= 0.0 && source.frameCount != 0 && source.frameRate > 0f)
			{
				num = (float)source.frameCount / source.frameRate;
			}
			_lastKnownTimeSeconds = Math.Max(_lastKnownTimeSeconds, num);
			source.Stop();
			StopSeparateAudioDecoder();
			StopAudioSources();
			source.playbackSpeed = 1f;
			_separateAudioSource.pitch = 1f;
			ClearRenderTexture();
			SetStatusOverlay("Video Ended!");
			this.Ended?.Invoke();
		}

		private void StartSeparateAudioDecoder(double startSeconds)
		{
			StopSeparateAudioDecoder();
			_separateAudioStartSeconds = Math.Max(0.0, startSeconds);
			_separateAudioPrepared = false;
			_separateAudioDecoderFailed = false;
			_separateAudioErrorReported = false;
			ResetSeparateAudioBuffer();
			try
			{
				_separateAudioSource.Stop();
			}
			catch
			{
			}
			_separateAudioPaused = false;
			_separateAudioSource.pitch = (float)_sharedClockRate;
			_separateAudioFilter.Active = false;
			string text = SubtitleManager.FindFfmpegPath();
			if (string.IsNullOrWhiteSpace(text) || !File.Exists(text))
			{
				_separateAudioDecoderFailed = true;
				_logger.LogError((object)"SyncVideo separate audio decoder could not find FFmpeg.");
				return;
			}
			string text2 = (_separateAudioUrl ?? string.Empty).Replace("\"", "\\\"");
			string text3 = _separateAudioStartSeconds.ToString("0.###", CultureInfo.InvariantCulture);
			ProcessStartInfo processStartInfo = new ProcessStartInfo();
			processStartInfo.FileName = text;
			processStartInfo.Arguments = "-hide_banner -loglevel error -nostdin -fflags nobuffer -flags low_delay -analyzeduration 0 -probesize 32768 -reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 2 -ss " + text3 + " -i \"" + text2 + "\" -vn -ac 2 -ar 48000 -acodec pcm_s16le -f s16le -flush_packets 1 pipe:1";
			processStartInfo.UseShellExecute = false;
			processStartInfo.CreateNoWindow = true;
			processStartInfo.RedirectStandardOutput = true;
			processStartInfo.RedirectStandardError = true;
			ProcessStartInfo startInfo = processStartInfo;
			try
			{
				_separateAudioCts = new CancellationTokenSource();
				_separateAudioProcess = new Process
				{
					StartInfo = startInfo,
					EnableRaisingEvents = false
				};
				_separateAudioProcess.ErrorDataReceived += delegate(object sender, DataReceivedEventArgs args)
				{
					if (!string.IsNullOrWhiteSpace(args.Data) && !_separateAudioDecoderFailed)
					{
						_logger.LogWarning((object)("SyncVideo separate audio FFmpeg: " + args.Data));
					}
				};
				_separateAudioProcess.Start();
				_separateAudioProcess.BeginErrorReadLine();
				CancellationToken token = _separateAudioCts.Token;
				_separateAudioReadThread = new Thread((ThreadStart)delegate
				{
					ReadSeparateAudioLoop(_separateAudioProcess, token);
				});
				_separateAudioReadThread.IsBackground = true;
				_separateAudioReadThread.Name = "SyncVideoYouTubeAudio";
				_separateAudioReadThread.Start();
			}
			catch (Exception ex)
			{
				_separateAudioDecoderFailed = true;
				_logger.LogError((object)("SyncVideo separate audio decoder failed to start: " + ex.Message));
				StopSeparateAudioDecoderProcessOnly();
			}
		}

		private void ReadSeparateAudioLoop(Process process, CancellationToken token)
		{
			byte[] array = new byte[16384];
			try
			{
				Stream baseStream = process.StandardOutput.BaseStream;
				while (!token.IsCancellationRequested)
				{
					int num = baseStream.Read(array, 0, array.Length);
					if (num <= 0)
					{
						break;
					}
					int num2 = num / 2;
					int num3 = 0;
					while (num3 < num2 && !token.IsCancellationRequested)
					{
						int num4 = 0;
						lock (_separateAudioBufferLock)
						{
							int val = _separateAudioBuffer.Length - _separateAudioBufferedSamples;
							int num5 = Math.Min(val, num2 - num3);
							for (int i = 0; i < num5; i++)
							{
								int num6 = (num3 + i) * 2;
								short num7 = (short)(array[num6] | (array[num6 + 1] << 8));
								_separateAudioBuffer[_separateAudioWritePosition] = (float)num7 / 32768f;
								_separateAudioWritePosition++;
								if (_separateAudioWritePosition >= _separateAudioBuffer.Length)
								{
									_separateAudioWritePosition = 0;
								}
							}
							_separateAudioBufferedSamples += num5;
							num3 += num5;
							num4 = num5;
						}
						if (num4 == 0)
						{
							Thread.Sleep(5);
						}
					}
				}
				if (!token.IsCancellationRequested)
				{
					process.WaitForExit();
					if (process.ExitCode != 0)
					{
						_separateAudioDecoderFailed = true;
					}
				}
			}
			catch (Exception ex)
			{
				if (!token.IsCancellationRequested)
				{
					_separateAudioDecoderFailed = true;
					_logger.LogError((object)("SyncVideo separate audio decoder error: " + ex.Message));
				}
			}
		}

		private void OnSeparateAudioRead(float[] data)
		{
			int num = 0;
			lock (_separateAudioBufferLock)
			{
				num = Math.Min(data.Length, _separateAudioBufferedSamples);
				for (int i = 0; i < num; i++)
				{
					data[i] = _separateAudioBuffer[_separateAudioReadPosition];
					_separateAudioReadPosition++;
					if (_separateAudioReadPosition >= _separateAudioBuffer.Length)
					{
						_separateAudioReadPosition = 0;
					}
				}
				_separateAudioBufferedSamples -= num;
			}
			for (int j = num; j < data.Length; j++)
			{
				data[j] = 0f;
			}
		}

		private int GetSeparateAudioBufferedSamples()
		{
			lock (_separateAudioBufferLock)
			{
				return _separateAudioBufferedSamples;
			}
		}

		private double GetSharedMediaTimeSeconds()
		{
			if (!_usingSeparateAudioStream)
			{
				return (_player.isPrepared || _player.isPlaying) ? _player.time : _lastKnownTimeSeconds;
			}
			if (!_sharedClockRunning)
			{
				return Math.Max(0.0, _sharedClockPausedTime);
			}
			double num = Math.Max(0.0, AudioSettings.dspTime - _sharedClockDspStart);
			return Math.Max(0.0, _sharedClockMediaStart + num * _sharedClockRate);
		}

		private void ResetSharedClock(double mediaTime, bool running)
		{
			_sharedClockPausedTime = (_sharedClockMediaStart = Math.Max(0.0, mediaTime));
			_sharedClockDspStart = AudioSettings.dspTime;
			_sharedClockRate = 1.0;
			_sharedClockRunning = running;
		}

		private void SetSharedClockRate(double rate)
		{
			double num = Math.Max(0.92, Math.Min(1.08, rate));
			if (!(Math.Abs(num - _sharedClockRate) < 0.0001))
			{
				_sharedClockPausedTime = (_sharedClockMediaStart = GetSharedMediaTimeSeconds());
				_sharedClockDspStart = AudioSettings.dspTime;
				_sharedClockRate = num;
				_separateAudioSource.pitch = (float)num;
			}
		}

		private void ResetSeparateAudioBuffer()
		{
			lock (_separateAudioBufferLock)
			{
				_separateAudioReadPosition = 0;
				_separateAudioWritePosition = 0;
				_separateAudioBufferedSamples = 0;
			}
		}

		private void StopSeparateAudioDecoder()
		{
			_separateAudioFilter.Active = false;
			try
			{
				_separateAudioSource.Stop();
			}
			catch
			{
			}
			_separateAudioPaused = false;
			if (_separateAudioCts != null)
			{
				try
				{
					_separateAudioCts.Cancel();
				}
				catch
				{
				}
			}
			StopSeparateAudioDecoderProcessOnly();
			if (_separateAudioReadThread != null && _separateAudioReadThread.IsAlive)
			{
				try
				{
					_separateAudioReadThread.Join(150);
				}
				catch
				{
				}
			}
			_separateAudioReadThread = null;
			if (_separateAudioCts != null)
			{
				try
				{
					_separateAudioCts.Dispose();
				}
				catch
				{
				}
				_separateAudioCts = null;
			}
			ResetSeparateAudioBuffer();
			_separateAudioPrepared = false;
		}

		private void StopSeparateAudioDecoderProcessOnly()
		{
			Process separateAudioProcess = _separateAudioProcess;
			_separateAudioProcess = null;
			if (separateAudioProcess == null)
			{
				return;
			}
			try
			{
				if (!separateAudioProcess.HasExited)
				{
					separateAudioProcess.Kill();
				}
			}
			catch
			{
			}
			try
			{
				separateAudioProcess.Dispose();
			}
			catch
			{
			}
		}

		private void UpdatePausedOverlay()
		{
			if (_player.isPrepared)
			{
				SetStatusOverlay((CurrentTimeSeconds <= 0.05) ? "Video Loaded!\nPress Play!" : "Paused!");
			}
		}

		private void SetStatusOverlay(string message)
		{
			StatusOverlayText = message ?? string.Empty;
		}

		private void RestoreMainAudioOutputMode()
		{
			try
			{
				_player.audioOutputMode = (VideoAudioOutputMode)(_useAudioSourceOutput ? 1 : 2);
			}
			catch
			{
			}
		}

		private void ResetAudioRouting()
		{
			if (_usingSeparateAudioStream || _useAudioSourceOutput)
			{
				return;
			}
			try
			{
				_player.audioOutputMode = (VideoAudioOutputMode)0;
				_player.audioOutputMode = (VideoAudioOutputMode)2;
			}
			catch
			{
			}
		}

		private void EnsureAudioSourceCount(int count)
		{
			if (_useAudioSourceOutput)
			{
				count = Math.Max(1, count);
				while (_audioSources.Count < count)
				{
					AudioSource val = _root.AddComponent<AudioSource>();
					val.playOnAwake = false;
					val.loop = false;
					val.spatialBlend = 0f;
					val.volume = 0f;
					_audioSources.Add(val);
				}
			}
		}

		private void StopAudioSources()
		{
			if (!_useAudioSourceOutput)
			{
				return;
			}
			for (int i = 0; i < _audioSources.Count; i++)
			{
				try
				{
					_audioSources[i].Stop();
				}
				catch
				{
				}
			}
		}

		private void ConfigureAudioTracks()
		{
			int num = Math.Max(1, _knownAudioTrackCount);
			int num2 = Mathf.Clamp(_selectedAudioTrack, 0, Math.Max(0, num - 1));
			if (HasCachedAudioTracks())
			{
				List<AudioTrackInfo> cachedAudioTracksSnapshot = GetCachedAudioTracksSnapshot();
				try
				{
					_player.controlledAudioTrackCount = (ushort)cachedAudioTracksSnapshot.Count;
				}
				catch
				{
				}
				EnsureAudioSourceCount(cachedAudioTracksSnapshot.Count);
				for (int i = 0; i < cachedAudioTracksSnapshot.Count; i++)
				{
					ushort num3 = (ushort)cachedAudioTracksSnapshot[i].UnityTrackIndex;
					try
					{
						_player.EnableAudioTrack(num3, i == num2);
					}
					catch
					{
					}
					if (_useAudioSourceOutput && num3 < _audioSources.Count)
					{
						try
						{
							_player.SetTargetAudioSource(num3, _audioSources[num3]);
						}
						catch
						{
						}
					}
				}
				AudioTrackInfo audioTrackInfo = ((num2 >= 0 && num2 < cachedAudioTracksSnapshot.Count) ? cachedAudioTracksSnapshot[num2] : null);
				if (audioTrackInfo != null)
				{
					return;
				}
			}
			try
			{
				_player.controlledAudioTrackCount = (ushort)num;
			}
			catch
			{
			}
			EnsureAudioSourceCount(num);
			for (int j = 0; j < num; j++)
			{
				try
				{
					_player.EnableAudioTrack((ushort)j, j == num2);
				}
				catch
				{
				}
				if (_useAudioSourceOutput && j < _audioSources.Count)
				{
					try
					{
						_player.SetTargetAudioSource((ushort)j, _audioSources[j]);
					}
					catch
					{
					}
				}
			}
		}

		private void ResetAudioTrackCache()
		{
			CancelAudioProbe();
			lock (_audioTrackLock)
			{
				_audioTracks.Clear();
			}
			_knownAudioTrackCount = 1;
			_isAudioProbing = false;
		}

		private void CancelAudioProbe()
		{
			try
			{
				_audioProbeCts?.Cancel();
			}
			catch
			{
			}
			try
			{
				_audioProbeCts?.Dispose();
			}
			catch
			{
			}
			_audioProbeCts = null;
		}

		private bool HasCachedAudioTracks()
		{
			lock (_audioTrackLock)
			{
				return _audioTracks.Count > 0;
			}
		}

		private AudioTrackInfo GetCachedAudioTrack(int trackIndex)
		{
			lock (_audioTrackLock)
			{
				if (trackIndex < 0 || trackIndex >= _audioTracks.Count)
				{
					return null;
				}
				return _audioTracks[trackIndex];
			}
		}

		private List<AudioTrackInfo> GetCachedAudioTracksSnapshot()
		{
			lock (_audioTrackLock)
			{
				return new List<AudioTrackInfo>(_audioTracks);
			}
		}

		private void ProbeAudioTracksIfSupported(string url)
		{
			if (string.IsNullOrWhiteSpace(url) || !UrlNormalizer.IsMkvUrl(url))
			{
				return;
			}
			string ffmpegPath = SubtitleManager.FindFfmpegPath();
			if (ffmpegPath == null)
			{
				return;
			}
			string ffprobePath = FindFfprobePath(ffmpegPath);
			CancelAudioProbe();
			CancellationTokenSource cts = new CancellationTokenSource();
			_audioProbeCts = cts;
			_isAudioProbing = true;
			this.AudioTracksChanged?.Invoke();
			Task.Run(delegate
			{
				List<AudioTrackInfo> detected = null;
				try
				{
					if (ffprobePath != null)
					{
						detected = ProbeAudioTracks(url, ffprobePath, cts.Token);
					}
					if ((detected == null || detected.Count == 0) && !cts.IsCancellationRequested)
					{
						detected = ProbeAudioTracksFromFfmpegStderr(url, ffmpegPath, cts.Token);
					}
				}
				catch (Exception ex)
				{
					_logger.LogWarning((object)("[MKV Audio] Probe error: " + ex.Message));
				}
				if (!cts.IsCancellationRequested)
				{
					SyncVideoPlugin.SyncController?.EnqueueMainThreadAction(delegate
					{
						if (!cts.IsCancellationRequested)
						{
							_isAudioProbing = false;
							if (detected != null && detected.Count > 0)
							{
								lock (_audioTrackLock)
								{
									_audioTracks.Clear();
									_audioTracks.AddRange(detected);
								}
								_knownAudioTrackCount = detected.Count;
								_selectedAudioTrack = Mathf.Clamp(_selectedAudioTrack, 0, Math.Max(0, _knownAudioTrackCount - 1));
								ConfigureAudioTracks();
								ApplyAudioState();
								_logger.LogInfo((object)$"[MKV Audio] Cached {detected.Count} audio track(s)!");
							}
							this.AudioTracksChanged?.Invoke();
						}
					});
				}
			}, cts.Token);
		}

		private static List<AudioTrackInfo> ProbeAudioTracks(string url, string ffprobePath, CancellationToken token)
		{
			List<AudioTrackInfo> result = new List<AudioTrackInfo>();
			ProcessStartInfo startInfo = new ProcessStartInfo
			{
				FileName = ffprobePath,
				Arguments = "-v error -probesize 100M -analyzeduration 100M -select_streams a -show_entries stream=index,codec_name,channels:stream_tags=language,title -of default=noprint_wrappers=0 " + QuoteArg(url),
				UseShellExecute = false,
				RedirectStandardOutput = true,
				RedirectStandardError = true,
				CreateNoWindow = true
			};
			using (Process process = new Process
			{
				StartInfo = startInfo
			})
			{
				process.Start();
				string output = process.StandardOutput.ReadToEnd();
				process.WaitForExit(15000);
				if (!process.HasExited)
				{
					try
					{
						process.Kill();
					}
					catch
					{
					}
					return result;
				}
				ParseFfprobeAudioStreams(output, result, token);
			}
			return result;
		}

		private static void ParseFfprobeAudioStreams(string output, List<AudioTrackInfo> result, CancellationToken token)
		{
			AudioTrackInfo audioTrackInfo = null;
			string[] array = (output ?? string.Empty).Replace("\r\n", "\n").Split(new char[1] { '\n' });
			foreach (string text in array)
			{
				if (token.IsCancellationRequested)
				{
					break;
				}
				string text2 = text.Trim();
				if (text2.Length == 0)
				{
					continue;
				}
				if (text2.StartsWith("index=", StringComparison.OrdinalIgnoreCase))
				{
					if (audioTrackInfo != null && audioTrackInfo.StreamIndex >= 0)
					{
						result.Add(audioTrackInfo);
					}
					audioTrackInfo = new AudioTrackInfo
					{
						StreamIndex = -1,
						UnityTrackIndex = result.Count
					};
					if (int.TryParse(text2.Substring("index=".Length).Trim(), out var result2))
					{
						audioTrackInfo.StreamIndex = result2;
					}
					continue;
				}
				if (audioTrackInfo == null)
				{
					audioTrackInfo = new AudioTrackInfo
					{
						StreamIndex = -1,
						UnityTrackIndex = result.Count
					};
				}
				if (text2.StartsWith("codec_name=", StringComparison.OrdinalIgnoreCase))
				{
					audioTrackInfo.Codec = text2.Substring("codec_name=".Length).Trim();
				}
				else if (text2.StartsWith("channels=", StringComparison.OrdinalIgnoreCase))
				{
					if (int.TryParse(text2.Substring("channels=".Length).Trim(), out var result3))
					{
						audioTrackInfo.Channels = result3;
					}
				}
				else if (text2.StartsWith("TAG:language=", StringComparison.OrdinalIgnoreCase))
				{
					string text3 = text2.Substring("TAG:language=".Length).Trim().Trim('[', ']');
					if (!string.IsNullOrWhiteSpace(text3) && !text3.Equals("N/A", StringComparison.OrdinalIgnoreCase))
					{
						audioTrackInfo.Language = text3;
					}
				}
				else if (text2.StartsWith("TAG:title=", StringComparison.OrdinalIgnoreCase))
				{
					string text4 = text2.Substring("TAG:title=".Length).Trim();
					if (!string.IsNullOrWhiteSpace(text4) && !text4.Equals("N/A", StringComparison.OrdinalIgnoreCase))
					{
						audioTrackInfo.Title = text4;
					}
				}
			}
			if (audioTrackInfo != null && audioTrackInfo.StreamIndex >= 0)
			{
				result.Add(audioTrackInfo);
			}
		}

		private static List<AudioTrackInfo> ProbeAudioTracksFromFfmpegStderr(string url, string ffmpegPath, CancellationToken token)
		{
			List<AudioTrackInfo> list = new List<AudioTrackInfo>();
			ProcessStartInfo startInfo = new ProcessStartInfo
			{
				FileName = ffmpegPath,
				Arguments = "-probesize 10000000 -analyzeduration 10000000 -i " + QuoteArg(url) + " -hide_banner",
				RedirectStandardError = true,
				RedirectStandardOutput = true,
				UseShellExecute = false,
				CreateNoWindow = true
			};
			using (Process process = new Process
			{
				StartInfo = startInfo
			})
			{
				process.Start();
				string text = process.StandardError.ReadToEnd();
				process.WaitForExit(15000);
				if (!process.HasExited)
				{
					try
					{
						process.Kill();
					}
					catch
					{
					}
					return list;
				}
				if (token.IsCancellationRequested)
				{
					return list;
				}
				AudioTrackInfo audioTrackInfo = null;
				string[] array = text.Split(new char[1] { '\n' });
				foreach (string text2 in array)
				{
					if (token.IsCancellationRequested)
					{
						break;
					}
					string text3 = text2.TrimEnd(new char[1] { '\r' });
					if (text3.IndexOf("Stream #", StringComparison.Ordinal) >= 0)
					{
						if (audioTrackInfo != null)
						{
							list.Add(audioTrackInfo);
							audioTrackInfo = null;
						}
						Match match = _audioStreamRx.Match(text3);
						if (match.Success)
						{
							int.TryParse(match.Groups[1].Value, out var result);
							string text4 = match.Groups[2].Value.Trim().Trim('[', ']');
							if (text4.Equals("und", StringComparison.OrdinalIgnoreCase))
							{
								text4 = string.Empty;
							}
							audioTrackInfo = new AudioTrackInfo
							{
								StreamIndex = result,
								UnityTrackIndex = list.Count,
								Language = text4
							};
						}
					}
					else if (audioTrackInfo != null)
					{
						Match match2 = _audioTitleLineRx.Match(text3);
						if (match2.Success)
						{
							audioTrackInfo.Title = match2.Groups[1].Value.Trim();
						}
					}
				}
				if (audioTrackInfo != null && !token.IsCancellationRequested)
				{
					list.Add(audioTrackInfo);
				}
			}
			return list;
		}

		private static string BuildAudioTrackDetail(AudioTrackInfo info)
		{
			string text = CleanTrackText(info.Title);
			string text2 = FormatLanguage(info.Language);
			string text3 = (string.IsNullOrWhiteSpace(info.Codec) ? string.Empty : info.Codec.Trim().ToUpperInvariant());
			string text4 = FormatChannels(info.Channels);
			string text5 = text;
			if (string.IsNullOrWhiteSpace(text5))
			{
				if (!string.IsNullOrWhiteSpace(text2))
				{
					text5 = text2;
				}
				if (!string.IsNullOrWhiteSpace(text4))
				{
					text5 = (string.IsNullOrWhiteSpace(text5) ? text4 : (text5 + " " + text4));
				}
				if (!string.IsNullOrWhiteSpace(text3))
				{
					text5 = (string.IsNullOrWhiteSpace(text5) ? text3 : (text5 + " " + text3));
				}
			}
			if (!string.IsNullOrWhiteSpace(text2) && text5.IndexOf(text2, StringComparison.OrdinalIgnoreCase) < 0)
			{
				text5 = text5 + " - [" + text2 + "]";
			}
			return text5.Trim();
		}

		private static string CleanTrackText(string value)
		{
			return (value ?? string.Empty).Replace("_", " ").Trim();
		}

		private static string FormatChannels(int channels)
		{
			return channels switch
			{
				1 => "1.0", 
				2 => "2.0", 
				6 => "5.1", 
				8 => "7.1", 
				_ => (channels > 0) ? (channels + "ch") : string.Empty, 
			};
		}

		private static string FormatLanguage(string language)
		{
			if (string.IsNullOrWhiteSpace(language) || language.Equals("und", StringComparison.OrdinalIgnoreCase))
			{
				return string.Empty;
			}
			switch (language.Trim().ToLowerInvariant())
			{
			case "en":
			case "eng":
				return "English";
			case "ja":
			case "jpn":
			case "jp":
				return "Japanese";
			case "es":
			case "spa":
				return "Spanish";
			case "fr":
			case "fre":
			case "fra":
				return "French";
			case "de":
			case "ger":
			case "deu":
				return "German";
			case "it":
			case "ita":
				return "Italian";
			case "pt":
			case "por":
				return "Portuguese";
			default:
				return language.Trim();
			}
		}

		private static string FindFfprobePath(string ffmpegPath)
		{
			try
			{
				if (!string.IsNullOrWhiteSpace(ffmpegPath))
				{
					string path = Path.GetDirectoryName(ffmpegPath) ?? string.Empty;
					string[] array = new string[2] { "ffprobe.exe", "ffprobe" };
					foreach (string path2 in array)
					{
						string text = Path.Combine(path, path2);
						if (File.Exists(text))
						{
							return text;
						}
					}
				}
			}
			catch
			{
			}
			string text2 = Environment.GetEnvironmentVariable("PATH") ?? string.Empty;
			string[] array2 = text2.Split(new char[1] { Path.PathSeparator });
			foreach (string text3 in array2)
			{
				string[] array3 = new string[2] { "ffprobe.exe", "ffprobe" };
				foreach (string path3 in array3)
				{
					try
					{
						string text4 = Path.Combine(text3.Trim(), path3);
						if (File.Exists(text4))
						{
							return text4;
						}
					}
					catch
					{
					}
				}
			}
			return null;
		}

		private static string QuoteArg(string value)
		{
			return "\"" + (value ?? string.Empty).Replace("\"", "\\\"") + "\"";
		}

		private static string ResolveSubtitleSourceUrl(string directPlayableUrl, string originalUrl)
		{
			if (!string.IsNullOrWhiteSpace(originalUrl) && UrlNormalizer.IsMkvUrl(originalUrl))
			{
				return originalUrl;
			}
			return directPlayableUrl ?? string.Empty;
		}

		private void ProbeSubtitlesIfSupported(string url)
		{
			_subtitleManager.Clear();
			if (string.IsNullOrWhiteSpace(url) || !SubtitleManager.IsSubtitleProbeSupported(url))
			{
				return;
			}
			string ffmpegPath = SubtitleManager.FindFfmpegPath();
			if (ffmpegPath == null)
			{
				return;
			}
			_subtitleManager.ProbeAsync(url, ffmpegPath, delegate
			{
				_subtitleManager.EagerExtractAllTracksAsync(url, ffmpegPath);
				SyncVideoPlugin.SyncController?.EnqueueMainThreadAction(delegate
				{
					SyncVideoPlugin.SyncController?.ReapplyLobbyTrackSelection();
				});
			});
		}

		private void ClearRenderTexture()
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_texture == (Object)null))
			{
				RenderTexture active = RenderTexture.active;
				RenderTexture.active = _texture;
				GL.Clear(true, true, Color.black);
				RenderTexture.active = active;
			}
		}
	}
	public enum HudMode
	{
		Off,
		UI,
		UIChat,
		UIAll
	}
	public static class HudManager
	{
		private static HudMode _currentMode;

		private static bool _savedShowChat;

		private static bool _savedShowNamePlates;

		private static bool _savedShowAFKEffects;

		private static bool _savedAfkMessages;

		private static bool _hasSavedSettings;

		private static bool _suppressAfkForLobby;

		private static float _afkTickTimer;

		private const float AfkTickInterval = 0.5f;

		private static CanvasGroup _gameplayCanvasGroup;

		private static bool _musicMuted;

		private static float _ambientSFXRestoreTimer;

		private const float AmbientSFXRestoreTimerSec = 2f;

		public static HudMode CurrentMode => _currentMode;

		private static object GetMusicPlayer()
		{
			try
			{
				if ((Object)(object)Core.Instance == (Object)null)
				{
					return null;
				}
				Traverse val = Traverse.Create((object)Core.Instance);
				object obj = val.Property("AudioManager", (object[])null).GetValue() ?? val.Property("audioManager", (object[])null).GetValue() ?? val.Field("audioManager").GetValue() ?? val.Field("AudioManager").GetValue();
				if (obj == null)
				{
					return null;
				}
				Traverse val2 = Traverse.Create(obj);
				return val2.Property("musicPlayer", (object[])null).GetValue() ?? val2.Property("MusicPlayer", (object[])null).GetValue() ?? val2.Field("musicPlayer").GetValue() ?? val2.Field("MusicPlayer").GetValue();
			}
			catch
			{
				return null;
			}
		}

		private static object GetAudioManager()
		{
			try
			{
				if ((Object)(object)Core.Instance == (Object)null)
				{
					return null;
				}
				Traverse val = Traverse.Create((object)Core.Instance);
				return val.Property("AudioManager", (object[])null).GetValue() ?? val.Property("audioManager", (object[])null).GetValue() ?? val.Field("audioManager").GetValue() ?? val.Field("AudioManager").GetValue();
			}
			catch
			{
				return null;
			}
		}

		public static string GetLabel()
		{
			return _currentMode switch
			{
				HudMode.Off => "Hide HUD: <color=green>Off</color>", 
				HudMode.UI => "Hide HUD: <color=yellow>UI</color>", 
				HudMode.UIChat => "Hide HUD: <color=yellow>UI+Chat</color>", 
				HudMode.UIAll => "Hide HUD: <color=yellow>Everything</color>", 
				_ => "Hide HUD", 
			};
		}

		public static void Cycle()
		{
			_currentMode = (HudMode)((int)(_currentMode + 1) % 4);
			Apply();
		}

		public static void Tick()
		{
			if (_ambientSFXRestoreTimer > 0f)
			{
				_ambientSFXRestoreTimer -= Time.unscaledDeltaTime;
				if (_ambientSFXRestoreTimer <= 0f)
				{
					_ambientSFXRestoreTimer = 0f;
					RestoreAudio();
				}
			}
			if (_currentMode < HudMode.UI)
			{
				if (!_suppressAfkForLobby)
				{
					return;
				}
				VideoLobbyManager lobbyManager = SyncVideoPlugin.LobbyManager;
				if (lobbyManager == null || !lobbyManager.InLobby)
				{
					return;
				}
			}
			_afkTickTimer += Time.unscaledDeltaTime;
			if (_afkTickTimer < 0.5f)
			{
				return;
			}
			_afkTickTimer = 0f;
			try
			{
				PlayerComponent local = PlayerComponent.GetLocal();
				if (local != null)
				{
					local.StopAFK();
				}
			}
			catch
			{
			}
			try
			{
				MPSettings instance = MPSettings.Instance;
				if (instance != null)
				{
					if (instance.ShowAFKEffects)
					{
						instance.ShowAFKEffects = false;
					}
					if (instance.AFKMessages)
					{
						instance.AFKMessages = false;
					}
				}
			}
			catch
			{
			}
		}

		public static void OnLobbyEnter()
		{
			MPSettings instance = MPSettings.Instance;
			if (instance != null && !_hasSavedSettings)
			{
				_savedShowChat = instance.ShowChat;
				_savedShowNamePlates = instance.ShowNamePlates;
				_savedShowAFKEffects = instance.ShowAFKEffects;
				_savedAfkMessages = instance.AFKMessages;
				_hasSavedSettings = true;
			}
			SyncVideoConfig settings = SyncVideoPlugin.Settings;
			_suppressAfkForLobby = settings != null && settings.SuppressAFK?.Value == true;
			_ambientSFXRestoreTimer = 0f;
			SyncVideoConfig settings2 = SyncVideoPlugin.Settings;
			if (settings2 != null && settings2.MuteMusicAndAmbient?.Value == true)
			{
				MuteForLobby();
			}
			Apply();
		}

		public static void Reset()
		{
			_currentMode = HudMode.Off;
			_suppressAfkForLobby = false;
			_afkTickTimer = 0f;
			ApplyGameplayUiHide(hide: false);
			MPSettings instance = MPSettings.Instance;
			if (instance != null)
			{
				instance.ShowChat = !_hasSavedSettings || _savedShowChat;
				instance.ShowNamePlates = !_hasSavedSettings || _savedShowNamePlates;
				instance.ShowAFKEffects = !_hasSavedSettings || _savedShowAFKEffects;
				instance.AFKMessages = !_hasSavedSettings || _savedAfkMessages;
			}
			_hasSavedSettings = false;
			if (_musicMuted)
			{
				_ambientSFXRestoreTimer = 2f;
			}
		}

		private static void MuteForLobby()
		{
			if (_musicMuted)
			{
				return;
			}
			object audioManager = GetAudioManager();
			if (audioManager == null)
			{
				_musicMuted = true;
				return;
			}
			Type type = audioManager.GetType();
			BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
			try
			{
				type.GetMethod("MuteMusic", bindingAttr)?.Invoke(audioManager, null);
			}
			catch
			{
			}
			try
			{
				if (type.GetField("ambienceAudioSources", bindingAttr)?.GetValue(audioManager) is AudioSource[] array)
				{
					AudioSource[] array2 = array;
					foreach (AudioSource val in array2)
					{
						if ((Object)(object)val != (Object)null)
						{
							val.mute = true;
						}
					}
				}
			}
			catch
			{
			}
			try
			{
				type.GetMethod("PauseAllGameplayLoopingSfx", bindingAttr)?.Invoke(audioManager, null);
			}
			catch
			{
			}
			_musicMuted = true;
		}

		private static void RestoreAudio()
		{
			if (!_musicMuted)
			{
				return;
			}
			object audioManager = GetAudioManager();
			BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
			try
			{
				audioManager?.GetType().GetMethod("UnMuteMusic", bindingAttr)?.Invoke(audioManager, null);
			}
			catch
			{
			}
			try
			{
				if (audioManager != null && audioMa