using System;
using System.Collections;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using CardShopCoop.Api;
using CardShopCoop.Attributes;
using CardShopCoop.Net;
using CardShopCoop.Net.Connection;
using HarmonyLib;
using UnityEngine;
using UnityEngine.SceneManagement;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("Devin 'Meepen' Korb")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("RTCGO Custom TV co-op sync for CardShopCoopCommunity. Optional separate download.")]
[assembly: AssemblyFileVersion("2.0.5.0")]
[assembly: AssemblyInformationalVersion("2.0.5+47c02fd3f32c1f8af9d0ee09dbb3f968482e64e0")]
[assembly: AssemblyProduct("CardShopCoopCommunity Custom TV")]
[assembly: AssemblyTitle("CardShopCoop.CustomTv")]
[assembly: AssemblyVersion("2.0.5.0")]
namespace CardShopCoop.CustomTv
{
[BepInPlugin("dev.meepen.cardshopcoop.customtv", "CardShopCoopCommunity Custom TV", "2.0.5")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class TvPlugin : BaseUnityPlugin
{
public const string Guid = "dev.meepen.cardshopcoop.customtv";
public const string Name = "CardShopCoopCommunity Custom TV";
public const string Version = "2.0.5";
}
}
namespace CardShopCoop.Modules.Tv
{
[ClientBehaviour]
public sealed class TvClientBehaviour : CoopBehaviour
{
private struct QualityState
{
public bool Armed;
public string Url;
public TvInterop.PlaybackUndoState Prior;
}
private struct GlobalInputState
{
public bool Next;
public bool Previous;
public bool Shuffle;
public bool Power;
public TvInterop.PlaybackUndoState Prior;
}
private const byte Next = 1;
private const byte Previous = 2;
private const byte Pause = 3;
private const byte Power = 4;
private const byte Shuffle = 5;
private const byte Seek = 6;
private const byte Open = 7;
private const string PredictionScope = "tv";
private static TvClientBehaviour _active;
private ICoopContext _context;
private Harmony _harmony;
private TvBaselineMessage _latestBaseline;
private TvMediaDeltaMessage _pendingMedia;
private TvPauseDeltaMessage _pendingPause;
private TvPowerDeltaMessage _pendingPower;
private TvShuffleDeltaMessage _pendingShuffle;
private TvSeekDeltaMessage _pendingSeek;
private TvBarrierDeltaMessage _pendingBarrier;
private TvPlaybackErrorDeltaMessage _pendingError;
private bool _shutdown;
private bool _joined;
private bool _clientBarrier;
private int _clientGeneration = -1;
private static double _seekObservedPosition;
private static bool _seekHeld;
private void OnEnable()
{
//IL_0066: Unknown result type (might be due to invalid IL or missing references)
//IL_0070: Expected O, but got Unknown
if (_shutdown || _harmony != null)
{
return;
}
_context = ((CoopBehaviour)this).Context;
bool flag = false;
try
{
ResetSessionState();
_context.Messages.RegisterAttributedHandlers((object)this);
flag = true;
CEventManager.AddListener<CEventPlayer_GameDataFinishLoaded>((EventDelegate<CEventPlayer_GameDataFinishLoaded>)OnWorldReady);
SceneManager.sceneLoaded += OnSceneLoaded;
_active = this;
_harmony = new Harmony("dev.meepen.cardshopcoop.tv.client.runtime");
ApplyPatches(_harmony);
OnControllerLifecycle();
}
catch (Exception ex)
{
CoopLog.Error("TV client initialization failed: " + ex);
Harmony harmony = _harmony;
if (harmony != null)
{
harmony.UnpatchSelf();
}
_harmony = null;
if (flag)
{
_context.Messages.UnregisterAttributedHandlers((object)this);
}
CEventManager.RemoveListener<CEventPlayer_GameDataFinishLoaded>((EventDelegate<CEventPlayer_GameDataFinishLoaded>)OnWorldReady);
SceneManager.sceneLoaded -= OnSceneLoaded;
if (_active == this)
{
_active = null;
}
ResetSessionState();
_context = null;
throw;
}
}
[OnFullyJoined]
private void MarkJoined(PeerConnection _)
{
_joined = true;
ApplyLatestState();
}
[MessageHandler(typeof(TvBaselineMessage))]
private void HandleBaseline(CoopMessageContext context, TvBaselineMessage message)
{
if (!_shutdown && TvInterop.Present && message.Generation >= _clientGeneration)
{
_latestBaseline = message;
ApplyLatestState();
}
}
[MessageHandler(typeof(TvMediaDeltaMessage))]
private void HandleMediaDelta(CoopMessageContext context, TvMediaDeltaMessage message)
{
if (_shutdown || !TvInterop.Present)
{
return;
}
if (message.Generation < _clientGeneration || (_pendingMedia != null && _pendingMedia.Generation > message.Generation))
{
CoopPredict.Ack(message.PredictionId);
return;
}
Supersede(ref _pendingMedia, message);
if (_pendingError != null && _pendingError.Generation < message.Generation)
{
CoopPredict.Ack(_pendingError.PredictionId);
_pendingError = null;
}
ReconcileWithoutController(message.PredictionId);
ApplyLatestState();
}
[MessageHandler(typeof(TvPauseDeltaMessage))]
private void HandlePauseDelta(CoopMessageContext context, TvPauseDeltaMessage message)
{
if (!_shutdown && TvInterop.Present)
{
Supersede(ref _pendingPause, message);
ReconcileWithoutController(message.PredictionId);
ApplyLatestState();
}
}
[MessageHandler(typeof(TvPowerDeltaMessage))]
private void HandlePowerDelta(CoopMessageContext context, TvPowerDeltaMessage message)
{
if (!_shutdown && TvInterop.Present)
{
Supersede(ref _pendingPower, message);
ReconcileWithoutController(message.PredictionId);
ApplyLatestState();
}
}
[MessageHandler(typeof(TvShuffleDeltaMessage))]
private void HandleShuffleDelta(CoopMessageContext context, TvShuffleDeltaMessage message)
{
if (!_shutdown && TvInterop.Present)
{
Supersede(ref _pendingShuffle, message);
ReconcileWithoutController(message.PredictionId);
ApplyLatestState();
}
}
[MessageHandler(typeof(TvSeekDeltaMessage))]
private void HandleSeekDelta(CoopMessageContext context, TvSeekDeltaMessage message)
{
if (!_shutdown && TvInterop.Present)
{
Supersede(ref _pendingSeek, message);
ReconcileWithoutController(message.PredictionId);
ApplyLatestState();
}
}
[MessageHandler(typeof(TvBarrierDeltaMessage))]
private void HandleBarrierDelta(CoopMessageContext context, TvBarrierDeltaMessage message)
{
if (!_shutdown && TvInterop.Present)
{
Supersede(ref _pendingBarrier, message);
ReconcileWithoutController(message.PredictionId);
ApplyLatestState();
}
}
[MessageHandler(typeof(TvPlaybackErrorDeltaMessage))]
private void HandlePlaybackError(CoopMessageContext context, TvPlaybackErrorDeltaMessage message)
{
if (!_shutdown && TvInterop.Present)
{
if (message.Generation < _clientGeneration || (_pendingMedia != null && _pendingMedia.Generation > message.Generation))
{
CoopPredict.Ack(message.PredictionId);
return;
}
Supersede(ref _pendingError, message);
ReconcileWithoutController(message.PredictionId);
ApplyLatestState();
}
}
internal void Shutdown()
{
if (!_shutdown)
{
_shutdown = true;
ICoopContext context = _context;
if (context != null)
{
context.Messages.UnregisterAttributedHandlers((object)this);
}
CEventManager.RemoveListener<CEventPlayer_GameDataFinishLoaded>((EventDelegate<CEventPlayer_GameDataFinishLoaded>)OnWorldReady);
SceneManager.sceneLoaded -= OnSceneLoaded;
if (_active == this)
{
_active = null;
}
Harmony harmony = _harmony;
if (harmony != null)
{
harmony.UnpatchSelf();
}
_harmony = null;
ResetSessionState();
_context = null;
}
}
private void OnDestroy()
{
Shutdown();
}
private void ResetSessionState()
{
TvInterop.ResetSession();
_latestBaseline = null;
_pendingMedia = null;
_pendingPause = null;
_pendingPower = null;
_pendingShuffle = null;
_pendingSeek = null;
_pendingBarrier = null;
_pendingError = null;
_joined = false;
_clientBarrier = false;
_clientGeneration = -1;
}
private void OnWorldReady(CEventPlayer_GameDataFinishLoaded _)
{
ApplyLatestState();
}
private void OnSceneLoaded(Scene _, LoadSceneMode __)
{
TvInterop.NotifySceneLoaded();
_clientGeneration = -1;
ApplyLatestState();
}
[OnClientDisconnected]
private void ForgetHost(PeerConnection connection, DisconnectInfo info)
{
if (connection != null && connection.Id == 1)
{
ResetSessionState();
}
}
private void OnControllerLifecycle()
{
if (TvInterop.Present)
{
ApplyLatestState();
}
}
private static void ReconcileWithoutController(Guid predictionId)
{
if (!TvInterop.ControllerReady)
{
CoopPredict.AckOrApply(predictionId, (Action)delegate
{
});
}
}
private static void Supersede<T>(ref T slot, T message) where T : class, IPredictedMessage
{
T val = slot;
if (val != null && ((IPredictedMessage)val).PredictionId != Guid.Empty)
{
CoopPredict.Ack(((IPredictedMessage)val).PredictionId);
}
slot = message;
}
private void ApplyLatestState()
{
if (_shutdown || !_joined || _context == null || !_context.InGame || !TvInterop.Present || TvInterop.ApplyingRemote || !TvInterop.ControllerReady)
{
return;
}
if (_latestBaseline != null)
{
TvBaselineMessage latestBaseline = _latestBaseline;
if (!string.IsNullOrWhiteSpace(latestBaseline.SourceUrl) && !TvInterop.EnrollmentReady)
{
TvInterop.RequestClientEnrollment();
return;
}
ApplyBaseline(latestBaseline);
_latestBaseline = null;
}
if (_pendingMedia != null && _pendingMedia.Generation < _clientGeneration)
{
CoopPredict.Ack(_pendingMedia.PredictionId);
_pendingMedia = null;
}
if (_pendingError != null && _pendingError.Generation < _clientGeneration)
{
CoopPredict.Ack(_pendingError.PredictionId);
_pendingError = null;
}
if (_pendingMedia != null && !string.IsNullOrWhiteSpace(_pendingMedia.SourceUrl) && !TvInterop.EnrollmentReady)
{
TvInterop.RequestClientEnrollment();
return;
}
ApplyMediaDelta();
ApplyPauseDelta();
ApplyPowerDelta();
ApplyShuffleDelta();
ApplySeekDelta();
ApplyBarrierDelta();
ApplyErrorDelta();
}
private void ApplyBaseline(TvBaselineMessage message)
{
_clientGeneration = message.Generation;
_clientBarrier = message.Barrier;
TvInterop.ApplyMediaState(message.SourceUrl, message.StreamUrl, message.StreamTitle, message.PlaylistUrl, message.IsLive, message.IsSegmentedVod, message.PlaylistIndex, message.Position, message.Paused, message.PoweredOff, message.Shuffle, message.Barrier, message.Resume, message.Generation);
}
private void ApplyMediaDelta()
{
if (_pendingMedia != null)
{
TvMediaDeltaMessage message = _pendingMedia;
CoopPredict.Confirm(message.PredictionId, (Action)delegate
{
_clientGeneration = message.Generation;
_clientBarrier = message.Barrier;
TvInterop.ApplyMediaState(message.SourceUrl, message.StreamUrl, message.StreamTitle, message.PlaylistUrl, message.IsLive, message.IsSegmentedVod, message.PlaylistIndex, message.Position, message.Paused, message.PoweredOff, message.Shuffle, message.Barrier, message.Resume, message.Generation);
});
_pendingMedia = null;
}
}
private void ApplyPauseDelta()
{
if (_pendingPause != null)
{
TvPauseDeltaMessage message = _pendingPause;
CoopPredict.AckOrApply(message.PredictionId, (Action)delegate
{
TvInterop.ApplyPaused(message.Paused);
});
_pendingPause = null;
}
}
private void ApplyPowerDelta()
{
if (_pendingPower != null)
{
TvPowerDeltaMessage message = _pendingPower;
CoopPredict.AckOrApply(message.PredictionId, (Action)delegate
{
TvInterop.ApplyPowered(message.PoweredOff);
});
_pendingPower = null;
}
}
private void ApplyShuffleDelta()
{
if (_pendingShuffle != null)
{
TvShuffleDeltaMessage message = _pendingShuffle;
CoopPredict.AckOrApply(message.PredictionId, (Action)delegate
{
TvInterop.ApplyShuffle(message.Shuffle);
});
_pendingShuffle = null;
}
}
private void ApplySeekDelta()
{
if (_pendingSeek != null)
{
TvSeekDeltaMessage message = _pendingSeek;
CoopPredict.Confirm(message.PredictionId, (Action)delegate
{
TvInterop.ApplySeek(message.Position);
});
_pendingSeek = null;
}
}
private void ApplyBarrierDelta()
{
if (_pendingBarrier != null)
{
TvBarrierDeltaMessage message = _pendingBarrier;
CoopPredict.Confirm(message.PredictionId, (Action)delegate
{
_clientBarrier = message.Barrier;
TvInterop.ApplyBarrier(message.Barrier, message.Resume);
});
_pendingBarrier = null;
}
}
private void ApplyErrorDelta()
{
if (_pendingError == null)
{
return;
}
TvPlaybackErrorDeltaMessage message = _pendingError;
if (message.Generation < _clientGeneration)
{
CoopPredict.Ack(message.PredictionId);
_pendingError = null;
return;
}
CoopPredict.Confirm(message.PredictionId, (Action)delegate
{
_clientGeneration = Math.Max(_clientGeneration, message.Generation);
_clientBarrier = false;
TvInterop.ApplyPlaybackError();
});
_pendingError = null;
}
private static void ApplyPatches(Harmony harmony)
{
//IL_001f: Unknown result type (might be due to invalid IL or missing references)
//IL_0029: Expected O, but got Unknown
//IL_0040: Unknown result type (might be due to invalid IL or missing references)
//IL_004a: Expected O, but got Unknown
//IL_0061: Unknown result type (might be due to invalid IL or missing references)
//IL_006b: Expected O, but got Unknown
//IL_0082: Unknown result type (might be due to invalid IL or missing references)
//IL_008c: Expected O, but got Unknown
//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
//IL_00ad: Expected O, but got Unknown
//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
//IL_00e2: Expected O, but got Unknown
//IL_00e2: Expected O, but got Unknown
//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
//IL_010d: Unknown result type (might be due to invalid IL or missing references)
//IL_0117: Expected O, but got Unknown
//IL_0117: Expected O, but got Unknown
//IL_012d: Unknown result type (might be due to invalid IL or missing references)
//IL_0142: Unknown result type (might be due to invalid IL or missing references)
//IL_014c: Expected O, but got Unknown
//IL_014c: Expected O, but got Unknown
//IL_0162: Unknown result type (might be due to invalid IL or missing references)
//IL_0177: Unknown result type (might be due to invalid IL or missing references)
//IL_0181: Expected O, but got Unknown
//IL_0181: Expected O, but got Unknown
//IL_0197: Unknown result type (might be due to invalid IL or missing references)
//IL_01ac: Unknown result type (might be due to invalid IL or missing references)
//IL_01b6: Expected O, but got Unknown
//IL_01b6: Expected O, but got Unknown
//IL_01cc: Unknown result type (might be due to invalid IL or missing references)
//IL_01e1: Unknown result type (might be due to invalid IL or missing references)
//IL_01eb: Expected O, but got Unknown
//IL_01eb: Expected O, but got Unknown
//IL_0201: Unknown result type (might be due to invalid IL or missing references)
//IL_0216: Unknown result type (might be due to invalid IL or missing references)
//IL_0220: Expected O, but got Unknown
//IL_0220: Expected O, but got Unknown
//IL_0236: Unknown result type (might be due to invalid IL or missing references)
//IL_024b: Unknown result type (might be due to invalid IL or missing references)
//IL_0255: Expected O, but got Unknown
//IL_0255: Expected O, but got Unknown
if (TvInterop.Present)
{
TryPatch(harmony, "OnEnable", null, new HarmonyMethod(typeof(TvClientBehaviour), "ControllerLifecyclePostfix", (Type[])null));
TryPatch(harmony, "Start", null, new HarmonyMethod(typeof(TvClientBehaviour), "ControllerLifecyclePostfix", (Type[])null));
TryPatch(harmony, "ChangeVideo", null, new HarmonyMethod(typeof(TvClientBehaviour), "ControllerLifecyclePostfix", (Type[])null));
TryPatch(harmony, "ChangeToUrl", null, new HarmonyMethod(typeof(TvClientBehaviour), "ChangeToUrlPostfix", (Type[])null));
TryPatch(harmony, "StreamYouTube", null, new HarmonyMethod(typeof(TvClientBehaviour), "StreamYouTubePostfix", (Type[])null));
TryPatch(harmony, "HandleGlobalInput", new HarmonyMethod(typeof(TvClientBehaviour), "GlobalInputPrefix", (Type[])null), new HarmonyMethod(typeof(TvClientBehaviour), "GlobalInputPostfix", (Type[])null));
TryPatch(harmony, "OnQualityChoice", new HarmonyMethod(typeof(TvClientBehaviour), "QualityPrefix", (Type[])null), new HarmonyMethod(typeof(TvClientBehaviour), "QualityPostfix", (Type[])null));
TryPatch(harmony, "ToggleGlobalPause", new HarmonyMethod(typeof(TvClientBehaviour), "CapturePrefix", (Type[])null), new HarmonyMethod(typeof(TvClientBehaviour), "PausePostfix", (Type[])null));
TryPatch(harmony, "RemoteNext", new HarmonyMethod(typeof(TvClientBehaviour), "CapturePrefix", (Type[])null), new HarmonyMethod(typeof(TvClientBehaviour), "NextPostfix", (Type[])null));
TryPatch(harmony, "RemotePrev", new HarmonyMethod(typeof(TvClientBehaviour), "CapturePrefix", (Type[])null), new HarmonyMethod(typeof(TvClientBehaviour), "PreviousPostfix", (Type[])null));
TryPatch(harmony, "RemoteShuffle", new HarmonyMethod(typeof(TvClientBehaviour), "CapturePrefix", (Type[])null), new HarmonyMethod(typeof(TvClientBehaviour), "ShufflePostfix", (Type[])null));
TryPatch(harmony, "RemotePower", new HarmonyMethod(typeof(TvClientBehaviour), "CapturePrefix", (Type[])null), new HarmonyMethod(typeof(TvClientBehaviour), "PowerPostfix", (Type[])null));
TryPatch(harmony, "OnLocalPrepared", new HarmonyMethod(typeof(TvClientBehaviour), "LocalPreparedPrefix", (Type[])null), new HarmonyMethod(typeof(TvClientBehaviour), "LocalPreparedPostfix", (Type[])null));
}
}
private static void TryPatch(Harmony harmony, string method, HarmonyMethod prefix, HarmonyMethod postfix)
{
try
{
MethodInfo methodInfo = AccessTools.Method(TvInterop.OptionalControllerType, method, (Type[])null, (Type[])null);
if (methodInfo == null)
{
CoopLog.Warn("TV client patch target missing: " + method);
}
else
{
harmony.Patch((MethodBase)methodInfo, prefix, postfix, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
}
}
catch (Exception ex)
{
CoopLog.Warn("TV client patch failed " + method + ": " + ex.Message);
}
}
private static void LocalPreparedPrefix(object __instance, object source)
{
_active?.BeforeLocalPrepared(__instance, source);
}
private static void LocalPreparedPostfix(object __instance, object source)
{
_active?.AfterLocalPrepared(__instance, source);
}
private static void ControllerLifecyclePostfix(object __instance)
{
TvInterop.RegisterController((Component)((__instance is Component) ? __instance : null));
_active?.OnControllerLifecycle();
}
private static void ChangeToUrlPostfix(string url, string title)
{
TvInterop.ChangeToUrlPostfix(url);
_active?.OnControllerLifecycle();
}
private static void StreamYouTubePostfix(string url)
{
TvInterop.StreamYouTubePostfix(url);
_active?.OnControllerLifecycle();
}
private void BeforeLocalPrepared(object instance, object source)
{
if (_clientBarrier && TvInterop.IsMainPreparedPlayer(instance, source))
{
TvInterop.SetSharedPausedForPrepare(paused: false);
}
}
private void AfterLocalPrepared(object instance, object source)
{
if (TvInterop.IsMainPreparedPlayer(instance, source))
{
TvInterop.ApplyPreparedPosition(instance);
ApplyLatestState();
if (_clientBarrier)
{
TvInterop.PausePreparedStream(instance);
}
}
}
private static bool CanPredict()
{
if ((Object)(object)_active != (Object)null && !_active._shutdown && _active._joined && _active._context != null && _active._context.InGame)
{
return TvInterop.Present;
}
return false;
}
private static void SendIntent(Guid predictionId, byte op, string url = null, string title = null, double value = 0.0)
{
_active._context.Send(1, (INetMessage)(object)new TvOpMessage
{
PredictionId = predictionId,
Op = op,
Url = url,
Title = title,
Value = value
});
}
private static void QualityPrefix(out QualityState __state)
{
__state = default(QualityState);
if (CanPredict() && !TvInterop.ApplyingRemote)
{
string pendingUrl = TvInterop.PendingUrl;
if (!string.IsNullOrWhiteSpace(pendingUrl))
{
__state.Armed = true;
__state.Url = pendingUrl;
__state.Prior = TvInterop.CapturePlaybackState();
}
}
}
private static void QualityPostfix(QualityState __state)
{
if (__state.Armed && CanPredict() && !TvInterop.ApplyingRemote)
{
ForwardMedia(7, __state.Prior, __state.Url, "Shared stream");
}
}
private static void GlobalInputPrefix(out GlobalInputState __state)
{
__state = default(GlobalInputState);
bool flag = Input.GetKey((KeyCode)308) || Input.GetKey((KeyCode)307);
double position = TvInterop.Position;
bool seekHeld = flag && (Input.GetKey((KeyCode)93) || Input.GetKey((KeyCode)91));
if (CanPredict() && !TvInterop.ApplyingRemote && _seekHeld && Math.Abs(position - _seekObservedPosition) >= 1.0)
{
ForwardSeek(_seekObservedPosition, position - _seekObservedPosition);
}
_seekObservedPosition = position;
_seekHeld = seekHeld;
if (!CanPredict() || TvInterop.ApplyingRemote || !flag)
{
return;
}
if (Input.GetKeyDown((KeyCode)61))
{
__state.Next = true;
}
else if (Input.GetKeyDown((KeyCode)45))
{
__state.Previous = true;
}
else if (Input.GetKeyDown((KeyCode)115))
{
__state.Shuffle = true;
}
else
{
if (!Input.GetKeyDown((KeyCode)111))
{
return;
}
__state.Power = true;
}
__state.Prior = TvInterop.CapturePlaybackState();
}
private static void GlobalInputPostfix(GlobalInputState __state)
{
if (CanPredict() && !TvInterop.ApplyingRemote)
{
if (__state.Next)
{
ForwardMedia(1, __state.Prior, null, null);
}
else if (__state.Previous)
{
ForwardMedia(2, __state.Prior, null, null);
}
else if (__state.Shuffle)
{
ForwardSelfApplied(5, __state.Prior);
}
else if (__state.Power)
{
ForwardSelfApplied(4, __state.Prior);
}
}
}
private static void CapturePrefix(out TvInterop.PlaybackUndoState __state)
{
__state = TvInterop.CapturePlaybackState();
}
private static void PausePostfix(TvInterop.PlaybackUndoState __state)
{
if (CanPredict() && !TvInterop.ApplyingRemote && TvInterop.Paused != __state.Paused)
{
CoopPredict.Predict("tv", (Action<Guid>)delegate(Guid predictionId)
{
SendIntent(predictionId, 3);
}, (Action)delegate
{
TvInterop.ApplyPaused(!__state.Paused);
}, (Action)delegate
{
TvInterop.ApplyPaused(__state.Paused);
});
}
}
private static void PowerPostfix(TvInterop.PlaybackUndoState __state)
{
if (CanPredict() && !TvInterop.ApplyingRemote && TvInterop.PoweredOff != __state.PoweredOff)
{
CoopPredict.Predict("tv", (Action<Guid>)delegate(Guid predictionId)
{
SendIntent(predictionId, 4);
}, (Action)delegate
{
TvInterop.ApplyPowered(!__state.PoweredOff);
}, (Action)delegate
{
TvInterop.ApplyPowered(__state.PoweredOff);
});
}
}
private static void ShufflePostfix(TvInterop.PlaybackUndoState __state)
{
if (CanPredict() && !TvInterop.ApplyingRemote && TvInterop.Shuffle != __state.Shuffle)
{
CoopPredict.Predict("tv", (Action<Guid>)delegate(Guid predictionId)
{
SendIntent(predictionId, 5);
}, (Action)delegate
{
TvInterop.ApplyShuffle(!__state.Shuffle);
}, (Action)delegate
{
TvInterop.ApplyShuffle(__state.Shuffle);
});
}
}
private static void NextPostfix(TvInterop.PlaybackUndoState __state)
{
ForwardMedia(1, __state, null, null);
}
private static void PreviousPostfix(TvInterop.PlaybackUndoState __state)
{
ForwardMedia(2, __state, null, null);
}
private static void ForwardMedia(byte op, TvInterop.PlaybackUndoState prior, string url, string title)
{
if (CanPredict() && !TvInterop.ApplyingRemote)
{
CoopPredict.Predict("tv", (Action<Guid>)delegate(Guid predictionId)
{
SendIntent(predictionId, op, url, title);
}, (Action)delegate
{
TvInterop.ApplyOperation(op, url, title, 0.0);
}, (Action)delegate
{
TvInterop.RestorePlaybackState(prior);
});
}
}
private static void ForwardSelfApplied(byte op, TvInterop.PlaybackUndoState prior)
{
if (TvInterop.ApplyOperation(op, null, null, 0.0))
{
CoopPredict.Predict("tv", (Action<Guid>)delegate(Guid predictionId)
{
SendIntent(predictionId, op);
}, (Action)delegate
{
TvInterop.ApplyOperation(op, null, null, 0.0);
}, (Action)delegate
{
TvInterop.RestorePlaybackState(prior);
});
}
}
private static void ForwardSeek(double fromPosition, double delta)
{
double target = Math.Max(0.0, Math.Min(86400.0, fromPosition + delta));
CoopPredict.Predict("tv", (Action<Guid>)delegate(Guid predictionId)
{
SendIntent(predictionId, 6, null, null, delta);
}, (Action)delegate
{
TvInterop.ApplySeek(target);
}, (Action)delegate
{
TvInterop.ApplySeek(fromPosition);
});
}
}
[ServerBehaviour]
public sealed class TvHostBehaviour : CoopBehaviour
{
private const byte Next = 1;
private const byte Previous = 2;
private const byte Pause = 3;
private const byte Power = 4;
private const byte Shuffle = 5;
private const byte Seek = 6;
private const byte Open = 7;
private static TvHostBehaviour _active;
private ICoopContext _context;
private Harmony _harmony;
private bool _shutdown;
private float _lastSeekInputTime;
private bool _hasHostState;
private string _lastUrl;
private string _lastSource;
private string _lastTitle;
private string _lastPlaylist;
private bool _lastLive;
private bool _lastSegmented;
private bool _lastPaused;
private bool _lastPowered;
private bool _lastShuffle;
private int _lastPlaylistIndex;
private double _lastPosition;
private bool _barrier;
private bool _resumePulse;
private int _generation;
private bool _hasPendingMediaOperation;
private byte _pendingMediaOperation;
private Guid _pendingMediaPredictionId;
private void OnEnable()
{
//IL_0055: Unknown result type (might be due to invalid IL or missing references)
//IL_005f: Expected O, but got Unknown
if (_shutdown || _harmony != null)
{
return;
}
_context = ((CoopBehaviour)this).Context;
bool flag = false;
try
{
ResetSessionState();
_context.Messages.RegisterAttributedHandlers((object)this);
flag = true;
_active = this;
SceneManager.sceneLoaded += OnSceneLoaded;
_harmony = new Harmony("dev.meepen.cardshopcoop.tv.host.runtime");
ApplyPatches(_harmony);
OnControllerLifecycle();
}
catch (Exception ex)
{
CoopLog.Error("TV host initialization failed: " + ex);
Harmony harmony = _harmony;
if (harmony != null)
{
harmony.UnpatchSelf();
}
_harmony = null;
if (flag)
{
_context.Messages.UnregisterAttributedHandlers((object)this);
}
SceneManager.sceneLoaded -= OnSceneLoaded;
if (_active == this)
{
_active = null;
}
ResetSessionState();
_context = null;
throw;
}
}
[OnFullyJoined]
private void SendJoinBaseline(PeerConnection connection)
{
//IL_000c: Unknown result type (might be due to invalid IL or missing references)
//IL_0012: Invalid comparison between Unknown and I4
//IL_0015: Unknown result type (might be due to invalid IL or missing references)
//IL_001b: Invalid comparison between Unknown and I4
if (_shutdown || connection == null || ((int)connection.State != 2 && (int)connection.State != 3))
{
return;
}
if (TvInterop.Present)
{
if ((TvInterop.IsPlayingStream || TvInterop.IsFetching || !string.IsNullOrWhiteSpace(TvInterop.PendingSourceUrl)) && string.IsNullOrWhiteSpace(TvInterop.SourceUrl))
{
return;
}
CaptureHostState(startBarrier: false);
}
_context.Send(connection.Id, (INetMessage)(object)BuildBaseline());
}
[MessageHandler(typeof(TvOpMessage))]
private void HandleOperation(CoopMessageContext context, TvOpMessage message)
{
if (_shutdown || message == null || !IsJoinedClient(context) || !TvInterop.Present || message.Op < 1 || message.Op > 7)
{
return;
}
bool flag = message.Op == 1 || message.Op == 2 || message.Op == 7;
if (flag && !TryBeginPendingMediaOperation(message.Op, message.PredictionId))
{
CoopPredict.Rollback(_context, context.Connection.Id, message.PredictionId);
return;
}
byte operation = (byte)(flag ? _pendingMediaOperation : 0);
Guid predictionId = (flag ? _pendingMediaPredictionId : Guid.Empty);
if (!TvInterop.ApplyOperation(message.Op, message.Url, message.Title, message.Value))
{
CoopPredict.Rollback(_context, context.Connection.Id, message.PredictionId);
if (flag)
{
ClearPendingMediaOperation(operation, predictionId);
}
}
else if (!flag)
{
PublishScalarDelta(message.Op, message.PredictionId);
}
}
internal void Shutdown()
{
if (!_shutdown)
{
_shutdown = true;
ICoopContext context = _context;
if (context != null)
{
context.Messages.UnregisterAttributedHandlers((object)this);
}
SceneManager.sceneLoaded -= OnSceneLoaded;
if (_active == this)
{
_active = null;
}
Harmony harmony = _harmony;
if (harmony != null)
{
harmony.UnpatchSelf();
}
_harmony = null;
ResetSessionState();
_context = null;
}
}
private void OnDestroy()
{
Shutdown();
}
private void ResetSessionState()
{
TvInterop.ResetSession();
_lastSeekInputTime = -0.25f;
_hasHostState = false;
_lastUrl = null;
_lastSource = null;
_lastTitle = null;
_lastPlaylist = null;
_lastLive = false;
_lastSegmented = false;
_lastPaused = false;
_lastPowered = false;
_lastShuffle = false;
_lastPlaylistIndex = 0;
_lastPosition = 0.0;
_barrier = false;
_resumePulse = false;
_generation = 0;
ClearPendingMediaOperation();
}
private static bool IsJoinedClient(CoopMessageContext context)
{
//IL_0022: Unknown result type (might be due to invalid IL or missing references)
//IL_0028: Invalid comparison between Unknown and I4
//IL_0030: Unknown result type (might be due to invalid IL or missing references)
//IL_0036: Invalid comparison between Unknown and I4
if (((context != null) ? context.Connection : null) != null && context.Connection.Id != 1)
{
if ((int)context.Connection.State != 2)
{
return (int)context.Connection.State == 3;
}
return true;
}
return false;
}
private void OnSceneLoaded(Scene _, LoadSceneMode __)
{
TvInterop.NotifySceneLoaded();
_hasHostState = false;
_lastSource = null;
_lastUrl = null;
_lastTitle = null;
_lastPlaylist = null;
_barrier = false;
_resumePulse = false;
_generation++;
ClearPendingMediaOperation();
OnControllerLifecycle();
}
private void OnControllerLifecycle()
{
if (TvInterop.Present)
{
TvInterop.AttachVideoErrorHandler(OnHostVideoError);
if (CaptureHostState(startBarrier: false) && !string.IsNullOrEmpty(_lastSource))
{
PublishMediaDelta(1, Guid.Empty, force: true);
}
}
}
private bool CaptureHostState(bool startBarrier)
{
bool isPlayingStream = TvInterop.IsPlayingStream;
bool isFetching = TvInterop.IsFetching;
bool poweredOff = TvInterop.PoweredOff;
bool flag = isPlayingStream || isFetching || !string.IsNullOrWhiteSpace(TvInterop.PendingSourceUrl);
if (isPlayingStream)
{
TvInterop.ClearPendingSourceUrl();
}
string text = ((!string.IsNullOrWhiteSpace(TvInterop.SourceUrl)) ? TvInterop.SourceUrl : (flag ? null : _lastSource));
if (flag && string.IsNullOrWhiteSpace(text))
{
return false;
}
string text2 = (flag ? TvInterop.StreamUrl : _lastUrl);
string text3 = (flag ? TvInterop.StreamTitle : _lastTitle);
string text4 = (flag ? TvInterop.PlaylistUrlValue : _lastPlaylist);
bool flag2 = flag && TvInterop.IsLive;
bool flag3 = flag && TvInterop.IsSegmentedVod;
bool flag4 = (flag ? TvInterop.Paused : _lastPaused);
bool flag5 = (flag ? TvInterop.Shuffle : _lastShuffle);
int num = (flag ? TvInterop.PlaylistIndexValue : _lastPlaylistIndex);
double position = TvInterop.Position;
bool flag6 = !string.Equals(text, _lastSource, StringComparison.Ordinal) || !string.Equals(text4, _lastPlaylist, StringComparison.Ordinal) || (flag && num != _lastPlaylistIndex);
bool result = !_hasHostState || flag6 || text2 != _lastUrl || text3 != _lastTitle || flag2 != _lastLive || flag3 != _lastSegmented || flag4 != _lastPaused || poweredOff != _lastPowered || flag5 != _lastShuffle || num != _lastPlaylistIndex || position != _lastPosition;
if (flag6)
{
_generation++;
if (startBarrier && !string.IsNullOrEmpty(text))
{
_barrier = true;
TvInterop.SetSharedPaused(paused: true);
}
}
_lastSource = text;
_lastUrl = text2;
_lastTitle = text3;
_lastPlaylist = text4;
_lastLive = flag2;
_lastSegmented = flag3;
_lastPaused = flag4;
_lastPowered = poweredOff;
_lastShuffle = flag5;
_lastPlaylistIndex = num;
_lastPosition = position;
_hasHostState = flag || _barrier || poweredOff || !string.IsNullOrEmpty(text);
return result;
}
private void PublishMediaDelta(byte operation, Guid predictionId, bool force)
{
if (!_shutdown && _context != null && _context.InGame && TvInterop.Present && (CaptureHostState(operation != 2) || force) && (force || !string.IsNullOrEmpty(_lastSource)))
{
_context.Broadcast((INetMessage)(object)new TvMediaDeltaMessage
{
PredictionId = predictionId,
Operation = operation,
SourceUrl = _lastSource,
StreamUrl = _lastUrl,
StreamTitle = _lastTitle,
PlaylistUrl = _lastPlaylist,
IsLive = _lastLive,
IsSegmentedVod = _lastSegmented,
PlaylistIndex = _lastPlaylistIndex,
Position = _lastPosition,
Paused = _lastPaused,
PoweredOff = _lastPowered,
Shuffle = _lastShuffle,
Barrier = _barrier,
Resume = _resumePulse,
Generation = _generation
});
_resumePulse = false;
ClearPendingMediaOperation(operation, predictionId);
}
}
private void PublishScalarDelta(byte operation, Guid predictionId)
{
if (!_shutdown && _context != null && _context.InGame && TvInterop.Present)
{
CaptureHostState(startBarrier: false);
switch (operation)
{
case 3:
_context.Broadcast((INetMessage)(object)new TvPauseDeltaMessage
{
PredictionId = predictionId,
Paused = _lastPaused
});
break;
case 4:
_context.Broadcast((INetMessage)(object)new TvPowerDeltaMessage
{
PredictionId = predictionId,
PoweredOff = _lastPowered
});
break;
case 5:
_context.Broadcast((INetMessage)(object)new TvShuffleDeltaMessage
{
PredictionId = predictionId,
Shuffle = _lastShuffle
});
break;
case 6:
_context.Broadcast((INetMessage)(object)new TvSeekDeltaMessage
{
PredictionId = predictionId,
Position = _lastPosition
});
break;
default:
throw new InvalidOperationException("Unknown scalar TV operation " + operation + ".");
}
}
}
private void PublishBarrierDelta(Guid predictionId)
{
if (!_shutdown && _context != null && _context.InGame && TvInterop.Present)
{
_context.Broadcast((INetMessage)(object)new TvBarrierDeltaMessage
{
PredictionId = predictionId,
Barrier = _barrier,
Resume = _resumePulse
});
_resumePulse = false;
}
}
private TvBaselineMessage BuildBaseline()
{
return new TvBaselineMessage
{
SourceUrl = _lastSource,
StreamUrl = _lastUrl,
StreamTitle = _lastTitle,
PlaylistUrl = _lastPlaylist,
IsLive = _lastLive,
IsSegmentedVod = _lastSegmented,
PlaylistIndex = _lastPlaylistIndex,
Position = _lastPosition,
Paused = _lastPaused,
PoweredOff = _lastPowered,
Shuffle = _lastShuffle,
Barrier = _barrier,
Resume = _resumePulse,
Generation = _generation
};
}
private bool TryBeginPendingMediaOperation(byte operation, Guid predictionId)
{
if (_hasPendingMediaOperation)
{
return false;
}
_hasPendingMediaOperation = true;
_pendingMediaOperation = operation switch
{
2 => 4,
1 => 3,
_ => 1,
};
_pendingMediaPredictionId = predictionId;
return true;
}
private void ClearPendingMediaOperation(byte operation, Guid predictionId)
{
if (_hasPendingMediaOperation && _pendingMediaOperation == operation && !(_pendingMediaPredictionId != predictionId))
{
_hasPendingMediaOperation = false;
_pendingMediaOperation = 0;
_pendingMediaPredictionId = Guid.Empty;
}
}
private void ClearPendingMediaOperation()
{
_hasPendingMediaOperation = false;
_pendingMediaOperation = 0;
_pendingMediaPredictionId = Guid.Empty;
}
private static void ApplyPatches(Harmony harmony)
{
//IL_001f: Unknown result type (might be due to invalid IL or missing references)
//IL_0029: Expected O, but got Unknown
//IL_0040: Unknown result type (might be due to invalid IL or missing references)
//IL_004a: Expected O, but got Unknown
//IL_0061: Unknown result type (might be due to invalid IL or missing references)
//IL_006b: Expected O, but got Unknown
//IL_0082: Unknown result type (might be due to invalid IL or missing references)
//IL_008c: Expected O, but got Unknown
//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
//IL_00ad: Expected O, but got Unknown
//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
//IL_00ce: Expected O, but got Unknown
//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
//IL_00ef: Expected O, but got Unknown
//IL_0106: Unknown result type (might be due to invalid IL or missing references)
//IL_0110: Expected O, but got Unknown
//IL_0127: Unknown result type (might be due to invalid IL or missing references)
//IL_0131: Expected O, but got Unknown
//IL_0148: Unknown result type (might be due to invalid IL or missing references)
//IL_0152: Expected O, but got Unknown
//IL_0169: Unknown result type (might be due to invalid IL or missing references)
//IL_0173: Expected O, but got Unknown
//IL_0189: Unknown result type (might be due to invalid IL or missing references)
//IL_019e: Unknown result type (might be due to invalid IL or missing references)
//IL_01a8: Expected O, but got Unknown
//IL_01a8: Expected O, but got Unknown
if (TvInterop.Present)
{
TryPatch(harmony, "OnEnable", null, new HarmonyMethod(typeof(TvHostBehaviour), "ControllerLifecyclePostfix", (Type[])null));
TryPatch(harmony, "Start", null, new HarmonyMethod(typeof(TvHostBehaviour), "ControllerLifecyclePostfix", (Type[])null));
TryPatch(harmony, "ChangeVideo", null, new HarmonyMethod(typeof(TvHostBehaviour), "ControllerLifecyclePostfix", (Type[])null));
TryPatch(harmony, "HandleGlobalInput", null, new HarmonyMethod(typeof(TvHostBehaviour), "HostGlobalInputPostfix", (Type[])null));
TryPatch(harmony, "StreamYouTube", null, new HarmonyMethod(typeof(TvHostBehaviour), "StreamYouTubePostfix", (Type[])null));
TryPatch(harmony, "ChangeToUrl", null, new HarmonyMethod(typeof(TvHostBehaviour), "ChangeToUrlPostfix", (Type[])null));
TryPatch(harmony, "PlayPlaylistItem", null, new HarmonyMethod(typeof(TvHostBehaviour), "PlaylistItemPostfix", (Type[])null));
TryPatch(harmony, "ToggleGlobalPause", null, new HarmonyMethod(typeof(TvHostBehaviour), "PausePostfix", (Type[])null));
TryPatch(harmony, "RemotePower", null, new HarmonyMethod(typeof(TvHostBehaviour), "PowerPostfix", (Type[])null));
TryPatch(harmony, "RemoteShuffle", null, new HarmonyMethod(typeof(TvHostBehaviour), "ShufflePostfix", (Type[])null));
TryPatch(harmony, "OnDisable", null, new HarmonyMethod(typeof(TvHostBehaviour), "ControllerDisablePostfix", (Type[])null));
TryPatch(harmony, "OnLocalPrepared", new HarmonyMethod(typeof(TvHostBehaviour), "LocalPreparedPrefix", (Type[])null), new HarmonyMethod(typeof(TvHostBehaviour), "LocalPreparedPostfix", (Type[])null));
}
}
private static void TryPatch(Harmony harmony, string method, HarmonyMethod prefix, HarmonyMethod postfix)
{
try
{
MethodInfo methodInfo = AccessTools.Method(TvInterop.OptionalControllerType, method, (Type[])null, (Type[])null);
if (methodInfo == null)
{
CoopLog.Warn("TV host patch target missing: " + method);
}
else
{
harmony.Patch((MethodBase)methodInfo, prefix, postfix, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
}
}
catch (Exception ex)
{
CoopLog.Warn("TV host patch failed " + method + ": " + ex.Message);
}
}
private static void StreamYouTubePostfix(string url)
{
TvInterop.StreamYouTubePostfix(url);
_active?.OnMediaSourceChanged();
}
private static void ChangeToUrlPostfix(string url, string title)
{
TvInterop.ChangeToUrlPostfix(url);
_active?.OnMediaSourceChanged();
}
private static void PlaylistItemPostfix()
{
if (!((Object)(object)_active == (Object)null) && !TvInterop.ApplyingRemote)
{
byte operation = (byte)(_active._hasPendingMediaOperation ? _active._pendingMediaOperation : 2);
Guid predictionId = (_active._hasPendingMediaOperation ? _active._pendingMediaPredictionId : Guid.Empty);
_active.PublishMediaDelta(operation, predictionId, force: true);
}
}
private static void PausePostfix()
{
_active?.OnScalarChanged(3);
}
private static void PowerPostfix()
{
_active?.OnScalarChanged(4);
}
private static void ShufflePostfix()
{
_active?.OnScalarChanged(5);
}
private void OnMediaSourceChanged()
{
byte operation = (byte)((!_hasPendingMediaOperation) ? 1 : _pendingMediaOperation);
Guid predictionId = (_hasPendingMediaOperation ? _pendingMediaPredictionId : Guid.Empty);
PublishMediaDelta(operation, predictionId, force: true);
}
private void OnScalarChanged(byte operation)
{
if (!TvInterop.ApplyingRemote)
{
PublishScalarDelta(operation, Guid.Empty);
}
}
private static void ControllerLifecyclePostfix(object __instance)
{
TvInterop.RegisterController((Component)((__instance is Component) ? __instance : null));
_active?.OnControllerLifecycle();
}
private static void ControllerDisablePostfix()
{
if ((Object)(object)_active != (Object)null && _active._barrier && !_active._shutdown)
{
_active.AbortBarrier("host TV controller playback was canceled");
_active.PublishBarrierDelta(Guid.Empty);
}
}
private static void HostGlobalInputPostfix()
{
//IL_002d: Unknown result type (might be due to invalid IL or missing references)
if ((Input.GetKey((KeyCode)308) || Input.GetKey((KeyCode)307)) && (Input.GetKey((KeyCode)93) || Input.GetKey((KeyCode)91) || !(Mathf.Abs(Input.mouseScrollDelta.y) <= 0.0001f)) && (TvInterop.IsPlayingStream || TvInterop.IsFetching) && !((Object)(object)_active == (Object)null))
{
float unscaledTime = Time.unscaledTime;
if (!float.IsNaN(unscaledTime) && !float.IsInfinity(unscaledTime) && unscaledTime - _active._lastSeekInputTime >= 0.25f)
{
_active._lastSeekInputTime = unscaledTime;
_active.PublishScalarDelta(6, Guid.Empty);
}
}
}
private static void LocalPreparedPrefix(object __instance, object source)
{
_active?.BeforeLocalPrepared(__instance, source);
}
private static void LocalPreparedPostfix(object __instance, object source)
{
_active?.AfterLocalPrepared(__instance, source);
}
private void BeforeLocalPrepared(object instance, object source)
{
if (_barrier && TvInterop.IsMainPreparedPlayer(instance, source))
{
TvInterop.SetSharedPausedForPrepare(paused: false);
}
}
private void AfterLocalPrepared(object instance, object source)
{
if (_barrier && TvInterop.IsMainPreparedPlayer(instance, source))
{
TvInterop.PausePreparedStream(instance);
CompleteBarrier();
PublishBarrierDelta(Guid.Empty);
}
}
private void OnHostVideoError(string message)
{
if (!_shutdown)
{
byte pendingMediaOperation = _pendingMediaOperation;
Guid pendingMediaPredictionId = _pendingMediaPredictionId;
CoopLog.Error("Host TV VideoPlayer failed: " + (message ?? "unknown error"));
TvInterop.AbortSharedPlayback();
AbortBarrier("host TV playback failed");
_lastSource = null;
_lastUrl = null;
_lastTitle = null;
_lastPlaylist = null;
_lastLive = false;
_lastSegmented = false;
_lastPaused = false;
_lastPowered = TvInterop.PoweredOff;
_lastShuffle = TvInterop.Shuffle;
_lastPlaylistIndex = 0;
_lastPosition = 0.0;
_hasHostState = _lastPowered;
if (_context != null && _context.InGame && TvInterop.Present)
{
_context.Broadcast((INetMessage)(object)new TvPlaybackErrorDeltaMessage
{
PredictionId = (_hasPendingMediaOperation ? pendingMediaPredictionId : Guid.Empty),
Generation = _generation
});
ClearPendingMediaOperation(pendingMediaOperation, pendingMediaPredictionId);
}
}
}
private void CompleteBarrier()
{
_barrier = false;
_resumePulse = true;
TvInterop.ResumePlayback();
}
private void AbortBarrier(string reason)
{
_barrier = false;
_resumePulse = true;
TvInterop.ResumePlayback();
CoopLog.Warn("TV load barrier aborted: " + reason);
}
}
internal static class TvInterop
{
internal enum FeatureAvailability
{
Absent,
Ready,
Drifted
}
internal readonly struct PlaybackUndoState
{
internal string SourceUrl { get; }
internal string StreamUrl { get; }
internal string StreamTitle { get; }
internal string PlaylistUrl { get; }
internal bool IsLive { get; }
internal bool IsSegmentedVod { get; }
internal int PlaylistIndex { get; }
internal double Position { get; }
internal bool Paused { get; }
internal bool PoweredOff { get; }
internal bool Shuffle { get; }
internal bool Barrier { get; }
internal int Generation { get; }
internal PlaybackUndoState(string sourceUrl, string url, string title, string playlistUrl, bool live, bool segmented, int playlistIndex, double position, bool paused, bool powered, bool shuffle, bool barrier, int generation)
{
SourceUrl = sourceUrl;
StreamUrl = url;
StreamTitle = title;
PlaylistUrl = playlistUrl;
IsLive = live;
IsSegmentedVod = segmented;
PlaylistIndex = playlistIndex;
Position = position;
Paused = paused;
PoweredOff = powered;
Shuffle = shuffle;
Barrier = barrier;
Generation = generation;
}
}
private const string AssemblyName = "RTCGOCustomScripts";
internal const double MaxPositionSeconds = 86400.0;
internal const double MaxSeekDeltaSeconds = 300.0;
private static readonly Type ControllerType = CoopReflection.OptionalType("Scripts.VideoPlayerController", "RTCGOCustomScripts");
private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
private static readonly FieldInfo Enrolled = Field("enrolledTVs");
private static readonly FieldInfo SharedVideoIndex = Field("sharedVideoIndex");
private static readonly FieldInfo StreamUrlField = Field("activeStreamUrl");
private static readonly FieldInfo StreamTitleField = Field("activeStreamTitle");
private static readonly FieldInfo PlaylistUrl = Field("activePlaylistUrl");
private static readonly FieldInfo PlaylistIndex = Field("activePlaylistIndex");
private static readonly FieldInfo Live = Field("isLiveStream");
private static readonly FieldInfo Segmented = Field("isSegmentedVod");
private static readonly FieldInfo Playing = Field("isPlayingStream");
private static readonly FieldInfo Fetching = Field("isFetchingUrl");
private static readonly FieldInfo PausedField = Field("isSharedPaused");
private static readonly FieldInfo Powered = Field("isPoweredOff");
private static readonly FieldInfo ShuffleField = Field("isShuffleMode");
private static readonly FieldInfo LastTime = Field("lastKnownMasterTime");
private static readonly FieldInfo PendingUrlField = Field("pendingYouTubeUrl");
private static readonly FieldInfo QualityOpen = Field("isQualityChoiceOpen");
private static readonly FieldInfo QualityRoot = Field("qualityChoiceRoot");
private static readonly FieldInfo VideoPlayerField = Field("m_VideoPlayer");
private static readonly EventInfo VideoErrorReceived = VideoPlayerField?.FieldType.GetEvent("errorReceived", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
private static readonly MethodInfo ChangeUrl = Method("ChangeToUrl", typeof(string), typeof(string));
private static readonly MethodInfo PlayPlaylistItem = Method("PlayPlaylistItem", typeof(int));
private static readonly MethodInfo Stream = Method("StreamYouTube", typeof(string));
private static readonly MethodInfo QualityChoice = Method("OnQualityChoice", typeof(bool));
private static readonly MethodInfo ChangeVideo = Method("ChangeVideo", typeof(int));
private static readonly MethodInfo BroadcastChange = Method("BroadcastChange", typeof(int));
private static readonly MethodInfo TogglePause = Method("ToggleGlobalPause");
private static readonly MethodInfo RemotePower = Method("RemotePower");
private static readonly MethodInfo Unfreeze = Method("UnfreezeGame");
private static readonly MethodInfo LifecycleEnable = Method("OnEnable");
private static readonly MethodInfo LifecycleStart = Method("Start");
private static readonly MethodInfo LifecycleInput = Method("HandleGlobalInput");
private static readonly MethodInfo LifecycleQuality = Method("OnQualityChoice");
private static readonly MethodInfo LifecyclePrepared = Method("OnLocalPrepared");
private static readonly MethodInfo RemotePlayPause = Method("RemotePlayPause");
private static readonly MethodInfo RemoteNext = Method("RemoteNext");
private static readonly MethodInfo RemotePrev = Method("RemotePrev");
private static readonly MethodInfo RemoteShuffle = Method("RemoteShuffle");
private static readonly FeatureAvailability AvailabilityState = ValidateSurface();
private static bool _logged;
private static bool _availabilityLogged;
private static Component _master;
private static string _pendingSourceUrl;
private static string _requestedSourceUrl;
private static string _launchedSource;
private static int _launchedPlaylistIndex;
private static bool _streamLaunchPending;
private static double _pendingPosition;
private static bool _hasPendingPosition;
private static int _pendingPositionGeneration;
private static int _playbackGeneration;
private static bool _barrierState;
private static object _attachedVideoPlayer;
private static Delegate _videoErrorDelegate;
private static Action<string> _videoErrorHandler;
internal static bool ApplyingRemote;
internal static Type OptionalControllerType => ControllerType;
internal static FeatureAvailability Availability
{
get
{
LogAvailability();
return AvailabilityState;
}
}
internal static string UnavailableReason
{
get
{
if (AvailabilityState == FeatureAvailability.Absent)
{
return "RTCGO Custom TV is not installed";
}
if (AvailabilityState != FeatureAvailability.Drifted)
{
return null;
}
return "RTCGO Custom TV reflection surface changed";
}
}
internal static bool Present
{
get
{
bool num = Availability == FeatureAvailability.Ready;
if (num && !_logged)
{
_logged = true;
CoopLog.Info("RTCGO Custom TV detected - shared playback is available");
}
return num;
}
}
internal static string StreamUrl => Get<string>(StreamUrlField);
internal static string StreamTitle => Get<string>(StreamTitleField);
internal static string PlaylistUrlValue => Get<string>(PlaylistUrl);
internal static bool IsPlayingStream => Get(Playing, fallback: false);
internal static bool IsFetching => Get(Fetching, fallback: false);
internal static bool IsLive => Get(Live, fallback: false);
internal static bool IsSegmentedVod => Get(Segmented, fallback: false);
internal static bool Paused => Get(PausedField, fallback: false);
internal static bool PoweredOff => Get(Powered, fallback: false);
internal static bool Shuffle => Get(ShuffleField, fallback: false);
internal static int PlaylistIndexValue => Get(PlaylistIndex, 1);
internal static double Position => GetDouble(LastTime, 0.0);
internal static bool IsPlaylist => !string.IsNullOrEmpty(PlaylistUrlValue);
internal static string SourceUrl => _requestedSourceUrl;
internal static string PendingSourceUrl => _pendingSourceUrl;
internal static int EnrolledCount => (Enrolled?.GetValue(null) as IList)?.Count ?? 0;
internal static bool ControllerReady => (Object)(object)Master() != (Object)null;
internal static bool EnrollmentReady => EnrolledCount > 0;
internal static string PendingUrl => Get<string>(PendingUrlField);
private static FieldInfo Field(string name)
{
return CoopReflection.OptionalField(ControllerType, name);
}
private static MethodInfo Method(string name, params Type[] arguments)
{
return CoopReflection.OptionalMethod(ControllerType, name, arguments);
}
private static FeatureAvailability ValidateSurface()
{
if (ControllerType == null)
{
return FeatureAvailability.Absent;
}
if (!typeof(MonoBehaviour).IsAssignableFrom(ControllerType))
{
return FeatureAvailability.Drifted;
}
FieldInfo[] array = new FieldInfo[17]
{
Enrolled, SharedVideoIndex, StreamUrlField, StreamTitleField, PlaylistUrl, PlaylistIndex, Live, Segmented, Playing, Fetching,
PausedField, Powered, ShuffleField, LastTime, PendingUrlField, QualityOpen, QualityRoot
};
for (int i = 0; i < array.Length; i++)
{
if (array[i] == null)
{
return FeatureAvailability.Drifted;
}
}
MethodInfo[] array2 = new MethodInfo[18]
{
ChangeUrl, PlayPlaylistItem, Stream, QualityChoice, ChangeVideo, BroadcastChange, TogglePause, RemotePower, Unfreeze, LifecycleEnable,
LifecycleStart, LifecycleInput, LifecycleQuality, LifecyclePrepared, RemotePlayPause, RemoteNext, RemotePrev, RemoteShuffle
};
for (int j = 0; j < array2.Length; j++)
{
if (array2[j] == null)
{
return FeatureAvailability.Drifted;
}
}
if (!Enrolled.IsStatic)
{
return FeatureAvailability.Drifted;
}
if (!typeof(IEnumerator).IsAssignableFrom(Stream.ReturnType))
{
return FeatureAvailability.Drifted;
}
if (VideoPlayerField == null || VideoErrorReceived == null)
{
return FeatureAvailability.Drifted;
}
Type fieldType = VideoPlayerField.FieldType;
if (CoopReflection.OptionalProperty(fieldType, "isPrepared") == null || CoopReflection.OptionalProperty(fieldType, "time") == null || CoopReflection.OptionalProperty(fieldType, "length") == null)
{
return FeatureAvailability.Drifted;
}
return FeatureAvailability.Ready;
}
private static void LogAvailability()
{
if (!_availabilityLogged)
{
_availabilityLogged = true;
if (AvailabilityState == FeatureAvailability.Drifted)
{
CoopLog.Error("TV integration disabled: " + UnavailableReason);
}
else if (AvailabilityState == FeatureAvailability.Absent)
{
CoopLog.Info("RTCGO Custom TV is not installed - shared TV disabled");
}
}
}
private static object Get(FieldInfo field)
{
if (!(field == null))
{
return field.GetValue(field.IsStatic ? null : Master());
}
return null;
}
private static T Get<T>(FieldInfo field, T fallback = default(T))
{
try
{
object obj = Get(field);
return (obj == null) ? fallback : ((T)obj);
}
catch (Exception ex)
{
CoopLog.Swallow(ex, "Get", "/_work/cardshopcoop-Xsfcsze8PGgsf/CardShopCoop/CardShopCoop/src/CardShopCoop.CustomTv/TvInterop.cs", 230);
return fallback;
}
}
private static Component Master()
{
try
{
if (Enrolled?.GetValue(null) is IList { Count: >0 } list)
{
object? obj = list[0];
Component val = (Component)((obj is Component) ? obj : null);
if ((Object)(object)val != (Object)null)
{
_master = val;
}
}
return _master;
}
catch (Exception ex)
{
CoopLog.Swallow(ex, "Master", "/_work/cardshopcoop-Xsfcsze8PGgsf/CardShopCoop/CardShopCoop/src/CardShopCoop.CustomTv/TvInterop.cs", 253);
return null;
}
}
internal static void RegisterController(Component controller)
{
if ((Object)(object)controller != (Object)null && ControllerType.IsInstanceOfType(controller))
{
_master = controller;
}
}
internal static void RecordPendingSourceUrl(string url)
{
if (!string.IsNullOrWhiteSpace(url))
{
_requestedSourceUrl = url;
_pendingSourceUrl = url;
}
}
internal static void RecordActiveSourceUrl(string url)
{
if (!string.IsNullOrWhiteSpace(url) && string.IsNullOrWhiteSpace(_requestedSourceUrl))
{
_requestedSourceUrl = url;
}
}
internal static void ClearPendingSourceUrl()
{
_pendingSourceUrl = null;
}
internal static void ClearSourceIdentity()
{
_pendingSourceUrl = null;
_requestedSourceUrl = null;
}
internal static void ResetSession()
{
_pendingSourceUrl = null;
_requestedSourceUrl = null;
ResetControllerState();
ApplyingRemote = false;
}
internal static void NotifySceneLoaded()
{
_pendingSourceUrl = null;
_requestedSourceUrl = null;
ResetControllerState();
ApplyingRemote = false;
}
private static void ResetControllerState()
{
if (_attachedVideoPlayer != null && (object)_videoErrorDelegate != null && VideoErrorReceived != null)
{
VideoErrorReceived.RemoveEventHandler(_attachedVideoPlayer, _videoErrorDelegate);
}
_master = null;
_launchedSource = null;
_launchedPlaylistIndex = 0;
_streamLaunchPending = false;
_pendingPosition = 0.0;
_hasPendingPosition = false;
_pendingPositionGeneration = 0;
_playbackGeneration = 0;
_barrierState = false;
_attachedVideoPlayer = null;
_videoErrorDelegate = null;
_videoErrorHandler = null;
}
internal static bool AttachVideoErrorHandler(Action<string> handler)
{
_videoErrorHandler = handler;
object obj = VideoPlayer(Master());
if (obj == null || VideoErrorReceived == null)
{
return false;
}
if (obj == _attachedVideoPlayer)
{
return true;
}
if (_attachedVideoPlayer != null && (object)_videoErrorDelegate != null)
{
VideoErrorReceived.RemoveEventHandler(_attachedVideoPlayer, _videoErrorDelegate);
}
MethodInfo method = typeof(TvInterop).GetMethod("OnVideoError", BindingFlags.Static | BindingFlags.NonPublic);
try
{
_videoErrorDelegate = Delegate.CreateDelegate(VideoErrorReceived.EventHandlerType, method);
VideoErrorReceived.AddEventHandler(obj, _videoErrorDelegate);
_attachedVideoPlayer = obj;
return true;
}
catch (Exception ex)
{
_videoErrorDelegate = null;
_attachedVideoPlayer = null;
CoopLog.Error("TV VideoPlayer error lifecycle hook failed: " + ex);
return false;
}
}
private static void OnVideoError(object _, string message)
{
_videoErrorHandler?.Invoke(message);
}
internal static void ApplyMediaState(string sourceUrl, string url, string title, string playlistUrl, bool live, bool segmented, int playlistIndex, double position, bool paused, bool powered, bool shuffle, bool barrier, bool resume, int generation)
{
if (!Present)
{
return;
}
Component val = Master();
if ((Object)(object)val == (Object)null)
{
return;
}
ApplyingRemote = true;
try
{
string launchedSource = _launchedSource;
if (!string.Equals(launchedSource, sourceUrl, StringComparison.Ordinal) || _launchedPlaylistIndex != playlistIndex)
{
_playbackGeneration = generation;
}
_barrierState = barrier;
SetField(Live, live, val);
SetField(Segmented, segmented, val);
SetField(StreamTitleField, title, val);
SetField(PlaylistUrl, playlistUrl, val);
SetField(PlaylistIndex, playlistIndex, val);
SetField(Powered, powered, val);
SetField(ShuffleField, shuffle, val);
if (string.IsNullOrEmpty(sourceUrl))
{
_hasPendingPosition = false;
ClearSourceIdentity();
}
else
{
_pendingPosition = position;
_hasPendingPosition = true;
_pendingPositionGeneration = generation;
}
bool flag = !string.IsNullOrEmpty(playlistUrl) && string.Equals(launchedSource, sourceUrl, StringComparison.Ordinal) && _launchedPlaylistIndex != playlistIndex;
if (!string.IsNullOrEmpty(sourceUrl) && (!string.Equals(launchedSource, sourceUrl, StringComparison.Ordinal) || flag || _streamLaunchPending))
{
_launchedSource = sourceUrl;
_launchedPlaylistIndex = playlistIndex;
_streamLaunchPending = true;
CoopLog.Info("TV client injecting shared stream: " + sourceUrl);
SetField(PendingUrlField, sourceUrl, val);
QualityChoice.Invoke(val, new object[1] { false });
_streamLaunchPending = false;
}
else if (string.IsNullOrEmpty(sourceUrl) && string.IsNullOrEmpty(playlistUrl))
{
bool num = !string.IsNullOrEmpty(_launchedSource) || _launchedPlaylistIndex != 0;
_launchedSource = null;
_launchedPlaylistIndex = 0;
_streamLaunchPending = false;
if (num)
{
BroadcastChange?.Invoke(val, new object[1] { Get(SharedVideoIndex, 0) });
}
}
if (string.IsNullOrEmpty(sourceUrl))
{
_hasPendingPosition = false;
}
else
{
RequestPosition(val, position);
}
SetSharedPaused(barrier || paused || powered);
if (resume && !powered)
{
ResumePlayback();
}
}
finally
{
ApplyingRemote = false;
}
}
internal static void ApplyPaused(bool paused)
{
if (Present && !((Object)(object)Master() == (Object)null))
{
SetSharedPaused(paused);
}
}
internal static void ApplyPowered(bool poweredOff)
{
if (!Present)
{
return;
}
Component val = Master();
if ((Object)(object)val == (Object)null || PoweredOff == poweredOff)
{
return;
}
if (RemotePower != null)
{
bool applyingRemote = ApplyingRemote;
ApplyingRemote = true;
try
{
RemotePower.Invoke(val, null);
}
finally
{
ApplyingRemote = applyingRemote;
}
}
SetField(Powered, poweredOff, val);
}
internal static void ApplyShuffle(bool shuffle)
{
if (!Present)
{
return;
}
Component val = Master();
if (!((Object)(object)val == (Object)null) && Shuffle != shuffle)
{
if (!ApplyRemoteShuffle())
{
SetField(ShuffleField, shuffle, val);
}
else if (Shuffle != shuffle)
{
SetField(ShuffleField, shuffle, val);
}
}
}
internal static void ApplySeek(double position)
{
if (Present && IsValidPosition(position))
{
Component val = Master();
if (!((Object)(object)val == (Object)null))
{
RequestPosition(val, Math.Min(position, 86400.0));
}
}
}
internal static void ApplyBarrier(bool barrier, bool resume)
{
if (Present && !((Object)(object)Master() == (Object)null))
{
_barrierState = barrier;
SetSharedPaused(barrier);
if (resume && !PoweredOff)
{
ResumePlayback();
}
}
}
internal static void ApplyPlaybackError()
{
if (Present)
{
_barrierState = false;
AbortSharedPlayback();
}
}
internal static PlaybackUndoState CapturePlaybackState()
{
return new PlaybackUndoState(SourceUrl, StreamUrl, StreamTitle, PlaylistUrlValue, IsLive, IsSegmentedVod, PlaylistIndexValue, Position, Paused, PoweredOff, Shuffle, _barrierState, _playbackGeneration);
}
internal static void RestorePlaybackState(PlaybackUndoState state)
{
ApplyMediaState(state.SourceUrl, state.StreamUrl, state.StreamTitle, state.PlaylistUrl, state.IsLive, state.IsSegmentedVod, state.PlaylistIndex, state.Position, state.Paused, state.PoweredOff, state.Shuffle, state.Barrier, resume: false, state.Generation);
}
internal static void SetSharedPaused(bool paused)
{
if (Paused != paused && TogglePause != null)
{
bool applyingRemote = ApplyingRemote;
ApplyingRemote = true;
try
{
TogglePause.Invoke(Master(), null);
}
finally
{
ApplyingRemote = applyingRemote;
}
}
if (Paused != paused)
{
SetField(PausedField, paused, Master());
}
}
internal static void SetSharedPausedForPrepare(bool paused)
{
SetField(PausedField, paused, Master());
}
internal static void ResumePlayback()
{
SetSharedPaused(paused: false);
}
internal static void AbortSharedPlayback()
{
Component val = Master();
try
{
SetSharedPaused(paused: false);
if ((Object)(object)val != (Object)null && !PoweredOff && RemotePower != null)
{
bool applyingRemote = ApplyingRemote;
ApplyingRemote = true;
try
{
RemotePower.Invoke(val, null);
return;
}
finally
{
ApplyingRemote = applyingRemote;
}
}
}
finally
{
ClearSourceIdentity();
_launchedSource = null;
_launchedPlaylistIndex = 0;
_streamLaunchPending = false;
_hasPendingPosition = false;
}
}
internal static bool ApplyRemoteShuffle()
{
Component val = Master();
if ((Object)(object)val == (Object)null || RemoteShuffle == null)
{
return false;
}
bool applyingRemote = ApplyingRemote;
ApplyingRemote = true;
try
{
RemoteShuffle.Invoke(val, null);
return true;
}
finally
{
ApplyingRemote = applyingRemote;
}
}
internal static bool IsMainPreparedPlayer(object instance, object source)
{
if (instance == null || !IsPlayingStream || Master() != instance)
{
return false;
}
return VideoPlayer((Component)((instance is Component) ? instance : null)) == source;
}
internal static void PausePreparedStream(object instance)
{
if (instance != null && Master() == instance)
{
SetSharedPaused(paused: true);
}
}
internal static bool ApplyPreparedPosition(object instance)
{
if (!_hasPendingPosition || instance == null || Master() != instance || _pendingPositionGeneration != _playbackGeneration)
{
return false;
}
object obj = VideoPlayer((Component)((instance is Component) ? instance : null));
if (obj == null || !GetProperty(obj, "isPrepared", fallback: false))
{
return false;
}
ApplyingRemote = true;
try
{
SetPlayerPosition(obj, _pendingPosition);
_hasPendingPosition = false;
return true;
}
finally
{
ApplyingRemote = false;
}
}
internal static bool RequestClientEnrollment()
{
if (!Present)
{
return false;
}
if (EnrolledCount > 0)
{
return false;
}
Component val = Master();
if ((Object)(object)val == (Object)null || ChangeVideo == null)
{
return false;
}
ChangeVideo.Invoke(val, new object[1] { Get(SharedVideoIndex, 0) });
return true;
}
internal static void StreamYouTubePostfix(string url)
{
RecordPendingSourceUrl(url);
}
internal static void ChangeToUrlPostfix(string url)
{
RecordActiveSourceUrl(url);
}
internal static bool ApplyOperation(byte op, string url, string title, double value)
{
//IL_02ba: Unknown result type (might be due to invalid IL or missing references)
if (!Present)
{
return false;
}
Component val = Master();
if ((Object)(object)val == (Object)null)
{
return false;
}
bool applyingRemote = ApplyingRemote;
ApplyingRemote = true;
try
{
switch (op)
{
case 1:
if (!IsPlaylist || PlayPlaylistItem == null)
{
return false;
}
PlayPlaylistItem.Invoke(val, new object[1] { PlaylistIndexValue + 1 });
return true;
case 2:
if (!IsPlaylist || PlayPlaylistItem == null)
{
return false;
}
PlayPlaylistItem.Invoke(val, new object[1] { Math.Max(1, PlaylistIndexValue - 1) });
return true;
case 3:
if (TogglePause == null)
{
return false;
}
TogglePause?.Invoke(val, null);
return true;
case 4:
if (RemotePower != null)
{
RemotePower.Invoke(val, null);
return true;
}
if (Powered == null)
{
return false;
}
Powered.SetValue(Powered.IsStatic ? null : val, !PoweredOff);
return true;
case 5:
if (ShuffleField == null)
{
return false;
}
if (ApplyRemoteShuffle())
{
return true;
}
ShuffleField.SetValue(ShuffleField.IsStatic ? null : val, !Shuffle);
return true;
case 6:
{
if (double.IsNaN(value) || double.IsInfinity(value) || Math.Abs(value) > 300.0)
{
return false;
}
object obj = VideoPlayer(val);
if (obj == null || !GetProperty(obj, "isPrepared", fallback: false))
{
return false;
}
double num = GetPropertyDouble(obj, "time", Position);
if (!IsValidPosition(num))
{
num = 0.0;
}
double position = num + value;
if (!IsValidPosition(position))
{
return false;
}
double num2 = ClampPosition(obj, position);
if (Math.Abs(num2 - num) <= 0.0001)
{
return false;
}
SetPlayerPosition(obj, num2);
return true;
}
case 7:
if (string.IsNullOrWhiteSpace(url))
{
return false;
}
SetField(PlaylistUrl, url.Contains("list=") ? url : null, val);
if (Stream != null)
{
((MonoBehaviour)val).StartCoroutine((IEnumerator)Stream.Invoke(val, new object[1] { url }));
}
else
{
if (!(ChangeUrl != null))
{
return false;
}
ChangeUrl.Invoke(val, new object[2]
{
url,
title ?? "Shared stream"
});
}
return true;
default:
return false;
}
}
finally
{
ApplyingRemote = applyingRemote;
}
}
internal static bool FinishQualitySelection()
{
if (!Present)
{
return false;
}
try
{
if (string.IsNullOrWhiteSpace(Get<string>(PendingUrlField)))
{
return false;
}
Unfreeze?.Invoke(Master(), null);
SetField(QualityOpen, false, Master());
object field = GetField(QualityRoot);
object obj = ((field is GameObject) ? field : null);
if (obj != null)
{
((GameObject)obj).SetActive(false);
}
return true;
}
catch (Exception ex)
{
CoopLog.Warn("TV quality selection failed: " + ex);
return false;
}
}
private static object VideoPlayer(Component master)
{
if (!((Object)(object)master == (Object)null) && !(VideoPlayerField == null))
{
return VideoPlayerField.GetValue(master);
}
return null;
}
private static void RequestPosition(Component master, double position)
{
VideoPlayer(master);
_pendingPosition = position;
_hasPendingPosition = true;
_pendingPositionGeneration = _playbackGeneration;
if (ApplyPosition(master, _pendingPosition))
{
_hasPendingPosition = false;
}
}
private static bool ApplyPosition(Component master, double position)
{
object obj = VideoPlayer(master);
if (obj == null || !GetProperty(obj, "isPrepared", fallback: false))
{
return false;
}
SetPlayerPosition(obj, position);
return true;
}
private static void SetPlayerPosition(object player, double position)
{
SetProperty(player, "time", position);
SetLastTime(position);
}
private static void SetLastTime(double position)
{
if (!(LastTime == null))
{
object value = Convert.ChangeType(position, LastTime.FieldType);
LastTime.SetValue(LastTime.IsStatic ? null : Master(), value);
}
}
private static bool IsValidPosition(double position)
{
if (!double.IsNaN(position) && !double.IsInfinity(position))
{
return position >= 0.0;
}
return false;
}
private static double ClampPosition(object player, double position)
{
if (!IsValidPosition(position))
{
return 0.0;
}
double propertyDouble = GetPropertyDouble(player, "length", double.NaN);
double val = ((IsValidPosition(propertyDouble) && propertyDouble > 0.0) ? Math.Min(propertyDouble, 86400.0) : 86400.0);
return Math.Min(position, val);
}
private static double GetDouble(FieldInfo field, double fallback)
{
try
{
object obj = Get(field);
double num = ((obj == null) ? fallback : Convert.ToDouble(obj));
return IsValidPosition(num) ? Math.Min(num, 86400.0) : fallback;
}
catch (Exception ex)
{
CoopLog.Swallow(ex, "GetDouble", "/_work/cardshopcoop-Xsfcsze8PGgsf/CardShopCoop/CardShopCoop/src/CardShopCoop.CustomTv/TvInterop.cs", 1076);
return fallback;
}
}
private static double GetPropertyDouble(object target, string name, double fallback)
{
try
{
object obj = CoopReflection.OptionalProperty(target.GetType(), name)?.GetValue(target, null);
double num = ((obj == null) ? fallback : Convert.ToDouble(obj));
return IsValidPosition(num) ? Math.Min(num, 86400.0) : fallback;
}
catch (Exception ex)
{
CoopLog.Swallow(ex, "GetPropertyDouble", "/_work/cardshopcoop-Xsfcsze8PGgsf/CardShopCoop/CardShopCoop/src/CardShopCoop.CustomTv/TvInterop.cs", 1092);
return fallback;
}
}
private static T GetProperty<T>(object target, string name, T fallback)
{
try
{
object obj = CoopReflection.OptionalProperty(target.GetType(), name)?.GetValue(target, null);
return (obj == null) ? fallback : ((T)Convert.ChangeType(obj, typeof(T)));
}
catch (Exception ex)
{
CoopLog.Swallow(ex, "GetProperty", "/_work/cardshopcoop-Xsfcsze8PGgsf/CardShopCoop/CardShopCoop/src/CardShopCoop.CustomTv/TvInterop.cs", 1107);
return fallback;
}
}
private static void SetProperty(object target, string name, object value)
{
PropertyInfo propertyInfo = target?.GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (!(propertyInfo == null) && propertyInfo.CanWrite)
{
object value2 = value;
if (value != null && propertyInfo.PropertyType != value.GetType())
{
value2 = Convert.ChangeType(value, propertyInfo.PropertyType);
}
propertyInfo.SetValue(target, value2, null);
}
}
private static object GetField(FieldInfo field)
{
return field?.GetValue(field.IsStatic ? null : Master());
}
private static void SetField(FieldInfo field, object value, object instance)
{
if (field == null)
{
return;
}
object obj = (field.IsStatic ? null : instance);
if (field.IsStatic || obj != null)
{
object value2 = value;
if (value != null && field.FieldType != value.GetType())
{
value2 = Convert.ChangeType(value, field.FieldType);
}
field.SetValue(obj, value2);
}
}
}
[NetworkMessage]
public sealed class TvBaselineMessage : INetMessage
{
public string SourceUrl;
public string StreamUrl;
public string StreamTitle;
public string PlaylistUrl;
public bool IsLive;
public bool IsSegmentedVod;
public int PlaylistIndex;
public double Position;
public bool Paused;
public bool PoweredOff;
public bool Shuffle;
public bool Barrier;
public bool Resume;
public int Generation;
}
[NetworkMessage]
public sealed class TvMediaDeltaMessage : IPredictedMessage, INetMessage
{
public const byte Open = 1;
public const byte Playlist = 2;
public const byte Next = 3;
public const byte Previous = 4;
public byte Operation;
public string SourceUrl;
public string StreamUrl;
public string StreamTitle;
public string PlaylistUrl;
public bool IsLive;
public bool IsSegmentedVod;
public int PlaylistIndex;
public double Position;
public bool Paused;
public bool PoweredOff;
public bool Shuffle;
public bool Barrier;
public bool Resume;
public int Generation;
public Guid PredictionId { get; set; }
}
[NetworkMessage]
public sealed class TvPauseDeltaMessage : IPredictedMessage, INetMessage
{
public bool Paused;
public Guid PredictionId { get; set; }
}
[NetworkMessage]
public sealed class TvPowerDeltaMessage : IPredictedMessage, INetMessage
{
public bool PoweredOff;
public Guid PredictionId { get; set; }
}
[NetworkMessage]
public sealed class TvShuffleDeltaMessage : IPredictedMessage, INetMessage
{
public bool Shuffle;
public Guid PredictionId { get; set; }
}
[NetworkMessage]
public sealed class TvSeekDeltaMessage : IPredictedMessage, INetMessage
{
public double Position;
public Guid PredictionId { get; set; }
}
[NetworkMessage]
public sealed class TvBarrierDeltaMessage : IPredictedMessage, INetMessage
{
public bool Barrier;
public bool Resume;
public Guid PredictionId { get; set; }
}
[NetworkMessage]
public sealed class TvPlaybackErrorDeltaMessage : IPredictedMessage, INetMessage
{
public int Generation;
public Guid PredictionId { get; set; }
}
[NetworkMessage]
public sealed class TvOpMessage : IPredictedMessage, INetMessage
{
public byte Op;
public string Url;
public string Title;
public double Value;
public Guid PredictionId { get; set; }
}
}