using UdonSharp; using UnityEngine; using VRC.SDK3.Components.Video; using VRC.SDK3.Image; using VRC.SDK3.UdonNetworkCalling; using VRC.SDK3.Video.Components.Base; using VRC.SDKBase; using VRC.Udon.Common; using VRC.Udon.Common.Interfaces; public enum ClueScreenType { Blank, Video, Map } [UdonBehaviourSyncMode(BehaviourSyncMode.Manual)] public class CaseVideoSyncPlayer : UdonSharpBehaviour { [SerializeField] private BaseVRCVideoPlayer _VideoPlayer; [Header("Meshes")] [SerializeField] private MeshRenderer _BlankScreenMesh; [SerializeField] private MeshRenderer _VideoScreenMesh; [SerializeField] private MeshRenderer _MapScreenMesh; [Header("Materials")] [SerializeField] private Material _MapScreenMaterial; [SerializeField] private Texture2D _PlaceholderMapTexture; [Header("Video Status")] [SerializeField] private VideoLoadIndicator[] _VideoLoadIndicators; [UdonSynced, FieldChangeCallback(nameof(SubMapIndex))] private int _SubMapIndex = -1; [UdonSynced, FieldChangeCallback(nameof(ShowScreen))] private ClueScreenType _ShowScreen = ClueScreenType.Blank; [UdonSynced, FieldChangeCallback(nameof(FlashCorrectAnswer))] private bool _FlashCorrectAnswer = false; [UdonSynced] private int _VideoIndex = -1; private int _OldVideoIndex = -1; [UdonSynced, FieldChangeCallback(nameof(PlayVideo))] private bool _VideoIsPlaying; [UdonSynced] private VRCUrl[] _CaseMapsList = new VRCUrl[0]; private VRCUrl[] _CaseMapsListCache = new VRCUrl[0]; [UdonSynced] private int[] _CachedMapIndices = new int[0]; private int[] _LoadedMaps = new int[0]; [UdonSynced] private VRCUrl[] _CaseVideoList = new VRCUrl[0]; private VRCUrl[] _CaseVideoListCache = new VRCUrl[0]; private VRCImageDownloader _MapDownloader; private Texture2D[] _MapImages = new Texture2D[0]; private IUdonEventReceiver _UdonEventReceiverThis; private int _MapDownloadIndex = 0; private bool _MapDownloadsInProgress = false; //private int _VideoLoadAttemptCounter = 0; //private bool _UseFallback = false; private const int IMAGES_PER_MAP_ATLAS = 6; private const int MAX_VIDEO_LOAD_ATTEMPTS = 5; void Start() { _MapDownloader = new VRCImageDownloader(); _UdonEventReceiverThis = (IUdonEventReceiver)this; _MapScreenMesh.sharedMaterial = _MapScreenMaterial; _UpdateMap(); } void OnDestroy() { _MapDownloader.Dispose(); } public override void OnDeserialization(DeserializationResult Result) { { // Map syncing and redownloading _ReloadMapList(); if (!_MapDownloadsInProgress) { bool MapsNeedLoading = false; if (_LoadedMaps.Length == _CachedMapIndices.Length) { for (int i = 0; i < _CachedMapIndices.Length; i++) { if (_CachedMapIndices[i] != _LoadedMaps[i]) { MapsNeedLoading = true; break; } } } else { MapsNeedLoading = true; } if (MapsNeedLoading) { QueueMapDownloads(_CachedMapIndices); } } } { // Video list syncing _ReloadVideoList(); } base.OnDeserialization(Result); } public void LoadLists(VRCUrl[] Maps, VRCUrl[] Videos) { _CaseMapsList = Maps; _ReloadMapList(); _CaseVideoList = Videos; _ReloadVideoList(); RequestSerialization(); } private void _ReloadMapList() { if (_IsUrlListDifferent(_CaseMapsList, _CaseMapsListCache)) { _CaseMapsListCache = (VRCUrl[])_CaseMapsList.Clone(); } } private void _ReloadVideoList() { if (_IsUrlListDifferent(_CaseVideoList, _CaseVideoListCache)) { _OldVideoIndex = -1; _CaseVideoListCache = (VRCUrl[])_CaseVideoList.Clone(); } if (_VideoIndex != _OldVideoIndex) { _LoadNewVideoIndex(); } } [NetworkCallable] public void QueueMapDownloads(int[] MapIndices) { if (_MapDownloadsInProgress) { // Concatenate both caches into a new bigger cache and await new downloads as usual int[] NewMapIndicesCache = new int[_CachedMapIndices.Length + MapIndices.Length]; Texture2D[] NewMapImages = new Texture2D[_CachedMapIndices.Length + MapIndices.Length]; for (int i = 0; i < _CachedMapIndices.Length; i++) { NewMapIndicesCache[i] = _CachedMapIndices[i]; NewMapImages[i] = _MapImages[i]; } for (int i = 0; i < MapIndices.Length; i++) { NewMapIndicesCache[i + _CachedMapIndices.Length] = MapIndices[i]; NewMapImages[i + _CachedMapIndices.Length] = _PlaceholderMapTexture; } _CachedMapIndices = NewMapIndicesCache; _MapImages = NewMapImages; } else { _MapDownloadsInProgress = true; _CachedMapIndices = MapIndices; _MapImages = new Texture2D[_CachedMapIndices.Length]; for (int i = 0; i < _MapImages.Length; i++) { _MapImages[i] = _PlaceholderMapTexture; } _MapDownloadIndex = 0; LoadMapFromIndex(_CachedMapIndices[_MapDownloadIndex]); } _LoadedMaps = (int[])_CachedMapIndices.Clone(); } private void LoadMapFromIndex(int MapIndex) { VRCUrl MapURL = _CaseMapsList[MapIndex]; TextureInfo AdditionalTextureInfo = new TextureInfo(); AdditionalTextureInfo.WrapModeU = TextureWrapMode.Clamp; AdditionalTextureInfo.WrapModeV = TextureWrapMode.Clamp; AdditionalTextureInfo.GenerateMipMaps = true; _MapDownloader.DownloadImage(MapURL, null, _UdonEventReceiverThis, AdditionalTextureInfo); } public override void OnImageLoadSuccess(IVRCImageDownload Result) { _MapImages[_MapDownloadIndex] = Result.Result; int MapPage = SubMapIndex / IMAGES_PER_MAP_ATLAS; if (MapPage == _MapDownloadIndex) { _UpdateSubMap(MapPage); } _MapDownloadIndex++; if (_MapDownloadIndex >= _CachedMapIndices.Length) { _MapDownloadsInProgress = false; } else { LoadMapFromIndex(_CachedMapIndices[_MapDownloadIndex]); } base.OnImageLoadSuccess(Result); } public override void OnImageLoadError(IVRCImageDownload Result) { _MapDownloadsInProgress = false; base.OnImageLoadError(Result); } [NetworkCallable] public void NextCorrectAnswerFrame() { if (FlashCorrectAnswer) { SubMapIndex = (SubMapIndex == 4) ? 3 : 4; _UpdateMap(false); SendCustomEventDelayedSeconds(nameof(NextCorrectAnswerFrame), 0.2f); } else { _VideoPlayer.Stop(); SubMapIndex = 0; ShowScreen = ClueScreenType.Blank; RequestSerialization(); } } private void _UpdateMap(bool Sync = true) { int MapPage = SubMapIndex / IMAGES_PER_MAP_ATLAS; if (MapPage < _MapImages.Length) { _UpdateSubMap(MapPage); ShowScreen = ClueScreenType.Map; } if (Sync) { RequestSerialization(); } } private void _UpdateSubMap(int MapPage = -1) { int SubmapIndexWrapped = SubMapIndex % IMAGES_PER_MAP_ATLAS; switch (SubmapIndexWrapped) { case 0: _MapScreenMaterial.SetVector("_MainTex_ST", new Vector4(0.5f, 0.33333333f, 0.0f, 0.66666666f)); break; case 1: _MapScreenMaterial.SetVector("_MainTex_ST", new Vector4(0.5f, 0.33333333f, 0.5f, 0.66666666f)); break; case 2: _MapScreenMaterial.SetVector("_MainTex_ST", new Vector4(0.5f, 0.33333333f, 0.0f, 0.33333333f)); break; case 3: _MapScreenMaterial.SetVector("_MainTex_ST", new Vector4(0.5f, 0.33333333f, 0.5f, 0.33333333f)); break; case 4: _MapScreenMaterial.SetVector("_MainTex_ST", new Vector4(0.5f, 0.33333333f, 0.0f, 0.0f)); break; case 5: _MapScreenMaterial.SetVector("_MainTex_ST", new Vector4(0.5f, 0.33333333f, 0.5f, 0.0f)); break; } if (MapPage >= 0) { _MapScreenMaterial.SetTexture("_EmissionMap", _MapImages[MapPage]); } } public void SetVideoIndex(int Index) { if (Index != _OldVideoIndex) { _VideoIndex = Index; _LoadNewVideoIndex(); RequestSerialization(); } } private void _LoadNewVideoIndex() { _OldVideoIndex = _VideoIndex; //_VideoLoadAttemptCounter = 0; //_UseFallback = false; _LoadVideo_Private(); } private void _LoadVideo_Private() { TryLoadURL(); } public void TryLoadURL() { if (_VideoIndex >= 0 && _VideoIndex < _CaseVideoList.Length) { _VideoPlayer.LoadURL(_CaseVideoList[_VideoIndex]); SetVideoLoadStatus(IndicationStatus.Loading); } } private void _PlayVideo_Private() { _VideoPlayer.Play(); SetVideoLoadStatus(IndicationStatus.Playing); } private void _StopVideo_Private() { _VideoPlayer.Stop(); //_UseFallback = false; _VideoIndex = -1; SetVideoLoadStatus(IndicationStatus.Idle); } public override void OnVideoReady() { if (_VideoIsPlaying) { _PlayVideo_Private(); } else { SetVideoLoadStatus(IndicationStatus.LoadSuccess); } base.OnVideoReady(); } public override void OnVideoError(VideoError VideoError) { switch(VideoError) { case VideoError.Unknown: Debug.LogError("[CaseVideoSyncPlayer] Unknown playback error."); break; case VideoError.InvalidURL: Debug.LogError("[CaseVideoSyncPlayer] Invalid URL."); break; case VideoError.AccessDenied: Debug.LogError("[CaseVideoSyncPlayer] Access denied."); break; case VideoError.PlayerError: Debug.LogError("[CaseVideoSyncPlayer] Error with video player."); break; case VideoError.RateLimited: Debug.LogError("[CaseVideoSyncPlayer] Rate limited. Attempting another reload in 2 seconds..."); SendCustomEventDelayedSeconds(nameof(TryLoadURL), 2.1f); return; } //if (_UseFallback) //{ _StopVideo_Private(); //} //else //{ // Debug.Log("[CaseVideoSyncPlayer] Attempting fallback in 5 seconds..."); // SendCustomEventDelayedSeconds(nameof(TryLoadFallbackURL), 5.5f); //} SetVideoLoadStatus(IndicationStatus.LoadFailure); base.OnVideoError(VideoError); } //public void TryLoadFallbackURL() //{ // _UseFallback = true; // TryLoadURL(); //} private void SetPlaybackStatus(bool Value) { if (_VideoIsPlaying != Value) { _VideoIsPlaying = Value; if (_VideoIsPlaying) _PlayVideo_Private(); else _StopVideo_Private(); RequestSerialization(); } } public override void OnVideoStart() { if (_VideoIsPlaying) { ShowScreen = ClueScreenType.Video; } else { _VideoPlayer.Stop(); } base.OnVideoStart(); } public override void OnVideoEnd() { _SwapToScreen(ClueScreenType.Blank, false); base.OnVideoEnd(); } public float GetVideoDuration() { if (_VideoPlayer.IsReady || _VideoPlayer.IsPlaying) { return _VideoPlayer.GetDuration(); } return 0.0f; } public float GetVideoTime() { if (_VideoPlayer.IsPlaying) { return _VideoPlayer.GetTime(); } return 0.0f; } public void SetVideoLoadStatus(IndicationStatus Status) { foreach (VideoLoadIndicator Indicator in _VideoLoadIndicators) { if (Indicator.GetOwner() == Networking.LocalPlayer.displayName) { Indicator.IndicateStatus = Status; } } } [NetworkCallable] public void ClearScreen() { FlashCorrectAnswer = false; ShowScreen = ClueScreenType.Blank; } private void _SwapToScreen(ClueScreenType Screen, bool StopAllVideos = true) { if (ShowScreen == Screen) return; switch (Screen) { case ClueScreenType.Blank: { _BlankScreenMesh.transform.localPosition = new Vector3(0.0f, 0.0f, 0.0f); _VideoScreenMesh.transform.localPosition = new Vector3(0.0f, -1000.0f, 0.0f); _MapScreenMesh.transform.localPosition = new Vector3(0.0f, -1000.0f, 0.0f); if (StopAllVideos) PlayVideo = false; break; } case ClueScreenType.Video: { _BlankScreenMesh.transform.localPosition = new Vector3(0.0f, -1000.0f, 0.0f); _VideoScreenMesh.transform.localPosition = new Vector3(0.0f, 0.0f, 0.0f); _MapScreenMesh.transform.localPosition = new Vector3(0.0f, -1000.0f, 0.0f); break; } case ClueScreenType.Map: { _BlankScreenMesh.transform.localPosition = new Vector3(0.0f, -1000.0f, 0.0f); _VideoScreenMesh.transform.localPosition = new Vector3(0.0f, -1000.0f, 0.0f); _MapScreenMesh.transform.localPosition = new Vector3(0.0f, 0.0f, 0.0f); if (StopAllVideos) PlayVideo = false; break; } } } private bool _IsUrlListDifferent(VRCUrl[] New, VRCUrl[] Cached) { if (New.Length == Cached.Length) { for (int i = 0; i < New.Length; i++) { if (New[i].ToString() != Cached[i].ToString()) { return true; } } } else { return true; } return false; } public int SubMapIndex { set { _SubMapIndex = value; if (_SubMapIndex >= 0 && !FlashCorrectAnswer) { _UpdateMap(); } } get => _SubMapIndex; } public ClueScreenType ShowScreen { set { _SwapToScreen(value); _ShowScreen = value; } get => _ShowScreen; } public bool FlashCorrectAnswer { set { _FlashCorrectAnswer = value; NextCorrectAnswerFrame(); RequestSerialization(); } get => _FlashCorrectAnswer; } public bool PlayVideo { set { SetPlaybackStatus(value); } get => _VideoIsPlaying; } }