using System; using System.Collections.Generic; using System.Threading.Tasks; using UnityEngine; namespace MyInfoMate.Vr.Net { /// /// Préchargement des médias d'une visite — la seconde moitié de l'item E4. /// /// Le cache disque seul ne suffit pas. Il se remplissait à la demande : une /// borne branchée dans une salle sans wifi avait son JSON, ses titres, son menu — et /// pas une image. Le contenu n'arrivait que si le visiteur ouvrait la section /// pendant que le réseau était là, ce qui est exactement l'inverse de ce qu'on /// promet. C'est l'argument n°1 qui a fait choisir Unity contre WebXR (§10 du plan) ; /// sans cette classe, il n'était pas tenu. /// /// Trois règles, toutes dictées par l'exploitation : /// /// Après le menu, jamais avant. Le visiteur voit son menu en une /// seconde ; le téléchargement se fait derrière lui. Bloquer le démarrage sur /// des gigaoctets de vidéo 360 ferait d'une borne un écran d'attente. /// Un par un. Dix téléchargements parallèles sur le wifi d'un musée se /// gênent, et une 360 de 2 Go n'a rien à gagner à partager la bande passante /// avec neuf vignettes. /// Un échec n'arrête rien. Chaque média manquant sera retenté au /// prochain démarrage ; les autres sont déjà là. /// /// public static class ContentPreloader { public struct Progress { public int Done; public int Total; public int Failed; /// Médias déjà sur le disque au démarrage — jamais retéléchargés. public int AlreadyCached; } /// /// Télécharge tout ce que le casque saura afficher hors ligne. Rend le bilan, qui /// n'a d'autre usage que le journal : rien de tout ceci ne doit se voir. /// public static async Task RunAsync( ConfigurationExport export, ApiClient client, Action advanced = null) { var urls = MediaUrlsOf(export); var progress = new Progress { Total = urls.Count }; if (urls.Count == 0) return progress; Debug.Log($"[Preload] {urls.Count} médias à mettre en cache."); foreach (var url in urls) { if (ContentCache.CachedPath(url) != null) { progress.AlreadyCached++; progress.Done++; } else if (await ContentCache.MediaPathAsync(client, url) != null) { progress.Done++; } else { // Volontairement silencieux côté visiteur : ContentCache a déjà // journalisé la cause, et un média manquant n'invalide pas la visite. progress.Failed++; } advanced?.Invoke(progress); } Debug.Log($"[Preload] Terminé — {progress.Done}/{progress.Total} disponibles " + $"hors ligne ({progress.AlreadyCached} déjà en cache, " + $"{progress.Failed} en échec), {ContentCache.SizeBytes() / 1048576} Mo sur le disque."); return progress; } /// /// Toutes les URL téléchargeables de la configuration, sans doublon et dans un /// ordre utile : les vignettes du menu d'abord, parce que ce sont elles /// qu'on voit en premier, et les gros médias ensuite. /// /// ⚠️ Les types ImageUrl / VideoUrl sont écartés : ce sont des /// liens externes (YouTube, Vimeo), qui ne se mettent pas en cache et ne /// s'affichent pas hors ligne. Les PDF et les JSON aussi — le casque ne les rend /// pas (§8 du plan), les télécharger ne ferait que remplir le disque. /// public static List MediaUrlsOf(ConfigurationExport export) { var urls = new List(); var seen = new HashSet(); void Add(string url) { if (string.IsNullOrEmpty(url)) return; if (!url.StartsWith("http", StringComparison.OrdinalIgnoreCase)) return; if (seen.Add(url)) urls.Add(url); } if (export?.Sections != null) foreach (var section in export.Sections) { Add(section.ImageSource); Add(section.Model3DSource); if (section.Points != null) foreach (var point in section.Points) Add(point.ImageUrl); } if (export?.Resources != null) foreach (var resource in export.Resources) if (IsDownloadable(resource.Type)) Add(resource.Url); return urls; } static bool IsDownloadable(ConfigurationExport.ResourceKind kind) { switch (kind) { case ConfigurationExport.ResourceKind.Image: case ConfigurationExport.ResourceKind.Video: case ConfigurationExport.ResourceKind.Audio: case ConfigurationExport.ResourceKind.Image360: case ConfigurationExport.ResourceKind.Video360: case ConfigurationExport.ResourceKind.Model3D: return true; default: return false; } } } }