using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using UnityEngine.Video; #if UNITY_EDITOR using UnityEditor; using UnityEditor.SceneManagement; #endif /// /// Throttled preload: uses queues and concurrency limits to load Resources in small time slices /// to avoid long frames on low-end devices. /// The opening presentation now uses two videos: vj01 plays once, then vj02 loops. /// public class preload_allScene : MonoBehaviour { [Header("Preload settings")] public string[] resourcesToPreload = new string[0]; [Header("UI references")] public Text progressText; [Tooltip("Optional Image whose fillAmount will be driven by the overall progress (0..1)")] public Image progressFillImage; [Tooltip("Optional Image to disable after 1 second")] public Image startDisableImage; [Tooltip("Documentation text normalized.")] public Text loadingInfoText; [Header("Opening Video")] public RawImage openingVideoRawImage; public VideoClip vj01Clip; public VideoClip vj02Clip; [Header("Behavior")] public bool startOnAwake = true; [Header("Throttle")] public int maxConcurrentResourceLoads = 2; [Tooltip("Max milliseconds spent per frame on driver work (approx)")] public int maxMilliSecondsPerFrame = 4; private readonly Queue resourceQueue = new Queue(); private readonly List<(string path, ResourceRequest rr)> activeResourceReqs = new List<(string, ResourceRequest)>(); private readonly List loadedResources = new List(); private Coroutine preloadCoroutine; private VideoPlayer videoPlayer1; private VideoPlayer videoPlayer2A; private VideoPlayer videoPlayer2B; private RenderTexture videoTexture1; private RenderTexture videoTexture2A; private RenderTexture videoTexture2B; private VideoPlayer activeLoopPlayer; private VideoPlayer standbyLoopPlayer; private bool waitingForLoopStartup; private Coroutine openingVideoStartupCoroutine; private void Awake() { Debug.Log("[preload_allScene] Awake called. Forcing Time.timeScale = 1."); Time.timeScale = 1f; EnsureOpeningVideoSetup(); SetVideoDisplayVisible(openingVideoRawImage, false); if (progressText == null) { progressText = GetComponentInChildren(true); } if (loadingInfoText == null) { var texts = GetComponentsInChildren(true); foreach (var t in texts) { if (t != progressText) { loadingInfoText = t; break; } } } } private void Start() { Debug.Log("[preload_allScene] Start called. resourcesToPreload count: " + (resourcesToPreload?.Length ?? 0)); StartOpeningVideoSequence(); if (startDisableImage != null) { StartCoroutine(DelayedDisableImage()); } StartPreload(); } private void EnsureOpeningVideoSetup() { TryAutoAssignVideoClips(); if (videoPlayer1 == null && openingVideoRawImage != null) { videoPlayer1 = CreateVideoPlayer("OpeningVideoPlayer_VJ01", vj01Clip, false, ref videoTexture1); if (videoPlayer1 != null) { videoPlayer1.loopPointReached += OnVj01Finished; } } if (videoPlayer2A == null && openingVideoRawImage != null) { videoPlayer2A = CreateVideoPlayer("OpeningVideoPlayer_VJ02_A", vj02Clip, false, ref videoTexture2A); if (videoPlayer2A != null) { videoPlayer2A.prepareCompleted += OnLoopPlayerPrepared; videoPlayer2A.loopPointReached += OnLoopVideoReachedEnd; } } if (videoPlayer2B == null && openingVideoRawImage != null) { videoPlayer2B = CreateVideoPlayer("OpeningVideoPlayer_VJ02_B", vj02Clip, false, ref videoTexture2B); if (videoPlayer2B != null) { videoPlayer2B.prepareCompleted += OnLoopPlayerPrepared; videoPlayer2B.loopPointReached += OnLoopVideoReachedEnd; } } } private void StartOpeningVideoSequence() { EnsureOpeningVideoSetup(); if (openingVideoStartupCoroutine != null) { StopCoroutine(openingVideoStartupCoroutine); } openingVideoStartupCoroutine = StartCoroutine(PrepareOpeningVideosAndStart()); } private IEnumerator PrepareOpeningVideosAndStart() { SetVideoDisplayVisible(openingVideoRawImage, false); if (videoPlayer1 != null && !videoPlayer1.isPrepared) { videoPlayer1.Prepare(); } if (videoPlayer2A != null && !videoPlayer2A.isPrepared) { videoPlayer2A.Prepare(); } if (videoPlayer2B != null && !videoPlayer2B.isPrepared) { videoPlayer2B.Prepare(); } while (!AreOpeningVideosPrepared()) { yield return null; } openingVideoStartupCoroutine = null; if (videoPlayer1 != null) { if (openingVideoRawImage != null) { openingVideoRawImage.texture = videoTexture1; } SetVideoDisplayVisible(openingVideoRawImage, true); ResetPlayerToStart(videoPlayer1); videoPlayer1.Play(); yield break; } StartLoopVideo2(); } private bool AreOpeningVideosPrepared() { if (videoPlayer1 != null && !videoPlayer1.isPrepared) { return false; } if (videoPlayer2A != null && !videoPlayer2A.isPrepared) { return false; } if (videoPlayer2B != null && !videoPlayer2B.isPrepared) { return false; } return true; } private IEnumerator DelayedDisableImage() { yield return new WaitForSeconds(1f); if (startDisableImage != null) { startDisableImage.gameObject.SetActive(false); Debug.Log("[preload_allScene] startDisableImage has been disabled after 1 second."); } } private VideoPlayer CreateVideoPlayer(string objectName, VideoClip clip, bool loop, ref RenderTexture renderTexture) { if (clip == null || openingVideoRawImage == null) { return null; } Transform existing = transform.Find(objectName); GameObject playerObject = existing != null ? existing.gameObject : new GameObject(objectName); playerObject.transform.SetParent(transform, false); var player = playerObject.GetComponent(); if (player == null) { player = playerObject.AddComponent(); } bool mustStartFromFirstFrame = clip == vj01Clip; player.playOnAwake = false; player.isLooping = loop; player.waitForFirstFrame = false; player.skipOnDrop = !mustStartFromFirstFrame; player.timeUpdateMode = VideoTimeUpdateMode.UnscaledGameTime; player.playbackSpeed = 1f; player.audioOutputMode = VideoAudioOutputMode.None; player.source = VideoSource.VideoClip; player.clip = clip; player.renderMode = VideoRenderMode.RenderTexture; int width = clip.width > 0 ? (int)clip.width : 1920; int height = clip.height > 0 ? (int)clip.height : 1080; EnsureRenderTexture(ref renderTexture, width, height, objectName + "_RT"); player.targetTexture = renderTexture; return player; } private static void SetVideoDisplayVisible(RawImage image, bool visible) { if (image == null) { return; } image.gameObject.SetActive(visible); } private static void ResetPlayerToStart(VideoPlayer player) { if (player == null) { return; } try { player.playbackSpeed = 1f; } catch { } try { player.Stop(); } catch { } try { player.frame = 0; } catch { } try { player.time = 0d; } catch { } } private void EnsureRenderTexture(ref RenderTexture renderTexture, int width, int height, string textureName) { if (renderTexture != null && renderTexture.width == width && renderTexture.height == height) { return; } ReleaseRenderTexture(ref renderTexture); renderTexture = new RenderTexture(width, height, 0, RenderTextureFormat.ARGB32); renderTexture.name = textureName; renderTexture.Create(); } private void ReleaseRenderTexture(ref RenderTexture renderTexture) { if (renderTexture == null) { return; } if (renderTexture.IsCreated()) { renderTexture.Release(); } Destroy(renderTexture); renderTexture = null; } private void OnVj01Finished(VideoPlayer source) { if (videoPlayer1 != null) { videoPlayer1.Stop(); } StartLoopVideo2(); } private void StartLoopVideo2() { if (videoPlayer2A == null && videoPlayer2B == null) { return; } SetVideoDisplayVisible(openingVideoRawImage, true); VideoPlayer firstPrepared = GetPreparedLoopPlayer(); if (firstPrepared != null) { StartPreparedLoopPlayer(firstPrepared); return; } waitingForLoopStartup = true; if (videoPlayer2A != null && !videoPlayer2A.isPrepared) { videoPlayer2A.Prepare(); } if (videoPlayer2B != null && !videoPlayer2B.isPrepared) { videoPlayer2B.Prepare(); } } private VideoPlayer GetPreparedLoopPlayer() { if (videoPlayer2A != null && videoPlayer2A.isPrepared) { return videoPlayer2A; } if (videoPlayer2B != null && videoPlayer2B.isPrepared) { return videoPlayer2B; } return null; } private void StartPreparedLoopPlayer(VideoPlayer player) { if (player == null) { return; } activeLoopPlayer = player; standbyLoopPlayer = player == videoPlayer2A ? videoPlayer2B : videoPlayer2A; waitingForLoopStartup = false; if (openingVideoRawImage != null) { openingVideoRawImage.texture = player.targetTexture; } SetVideoDisplayVisible(openingVideoRawImage, true); if (!activeLoopPlayer.isPlaying) { activeLoopPlayer.Play(); } if (standbyLoopPlayer != null && !standbyLoopPlayer.isPrepared) { standbyLoopPlayer.Prepare(); } } private void OnLoopPlayerPrepared(VideoPlayer source) { if (waitingForLoopStartup) { StartPreparedLoopPlayer(source); } } private void OnApplicationPause(bool pauseStatus) { if (!pauseStatus) { ResumeOpeningVideosIfNeeded(); } } private void OnApplicationFocus(bool hasFocus) { if (hasFocus) { ResumeOpeningVideosIfNeeded(); } } private void ResumeOpeningVideosIfNeeded() { if (openingVideoRawImage == null || !openingVideoRawImage.gameObject.activeInHierarchy) { return; } if (videoPlayer1 != null && openingVideoRawImage.texture == videoTexture1 && !videoPlayer1.isPlaying) { videoPlayer1.Play(); return; } if (activeLoopPlayer != null && openingVideoRawImage.texture == activeLoopPlayer.targetTexture && !activeLoopPlayer.isPlaying) { activeLoopPlayer.Play(); return; } VideoPlayer prepared = GetPreparedLoopPlayer(); if (prepared != null && !prepared.isPlaying) { StartPreparedLoopPlayer(prepared); } } private void OnLoopVideoReachedEnd(VideoPlayer source) { if (source != activeLoopPlayer) { return; } if (standbyLoopPlayer != null && standbyLoopPlayer.isPrepared) { VideoPlayer finishedPlayer = activeLoopPlayer; VideoPlayer nextPlayer = standbyLoopPlayer; if (openingVideoRawImage != null) { openingVideoRawImage.texture = nextPlayer.targetTexture; } if (!nextPlayer.isPlaying) { nextPlayer.Play(); } activeLoopPlayer = nextPlayer; standbyLoopPlayer = finishedPlayer; if (standbyLoopPlayer.isPlaying) { standbyLoopPlayer.Stop(); } standbyLoopPlayer.Prepare(); return; } source.Play(); } public void StartPreload() { if (preloadCoroutine != null) { return; } resourceQueue.Clear(); if (resourcesToPreload != null) { foreach (var r in resourcesToPreload) { if (!string.IsNullOrWhiteSpace(r)) { resourceQueue.Enqueue(r); } } } Debug.Log($"Preload starting. Resources queued: {resourceQueue.Count}"); UpdateProgressText(0f); UpdateLoadingInfo(string.Empty); if (resourceQueue.Count == 0) { UpdateProgressText(1f); UpdateLoadingInfo("Loading Complete"); return; } preloadCoroutine = StartCoroutine(PreloadRoutine()); } public void StopPreload() { if (preloadCoroutine != null) { StopCoroutine(preloadCoroutine); preloadCoroutine = null; } } private IEnumerator PreloadRoutine() { while (resourceQueue.Count > 0 || activeResourceReqs.Count > 0) { float frameStart = Time.realtimeSinceStartup; while (activeResourceReqs.Count < maxConcurrentResourceLoads && resourceQueue.Count > 0) { string path = resourceQueue.Dequeue(); UpdateLoadingInfo($"Loading: {path}"); ResourceRequest rr = null; try { rr = Resources.LoadAsync(path); } catch (System.Exception ex) { Debug.LogWarning($"Preload: failed to start loading resource '{path}': {ex}"); } if (rr != null) { activeResourceReqs.Add((path, rr)); Debug.Log($"Preload: started resource load '{path}'"); } if ((Time.realtimeSinceStartup - frameStart) * 1000f > maxMilliSecondsPerFrame) { break; } } for (int i = activeResourceReqs.Count - 1; i >= 0; i--) { var tuple = activeResourceReqs[i]; var rr = tuple.rr; if (rr.isDone) { if (rr.asset != null) { loadedResources.Add(rr.asset); } Debug.Log($"Preload: resource loaded '{tuple.path}'"); activeResourceReqs.RemoveAt(i); } if ((Time.realtimeSinceStartup - frameStart) * 1000f > maxMilliSecondsPerFrame) { break; } } float overall = CalculateOverallProgress(); UpdateProgressText(overall); bool anyActiveResources = activeResourceReqs.Count > 0 || resourceQueue.Count > 0; if (!anyActiveResources) { UpdateProgressText(1f); UpdateLoadingInfo("Loading Complete"); preloadCoroutine = null; Debug.Log("Preload complete (throttled). Resources cached: " + loadedResources.Count); yield break; } yield return null; } UpdateProgressText(1f); UpdateLoadingInfo("Loading Complete"); preloadCoroutine = null; } private float CalculateOverallProgress() { float sum = 0f; int items = 0; items += resourceQueue.Count + activeResourceReqs.Count; if (items == 0) { return 1f; } foreach (var (_, rr) in activeResourceReqs) { sum += rr.progress; } return Mathf.Clamp01(sum / items); } private void UpdateProgressText(float t) { int percent = Mathf.RoundToInt(t * 100f); if (progressFillImage != null) { progressFillImage.fillAmount = Mathf.Clamp01(t); } if (progressText != null) { progressText.text = percent + "%"; return; } var textComponent = GetComponentInChildren(true); if (textComponent != null) { progressText = textComponent; progressText.text = percent + "%"; } } private void UpdateLoadingInfo(string message) { if (loadingInfoText != null) { loadingInfoText.text = message; return; } var texts = GetComponentsInChildren(true); foreach (var t in texts) { if (t != progressText) { loadingInfoText = t; loadingInfoText.text = message; return; } } } private void OnDestroy() { if (openingVideoStartupCoroutine != null) { StopCoroutine(openingVideoStartupCoroutine); openingVideoStartupCoroutine = null; } if (videoPlayer1 != null) { videoPlayer1.loopPointReached -= OnVj01Finished; } if (videoPlayer2A != null) { videoPlayer2A.prepareCompleted -= OnLoopPlayerPrepared; videoPlayer2A.loopPointReached -= OnLoopVideoReachedEnd; } if (videoPlayer2B != null) { videoPlayer2B.prepareCompleted -= OnLoopPlayerPrepared; videoPlayer2B.loopPointReached -= OnLoopVideoReachedEnd; } ReleaseRenderTexture(ref videoTexture1); ReleaseRenderTexture(ref videoTexture2A); ReleaseRenderTexture(ref videoTexture2B); } #if UNITY_EDITOR private void OnValidate() { TryAutoAssignVideoClips(); } private void TryAutoAssignVideoClips() { bool changed = false; if (vj01Clip == null) { vj01Clip = AssetDatabase.LoadAssetAtPath("Assets/op/opVideo/vj01.mp4"); changed |= vj01Clip != null; } if (vj02Clip == null) { vj02Clip = AssetDatabase.LoadAssetAtPath("Assets/op/opVideo/vj02.mp4"); changed |= vj02Clip != null; } if (changed) { EditorUtility.SetDirty(this); if (gameObject.scene.IsValid()) { EditorSceneManager.MarkSceneDirty(gameObject.scene); } } } #else private void TryAutoAssignVideoClips() { } #endif }