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)
{
+1
View File
@@ -7,6 +7,7 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
<EnableWindowsTargeting Condition="$([MSBuild]::IsOSPlatform('windows')) == false">true</EnableWindowsTargeting>
<ApplicationIcon>icon.ico</ApplicationIcon>
<AssemblyName>TV Player</AssemblyName>
</PropertyGroup>
+24 -8
View File
@@ -21,17 +21,33 @@ namespace TV_Player
private async Task GetPrograms(string name,string m3uLink)
{
System.Diagnostics.Debug.WriteLine($"[ProgramsData] Starting download of: {m3uLink}");
//string m3uLink = "http://pl.da-tv.vip/a71e77fa/835b3216/tv.m3u";
var result = await M3UParser.DownloadM3UFromWebAsync(m3uLink);
try
{
var result = await M3UParser.DownloadM3UFromWebAsync(m3uLink);
System.Diagnostics.Debug.WriteLine($"[ProgramsData] Downloaded {result.programList.Count} programs");
programsSubject.OnNext(result.programList);
programsSubject.OnNext(result.programList);
var groupping = result.programList.GroupBy(item => item.GroupTitle)
.Select(group => new GroupInfo() { Name = group.Key, Count = group.Count() })
.ToList();
groupsSubject.OnNext(groupping);
await Task.Run(() => GetProgramGuide(name,result.programGuide));
var groupping = result.programList.GroupBy(item => item.GroupTitle)
.Select(group => new GroupInfo() { Name = group.Key, Count = group.Count() })
.OrderBy(g => g.Name)
.ToList();
System.Diagnostics.Debug.WriteLine($"[ProgramsData] Publishing {groupping.Count} groups: {string.Join(", ", groupping.Select(g => $"{g.Name}({g.Count})"))}");
if (groupping.Count == 0)
{
System.Diagnostics.Debug.WriteLine("[ProgramsData] WARNING: No groups found! Check if programs have 'group-title' metadata.");
}
groupsSubject.OnNext(groupping);
await Task.Run(() => GetProgramGuide(name, result.programGuide));
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[ProgramsData] ERROR downloading programs: {ex.Message}");
throw;
}
}
public Task<ProgramGuide> GetGuideByProgram(string channelID)
+18 -1
View File
@@ -5,9 +5,26 @@ namespace TV_Player.ViewModels
{
public static class SettingsModel
{
private static readonly string AppDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "TVPlayer");
private static readonly string AppDataFolder = Path.Combine(GetWritableAppDataFolder(), "TVPlayer");
private static readonly string SettingsFilePath = Path.Combine(AppDataFolder, "settings.json");
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 Dictionary<string,string> Playlists { get; set; }
public static bool StartFullScreen { get; set; }
public static bool StartFromLastScreen { get; set; }
+246
View File
@@ -0,0 +1,246 @@
# WPF Build Instructions
## Overview
This is a Windows TV Player application built with .NET WPF (Windows Presentation Foundation) targeting net8.0-windows.
## Prerequisites
### System Requirements
- Windows 10 Build 19041 or later (or Windows 11)
- Visual Studio 2022 17.0 or later with:
- .NET desktop development workload
- Windows Forms development tools
- XAML tools
### Development Setup
- .NET 8 SDK or later
- Visual Studio 2022 or JetBrains Rider
## Installation
### Clone and Open Project
```bash
cd "TV Player WPF"
```
### Restore Dependencies
```bash
dotnet restore
```
## Building
### Build for Release
```bash
dotnet build --configuration Release --framework net8.0-windows10.0.19041.0
```
### Build for Debug
```bash
dotnet build --configuration Debug --framework net8.0-windows10.0.19041.0
```
### Build for Distribution
```bash
dotnet publish --configuration Release --framework net8.0-windows10.0.19041.0 -p:PublishProfile=FolderProfile
```
## Running
### Run from Visual Studio
1. Open `TV Player WPF.csproj` in Visual Studio
2. Press F5 to start with debugging
3. Press Ctrl+F5 to start without debugging
### Run from Command Line
```bash
dotnet run --configuration Debug --framework net8.0-windows10.0.19041.0
```
### Run Published Application
```bash
# After publishing
./bin/Release/net8.0-windows10.0.19041.0/publish/TV Player WPF.exe
```
## Project Structure
```
TV Player WPF/
├── ViewModels/ # MVVM ViewModels with INotifyPropertyChanged
│ ├── MainViewModel.cs # Main window logic
│ ├── PlayerViewModel.cs
│ ├── SettingsViewModel.cs
│ └── ...
├── PlaylistWorker/ # M3U and EPG parsing
│ ├── M3UParser.cs
│ └── M3UInfo.cs
├── MainWindow.xaml # Main UI
├── VideoPlayer.xaml # Video player UI
├── Assets/ # Application resources
│ ├── AppStyle.xaml
│ └── Images/
└── TV Player WPF.csproj # Project file
```
## Configuration
### Playlist Settings
The application loads playlists from configured URLs:
- **Default M3U URL**: Configured in TVPlayerViewModel
- **EPG URL**: Extracted from M3U file or set in settings
To change the playlist:
1. Go to Settings in the application
2. Enter the M3U playlist URL
3. Save settings
Settings are persisted in AppData.
### Application Settings
User preferences are stored in:
```
%localappdata%\TV_Player\settings.json
```
## Key Features
### MVVM Architecture
- Proper separation of concerns with ViewModels
- Data binding through INotifyPropertyChanged
- Commands for user interactions
### Error Handling
- Comprehensive exception handling with logging
- User-friendly error messages
- Debug output for troubleshooting
### Resource Management
- Proper disposal of resources
- No memory leaks from subscriptions
- Clean shutdown sequence
### Performance
- Asynchronous network operations
- Lazy-loaded playlist data
- Caching of EPG information
## Keyboard Shortcuts
- **Escape**: Exit application
- **Backspace**: Go back/navigate
- **F11**: Toggle fullscreen
- **Arrow Keys**: Navigate UI
## Registry Settings
The application may create registry entries in:
```
HKEY_CURRENT_USER\Software\TV_Player
```
These include:
- Last played channel
- Window state and position
- User preferences
## Troubleshooting
### Application Won't Start
1. Check .NET 8 is installed: `dotnet --version`
2. Verify Windows version: `winver` (must be 19041+)
3. Check event viewer for errors: `eventvwr.msc`
### Playlist Download Fails
1. Check network connectivity
2. Verify URL is correct
3. Check firewall settings allow outbound HTTP/HTTPS
4. Check Debug output for detailed error
### Video Won't Play
1. Verify stream URL is valid
2. Check network connection to stream server
3. Verify media format is supported
4. Check sufficient bandwidth available
### Performance Issues
1. Check Task Manager for CPU/memory usage
2. Disable hardware acceleration in settings if available
3. Clear EPG cache and reload
4. Close unnecessary background applications
## Development
### Debug Logging
When running in Debug configuration, detailed logs are sent to Output window in Visual Studio.
Enable more detailed logging:
1. Open Debug > Windows > Output
2. Select "Debug" from dropdown
### Code Organization
- **ViewModels**: Business logic and state management
- **Views**: XAML UI definitions
- **PlaylistWorker**: Network and parsing operations
- **Assets**: Application resources and styling
### Common Tasks
#### Add New ViewModel
```csharp
public class MyViewModel : ObservableViewModelBase
{
private string _property;
public string Property
{
get => _property;
set => SetProperty(ref _property, value);
}
}
```
#### Handle Exceptions
```csharp
try
{
await FetchPlaylistAsync();
}
catch (HttpRequestException ex)
{
Debug.WriteLine($"Network error: {ex.Message}");
// Show user-friendly error
}
```
## Publishing
### Create Installer
Use Visual Studio Setup Project or:
```bash
dotnet publish --configuration Release \
--framework net8.0-windows10.0.19041.0 \
--output ./publish
```
Then package with your installer tool (NSIS, WiX, etc.)
### Self-Contained Deployment
```bash
dotnet publish --configuration Release \
--framework net8.0-windows10.0.19041.0 \
--self-contained \
--output ./publish-standalone
```
This creates an executable that doesn't require .NET runtime installed.
## Performance Optimization
- Use **Release** build for distribution
- Enable **ReadyToRun**: `-p:PublishReadyToRun=true`
- Enable **PublishTrimmed**: `-p:PublishTrimmed=true` (advanced)
- Use **PublishAot** for maximum performance (requires additional testing)
## Version History
See CHANGELOG.md for detailed version information.