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:
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user