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:
@@ -0,0 +1,72 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vlc="clr-namespace:LibVLCSharp.Avalonia;assembly=LibVLCSharp.Avalonia"
|
||||
x:Class="TV_Player.AvaloniaApp.Views.PlayerView">
|
||||
<Grid RowDefinitions="*,Auto" ColumnDefinitions="280,*">
|
||||
<Border Grid.RowSpan="2"
|
||||
Background="#AA111827"
|
||||
Padding="16"
|
||||
CornerRadius="16"
|
||||
IsVisible="{Binding ProgramGuideVisible}">
|
||||
<StackPanel Spacing="10">
|
||||
<TextBlock Text="Upcoming" FontSize="22" FontWeight="SemiBold" />
|
||||
<ItemsControl ItemsSource="{Binding Programs}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Padding="8" Background="#1F2937" CornerRadius="10" Margin="0,0,0,8">
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Text="{Binding Title}" TextWrapping="Wrap" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock Text="{Binding StartTime, StringFormat='{}{0:HH:mm}'}" />
|
||||
<TextBlock Text="-" />
|
||||
<TextBlock Text="{Binding EndTime, StringFormat='{}{0:HH:mm}'}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Grid.Column="1" Background="#0F172A" CornerRadius="20" Padding="20" Margin="16,0,0,16">
|
||||
<Grid RowDefinitions="Auto,*,Auto">
|
||||
<TextBlock Text="{Binding TopPanelTitle}" FontSize="30" FontWeight="Bold" TextAlignment="Center" Margin="0,0,0,16" />
|
||||
<Grid Grid.Row="1">
|
||||
<vlc:VideoView x:Name="VideoView" />
|
||||
<Border Background="#66000000" Padding="14" CornerRadius="12"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="20"
|
||||
IsVisible="{Binding HasPlaybackStatus}">
|
||||
<StackPanel Spacing="8" MaxWidth="720">
|
||||
<TextBlock Text="{Binding PlaybackStatus}" TextWrapping="Wrap" TextAlignment="Center" />
|
||||
<SelectableTextBlock Text="{Binding StreamUrl}" TextWrapping="Wrap" IsVisible="False" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center" Spacing="12">
|
||||
<Button Content="Previous" Command="{Binding PreviousCommand}" />
|
||||
<Button Content="Open Externally" Command="{Binding OpenStreamCommand}" />
|
||||
<Button Content="Next" Command="{Binding NextCommand}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border Grid.Row="1" Grid.Column="1" Background="#AA111827" Padding="16" CornerRadius="16">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto,Auto,Auto" VerticalAlignment="Center">
|
||||
<Button Content="Back" Command="{Binding BackCommand}" Margin="0,0,12,0" />
|
||||
<StackPanel Grid.Column="1" IsVisible="{Binding IsProgramInfoVisible}" Spacing="6">
|
||||
<TextBlock Text="{Binding ProgramGuideText}" FontSize="18" FontWeight="SemiBold" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock Text="{Binding StartProgram}" />
|
||||
<ProgressBar Width="260" Maximum="100" Value="{Binding DurationValue}" />
|
||||
<TextBlock Text="{Binding EndProgram}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<Button Grid.Column="2" Content="Guide" Command="{Binding ShowProgramListCommand}" Margin="0,0,12,0" />
|
||||
<Button Grid.Column="3" Content="Fullscreen" Command="{Binding FullscreenCommand}" Margin="0,0,12,0" />
|
||||
<Button Grid.Column="4" Content="Open" Command="{Binding OpenStreamCommand}" Margin="0,0,12,0" />
|
||||
<Button Grid.Column="5" Content="Close" Command="{Binding CloseAppCommand}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,109 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using LibVLCSharp.Shared;
|
||||
using System.ComponentModel;
|
||||
using TV_Player.AvaloniaApp.ViewModels;
|
||||
|
||||
namespace TV_Player.AvaloniaApp.Views;
|
||||
|
||||
public partial class PlayerView : UserControl
|
||||
{
|
||||
private LibVLC? _libVlc;
|
||||
private MediaPlayer? _mediaPlayer;
|
||||
private PlayerViewModel? _viewModel;
|
||||
private bool _initialized;
|
||||
|
||||
public PlayerView()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
DataContextChanged += OnDataContextChanged;
|
||||
AttachedToVisualTree += OnAttachedToVisualTree;
|
||||
DetachedFromVisualTree += OnDetachedFromVisualTree;
|
||||
}
|
||||
|
||||
private void OnAttachedToVisualTree(object? sender, Avalonia.VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
if (_initialized)
|
||||
return;
|
||||
|
||||
Core.Initialize();
|
||||
_libVlc = new LibVLC(enableDebugLogs: true);
|
||||
_mediaPlayer = new MediaPlayer(_libVlc)
|
||||
{
|
||||
EnableHardwareDecoding = true
|
||||
};
|
||||
|
||||
VideoView.MediaPlayer = _mediaPlayer;
|
||||
_initialized = true;
|
||||
PlayCurrentStream();
|
||||
}
|
||||
|
||||
private void OnDetachedFromVisualTree(object? sender, Avalonia.VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
if (_viewModel != null)
|
||||
{
|
||||
_viewModel.PropertyChanged -= OnViewModelPropertyChanged;
|
||||
}
|
||||
|
||||
_mediaPlayer?.Stop();
|
||||
_mediaPlayer?.Dispose();
|
||||
_libVlc?.Dispose();
|
||||
_mediaPlayer = null;
|
||||
_libVlc = null;
|
||||
_initialized = false;
|
||||
}
|
||||
|
||||
private void OnDataContextChanged(object? sender, System.EventArgs e)
|
||||
{
|
||||
if (_viewModel != null)
|
||||
{
|
||||
_viewModel.PropertyChanged -= OnViewModelPropertyChanged;
|
||||
}
|
||||
|
||||
_viewModel = DataContext as PlayerViewModel;
|
||||
if (_viewModel != null)
|
||||
{
|
||||
_viewModel.PropertyChanged += OnViewModelPropertyChanged;
|
||||
}
|
||||
|
||||
PlayCurrentStream();
|
||||
}
|
||||
|
||||
private void OnViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(PlayerViewModel.StreamUrl))
|
||||
{
|
||||
PlayCurrentStream();
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayCurrentStream()
|
||||
{
|
||||
if (!_initialized || _viewModel == null || _mediaPlayer == null || _libVlc == null)
|
||||
return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_viewModel.StreamUrl))
|
||||
{
|
||||
_viewModel.SetPlaybackStatus("No stream URL available for this channel.");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (_mediaPlayer.Media != null)
|
||||
{
|
||||
_mediaPlayer.Stop();
|
||||
_mediaPlayer.Media.Dispose();
|
||||
}
|
||||
|
||||
var media = new Media(_libVlc, new Uri(_viewModel.StreamUrl));
|
||||
_mediaPlayer.Media = media;
|
||||
_mediaPlayer.Play();
|
||||
_viewModel.SetPlaybackStatus("Playing with embedded VLC.");
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
_viewModel.SetPlaybackStatus($"Embedded playback failed: {ex.Message}. Use Open Externally as a fallback.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="TV_Player.AvaloniaApp.Views.PlaylistsGroupView"
|
||||
x:Name="Root">
|
||||
<ScrollViewer>
|
||||
<ItemsControl ItemsSource="{Binding Programs}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Width="260"
|
||||
Height="120"
|
||||
Margin="10"
|
||||
Padding="18"
|
||||
CornerRadius="16"
|
||||
Background="#AA111827"
|
||||
BorderBrush="#EAB308"
|
||||
BorderThickness="2"
|
||||
Command="{Binding DataContext.SelectPlaylistCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
CommandParameter="{Binding}">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="{Binding Name}" FontSize="24" FontWeight="Bold" TextWrapping="Wrap" />
|
||||
<TextBlock Text="Open playlist" Foreground="#D1D5DB" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,12 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace TV_Player.AvaloniaApp.Views;
|
||||
|
||||
public partial class PlaylistsGroupView : UserControl
|
||||
{
|
||||
public PlaylistsGroupView()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="TV_Player.AvaloniaApp.Views.ProgramsGroupView">
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<!-- Debug Info Header -->
|
||||
<Border Grid.Row="0" Background="#1a1a2e" Padding="16" Margin="0,0,0,4">
|
||||
<TextBlock Text="{Binding Programs.Count, StringFormat='Groups Loaded: {0}'}"
|
||||
Foreground="#EAB308"
|
||||
FontSize="14"
|
||||
FontWeight="Bold" />
|
||||
</Border>
|
||||
|
||||
<!-- Groups List -->
|
||||
<ScrollViewer Grid.Row="1">
|
||||
<ItemsControl ItemsSource="{Binding Programs}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Width="260"
|
||||
Height="140"
|
||||
Margin="10"
|
||||
Padding="18"
|
||||
CornerRadius="16"
|
||||
Background="#AA111827"
|
||||
BorderBrush="#EAB308"
|
||||
BorderThickness="2"
|
||||
Command="{Binding DataContext.SelectGroupCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
CommandParameter="{Binding}">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="{Binding Name}" FontSize="24" FontWeight="Bold" TextWrapping="Wrap" />
|
||||
<TextBlock Text="{Binding Count, StringFormat='Channels: {0}'}" Foreground="#D1D5DB" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,12 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace TV_Player.AvaloniaApp.Views;
|
||||
|
||||
public partial class ProgramsGroupView : UserControl
|
||||
{
|
||||
public ProgramsGroupView()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="TV_Player.AvaloniaApp.Views.ProgramsListView">
|
||||
<ScrollViewer>
|
||||
<ItemsControl ItemsSource="{Binding Programs}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Width="160"
|
||||
Height="170"
|
||||
Margin="8"
|
||||
Padding="10"
|
||||
CornerRadius="14"
|
||||
Background="#AA111827"
|
||||
BorderBrush="#EAB308"
|
||||
BorderThickness="2"
|
||||
Command="{Binding DataContext.SelectProgramCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
CommandParameter="{Binding}">
|
||||
<StackPanel Spacing="10">
|
||||
<Border Height="90" Background="#1F2937" CornerRadius="10">
|
||||
<Image Source="{Binding Logo}" Stretch="Uniform" />
|
||||
</Border>
|
||||
<TextBlock Text="{Binding Name}" TextWrapping="Wrap" TextAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,12 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace TV_Player.AvaloniaApp.Views;
|
||||
|
||||
public partial class ProgramsListView : UserControl
|
||||
{
|
||||
public ProgramsListView()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="TV_Player.AvaloniaApp.Views.SettingsView">
|
||||
<ScrollViewer>
|
||||
<StackPanel Width="900" HorizontalAlignment="Center" Spacing="18">
|
||||
<TextBlock Text="Settings" FontSize="30" FontWeight="Bold" HorizontalAlignment="Center" />
|
||||
|
||||
<Border Background="#AA111827" Padding="18" CornerRadius="16">
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="Playlist URL" />
|
||||
<TextBox Text="{Binding PlaylistURL}" Watermark="https://example.com/playlist.m3u" />
|
||||
<TextBlock Text="Playlist name" />
|
||||
<TextBox Text="{Binding PlaylistName}" Watermark="My IPTV" />
|
||||
<Button Content="Add playlist" Command="{Binding AddPlaylistCommand}" HorizontalAlignment="Left" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Background="#AA111827" Padding="18" CornerRadius="16">
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="Playlists" FontSize="22" FontWeight="SemiBold" />
|
||||
<ItemsControl ItemsSource="{Binding Playlists}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid ColumnDefinitions="220,*,Auto" Margin="0,4">
|
||||
<TextBlock Text="{Binding Key}" VerticalAlignment="Center" Margin="0,0,12,0" />
|
||||
<TextBlock Grid.Column="1" Text="{Binding Value}" VerticalAlignment="Center" TextWrapping="Wrap" Margin="0,0,12,0" />
|
||||
<Button Grid.Column="2"
|
||||
Content="Remove"
|
||||
Command="{Binding DataContext.PlaylistDeleteCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
CommandParameter="{Binding}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<CheckBox IsChecked="{Binding StartFullScreen}" Content="Start in fullscreen" />
|
||||
<CheckBox IsChecked="{Binding StartLastScreen}" Content="Remember last screen" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="12" HorizontalAlignment="Center">
|
||||
<Button Content="Back" Command="{Binding BackCommand}" MinWidth="120" />
|
||||
<Button Content="Save" Command="{Binding SaveCommand}" MinWidth="120" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,12 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace TV_Player.AvaloniaApp.Views;
|
||||
|
||||
public partial class SettingsView : UserControl
|
||||
{
|
||||
public SettingsView()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user