From 24ca481b649d825cd5e46efff8baeb5b78584c8c Mon Sep 17 00:00:00 2001 From: Vladimir Date: Fri, 10 Apr 2026 12:48:12 +0300 Subject: [PATCH] Support local file paths for M3U playlists; UI tweaks Added support for loading M3U playlists from both web URLs and local file paths, with logic to resolve relative paths. Improved exception handling in M3UParser. Enabled toggling of window border style in fullscreen mode, with data binding for WindowStyle. Removed IsValidUrl check to allow local file sources in settings. Cleaned up usings and formatting. Removed .NET SDK version from global.json. --- TV Player Core/M3UParser.cs | 6 ++++ TV Player Core/ProgramsData.cs | 35 +++++++++++++++++-- TV Player WPF/MainWindow.xaml | 2 +- TV Player WPF/ViewModels/MainViewModel.cs | 12 +++++++ .../ViewModels/ProgramsGroupViewModel.cs | 3 +- TV Player WPF/ViewModels/SettingsViewModel.cs | 10 ++---- global.json | 7 +--- 7 files changed, 57 insertions(+), 18 deletions(-) diff --git a/TV Player Core/M3UParser.cs b/TV Player Core/M3UParser.cs index 808a4fa..90602dc 100644 --- a/TV Player Core/M3UParser.cs +++ b/TV Player Core/M3UParser.cs @@ -221,6 +221,7 @@ namespace TV_Player { try { + using var client = new HttpClient(); using var request = new HttpRequestMessage(); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/text")); @@ -240,6 +241,11 @@ namespace TV_Player System.Diagnostics.Debug.WriteLine($"Invalid URL: {url} - {ex.Message}"); throw; } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Error reading file from {url}: {ex.Message}"); + throw; + } } public static async Task<(List programList, string programGuide)> DownloadM3UFromWebAsync(string url) diff --git a/TV Player Core/ProgramsData.cs b/TV Player Core/ProgramsData.cs index b4c598a..bb70979 100644 --- a/TV Player Core/ProgramsData.cs +++ b/TV Player Core/ProgramsData.cs @@ -1,5 +1,10 @@ -using System.Reactive; +using System; +using System.IO; +using System.Linq; +using System.Reactive; using System.Reactive.Subjects; +using System.Threading.Tasks; +using System.Collections.Generic; namespace TV_Player { @@ -25,7 +30,33 @@ namespace TV_Player //string m3uLink = "http://pl.da-tv.vip/a71e77fa/835b3216/tv.m3u"; try { - var result = await M3UParser.DownloadM3UFromWebAsync(m3uLink); + // if link is a well-formed absolute URI load from web, otherwise treat as a local file path + // for relative paths try resolving against the application's base directory + var result = (programList: new List(), programGuide: string.Empty); + if (Uri.IsWellFormedUriString(m3uLink, UriKind.Absolute)) + { + System.Diagnostics.Debug.WriteLine("[ProgramsData] Detected URL, downloading from web"); + result = await M3UParser.DownloadM3UFromWebAsync(m3uLink); + } + else + { + // try as provided path first + var candidate = m3uLink; + if (!Path.IsPathRooted(candidate)) + { + candidate = Path.Combine(AppContext.BaseDirectory ?? string.Empty, candidate); + } + + if (File.Exists(candidate)) + { + System.Diagnostics.Debug.WriteLine($"[ProgramsData] Detected local file, loading from disk: {candidate}"); + result = await M3UParser.DownloadM3UFromWebAsync(candidate); + } + else + { + throw new FileNotFoundException($"M3U file not found: {m3uLink}"); + } + } System.Diagnostics.Debug.WriteLine($"[ProgramsData] Downloaded {result.programList.Count} programs"); programsSubject.OnNext(result.programList); diff --git a/TV Player WPF/MainWindow.xaml b/TV Player WPF/MainWindow.xaml index bba0fc8..1145023 100644 --- a/TV Player WPF/MainWindow.xaml +++ b/TV Player WPF/MainWindow.xaml @@ -4,7 +4,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:TV_Player" - mc:Ignorable="d" WindowStyle="None" WindowState="{Binding CurrentWindowState}" + mc:Ignorable="d" WindowStyle="{Binding CurrentWindowStyle}" WindowState="{Binding CurrentWindowState}" Title="TV" Height="450" Width="800" KeyDown="UserControl_KeyDown"> diff --git a/TV Player WPF/ViewModels/MainViewModel.cs b/TV Player WPF/ViewModels/MainViewModel.cs index 1e2537d..004e226 100644 --- a/TV Player WPF/ViewModels/MainViewModel.cs +++ b/TV Player WPF/ViewModels/MainViewModel.cs @@ -36,6 +36,14 @@ namespace TV_Player get => _currentWindowState; set => SetProperty(ref _currentWindowState, value); } + + private WindowStyle _currentWindowStyle; + public WindowStyle CurrentWindowStyle + { + get => _currentWindowStyle; + set => SetProperty(ref _currentWindowStyle, value); + } + public ICommand OnKeyDownCommand { get; } public ICommand FullscreenCommand { get; } @@ -53,6 +61,7 @@ namespace TV_Player FullscreenCommand = new RelayCommand(OnFullSctreenButtonClick); SettingsCommand = new RelayCommand(OnSettingsButtonClick); CloseAppCommand = new RelayCommand(OnCloseAppButtonClick); + CurrentWindowStyle = WindowStyle.SingleBorderWindow; } public void OnFullSctreenButtonClick() @@ -60,10 +69,13 @@ namespace TV_Player if (CurrentWindowState == WindowState.Normal) { CurrentWindowState = WindowState.Maximized; + CurrentWindowStyle = WindowStyle.None; + } else { CurrentWindowState = WindowState.Normal; + CurrentWindowStyle = WindowStyle.SingleBorderWindow; } } diff --git a/TV Player WPF/ViewModels/ProgramsGroupViewModel.cs b/TV Player WPF/ViewModels/ProgramsGroupViewModel.cs index 59e5e8f..bc29878 100644 --- a/TV Player WPF/ViewModels/ProgramsGroupViewModel.cs +++ b/TV Player WPF/ViewModels/ProgramsGroupViewModel.cs @@ -21,7 +21,8 @@ namespace TV_Player public ProgramsGroupViewModel() { ItemSelectedCommand = new RelayCommand(OnItemSelected); - _groupInformationSubscriber = TVPlayerViewModel.Instance.CurrentProgrmsData.GroupsInformation.Subscribe(x => Programs = SettingsModel.HiddenGroups == null ? x : x.Where(g => !SettingsModel.HiddenGroups.Contains(g.Name.ToLower())).ToList()); + _groupInformationSubscriber = TVPlayerViewModel.Instance.CurrentProgrmsData.GroupsInformation.Subscribe(x => + Programs = SettingsModel.HiddenGroups == null ? x : x.Where(g => !SettingsModel.HiddenGroups.Contains(g.Name.ToLower())).ToList()); TVPlayerViewModel.Instance.TopPanelVisible(true, "Groups"); diff --git a/TV Player WPF/ViewModels/SettingsViewModel.cs b/TV Player WPF/ViewModels/SettingsViewModel.cs index e3e3aee..cb60787 100644 --- a/TV Player WPF/ViewModels/SettingsViewModel.cs +++ b/TV Player WPF/ViewModels/SettingsViewModel.cs @@ -67,16 +67,10 @@ namespace TV_Player.ViewModels var name = PlaylistName?.Trim(); if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(url)) return; - if (IsValidUrl(url)) - Playlists.Add(new KeyValuePair(name, url)); + Playlists.Add(new KeyValuePair(name, url)); } - private bool IsValidUrl(string url) - { - Uri uriResult; - return Uri.TryCreate(url, UriKind.Absolute, out uriResult) - && (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps); - } + private void OnPlaylistDeleteCommand(KeyValuePair pair) { diff --git a/global.json b/global.json index ed75c1b..8b13789 100644 --- a/global.json +++ b/global.json @@ -1,6 +1 @@ -{ - "sdk": { - "version": "8.0.417", - "rollForward": "latestPatch" - } -} +