using System.Collections.ObjectModel; using System.IO; using System.Text.Json; using System.Text.Json.Serialization; namespace VideoDownloader.App.Browser.Features; /// 재생목록 트랙. Kind: Direct/Hls/Dash/Social. public sealed record MediaTrack( string Url, string Title, string Kind, DateTime AddedAt, bool Played = false, string? Referer = null, string? LocalFile = null); /// /// T2-10 미디어 재생목록(Brave Playlist 패턴): 브라우저에서 감지된 미디어를 /// 쌓아두고 앱 내에서 연속 재생. JSON 영속(playlist.json). /// public sealed class MediaPlaylist { public ObservableCollection Tracks { get; } = new(); public int CurrentIndex { get; set; } = -1; [JsonIgnore] public MediaTrack? Current => CurrentIndex >= 0 && CurrentIndex < Tracks.Count ? Tracks[CurrentIndex] : null; public event Action? Changed; /// 추가(중복 URL 무시). 새 항목이 첫 트랙이면 자동 선택. public void Add(MediaTrack track) { if (Tracks.Any(t => t.Url == track.Url)) return; Tracks.Add(track); if (CurrentIndex < 0) CurrentIndex = Tracks.Count - 1; Changed?.Invoke(); } public void Remove(MediaTrack track) { var idx = Tracks.IndexOf(track); if (idx < 0) return; Tracks.RemoveAt(idx); if (CurrentIndex >= Tracks.Count) CurrentIndex = Tracks.Count - 1; Changed?.Invoke(); } public void Clear() { Tracks.Clear(); CurrentIndex = -1; Changed?.Invoke(); } /// 다음 트랙 인덱스(순환). 반환 후 CurrentIndex 갱신. public int Next() { if (Tracks.Count == 0) return -1; CurrentIndex = (CurrentIndex + 1) % Tracks.Count; return CurrentIndex; } /// 이전 트랙 인덱스(순환). public int Previous() { if (Tracks.Count == 0) return -1; CurrentIndex = (CurrentIndex - 1 + Tracks.Count) % Tracks.Count; return CurrentIndex; } public void MarkPlayed(MediaTrack track) { var idx = Tracks.IndexOf(track); if (idx < 0) return; Tracks[idx] = track with { Played = true }; Changed?.Invoke(); } public void Save(string path) { try { Directory.CreateDirectory(Path.GetDirectoryName(path)!); var dto = new { tracks = Tracks.ToList(), currentIndex = CurrentIndex }; var tmp = path + ".tmp"; File.WriteAllText(tmp, JsonSerializer.Serialize(dto)); File.Move(tmp, path, overwrite: true); } catch { } } public void Load(string path) { try { if (!File.Exists(path)) return; using var doc = JsonDocument.Parse(File.ReadAllText(path)); var root = doc.RootElement; Tracks.Clear(); if (root.TryGetProperty("tracks", out var arr)) foreach (var t in arr.EnumerateArray()) { var track = t.Deserialize(); if (track != null) Tracks.Add(track); } CurrentIndex = root.TryGetProperty("currentIndex", out var ci) ? ci.GetInt32() : -1; if (CurrentIndex >= Tracks.Count) CurrentIndex = Tracks.Count - 1; } catch { Tracks.Clear(); CurrentIndex = -1; } } }