add settings screen
get playlist from settings, in first run - open Settings
This commit is contained in:
@@ -216,4 +216,82 @@ c-0.781-0.781-0.788-2.047-0.007-2.828L51.438,14.43c1.754-1.755,1.753-4.61-0.001-
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<LinearGradientBrush x:Key="YellowGradient" >
|
||||
<GradientStop Offset="0" Color="LightGray"/>
|
||||
<GradientStop Offset="1" Color="Gray"/>
|
||||
</LinearGradientBrush>
|
||||
|
||||
<Style x:Key="{x:Type CheckBox}" TargetType="CheckBox">
|
||||
<Setter Property="SnapsToDevicePixels" Value="true"/>
|
||||
<Setter Property="Background" Value="{StaticResource YellowGradient}" />
|
||||
<Setter Property="OverridesDefaultStyle" Value="true" />
|
||||
<Setter Property="FontSize" Value="18" />
|
||||
<Setter Property="FontFamily" Value="Arial" />
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
<Setter Property="Foreground" Value="#FF03428f" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="CheckBox">
|
||||
<BulletDecorator Background="Transparent">
|
||||
<BulletDecorator.Bullet>
|
||||
<Grid FlowDirection="LeftToRight">
|
||||
<Border Name="Border"
|
||||
Background="White"
|
||||
CornerRadius="1"
|
||||
BorderBrush="#FF92c8e5"
|
||||
BorderThickness="2"
|
||||
Width="46"
|
||||
Height="38"
|
||||
>
|
||||
<Border
|
||||
Name="ShadowBorder"
|
||||
CornerRadius="2"
|
||||
Padding="2"
|
||||
ClipToBounds="True"
|
||||
Background="Transparent"
|
||||
BorderBrush="#FF92c8e5"
|
||||
Margin="-2,-2,0,0"
|
||||
BorderThickness="2,2,0,0" >
|
||||
<Border.Effect>
|
||||
<DropShadowEffect ShadowDepth="3" BlurRadius="7"/>
|
||||
</Border.Effect>
|
||||
|
||||
</Border>
|
||||
|
||||
</Border>
|
||||
<Path x:Name="CheckMark" SnapsToDevicePixels="False" Data="F1M1,14.489C1,14.489 4.963,10.525 4.963,10.525 4.963,10.525 11.328,16.89 11.328,16.89 11.328,16.89 27.034,1.183 27.034,1.183 27.034,1.183 31,5.146 31,5.146 31,5.146 11.328,24.817 11.328,24.817 11.328,24.817 1,14.489 1,14.489z" Fill="#FFFFFFFF" Height="26" Canvas.Left="0" Canvas.Top="0" Width="32"/>
|
||||
</Grid>
|
||||
</BulletDecorator.Bullet>
|
||||
<ContentPresenter Margin="4,0,0,0" TextBlock.Foreground="{TemplateBinding Foreground}"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Left"
|
||||
RecognizesAccessKey="True"/>
|
||||
</BulletDecorator>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked" Value="false">
|
||||
<Setter TargetName="CheckMark" Property="Visibility" Value="Collapsed"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsChecked" Value="true">
|
||||
<Setter TargetName="Border" Property="Background" Value="{Binding Background,RelativeSource={RelativeSource AncestorType={x:Type CheckBox}}}" />
|
||||
<Setter TargetName="Border" Property="BorderBrush" Value="{Binding Background, RelativeSource={RelativeSource AncestorType={x:Type CheckBox}}}" />
|
||||
<Setter TargetName="ShadowBorder" Property="Visibility" Value="Hidden" />
|
||||
|
||||
</Trigger>
|
||||
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="TextBox" TargetType="TextBox">
|
||||
<Setter Property="FontFamily" Value="Arial"></Setter>
|
||||
<Setter Property="FontWeight" Value="Bold"></Setter>
|
||||
<Setter Property="FontSize" Value="25"></Setter>
|
||||
<Setter Property="Foreground" Value="Gray"></Setter>
|
||||
<Setter Property="HorizontalAlignment" Value="Center"></Setter>
|
||||
<Setter Property="VerticalAlignment" Value="Center"></Setter>
|
||||
<Setter Property="TextAlignment" Value="Left"></Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -20,17 +20,17 @@
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Visibility="{Binding IsTopPanelVisible,Converter={StaticResource BooleanToVisibilityConverterKey}}" Height="70">
|
||||
<Grid Visibility="{Binding IsTopPanelVisible,Converter={StaticResource BooleanToVisibilityConverterKey}}" Height="80">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="70"/>
|
||||
<ColumnDefinition Width="70"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="70"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Height="50" Width="50" Margin="10,0,0,0" Style="{DynamicResource ButtonGear}" />
|
||||
<Button Grid.Column="1" Height="50" Width="50" Margin="10,0,0,0" Style="{DynamicResource ButtonBack}" Command="{Binding BackCommand}" />
|
||||
<Button Height="70" Width="70" Margin="10,0,0,0" Style="{DynamicResource ButtonGear}" Command="{Binding SettingsCommand}" />
|
||||
<Button Grid.Column="1" Height="70" Width="70" Margin="10,0,0,0" Style="{DynamicResource ButtonBack}" Command="{Binding BackCommand}" />
|
||||
<TextBlock Grid.Column="2" FontSize="30" Foreground="White" Text="{Binding TopPanelTitle}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
<Button Grid.Column="4" Height="50" Width="50" Margin="10,0,0,0" Style="{DynamicResource ButtonFullScreen}" Command="{Binding FullscreenCommand}" />
|
||||
<Button Grid.Column="4" Height="70" Width="70" Margin="10,0,0,0" Style="{DynamicResource ButtonFullScreen}" Command="{Binding FullscreenCommand}" />
|
||||
</Grid>
|
||||
<ContentControl Grid.Row="1" Name="ControlContainer" Content="{Binding Control}" />
|
||||
</Grid>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<UserControl x:Class="TV_Player.Settings"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:tv_player="clr-namespace:TV_Player"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<StackPanel x:Name="groupsGrid" VerticalAlignment="Center">
|
||||
<Label Margin="10" Foreground="White" FontSize="25" FontWeight="Bold" FontFamily="Arial">Адрес плейлиста</Label>
|
||||
<TextBox Margin="10" Style="{StaticResource TextBox}" HorizontalAlignment="Stretch" Text="{Binding PlaylistURL}"></TextBox>
|
||||
<CheckBox Margin="10" Foreground="White" FontSize="25" IsChecked="{Binding StartFullScreen}">Откывать во весь экран</CheckBox>
|
||||
<CheckBox Margin="10" Foreground="White" FontSize="25" IsChecked="{Binding StartLastScreen}">Запоминать последний выбор</CheckBox>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Button Grid.Column="1" Height="70" Width="70" Margin="10,0,50,0" Style="{DynamicResource ButtonBack}" Command="{Binding BackCommand}" />
|
||||
<Button HorizontalAlignment="Center" FontSize="25" Command="{Binding SaveCommand}">Сохранить</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,15 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace TV_Player
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for ProgramsGroupGrid.xaml
|
||||
/// </summary>
|
||||
public partial class Settings : UserControl
|
||||
{
|
||||
public Settings()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
<TreatAsUsed>true</TreatAsUsed>
|
||||
</PackageReference>
|
||||
<PackageReference Include="LibVLCSharp.WPF" Version="3.8.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="System.Reactive" Version="6.0.0" />
|
||||
<PackageReference Include="VideoLAN.LibVLC.Windows" Version="3.0.20" />
|
||||
</ItemGroup>
|
||||
@@ -30,6 +31,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Settings.xaml.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Update="VideoPlayer.xaml.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
|
||||
@@ -4,47 +4,51 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vlc="clr-namespace:LibVLCSharp.WPF;assembly=LibVLCSharp.WPF"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:TV_Player"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
Unloaded="UserControl_Unloaded">
|
||||
<UserControl.Resources>
|
||||
<local:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverterKey"/>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<vlc:VideoView x:Name="VideoView" Panel.ZIndex="1">
|
||||
<Grid Panel.ZIndex="10" IsHitTestVisible="True" Background="#01FFFFFF" VerticalAlignment="Stretch"
|
||||
HorizontalAlignment="Stretch" MouseLeftButtonDown="MyUserControl_MouseDown">
|
||||
<Grid x:Name="overlayPanel" Visibility="Visible">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="70"/>
|
||||
<RowDefinition Height="80"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="70"/>
|
||||
<RowDefinition Height="80"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Background="#70000000" >
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="70"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="70"/>
|
||||
<ColumnDefinition Width="80"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Height="50" Width="50" Margin="10,0,0,0" Style="{DynamicResource ButtonBack}" Command="{Binding BackCommand}" />
|
||||
<Button Height="50" Width="70" Margin="10,0,0,0" Style="{DynamicResource ButtonBack}" Command="{Binding BackCommand}" />
|
||||
<TextBlock Grid.Column="1" FontSize="20" Foreground="White" Text="{Binding TopPanelTitle}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
<Button Grid.Column="4" Height="50" Width="50" Margin="10,0,0,0" Style="{DynamicResource ButtonFullScreen}" Command="{Binding FullscreenCommand}" />
|
||||
<Button Grid.Column="4" Height="70" Width="50" Margin="10,0,0,0" Style="{DynamicResource ButtonFullScreen}" Command="{Binding FullscreenCommand}" />
|
||||
</Grid>
|
||||
<Grid Grid.Row="2" Background="#70000000">
|
||||
<Grid Grid.Row="2" Background="#B0000000">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="180"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="70"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.RowSpan="2" Orientation="Horizontal" >
|
||||
<Button Grid.RowSpan="2" Height="70" Width="50" Margin="10,0,10,0" Style="{DynamicResource ButtonDown}" Command="{Binding PreviousCommand}" />
|
||||
<TextBlock FontSize="15" Foreground="White" Text="Ch" HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
<Button Grid.RowSpan="2" Height="70" Width="50" Margin="10,0,10,0" Style="{DynamicResource ButtonUp}" Command="{Binding NextCommand}" />
|
||||
</StackPanel>
|
||||
<Grid Grid.Column="1" x:Name="programInfo" Visibility="{Binding IsProgramInfoVisible,Converter={StaticResource BooleanToVisibilityConverterKey}}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel Grid.RowSpan="2" Orientation="Horizontal" >
|
||||
<Button Grid.RowSpan="2" Height="50" Width="50" Margin="10,0,10,0" Style="{DynamicResource ButtonDown}" Command="{Binding PreviousCommand}" />
|
||||
<TextBlock FontSize="15" Foreground="White" Text="Ch" HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
<Button Grid.RowSpan="2" Height="50" Width="50" Margin="10,0,10,0" Style="{DynamicResource ButtonUp}" Command="{Binding NextCommand}" />
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="1" FontSize="20" Foreground="White" Text="{Binding ProgramGuideText}" HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Grid.Column="1" Grid.Row="1">
|
||||
<TextBlock FontSize="20" Foreground="White" Text="{Binding ProgramGuideText}" HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Grid.Row="1">
|
||||
<TextBlock FontSize="15" Foreground="White" Text="{Binding StartProgram}" HorizontalAlignment="Center" />
|
||||
<ProgressBar Height="10" Foreground="Yellow" Name="progressBar" Value="{Binding DurationValue}" Maximum="100" VerticalAlignment="Center" HorizontalAlignment="Center" Width="400"/>
|
||||
<TextBlock FontSize="15" Foreground="White" Text="{Binding EndProgram}" HorizontalAlignment="Center" />
|
||||
@@ -52,6 +56,7 @@
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</vlc:VideoView>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -3,6 +3,7 @@ using LibVLCSharp.WPF;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace TV_Player
|
||||
{
|
||||
@@ -16,11 +17,17 @@ namespace TV_Player
|
||||
private LibVLC _libVLC;
|
||||
private MediaPlayer _mediaPlayer;
|
||||
private PlayerViewModel _viewModel;
|
||||
private DispatcherTimer _overlayAutoHideTimer;
|
||||
|
||||
public VideoPlayer()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_overlayAutoHideTimer = new DispatcherTimer();
|
||||
_overlayAutoHideTimer.Interval = TimeSpan.FromSeconds(3);
|
||||
_overlayAutoHideTimer.Tick += _overlayAutoHideTimer_Tick;
|
||||
_overlayAutoHideTimer.Start();
|
||||
|
||||
_libVLC = new LibVLC(enableDebugLogs: true);
|
||||
_mediaPlayer = new MediaPlayer(_libVLC);
|
||||
this.DataContextChanged += (sender, e) =>
|
||||
@@ -29,7 +36,6 @@ namespace TV_Player
|
||||
_viewModel.SourceUrlChangedEvent += _viewModel_SourceUrlChangedEvent;
|
||||
};
|
||||
|
||||
|
||||
VideoView.Loaded += (sender, e) =>
|
||||
{
|
||||
VideoView.MediaPlayer = _mediaPlayer;
|
||||
@@ -90,12 +96,20 @@ namespace TV_Player
|
||||
ShowOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
private void _overlayAutoHideTimer_Tick(object? sender, EventArgs e)
|
||||
{
|
||||
HideOverlay();
|
||||
}
|
||||
|
||||
public void ShowOverlay()
|
||||
{
|
||||
_overlayAutoHideTimer.Start();
|
||||
overlayPanel.Visibility = Visibility.Visible;
|
||||
}
|
||||
public void HideOverlay()
|
||||
{
|
||||
_overlayAutoHideTimer.Stop();
|
||||
overlayPanel.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
private void UserControl_Unloaded(object sender, RoutedEventArgs e)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
namespace TV_Player
|
||||
{
|
||||
[Serializable]
|
||||
public class GroupInfo
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Channels;
|
||||
using System.Xml;
|
||||
|
||||
namespace TV_Player
|
||||
@@ -28,7 +27,6 @@ namespace TV_Player
|
||||
public static class M3UParser
|
||||
{
|
||||
|
||||
|
||||
public static async Task<List<ProgramGuide>> DownloadGuideFromWebAsync(string url)
|
||||
{
|
||||
List<ProgramGuide> epgChannels = new List<ProgramGuide>(); ;
|
||||
@@ -46,7 +44,6 @@ namespace TV_Player
|
||||
return epgChannels;
|
||||
}
|
||||
|
||||
|
||||
private static List<ProgramGuide> ParseEpg(string epgData)
|
||||
{
|
||||
List<ProgramGuide> epgChannels = new List<ProgramGuide>();
|
||||
@@ -92,46 +89,16 @@ namespace TV_Player
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
catch (Exception ex)
|
||||
{
|
||||
}
|
||||
}
|
||||
//using (XmlReader reader = XmlReader.Create(new System.IO.StringReader(epgData)))
|
||||
//{
|
||||
// ProgramGuide currentChannel = null;
|
||||
// XmlDocument doc = new XmlDocument();
|
||||
// doc.LoadXml(epgData);
|
||||
|
||||
// XmlNodeList channelNodes = doc.SelectNodes("//channel");
|
||||
|
||||
// foreach (XmlNode channelNode in channelNodes)
|
||||
// {
|
||||
// ProgramGuide channel = new ProgramGuide();
|
||||
// channel.Id = channelNode.Attributes["id"].Value;
|
||||
// channel.DisplayName = channelNode.SelectSingleNode("display-name").InnerText;
|
||||
|
||||
// XmlNodeList programNodes = doc.SelectNodes($"//programme[@channel='{channel.Id}']");
|
||||
// foreach (XmlNode programNode in programNodes)
|
||||
// {
|
||||
// ProgramInfo program = new ProgramInfo();
|
||||
// program.Title = programNode.SelectSingleNode("title").InnerText;
|
||||
// program.StartTime = DateTime.ParseExact(programNode.Attributes["start"].Value, "yyyyMMddHHmmss zzz", null);
|
||||
// program.StopTime = DateTime.ParseExact(programNode.Attributes["stop"].Value, "yyyyMMddHHmmss zzz", null);
|
||||
|
||||
// channel.Programs.Add(program);
|
||||
// }
|
||||
|
||||
// epgChannels.Add(channel);
|
||||
// }
|
||||
//}
|
||||
|
||||
return epgChannels;
|
||||
}
|
||||
|
||||
|
||||
public static async Task<List<M3UInfo>> DownloadM3UFromWebAsync(string url)
|
||||
private static async Task<string> ReadFile(string url)
|
||||
{
|
||||
List<M3UInfo> playlistItems = new List<M3UInfo>();
|
||||
|
||||
string responseBody;
|
||||
using (var client = new HttpClient())
|
||||
using (var request = new HttpRequestMessage())
|
||||
{
|
||||
@@ -140,15 +107,18 @@ namespace TV_Player
|
||||
request.RequestUri = new Uri(url);
|
||||
var response = await client.GetAsync(url);
|
||||
response.EnsureSuccessStatusCode();
|
||||
string responseBody = await response.Content.ReadAsStringAsync();
|
||||
responseBody = await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
return responseBody;
|
||||
}
|
||||
|
||||
public static async Task<(List<M3UInfo> programList, string programGuide)> DownloadM3UFromWebAsync(string url)
|
||||
{
|
||||
var fileData=await ReadFile(url);
|
||||
// Parse M3U content
|
||||
playlistItems = ParseM3UFromString(responseBody);
|
||||
return ParseM3UFromString(fileData);
|
||||
}
|
||||
return playlistItems;
|
||||
}
|
||||
|
||||
static string[] SplitStringBeforeSeparator(string input, string separator)
|
||||
private static string[] SplitStringBeforeSeparator(string input, string separator)
|
||||
{
|
||||
string[] parts = input.Split(separator);
|
||||
|
||||
@@ -166,10 +136,10 @@ namespace TV_Player
|
||||
return parts;
|
||||
}
|
||||
|
||||
static List<M3UInfo> ParseM3UFromString(string content)
|
||||
private static (List<M3UInfo> programList, string programGuide) ParseM3UFromString(string content)
|
||||
{
|
||||
List<M3UInfo> playlistItems = new List<M3UInfo>();
|
||||
|
||||
string programGuideLink = string.Empty;
|
||||
try
|
||||
{
|
||||
var m3u = SplitStringBeforeSeparator(content, "#EXT");
|
||||
@@ -183,6 +153,10 @@ namespace TV_Player
|
||||
playlistItems.Add(m3uInfo);
|
||||
}
|
||||
}
|
||||
if (line.StartsWith("#EXTM3U"))
|
||||
{
|
||||
programGuideLink=ExtractXtvgUrl(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -190,11 +164,10 @@ namespace TV_Player
|
||||
Console.WriteLine("Error reading M3U file: " + ex.Message);
|
||||
}
|
||||
|
||||
return playlistItems;
|
||||
return (playlistItems,programGuideLink);
|
||||
}
|
||||
|
||||
|
||||
static bool TryParseM3ULine(string m3uLine, out M3UInfo? info)
|
||||
private static bool TryParseM3ULine(string m3uLine, out M3UInfo? info)
|
||||
{
|
||||
info = null;
|
||||
string pattern = @"#EXTINF:\d+ CUID=""(?<CUID>.*?)"" number=""(?<Number>.*?)"" tvg-id=""(?<TvgID>.*?)"" tvg-name=""(?<TvgName>.*?)"".*?tvg-logo=""(?<Logo>.*?)"" group-title=""(?<GroupTitle>.*?)""[^,]*,(?<Name>.*)[^\r](?<URL>.*)$";
|
||||
@@ -219,5 +192,23 @@ namespace TV_Player
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string ExtractXtvgUrl(string m3uEntry)
|
||||
{
|
||||
// Define a regular expression pattern to match x-tvg-url attribute
|
||||
string pattern = @"x-tvg-url=""(.*?)""";
|
||||
|
||||
// Use Regex.Match to find the first match
|
||||
Match match = Regex.Match(m3uEntry, pattern);
|
||||
|
||||
// Check if a match is found and get the value from the capturing group
|
||||
if (match.Success && match.Groups.Count > 1)
|
||||
{
|
||||
return match.Groups[1].Value;
|
||||
}
|
||||
|
||||
// Return null or an empty string if no match is found
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using TV_Player.ViewModels;
|
||||
|
||||
namespace TV_Player
|
||||
{
|
||||
@@ -48,11 +49,14 @@ namespace TV_Player
|
||||
public Action ButtonBackAction { get; set; }
|
||||
public ICommand BackCommand { get; }
|
||||
|
||||
public ICommand SettingsCommand{ get; }
|
||||
|
||||
public MainViewModel()
|
||||
{
|
||||
|
||||
BackCommand = new RelayCommand(OnButtonBackClick);
|
||||
FullscreenCommand = new RelayCommand(OnFullSctreenButtonClick);
|
||||
SettingsCommand = new RelayCommand(OnSettingsButtonClick);
|
||||
|
||||
CurrentWindowStyle = WindowStyle.SingleBorderWindow;
|
||||
}
|
||||
@@ -76,5 +80,10 @@ namespace TV_Player
|
||||
|
||||
ButtonBackAction?.Invoke();
|
||||
}
|
||||
|
||||
private void OnSettingsButtonClick()
|
||||
{
|
||||
TVPlayerViewModel.Instance.ShowSettingsScreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,13 @@ namespace TV_Player
|
||||
|
||||
private M3UInfo _currentProgram;
|
||||
|
||||
private bool _isProgramInfoVisible;
|
||||
public bool IsProgramInfoVisible
|
||||
{
|
||||
get => _isProgramInfoVisible;
|
||||
set => SetProperty(ref _isProgramInfoVisible, value);
|
||||
}
|
||||
|
||||
private string _topPaneTitle;
|
||||
public string TopPanelTitle
|
||||
{
|
||||
@@ -71,7 +78,7 @@ namespace TV_Player
|
||||
PreviousCommand = new RelayCommand(PreviousProgram);
|
||||
FullscreenCommand = new RelayCommand(TVPlayerViewModel.Instance.FullScreenToggle);
|
||||
|
||||
_programSubscriber = ProgramsData.Instance.AllPrograms.Subscribe(x =>
|
||||
_programSubscriber = TVPlayerViewModel.Instance.PlaylistData.AllPrograms.Subscribe(x =>
|
||||
{
|
||||
_programs = x.Where(p => p.GroupTitle == _currentProgram.GroupTitle).ToList();
|
||||
_currentProgramIndex = _programs.Select((program, index) => new { program, index })
|
||||
@@ -89,7 +96,7 @@ namespace TV_Player
|
||||
TopPanelTitle = _currentProgram.Name;
|
||||
SourceUrlChangedEvent?.Invoke(_currentProgram.Url);
|
||||
|
||||
_programGuideDisposable = ProgramsData.Instance.ProgramGuideInfo.Subscribe(x =>
|
||||
_programGuideDisposable = TVPlayerViewModel.Instance.PlaylistData.ProgramGuideInfo.Subscribe(x =>
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -125,23 +132,33 @@ namespace TV_Player
|
||||
}
|
||||
private void UpdateScreenInfo()
|
||||
{
|
||||
_currentProgramInfo = _currentGuide.Programs.FirstOrDefault(d => d.StartTime <= DateTime.Now && d.StopTime >= DateTime.Now);
|
||||
if (_currentProgramInfo.Title != ProgramGuideText)
|
||||
try
|
||||
{
|
||||
_currentProgramInfo = _currentGuide.Programs.FirstOrDefault(d => d.StartTime <= DateTime.Now && d.StopTime >= DateTime.Now);
|
||||
if (_currentProgramInfo == null)
|
||||
{
|
||||
IsProgramInfoVisible = false;
|
||||
}
|
||||
else if (_currentProgramInfo.Title != ProgramGuideText)
|
||||
{
|
||||
IsProgramInfoVisible = true;
|
||||
ProgramGuideText = _currentProgramInfo.Title;
|
||||
StartProgram = _currentProgramInfo.StartTime.ToShortTimeString();
|
||||
EndProgram = _currentProgramInfo.StopTime.ToShortTimeString();
|
||||
}
|
||||
|
||||
var programMinutes = (_currentProgramInfo.StopTime - _currentProgramInfo.StartTime).TotalMinutes;
|
||||
DurationValue = (int)((DateTime.Now - _currentProgramInfo.StartTime).TotalMinutes / programMinutes * 100);
|
||||
}
|
||||
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
}
|
||||
private void OnButtonBackClick()
|
||||
{
|
||||
var groupInfo = new GroupInfo() { Name = _currentProgram.GroupTitle, Count = 0 };
|
||||
|
||||
var programListViewModel = new ProgramsListViewModel(groupInfo);
|
||||
var conrtrol = new ProgramsList();
|
||||
TVPlayerViewModel.Instance.SetPageContext(conrtrol, programListViewModel);
|
||||
TVPlayerViewModel.Instance.ShowProgramsListScreen(groupInfo);
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
@@ -4,16 +4,6 @@ namespace TV_Player
|
||||
{
|
||||
public class ProgramsData
|
||||
{
|
||||
private static ProgramsData _instance;
|
||||
public static ProgramsData Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
_instance ??= new ProgramsData();
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ReplaySubject<List<M3UInfo>> programsSubject = new ReplaySubject<List<M3UInfo>>();
|
||||
private readonly ReplaySubject<List<GroupInfo>> groupsSubject = new ReplaySubject<List<GroupInfo>>();
|
||||
private readonly ReplaySubject<List<ProgramGuide>> programGuideSubject = new ReplaySubject<List<ProgramGuide>>();
|
||||
@@ -21,29 +11,35 @@ namespace TV_Player
|
||||
public IObservable<List<GroupInfo>> GroupsInformation => groupsSubject;
|
||||
|
||||
public IObservable<List<ProgramGuide>> ProgramGuideInfo => programGuideSubject;
|
||||
private ProgramsData()
|
||||
public ProgramsData()
|
||||
{
|
||||
Task.Run(() => GetPrograms());
|
||||
Task.Run(() => GetProgramGuide());
|
||||
}
|
||||
private async Task GetPrograms()
|
||||
|
||||
private async Task GetPrograms(string m3uLink)
|
||||
{
|
||||
string m3uLink = "http://pl.da-tv.vip/a71e77fa/835b3216/tv.m3u";
|
||||
var programs = await M3UParser.DownloadM3UFromWebAsync(m3uLink);
|
||||
//string m3uLink = "http://pl.da-tv.vip/a71e77fa/835b3216/tv.m3u";
|
||||
var result = await M3UParser.DownloadM3UFromWebAsync(m3uLink);
|
||||
|
||||
programsSubject.OnNext(programs);
|
||||
programsSubject.OnNext(result.programList);
|
||||
|
||||
var groupping = programs.GroupBy(item => item.GroupTitle)
|
||||
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(result.programGuide));
|
||||
}
|
||||
|
||||
private async Task GetProgramGuide()
|
||||
private async Task GetProgramGuide(string guideLink)
|
||||
{
|
||||
string guideLink = "http://epg.da-tv.vip/107-light.xml";
|
||||
//string guideLink = "http://epg.da-tv.vip/107-light.xml";
|
||||
var programGuide = await M3UParser.DownloadGuideFromWebAsync(guideLink);
|
||||
programGuideSubject.OnNext(programGuide);
|
||||
}
|
||||
|
||||
internal void GetData(string playlistURL)
|
||||
{
|
||||
Task.Run(() => GetPrograms(playlistURL));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,16 +20,14 @@ namespace TV_Player
|
||||
public ProgramsGroupViewModel()
|
||||
{
|
||||
ItemSelectedCommand = new RelayCommand(OnItemSelected);
|
||||
_groupInformationSubscriber = ProgramsData.Instance.GroupsInformation.Subscribe(x=>Programs = x);
|
||||
_groupInformationSubscriber = TVPlayerViewModel.Instance.PlaylistData.GroupsInformation.Subscribe(x=>Programs = x);
|
||||
|
||||
TVPlayerViewModel.Instance.TopPanelVisible(true, "Группы");
|
||||
}
|
||||
|
||||
private void OnItemSelected()
|
||||
{
|
||||
var programListViewModel = new ProgramsListViewModel(SelectedItem);
|
||||
var conrtrol = new ProgramsList();
|
||||
TVPlayerViewModel.Instance.SetPageContext(conrtrol, programListViewModel);
|
||||
TVPlayerViewModel.Instance.ShowProgramsListScreen(SelectedItem);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -16,26 +16,22 @@ namespace TV_Player
|
||||
public M3UInfo SelectedItem { get; set; }
|
||||
public ICommand ItemSelectedCommand { get; }
|
||||
private IDisposable _programSubscriber;
|
||||
|
||||
public ProgramsListViewModel(GroupInfo groupInfo)
|
||||
{
|
||||
TVPlayerViewModel.Instance.TopPanelVisible(true, groupInfo.Name);
|
||||
ItemSelectedCommand = new RelayCommand(OnItemSelected);
|
||||
_programSubscriber = ProgramsData.Instance.AllPrograms.Subscribe(x => Programs = x.Where(p => p.GroupTitle == groupInfo.Name).ToList());
|
||||
_programSubscriber = TVPlayerViewModel.Instance.PlaylistData.AllPrograms.Subscribe(x => Programs = x.Where(p => p.GroupTitle == groupInfo.Name).ToList());
|
||||
|
||||
TVPlayerViewModel.Instance.SetBackButtonAction(new Action(() =>
|
||||
{
|
||||
var programGroupViewModel = new ProgramsGroupViewModel();
|
||||
var conrtrol = new ProgramsGroupGrid();
|
||||
TVPlayerViewModel.Instance.SetPageContext(conrtrol, programGroupViewModel);
|
||||
TVPlayerViewModel.Instance.ShowProgramsGroupScreen();
|
||||
}));
|
||||
}
|
||||
|
||||
private void OnItemSelected()
|
||||
{
|
||||
var playerViewModel = new PlayerViewModel(SelectedItem);
|
||||
var conrtrol = new VideoPlayer();
|
||||
conrtrol.SourceUrl = SelectedItem.Url;
|
||||
TVPlayerViewModel.Instance.SetPageContext(conrtrol, playerViewModel);
|
||||
TVPlayerViewModel.Instance.ShowPlayerScreen(SelectedItem);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace TV_Player.ViewModels
|
||||
{
|
||||
//public class RelayCommand : ICommand
|
||||
//{
|
||||
// private readonly Action _execute;
|
||||
// private readonly Func<bool> _canExecute;
|
||||
|
||||
// public event EventHandler CanExecuteChanged;
|
||||
|
||||
// public RelayCommand(Action execute, Func<bool> canExecute = null)
|
||||
// {
|
||||
// _execute = execute ?? throw new ArgumentNullException(nameof(execute));
|
||||
// _canExecute = canExecute;
|
||||
// }
|
||||
|
||||
// public bool CanExecute(object parameter)
|
||||
// {
|
||||
// return _canExecute == null || _canExecute();
|
||||
// }
|
||||
|
||||
// public void Execute(object parameter)
|
||||
// {
|
||||
// _execute();
|
||||
// }
|
||||
|
||||
// public void RaiseCanExecuteChanged()
|
||||
// {
|
||||
// CanExecuteChanged?.Invoke(this, EventArgs.Empty);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.IO;
|
||||
|
||||
namespace TV_Player.ViewModels
|
||||
{
|
||||
public static class SettingsModel
|
||||
{
|
||||
private const string _filePath = "settings.json";
|
||||
|
||||
public static string PlaylistURL { get; set; }
|
||||
public static bool StartFullScreen { get; set; }
|
||||
public static bool StartFromLastScreen { get; set; }
|
||||
public static string LastScreen { get; set; }
|
||||
public static GroupInfo Group { get; set; }
|
||||
public static M3UInfo Program { get; set; }
|
||||
|
||||
|
||||
public static void SaveSetttings()
|
||||
{
|
||||
// Create an anonymous object to hold the properties
|
||||
var dataToSerialize = new
|
||||
{
|
||||
PlaylistURL,
|
||||
StartFromLastScreen,
|
||||
StartFullScreen,
|
||||
LastScreen,
|
||||
Group,
|
||||
Program
|
||||
};
|
||||
|
||||
// Serialize the object to JSON
|
||||
string json = JsonConvert.SerializeObject(dataToSerialize, Formatting.Indented);
|
||||
|
||||
// Save the JSON to a file
|
||||
File.WriteAllText(_filePath, json);
|
||||
}
|
||||
|
||||
public static void LoadSettings()
|
||||
{
|
||||
var loadedData = new
|
||||
{
|
||||
PlaylistURL = default(string),
|
||||
LastScreen = default(string),
|
||||
Group = default(GroupInfo),
|
||||
Program = default(M3UInfo),
|
||||
StartFromLastScreen = default(bool),
|
||||
StartFullScreen = default(bool)
|
||||
};
|
||||
if (File.Exists(_filePath))
|
||||
{
|
||||
// Read the JSON content from the file
|
||||
string json = File.ReadAllText(_filePath);
|
||||
loadedData = JsonConvert.DeserializeAnonymousType(json, loadedData);
|
||||
}
|
||||
// Assign the values to the properties
|
||||
PlaylistURL = loadedData.PlaylistURL;
|
||||
LastScreen = loadedData.LastScreen;
|
||||
Group = loadedData.Group;
|
||||
Program = loadedData.Program;
|
||||
StartFromLastScreen = loadedData.StartFromLastScreen;
|
||||
StartFullScreen = loadedData.StartFullScreen;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace TV_Player.ViewModels
|
||||
{
|
||||
internal class SettingsViewModel : ObservableViewModelBase
|
||||
{
|
||||
private string _playlistURL;
|
||||
public string PlaylistURL
|
||||
{
|
||||
get => _playlistURL;
|
||||
set => SetProperty(ref _playlistURL, value);
|
||||
}
|
||||
|
||||
private bool _startFullScreen;
|
||||
public bool StartFullScreen
|
||||
{
|
||||
get => _startFullScreen;
|
||||
set => SetProperty(ref _startFullScreen, value);
|
||||
}
|
||||
|
||||
private bool _startLastScreen;
|
||||
public bool StartLastScreen
|
||||
{
|
||||
get => _startLastScreen;
|
||||
set => SetProperty(ref _startLastScreen, value);
|
||||
}
|
||||
|
||||
public ICommand SaveCommand { get; }
|
||||
public ICommand BackCommand { get; }
|
||||
|
||||
public SettingsViewModel()
|
||||
{
|
||||
TVPlayerViewModel.Instance.TopPanelVisible(false, "");
|
||||
|
||||
SaveCommand = new RelayCommand(OnSaveSettings);
|
||||
BackCommand = new RelayCommand(OnBackCommand);
|
||||
|
||||
StartFullScreen = SettingsModel.StartFullScreen;
|
||||
StartLastScreen = SettingsModel.StartFromLastScreen;
|
||||
PlaylistURL = SettingsModel.PlaylistURL;
|
||||
}
|
||||
|
||||
private void OnBackCommand()
|
||||
{
|
||||
TVPlayerViewModel.Instance.SelectScreen();
|
||||
}
|
||||
|
||||
private void OnSaveSettings()
|
||||
{
|
||||
SettingsModel.StartFullScreen = StartFullScreen;
|
||||
SettingsModel.StartFromLastScreen = StartLastScreen;
|
||||
SettingsModel.PlaylistURL = PlaylistURL;
|
||||
|
||||
SettingsModel.SaveSetttings();
|
||||
TVPlayerViewModel.Instance.InitializeTVWithData();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ namespace TV_Player.ViewModels
|
||||
public class TVPlayerViewModel : IDisposable
|
||||
{
|
||||
private readonly MainViewModel _mainViewModel;
|
||||
public ProgramsData PlaylistData { get; private set; }
|
||||
|
||||
public Action ButtonBackAction { get; set; }
|
||||
|
||||
@@ -21,6 +22,8 @@ namespace TV_Player.ViewModels
|
||||
|
||||
public TVPlayerViewModel()
|
||||
{
|
||||
PlaylistData = new ProgramsData();
|
||||
|
||||
_mainViewModel = new MainViewModel();
|
||||
var mainWindow = new MainWindow();
|
||||
mainWindow.DataContext = _mainViewModel;
|
||||
@@ -28,19 +31,81 @@ namespace TV_Player.ViewModels
|
||||
mainWindow.Show();
|
||||
_instance = this;
|
||||
|
||||
ShowInitialScreen();
|
||||
SettingsModel.LoadSettings();
|
||||
InitializeTVWithData();
|
||||
}
|
||||
|
||||
private void ShowInitialScreen()
|
||||
public void InitializeTVWithData()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(SettingsModel.PlaylistURL))
|
||||
{
|
||||
if (SettingsModel.StartFullScreen)
|
||||
FullScreenToggle();
|
||||
PlaylistData.GetData(SettingsModel.PlaylistURL);
|
||||
if (SettingsModel.StartFromLastScreen)
|
||||
SelectScreen();
|
||||
else
|
||||
ShowProgramsGroupScreen();
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowSettingsScreen();
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectScreen()
|
||||
{
|
||||
switch (SettingsModel.LastScreen)
|
||||
{
|
||||
case "ProgramsListViewModel":
|
||||
ShowProgramsListScreen(SettingsModel.Group);
|
||||
break;
|
||||
case "PlayerViewModel":
|
||||
ShowPlayerScreen(SettingsModel.Program);
|
||||
break;
|
||||
default:
|
||||
ShowProgramsGroupScreen();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void ShowProgramsGroupScreen()
|
||||
{
|
||||
var vm = new ProgramsGroupViewModel();
|
||||
|
||||
var control = new ProgramsGroupGrid();
|
||||
control.DataContext = vm;
|
||||
|
||||
SettingsModel.LastScreen = nameof(ProgramsGroupViewModel);
|
||||
SetPageContext(control, vm);
|
||||
}
|
||||
|
||||
public void ShowProgramsListScreen(GroupInfo group)
|
||||
{
|
||||
SettingsModel.Group = group;
|
||||
var programListViewModel = new ProgramsListViewModel(group);
|
||||
var conrtrol = new ProgramsList();
|
||||
SettingsModel.LastScreen = nameof(ProgramsListViewModel);
|
||||
SetPageContext(conrtrol, programListViewModel);
|
||||
}
|
||||
|
||||
public void ShowPlayerScreen(M3UInfo program)
|
||||
{
|
||||
SettingsModel.Program = program;
|
||||
var playerViewModel = new PlayerViewModel(program);
|
||||
var conrtrol = new VideoPlayer();
|
||||
conrtrol.SourceUrl = program.Url;
|
||||
SettingsModel.LastScreen = nameof(PlayerViewModel);
|
||||
SetPageContext(conrtrol, playerViewModel);
|
||||
}
|
||||
|
||||
public void ShowSettingsScreen()
|
||||
{
|
||||
var playerViewModel = new SettingsViewModel();
|
||||
var conrtrol = new Settings();
|
||||
SetPageContext(conrtrol, playerViewModel);
|
||||
}
|
||||
|
||||
public void TopPanelVisible(bool value, string title)
|
||||
{
|
||||
_mainViewModel.IsTopPanelVisible = value;
|
||||
@@ -57,13 +122,15 @@ namespace TV_Player.ViewModels
|
||||
_mainViewModel.ButtonBackAction = action;
|
||||
}
|
||||
|
||||
public void SetPageContext(ContentControl control, object viewModel)
|
||||
private void SetPageContext(ContentControl control, object viewModel)
|
||||
{
|
||||
if (_mainViewModel.Control is IDisposable disposable)
|
||||
disposable.Dispose();
|
||||
control.DataContext = viewModel;
|
||||
|
||||
_mainViewModel.Control = control;
|
||||
|
||||
SettingsModel.SaveSetttings();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
Reference in New Issue
Block a user