feat: Implement Playlists and Programs Management

- Added PlaylistsGroupViewModel to manage playlists and selection.
- Introduced ProgramsGroupViewModel for handling program groups and subscriptions.
- Created ProgramsListViewModel to manage individual program listings.
- Developed SettingsViewModel for user settings including playlist management.
- Implemented TVPlayerViewModel as the main view model coordinating screens and data.
- Added PlayerView for video playback with LibVLC integration.
- Created XAML views for PlaylistsGroup, ProgramsGroup, ProgramsList, and Settings.
- Added sample M3U playlist for testing.
- Documented WPF build instructions and project structure in WPF-BUILD.md.
- Configured global.json for .NET SDK versioning.
This commit is contained in:
Vladimir
2026-03-22 12:11:24 +02:00
parent a6ec011e79
commit 1e8e444376
82 changed files with 2970 additions and 1687 deletions
+26 -3
View File
@@ -30,11 +30,27 @@ namespace TV_Player
}
public static class M3UParser
{
private static string GetWritableAppDataFolder()
{
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
if (!string.IsNullOrWhiteSpace(localAppData))
{
return localAppData;
}
var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
if (!string.IsNullOrWhiteSpace(appData))
{
return appData;
}
return AppContext.BaseDirectory;
}
public static async Task DownloadGuideFromWebAsync(string name, string url)
{
var fileName = name + "_guide.xml";
string programDataPath = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
string programDataPath = GetWritableAppDataFolder();
string filePath = Path.Combine(programDataPath, "TVPlayer", fileName);
@@ -133,7 +149,7 @@ namespace TV_Player
ProgramGuide channel = null;
var fileName = groupName + "_guide.xml";
string programDataPath = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
string programDataPath = GetWritableAppDataFolder();
string filePath = Path.Combine(programDataPath, "TVPlayer", fileName);
if (!File.Exists(filePath))
@@ -268,10 +284,11 @@ namespace TV_Player
{
if (string.IsNullOrWhiteSpace(content))
{
System.Diagnostics.Debug.WriteLine("M3U content is empty");
System.Diagnostics.Debug.WriteLine("[M3UParser] M3U content is empty");
return (playlistItems, programGuideLink);
}
System.Diagnostics.Debug.WriteLine($"[M3UParser] Starting parse of M3U content ({content.Length} bytes)");
var m3u = SplitStringBeforeSeparator(content, "#EXT");
foreach (var line in m3u)
@@ -281,7 +298,10 @@ namespace TV_Player
if (TryParseM3ULine(line, out var m3uInfo))
{
if (!string.IsNullOrEmpty(m3uInfo?.Url))
{
playlistItems.Add(m3uInfo);
System.Diagnostics.Debug.WriteLine($"[M3UParser] Parsed: {m3uInfo.Name} -> group='{m3uInfo.GroupTitle}'");
}
}
}
if (line.StartsWith("#EXTM3U"))
@@ -289,6 +309,9 @@ namespace TV_Player
programGuideLink = ExtractXtvgUrl(line);
}
}
var groupSummary = playlistItems.GroupBy(p => p.GroupTitle).Select(g => $"{g.Key}({g.Count()})").ToList();
System.Diagnostics.Debug.WriteLine($"[M3UParser] Parse complete: {playlistItems.Count} programs in {groupSummary.Count} groups: {string.Join(", ", groupSummary)}");
}
catch (ArgumentException ex)
{