add close button

This commit is contained in:
Vova
2024-05-05 16:19:58 +03:00
parent 3dfeea71f0
commit 12289c52f8
20 changed files with 327 additions and 191 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Assets/GroupButtonStyle.xaml"/>
<ResourceDictionary Source="/Assets/AppStyle.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
@@ -45,6 +45,43 @@ 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>
</Style>
<Style x:Key="ButtonConfirm" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="LightGreen" Height="50" Width="50" CornerRadius="300">
<Path Width="40" Height="40" Fill="Gray" Stretch="Fill" Data="M50.131649,63.741954 C47.860649,63.741954 45.726649,62.857954 44.120649,61.252954 L17.736,42.258 C16.09,40.613 15.19,38.337 15.257,36.003 15.189,33.666 16.091317,31.389 17.737317,29.742 L29.656036,13.830155 C31.261036,12.225155 33.393719,11.341155 35.664719,11.341155 37.935719,11.341155 40.069719,12.225155 41.674719,13.830155 44.988719,17.144155 44.988719,22.537155 41.674719,25.851155 L35.519,36 56.141649,49.230954 C59.455649,52.544954 59.455649,57.937954 56.141649,61.251954 54.535649,62.857954 52.401649,63.741954 50.131649,63.741954 z M35.664719,15.341155 C34.462719,15.341155 33.332719,15.809155 32.482719,16.658155 L21.038,32.57 C20.147,33.463 20.205,34.654 20.205,35.925 20.205,35.976 20.205,36.026 20.205,36.076 20.205,37.347 20.147,38.537 21.038,39.429 L47.186649,58.423954 C48.036649,59.273954 49.048649,59.740954 50.249649,59.740954 51.452649,59.740954 52.522649,59.272954 53.372649,58.423954 55.127649,56.668954 55.097649,53.813954 53.342649,52.058954 L31.292,37.414 C30.511,36.633 30.504,35.367 31.285,34.586 L38.850719,23.024155 C40.604719,21.269155 40.603719,18.414155 38.849719,16.659155 37.999719,15.809155 36.866719,15.341155 35.664719,15.341155 z" >
<Path.RenderTransform>
<TransformGroup>
<TranslateTransform X="-42"/>
<RotateTransform Angle="-90"/>
</TransformGroup>
</Path.RenderTransform>
</Path>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ButtonClose" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="Red" Height="50" Width="50" CornerRadius="300">
<Path Fill="#FFF4F4F5" HorizontalAlignment="Center" Height="35" Stroke="Black" Stretch="Fill" VerticalAlignment="Center" Width="30">
<Path.Data>
<PathGeometry Figures="M37.037194,7.2021995 L26.096188,22.041976 14.061081,7.7110519 11.032645,10.623983 23.403814,25.437079 12.403519,38.730884 16.26689,41.383471 26.112258,28.966796 36.580749,41.639484 40.194871,38.695324 28.770656,25.593375 40.319496,9.9857232 z"/>
</Path.Data>
</Path>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ButtonUp" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
+4 -2
View File
@@ -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="{Binding CurrentWindowStyle}" WindowState="{Binding CurrentWindowState}"
mc:Ignorable="d" WindowStyle="None" WindowState="{Binding CurrentWindowState}"
Title="TV" Height="450" Width="800">
<Window.Resources>
<local:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverterKey"/>
@@ -26,11 +26,13 @@
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<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="70" Width="70" Margin="10,0,0,0" Style="{DynamicResource ButtonFullScreen}" Command="{Binding FullscreenCommand}" />
<Button Grid.Column="3" Height="70" Width="70" 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 ButtonClose}" Command="{Binding CloseAppCommand}" />
</Grid>
<ContentControl Grid.Row="1" Name="ControlContainer" Content="{Binding Control}" />
</Grid>
+2 -1
View File
@@ -12,7 +12,8 @@
<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>
<Button HorizontalAlignment="Center" Height="70" Width="70" Style="{DynamicResource ButtonConfirm}" Command="{Binding SaveCommand}" />
</StackPanel>
</StackPanel>
</UserControl>
+8 -12
View File
@@ -37,14 +37,8 @@ namespace TV_Player
set => SetProperty(ref _currentWindowState, value);
}
private WindowStyle _currentWindowStyle;
public WindowStyle CurrentWindowStyle
{
get => _currentWindowStyle;
set => SetProperty(ref _currentWindowStyle, value);
}
public ICommand FullscreenCommand { get; }
public ICommand CloseAppCommand { get; }
public Action ButtonBackAction { get; set; }
public ICommand BackCommand { get; }
@@ -57,24 +51,26 @@ namespace TV_Player
BackCommand = new RelayCommand(OnButtonBackClick);
FullscreenCommand = new RelayCommand(OnFullSctreenButtonClick);
SettingsCommand = new RelayCommand(OnSettingsButtonClick);
CurrentWindowStyle = WindowStyle.SingleBorderWindow;
CloseAppCommand = new RelayCommand(OnCloseAppButtonClick);
}
public void OnFullSctreenButtonClick()
{
if (CurrentWindowStyle == WindowStyle.SingleBorderWindow)
if (CurrentWindowState == WindowState.Normal)
{
CurrentWindowStyle = WindowStyle.None;
CurrentWindowState = WindowState.Maximized;
}
else
{
CurrentWindowStyle = WindowStyle.SingleBorderWindow;
CurrentWindowState = WindowState.Normal;
}
}
private void OnCloseAppButtonClick()
{
Environment.Exit(0);
}
private void OnButtonBackClick()
{
+2 -1
View File
@@ -4,7 +4,8 @@
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:TV_Player.MAUI"
Shell.FlyoutBehavior="Disabled">
Shell.FlyoutBehavior="Disabled"
Background="DarkGray">
<ShellContent
ContentTemplate="{DataTemplate local:MainPage}"
+2
View File
@@ -2,9 +2,11 @@
{
public partial class AppShell : Shell
{
private TVPlayerViewModel _tvPlayer;
public AppShell()
{
InitializeComponent();
_tvPlayer = new TVPlayerViewModel();
}
}
}
+15 -11
View File
@@ -15,20 +15,24 @@
SelectedItem="{Binding SelectedItem}"
SelectionChangedCommand="{Binding ItemSelectedCommand}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="local:M3UInfo">
<Label Text="{Binding Name}"/>
<DataTemplate x:DataType="local:GroupInfo">
<Border x:Name="ButtonBorder">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="0.4*"/>
</Grid.RowDefinitions>
<Rectangle RadiusX="15" RadiusY="15" x:Name="Border" StrokeThickness="2" Stroke="Yellow" Grid.RowSpan="2" Fill="#B0000000"/>
<Label x:Name="groupName" Text="{TemplateBinding GroupName}" FontSize="15" TextColor="White" HorizontalOptions="Center" VerticalOptions="Center"/>
<HorizontalStackLayout Grid.Row="1" >
<Label Text="{TemplateBinding ProgramsCount}" FontSize="10" TextColor="White" HorizontalOptions="Center" VerticalOptions="Center" LineHeight="10"/>
<Label FontSize="10" TextColor="White" HorizontalOptions="Center" VerticalOptions="Center" LineHeight="10">программ</Label >
</HorizontalStackLayout>
</Grid>
</Border>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<!--<Label
Text="Welcome to .NET Multi-platform App UI"
SemanticProperties.HeadingLevel="Level2"
SemanticProperties.Description="Welcome to dot net Multi platform App U I"
FontSize="18"
HorizontalOptions="Center" />-->
</VerticalStackLayout>
</ScrollView>
+2 -6
View File
@@ -1,5 +1,5 @@
using CommunityToolkit.Maui;
using Microsoft.Extensions.Logging;
using CommunityToolkit.Maui;
namespace TV_Player.MAUI
{
@@ -17,10 +17,6 @@ namespace TV_Player.MAUI
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
#if DEBUG
builder.Logging.AddDebug();
#endif
return builder.Build();
}
}
+3 -8
View File
@@ -1,11 +1,9 @@
using LibVLCSharp.Shared;
namespace TV_Player.MAUI
{
public class MediaViewer : ContentView
{
private LibVLC _libVLC;
private MediaPlayer _mediaPlayer;
//private MediaPlayer _mediaPlayer;
public static BindableProperty StreamUrlProperty = BindableProperty.Create(nameof(StreamUrl)
, typeof(string)
@@ -29,16 +27,13 @@ namespace TV_Player.MAUI
private void InitializeMediaPlayer()
{
_libVLC = new LibVLC();
_mediaPlayer = new MediaPlayer(_libVLC);
// var media = new Media(_libVLC, new Uri("http://ost.da-tv.vip/uPVtzdGJfdG9rZW5dIiwibCI6ImE3MWU3N2ZhIiwicCI6ImE3MWU3N2ZhODM1YjMyMTYiLCJjIjoiNDk3IiwidCI6ImUzNjAwZTEwZmFmMGVhYjhhYWY1YTU2YzRkN2VjZTE5IiwiZCI6IjIzMTQ2IiwiciI6IjIzMDM4IiwibSI6InR2IiwiZHQiOiIwIn0eyJ1IjoiaHR0cDovLzQ1LjkzLjQ2LjI3Ojg4ODcvODM2MS92aWRlby5tM3U4P3Rva2V/video.m3u8"));
//_mediaPlayer.Play();
}
public void Play()
{
var media = new Media(_libVLC, new Uri("http://ost.da-tv.vip/uPVtzdGJfdG9rZW5dIiwibCI6ImE3MWU3N2ZhIiwicCI6ImE3MWU3N2ZhODM1YjMyMTYiLCJjIjoiNDk3IiwidCI6ImUzNjAwZTEwZmFmMGVhYjhhYWY1YTU2YzRkN2VjZTE5IiwiZCI6IjIzMTQ2IiwiciI6IjIzMDM4IiwibSI6InR2IiwiZHQiOiIwIn0eyJ1IjoiaHR0cDovLzQ1LjkzLjQ2LjI3Ojg4ODcvODM2MS92aWRlby5tM3U4P3Rva2V/video.m3u8"));
_mediaPlayer.Play(media);
}
}
}
+2 -13
View File
@@ -1,23 +1,12 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TV_Player.MAUI.PlayerPage"
xmlns:local="clr-namespace:TV_Player.MAUI"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
Title="PlayerPage"
xmlns:vlc="clr-namespace:LibVLCSharp.WPF;assembly=LibVLCSharp.WPF"
Loaded="ContentPage_Loaded">
>
<StackLayout>
<!--<toolkit:MediaElement x:Name="mediaElement"
ShouldAutoPlay="True"
ShouldShowPlaybackControls="True"
Source="http://ost.da-tv.vip/uPVtzdGJfdG9rZW5dIiwibCI6ImE3MWU3N2ZhIiwicCI6ImE3MWU3N2ZhODM1YjMyMTYiLCJjIjoiNDk3IiwidCI6ImUzNjAwZTEwZmFmMGVhYjhhYWY1YTU2YzRkN2VjZTE5IiwiZCI6IjIzMTQ2IiwiciI6IjIzMDM4IiwibSI6InR2IiwiZHQiOiIwIn0eyJ1IjoiaHR0cDovLzQ1LjkzLjQ2LjI3Ojg4ODcvODM2MS92aWRlby5tM3U4P3Rva2V/video.m3u8"
HeightRequest="300"
WidthRequest="400"
/>-->
<!--<local:MediaViewer x:Name="libVLCSharpView" WidthRequest="300" HeightRequest="300" HorizontalOptions="CenterAndExpand" StreamUrl="{Binding URLSource,Source={x:Reference MyCustomView}}"/>-->
<vlc:VideoView x:Name="VideoView" Panel.ZIndex="1"/>
<toolkit:MediaElement x:Name="mediaElement"/>
</StackLayout>
</ContentPage>
+48 -81
View File
@@ -1,111 +1,78 @@
using LibVLCSharp.Shared;
namespace TV_Player.MAUI;
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using System.Timers;
public partial class PlayerPage : ContentPage
{
public static BindableProperty StreamUrlProperty = BindableProperty.Create(nameof(StreamUrl)
, typeof(string)
, typeof(MediaViewer)
, ""
, defaultBindingMode: BindingMode.TwoWay);
public string StreamUrl
{
get => (string)GetValue(StreamUrlProperty);
set
{
SetValue(StreamUrlProperty, value);
}
}
private const string StreamUrl = "http://ost.da-tv.vip/uPVtzdGJfdG9rZW5dIiwibCI6ImE3MWU3N2ZhIiwicCI6ImE3MWU3N2ZhODM1YjMyMTYiLCJjIjoiOTcyIiwidCI6ImUzNjAwZTEwZmFmMGVhYjhhYWY1YTU2YzRkN2VjZTE5IiwiZCI6IjIzMTQ2IiwiciI6IjIzMDM4IiwibSI6InR2IiwiZHQiOiIwIn0eyJ1IjoiaHR0cDovLzQ1LjkzLjQ2LjI3Ojg4ODcvODQwMC92aWRlby5tM3U4P3Rva2V/tracks-v1a1/mono.m3u8?cid=972&did=23146&m=1&rid=23038&token=e3600e10faf0eab8aaf5a56c4d7ece19";
//MediaPlayer _mediaPlayer;
private const int RefreshIntervalMs = 1000; // Refresh interval in milliseconds
private readonly string tempFilePath;
private Timer timer;
LibVLC _libVLC;
MediaPlayer _mediaPlayer;
public VideoPlayer()
public PlayerPage()
{
InitializeComponent();
_libVLC = new LibVLC(enableDebugLogs: true);
_mediaPlayer = new MediaPlayer(_libVLC);
tempFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "temp_media.mp4");
StartStreaming();
}
VideoView.Loaded += (sender, e) =>
private void StartStreaming()
{
try
{
VideoView.MediaPlayer = _mediaPlayer;
VideoView.MouseLeftButtonDown += VideoView_MouseLeftButtonDown;
VideoView.MediaPlayer.EnableMouseInput = false;
VideoView.PreviewMouseLeftButtonDown += VideoView_MouseLeftButtonDown;
AutoPlay();
};
Unloaded += VideoPlayer_Unloaded;
}
private void VideoView_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
ToggleOverlay();
}
private void VideoPlayer_Unloaded(object sender, RoutedEventArgs e)
{
VideoView.Dispose();
}
void PauseButton_Click(object sender, RoutedEventArgs e)
{
if (VideoView.MediaPlayer.IsPlaying)
// Initialize the timer
timer = new Timer(RefreshIntervalMs);
timer.Elapsed += async (sender, e) => await UpdateTempFile();
timer.AutoReset = true;
timer.Start();
}
catch (Exception ex)
{
VideoView.MediaPlayer.Pause();
Console.WriteLine($"Error starting stream: {ex.Message}");
}
}
private void AutoPlay()
private async Task UpdateTempFile()
{
if (!VideoView.MediaPlayer.IsPlaying)
try
{
// Download the stream data
byte[] streamData = await DownloadStreamData(StreamUrl);
using (var media = new Media(_libVLC, new Uri(SourceUrl)))
VideoView.MediaPlayer.Play(media);
// Write the stream data to the temporary file
if (streamData != null)
{
await File.WriteAllBytesAsync(tempFilePath, streamData);
}
}
catch (Exception ex)
{
Console.WriteLine($"Error updating temporary file: {ex.Message}");
}
}
private void MyUserControl_MouseDown(object sender, MouseButtonEventArgs e)
private async Task<byte[]> DownloadStreamData(string streamUrl)
{
ToggleOverlay();
}
private void MyUserControl_TouchDown(object sender, TouchEventArgs e)
{
ToggleOverlay();
}
private void ToggleOverlay()
{
if (overlayPanel.Visibility == Visibility.Visible)
using (HttpClient client = new HttpClient())
{
HideOverlay();
}
else
{
ShowOverlay();
// Download the stream asynchronously
return await client.GetByteArrayAsync(streamUrl);
}
}
public void ShowOverlay()
protected override void OnAppearing()
{
overlayPanel.Visibility = Visibility.Visible;
}
public void HideOverlay()
{
overlayPanel.Visibility = Visibility.Collapsed;
}
private void UserControl_Unloaded(object sender, RoutedEventArgs e)
{
VideoView.MediaPlayer?.Dispose();
base.OnAppearing();
// Set the MediaElement source to the temporary file when the page appears
mediaElement.Source = tempFilePath;
mediaElement.ShouldAutoPlay = true; // Auto-play the media
}
}
+10 -16
View File
@@ -46,23 +46,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Maui.MediaElement" Version="3.0.1" />
<PackageReference Include="LibVLCSharp" Version="3.8.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="7.0.0" />
<PackageReference Include="CommunityToolkit.Maui.MediaElement" Version="3.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0" />
<PackageReference Include="System.IO" Version="4.3.0" />
<PackageReference Include="System.Reactive" Version="6.0.0" />
<PackageReference Include="VideoLAN.LibVLC.Windows" Version="3.0.20" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0-ios'">
<PackageReference Include="CommunityToolkit.Maui">
<Version>7.0.1</Version>
</PackageReference>
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0-windows10.0.19041.0'">
<PackageReference Include="CommunityToolkit.Maui">
<Version>7.0.1</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
@@ -86,4 +73,11 @@
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Update="Microsoft.Maui.Controls" Version="8.0.20" />
</ItemGroup>
<ItemGroup>
<PackageReference Update="Microsoft.Maui.Controls.Compatibility" Version="8.0.20" />
</ItemGroup>
</Project>
+1
View File
@@ -1,5 +1,6 @@
namespace TV_Player.MAUI
{
[Serializable]
public class GroupInfo
{
public string Name { get; set; }
+124 -13
View File
@@ -1,14 +1,34 @@
using System.Net.Http.Headers;
using System.Text.RegularExpressions;
using System.Xml;
namespace TV_Player.MAUI
{
public class ProgramInfo : ObservableViewModelBase
{
private string _title;
private int _durationValue;
public string Title { get => _title; set => SetProperty(ref _title, value); }
public DateTime StartTime { get; set; }
public DateTime StopTime { get; set; }
public int DurationValue { get => _durationValue; set => SetProperty(ref _durationValue, value); }
}
public class ProgramGuide
{
public string Id { get; set; }
public string DisplayName { get; set; }
public List<ProgramInfo> Programs { get; set; } = new List<ProgramInfo>();
}
public static class M3UParser
{
public static async Task<List<M3UInfo>> DownloadM3UFromWebAsync(string url)
{
List<M3UInfo> playlistItems = new List<M3UInfo>();
public static async Task<List<ProgramGuide>> DownloadGuideFromWebAsync(string url)
{
List<ProgramGuide> epgChannels = new List<ProgramGuide>(); ;
using (var client = new HttpClient())
using (var request = new HttpRequestMessage())
{
@@ -18,14 +38,84 @@ namespace TV_Player.MAUI
var response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
// Parse M3U content
playlistItems = ParseM3UFromString(responseBody);
epgChannels = ParseEpg(responseBody);
}
return playlistItems;
return epgChannels;
}
static string[] SplitStringBeforeSeparator(string input, string separator)
private static List<ProgramGuide> ParseEpg(string epgData)
{
List<ProgramGuide> epgChannels = new List<ProgramGuide>();
XmlReaderSettings settings = new XmlReaderSettings();
settings.IgnoreWhitespace = true;
settings.IgnoreComments = true;
using (XmlReader reader = XmlReader.Create(new System.IO.StringReader(epgData), settings))
{
try
{
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "channel")
{
ProgramGuide channel = new ProgramGuide();
channel.Id = reader.GetAttribute("id");
reader.Read();
channel.DisplayName = reader.ReadElementContentAsString();
epgChannels.Add(channel);
continue;
}
if (reader.NodeType == XmlNodeType.Element && reader.Name == "programme")
{
ProgramInfo program = new ProgramInfo();
var id = reader.GetAttribute("channel");
var channel = epgChannels.FirstOrDefault(x => x.Id == id);
program.StartTime = DateTime.ParseExact(reader.GetAttribute("start"), "yyyyMMddHHmmss zzz", null);
program.StopTime = DateTime.ParseExact(reader.GetAttribute("stop"), "yyyyMMddHHmmss zzz", null);
reader.Read();
program.Title = reader.ReadElementContentAsString();
channel.Programs.Add(program);
}
else if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "channel")
{
break;
}
}
}
catch{}
}
return epgChannels;
}
private static async Task<string> ReadFile(string url)
{
string responseBody;
using (var client = new HttpClient())
using (var request = new HttpRequestMessage())
{
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/text"));
request.Method = HttpMethod.Get;
request.RequestUri = new Uri(url);
var response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
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
return ParseM3UFromString(fileData);
}
private static string[] SplitStringBeforeSeparator(string input, string separator)
{
string[] parts = input.Split(separator);
@@ -43,10 +133,10 @@ namespace TV_Player.MAUI
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");
@@ -60,6 +150,10 @@ namespace TV_Player.MAUI
playlistItems.Add(m3uInfo);
}
}
if (line.StartsWith("#EXTM3U"))
{
programGuideLink=ExtractXtvgUrl(line);
}
}
}
catch (Exception ex)
@@ -67,11 +161,10 @@ namespace TV_Player.MAUI
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>.*)$";
@@ -96,5 +189,23 @@ namespace TV_Player.MAUI
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;
}
}
}
+4 -1
View File
@@ -4,6 +4,7 @@ namespace TV_Player.MAUI
{
public class MainViewModel : ObservableViewModelBase
{
private IDisposable _groupsSubscriber;
private List<GroupInfo> _programs;
public List<GroupInfo> Programs
{
@@ -17,7 +18,7 @@ namespace TV_Player.MAUI
public MainViewModel()
{
ItemSelectedCommand = new Command(OnItemSelected);
ProgramsData.Instance.GroupsInformation.Subscribe(x=>Programs = x);
_groupsSubscriber = TVPlayerViewModel.Instance.PlaylistData.GroupsInformation.Subscribe(x => Programs = x);
}
private void OnItemSelected()
@@ -33,6 +34,8 @@ namespace TV_Player.MAUI
};
// Navigate to the OtherPage
navigation.PushAsync(programPage);
_groupsSubscriber.Dispose();
}
}
}
+3 -3
View File
@@ -1,5 +1,4 @@
using LibVLCSharp.Shared;
using System.Windows.Input;
using System.Windows.Input;
namespace TV_Player.MAUI
{
@@ -26,6 +25,7 @@ namespace TV_Player.MAUI
//_libVLC = new LibVLC();
//_mediaPlayer = new MediaPlayer(new Media(_libVLC, new Uri(_currentProgram.Url)));
//_mediaPlayer.Play();
URLSource = _currentProgram.Url;
PlayCommand = new Command(OnPlayButtonClicked);
}
@@ -40,7 +40,7 @@ namespace TV_Player.MAUI
{
URLSource = _currentProgram.Url;
}
catch (Exception ex)
catch
{
// Handle exceptions
}
+4 -1
View File
@@ -4,6 +4,7 @@ namespace TV_Player.MAUI
{
public class ProgramViewModel : ObservableViewModelBase
{
private IDisposable _programSubscriber;
private List<M3UInfo> _programs;
public List<M3UInfo> Programs
{
@@ -17,7 +18,7 @@ namespace TV_Player.MAUI
public ProgramViewModel(GroupInfo groupInfo)
{
ItemSelectedCommand = new Command(OnItemSelected);
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());
}
private void OnItemSelected()
@@ -33,6 +34,8 @@ namespace TV_Player.MAUI
};
// Navigate to the OtherPage
navigation.PushAsync(playerPage);
_programSubscriber.Dispose();
}
}
}
+23 -17
View File
@@ -4,36 +4,42 @@ namespace TV_Player.MAUI
{
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>>();
public IObservable<List<M3UInfo>> AllPrograms => programsSubject;
public IObservable<List<GroupInfo>> GroupsInformation => groupsSubject;
private ProgramsData()
public IObservable<List<ProgramGuide>> ProgramGuideInfo => programGuideSubject;
public ProgramsData()
{
_=Initialize();
}
private async Task Initialize()
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(string guideLink)
{
//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));
}
}
}
+28
View File
@@ -0,0 +1,28 @@
namespace TV_Player.MAUI
{
public class TVPlayerViewModel
{
public ProgramsData PlaylistData { get; private set; }
public Action ButtonBackAction { get; set; }
private static TVPlayerViewModel _instance;
public static TVPlayerViewModel Instance
{
get
{
if (_instance == null)
_instance = new TVPlayerViewModel();
return _instance;
}
}
public TVPlayerViewModel()
{
PlaylistData = new ProgramsData();
PlaylistData.GetData("http://pl.da-tv.vip/a71e77fa/835b3216/tv.m3u");
}
}
}