Decompiled source of SyncVideo v1.6.1
plugins/SyncVideo/SyncVideo.dll
Decompiled a week ago
The result has been truncated due to the large size, download it to view full contents!
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