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> <Application.Resources>
<ResourceDictionary> <ResourceDictionary>
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Assets/GroupButtonStyle.xaml"/> <ResourceDictionary Source="/Assets/AppStyle.xaml"/>
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
</ResourceDictionary> </ResourceDictionary>
</Application.Resources> </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> </Setter>
</Style> </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}"> <Style x:Key="ButtonUp" TargetType="{x:Type Button}">
<Setter Property="Template"> <Setter Property="Template">
<Setter.Value> <Setter.Value>
+4 -2
View File
@@ -4,7 +4,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TV_Player" 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"> Title="TV" Height="450" Width="800">
<Window.Resources> <Window.Resources>
<local:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverterKey"/> <local:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverterKey"/>
@@ -26,11 +26,13 @@
<ColumnDefinition Width="80"/> <ColumnDefinition Width="80"/>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
<ColumnDefinition Width="80"/> <ColumnDefinition Width="80"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Button Height="70" Width="70" Margin="10,0,0,0" Style="{DynamicResource ButtonGear}" Command="{Binding SettingsCommand}" /> <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}" /> <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"/> <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> </Grid>
<ContentControl Grid.Row="1" Name="ControlContainer" Content="{Binding Control}" /> <ContentControl Grid.Row="1" Name="ControlContainer" Content="{Binding Control}" />
</Grid> </Grid>
+2 -1
View File
@@ -12,7 +12,8 @@
<CheckBox Margin="10" Foreground="White" FontSize="25" IsChecked="{Binding StartLastScreen}">Запоминать последний выбор</CheckBox> <CheckBox Margin="10" Foreground="White" FontSize="25" IsChecked="{Binding StartLastScreen}">Запоминать последний выбор</CheckBox>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center"> <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 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>
</StackPanel> </StackPanel>
</UserControl> </UserControl>
+8 -12
View File
@@ -37,14 +37,8 @@ namespace TV_Player
set => SetProperty(ref _currentWindowState, value); set => SetProperty(ref _currentWindowState, value);
} }
private WindowStyle _currentWindowStyle;
public WindowStyle CurrentWindowStyle
{
get => _currentWindowStyle;
set => SetProperty(ref _currentWindowStyle, value);
}
public ICommand FullscreenCommand { get; } public ICommand FullscreenCommand { get; }
public ICommand CloseAppCommand { get; }
public Action ButtonBackAction { get; set; } public Action ButtonBackAction { get; set; }
public ICommand BackCommand { get; } public ICommand BackCommand { get; }
@@ -57,24 +51,26 @@ namespace TV_Player
BackCommand = new RelayCommand(OnButtonBackClick); BackCommand = new RelayCommand(OnButtonBackClick);
FullscreenCommand = new RelayCommand(OnFullSctreenButtonClick); FullscreenCommand = new RelayCommand(OnFullSctreenButtonClick);
SettingsCommand = new RelayCommand(OnSettingsButtonClick); SettingsCommand = new RelayCommand(OnSettingsButtonClick);
CloseAppCommand = new RelayCommand(OnCloseAppButtonClick);
CurrentWindowStyle = WindowStyle.SingleBorderWindow;
} }
public void OnFullSctreenButtonClick() public void OnFullSctreenButtonClick()
{ {
if (CurrentWindowStyle == WindowStyle.SingleBorderWindow) if (CurrentWindowState == WindowState.Normal)
{ {
CurrentWindowStyle = WindowStyle.None;
CurrentWindowState = WindowState.Maximized; CurrentWindowState = WindowState.Maximized;
} }
else else
{ {
CurrentWindowStyle = WindowStyle.SingleBorderWindow;
CurrentWindowState = WindowState.Normal; CurrentWindowState = WindowState.Normal;
} }
} }
private void OnCloseAppButtonClick()
{
Environment.Exit(0);
}
private void OnButtonBackClick() private void OnButtonBackClick()
{ {
+2 -1
View File
@@ -4,7 +4,8 @@
xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:TV_Player.MAUI" xmlns:local="clr-namespace:TV_Player.MAUI"
Shell.FlyoutBehavior="Disabled"> Shell.FlyoutBehavior="Disabled"
Background="DarkGray">
<ShellContent <ShellContent
ContentTemplate="{DataTemplate local:MainPage}" ContentTemplate="{DataTemplate local:MainPage}"
+2
View File
@@ -2,9 +2,11 @@
{ {
public partial class AppShell : Shell public partial class AppShell : Shell
{ {
private TVPlayerViewModel _tvPlayer;
public AppShell() public AppShell()
{ {
InitializeComponent(); InitializeComponent();
_tvPlayer = new TVPlayerViewModel();
} }
} }
} }
+15 -11
View File
@@ -15,20 +15,24 @@
SelectedItem="{Binding SelectedItem}" SelectedItem="{Binding SelectedItem}"
SelectionChangedCommand="{Binding ItemSelectedCommand}"> SelectionChangedCommand="{Binding ItemSelectedCommand}">
<CollectionView.ItemTemplate> <CollectionView.ItemTemplate>
<DataTemplate x:DataType="local:M3UInfo"> <DataTemplate x:DataType="local:GroupInfo">
<Label Text="{Binding Name}"/> <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> </DataTemplate>
</CollectionView.ItemTemplate> </CollectionView.ItemTemplate>
</CollectionView> </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> </VerticalStackLayout>
</ScrollView> </ScrollView>
+2 -6
View File
@@ -1,5 +1,5 @@
using CommunityToolkit.Maui;
using Microsoft.Extensions.Logging; using CommunityToolkit.Maui;
namespace TV_Player.MAUI namespace TV_Player.MAUI
{ {
@@ -17,10 +17,6 @@ namespace TV_Player.MAUI
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
}); });
#if DEBUG
builder.Logging.AddDebug();
#endif
return builder.Build(); return builder.Build();
} }
} }
+3 -8
View File
@@ -1,11 +1,9 @@
using LibVLCSharp.Shared;
namespace TV_Player.MAUI namespace TV_Player.MAUI
{ {
public class MediaViewer : ContentView public class MediaViewer : ContentView
{ {
private LibVLC _libVLC; //private MediaPlayer _mediaPlayer;
private MediaPlayer _mediaPlayer;
public static BindableProperty StreamUrlProperty = BindableProperty.Create(nameof(StreamUrl) public static BindableProperty StreamUrlProperty = BindableProperty.Create(nameof(StreamUrl)
, typeof(string) , typeof(string)
@@ -29,16 +27,13 @@ namespace TV_Player.MAUI
private void InitializeMediaPlayer() 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")); // var media = new Media(_libVLC, new Uri("http://ost.da-tv.vip/uPVtzdGJfdG9rZW5dIiwibCI6ImE3MWU3N2ZhIiwicCI6ImE3MWU3N2ZhODM1YjMyMTYiLCJjIjoiNDk3IiwidCI6ImUzNjAwZTEwZmFmMGVhYjhhYWY1YTU2YzRkN2VjZTE5IiwiZCI6IjIzMTQ2IiwiciI6IjIzMDM4IiwibSI6InR2IiwiZHQiOiIwIn0eyJ1IjoiaHR0cDovLzQ1LjkzLjQ2LjI3Ojg4ODcvODM2MS92aWRlby5tM3U4P3Rva2V/video.m3u8"));
//_mediaPlayer.Play(); //_mediaPlayer.Play();
} }
public void 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" <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TV_Player.MAUI.PlayerPage" x:Class="TV_Player.MAUI.PlayerPage"
xmlns:local="clr-namespace:TV_Player.MAUI" xmlns:local="clr-namespace:TV_Player.MAUI"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit" xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
Title="PlayerPage" Title="PlayerPage"
xmlns:vlc="clr-namespace:LibVLCSharp.WPF;assembly=LibVLCSharp.WPF" >
Loaded="ContentPage_Loaded">
<StackLayout> <StackLayout>
<!--<toolkit:MediaElement x:Name="mediaElement" <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"/>
</StackLayout> </StackLayout>
</ContentPage> </ContentPage>
+50 -83
View File
@@ -1,111 +1,78 @@
using LibVLCSharp.Shared;
namespace TV_Player.MAUI; 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 partial class PlayerPage : ContentPage
{ {
public static BindableProperty StreamUrlProperty = BindableProperty.Create(nameof(StreamUrl)
, typeof(string) private const string StreamUrl = "http://ost.da-tv.vip/uPVtzdGJfdG9rZW5dIiwibCI6ImE3MWU3N2ZhIiwicCI6ImE3MWU3N2ZhODM1YjMyMTYiLCJjIjoiOTcyIiwidCI6ImUzNjAwZTEwZmFmMGVhYjhhYWY1YTU2YzRkN2VjZTE5IiwiZCI6IjIzMTQ2IiwiciI6IjIzMDM4IiwibSI6InR2IiwiZHQiOiIwIn0eyJ1IjoiaHR0cDovLzQ1LjkzLjQ2LjI3Ojg4ODcvODQwMC92aWRlby5tM3U4P3Rva2V/tracks-v1a1/mono.m3u8?cid=972&did=23146&m=1&rid=23038&token=e3600e10faf0eab8aaf5a56c4d7ece19";
, typeof(MediaViewer) //MediaPlayer _mediaPlayer;
, "" private const int RefreshIntervalMs = 1000; // Refresh interval in milliseconds
, defaultBindingMode: BindingMode.TwoWay); private readonly string tempFilePath;
private Timer timer;
public string StreamUrl public PlayerPage()
{
get => (string)GetValue(StreamUrlProperty);
set
{
SetValue(StreamUrlProperty, value);
}
}
LibVLC _libVLC;
MediaPlayer _mediaPlayer;
public VideoPlayer()
{ {
InitializeComponent(); InitializeComponent();
_libVLC = new LibVLC(enableDebugLogs: true); tempFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "temp_media.mp4");
_mediaPlayer = new MediaPlayer(_libVLC); StartStreaming();
}
VideoView.Loaded += (sender, e) => private void StartStreaming()
{
try
{ {
VideoView.MediaPlayer = _mediaPlayer; // Initialize the timer
VideoView.MouseLeftButtonDown += VideoView_MouseLeftButtonDown; timer = new Timer(RefreshIntervalMs);
VideoView.MediaPlayer.EnableMouseInput = false; timer.Elapsed += async (sender, e) => await UpdateTempFile();
VideoView.PreviewMouseLeftButtonDown += VideoView_MouseLeftButtonDown; timer.AutoReset = true;
AutoPlay(); timer.Start();
}; }
Unloaded += VideoPlayer_Unloaded; catch (Exception ex)
}
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)
{ {
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))) // Write the stream data to the temporary file
VideoView.MediaPlayer.Play(media); if (streamData != null)
{
await File.WriteAllBytesAsync(tempFilePath, streamData);
}
}
catch (Exception ex)
{
Console.WriteLine($"Error updating temporary file: {ex.Message}");
} }
} }
private async Task<byte[]> DownloadStreamData(string streamUrl)
private void MyUserControl_MouseDown(object sender, MouseButtonEventArgs e)
{ {
ToggleOverlay(); using (HttpClient client = new HttpClient())
}
private void MyUserControl_TouchDown(object sender, TouchEventArgs e)
{
ToggleOverlay();
}
private void ToggleOverlay()
{
if (overlayPanel.Visibility == Visibility.Visible)
{ {
HideOverlay(); // Download the stream asynchronously
} return await client.GetByteArrayAsync(streamUrl);
else
{
ShowOverlay();
} }
} }
public void ShowOverlay() protected override void OnAppearing()
{ {
overlayPanel.Visibility = Visibility.Visible; base.OnAppearing();
// Set the MediaElement source to the temporary file when the page appears
mediaElement.Source = tempFilePath;
mediaElement.ShouldAutoPlay = true; // Auto-play the media
} }
}
public void HideOverlay()
{
overlayPanel.Visibility = Visibility.Collapsed;
}
private void UserControl_Unloaded(object sender, RoutedEventArgs e)
{
VideoView.MediaPlayer?.Dispose();
}
}
+10 -16
View File
@@ -46,23 +46,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CommunityToolkit.Maui.MediaElement" Version="3.0.1" /> <PackageReference Include="CommunityToolkit.Maui.MediaElement" Version="3.1.0" />
<PackageReference Include="LibVLCSharp" Version="3.8.2" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="7.0.0" /> <PackageReference Include="System.IO" Version="4.3.0" />
<PackageReference Include="System.Reactive" Version="6.0.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>
<ItemGroup> <ItemGroup>
@@ -86,4 +73,11 @@
</None> </None>
</ItemGroup> </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> </Project>
+1
View File
@@ -1,5 +1,6 @@
namespace TV_Player.MAUI namespace TV_Player.MAUI
{ {
[Serializable]
public class GroupInfo public class GroupInfo
{ {
public string Name { get; set; } public string Name { get; set; }
+124 -13
View File
@@ -1,14 +1,34 @@
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Xml;
namespace TV_Player.MAUI 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 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 client = new HttpClient())
using (var request = new HttpRequestMessage()) using (var request = new HttpRequestMessage())
{ {
@@ -18,14 +38,84 @@ namespace TV_Player.MAUI
var response = await client.GetAsync(url); var response = await client.GetAsync(url);
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync(); string responseBody = await response.Content.ReadAsStringAsync();
epgChannels = ParseEpg(responseBody);
// Parse M3U content
playlistItems = ParseM3UFromString(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); string[] parts = input.Split(separator);
@@ -43,10 +133,10 @@ namespace TV_Player.MAUI
return parts; return parts;
} }
static List<M3UInfo> ParseM3UFromString(string content) private static (List<M3UInfo> programList, string programGuide) ParseM3UFromString(string content)
{ {
List<M3UInfo> playlistItems = new List<M3UInfo>(); List<M3UInfo> playlistItems = new List<M3UInfo>();
string programGuideLink = string.Empty;
try try
{ {
var m3u = SplitStringBeforeSeparator(content, "#EXT"); var m3u = SplitStringBeforeSeparator(content, "#EXT");
@@ -60,6 +150,10 @@ namespace TV_Player.MAUI
playlistItems.Add(m3uInfo); playlistItems.Add(m3uInfo);
} }
} }
if (line.StartsWith("#EXTM3U"))
{
programGuideLink=ExtractXtvgUrl(line);
}
} }
} }
catch (Exception ex) catch (Exception ex)
@@ -67,11 +161,10 @@ namespace TV_Player.MAUI
Console.WriteLine("Error reading M3U file: " + ex.Message); Console.WriteLine("Error reading M3U file: " + ex.Message);
} }
return playlistItems; return (playlistItems,programGuideLink);
} }
private static bool TryParseM3ULine(string m3uLine, out M3UInfo? info)
static bool TryParseM3ULine(string m3uLine, out M3UInfo? info)
{ {
info = null; 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>.*)$"; 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; 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 public class MainViewModel : ObservableViewModelBase
{ {
private IDisposable _groupsSubscriber;
private List<GroupInfo> _programs; private List<GroupInfo> _programs;
public List<GroupInfo> Programs public List<GroupInfo> Programs
{ {
@@ -17,7 +18,7 @@ namespace TV_Player.MAUI
public MainViewModel() public MainViewModel()
{ {
ItemSelectedCommand = new Command(OnItemSelected); ItemSelectedCommand = new Command(OnItemSelected);
ProgramsData.Instance.GroupsInformation.Subscribe(x=>Programs = x); _groupsSubscriber = TVPlayerViewModel.Instance.PlaylistData.GroupsInformation.Subscribe(x => Programs = x);
} }
private void OnItemSelected() private void OnItemSelected()
@@ -33,6 +34,8 @@ namespace TV_Player.MAUI
}; };
// Navigate to the OtherPage // Navigate to the OtherPage
navigation.PushAsync(programPage); 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 namespace TV_Player.MAUI
{ {
@@ -26,6 +25,7 @@ namespace TV_Player.MAUI
//_libVLC = new LibVLC(); //_libVLC = new LibVLC();
//_mediaPlayer = new MediaPlayer(new Media(_libVLC, new Uri(_currentProgram.Url))); //_mediaPlayer = new MediaPlayer(new Media(_libVLC, new Uri(_currentProgram.Url)));
//_mediaPlayer.Play(); //_mediaPlayer.Play();
URLSource = _currentProgram.Url;
PlayCommand = new Command(OnPlayButtonClicked); PlayCommand = new Command(OnPlayButtonClicked);
} }
@@ -40,7 +40,7 @@ namespace TV_Player.MAUI
{ {
URLSource = _currentProgram.Url; URLSource = _currentProgram.Url;
} }
catch (Exception ex) catch
{ {
// Handle exceptions // Handle exceptions
} }
+4 -1
View File
@@ -4,6 +4,7 @@ namespace TV_Player.MAUI
{ {
public class ProgramViewModel : ObservableViewModelBase public class ProgramViewModel : ObservableViewModelBase
{ {
private IDisposable _programSubscriber;
private List<M3UInfo> _programs; private List<M3UInfo> _programs;
public List<M3UInfo> Programs public List<M3UInfo> Programs
{ {
@@ -17,7 +18,7 @@ namespace TV_Player.MAUI
public ProgramViewModel(GroupInfo groupInfo) public ProgramViewModel(GroupInfo groupInfo)
{ {
ItemSelectedCommand = new Command(OnItemSelected); 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() private void OnItemSelected()
@@ -33,6 +34,8 @@ namespace TV_Player.MAUI
}; };
// Navigate to the OtherPage // Navigate to the OtherPage
navigation.PushAsync(playerPage); navigation.PushAsync(playerPage);
_programSubscriber.Dispose();
} }
} }
} }
+25 -19
View File
@@ -4,36 +4,42 @@ namespace TV_Player.MAUI
{ {
public class ProgramsData 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<M3UInfo>> programsSubject = new ReplaySubject<List<M3UInfo>>();
private readonly ReplaySubject<List<GroupInfo>> groupsSubject = new ReplaySubject<List<GroupInfo>>(); 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<M3UInfo>> AllPrograms => programsSubject;
public IObservable<List<GroupInfo>> GroupsInformation => groupsSubject; 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"; //string m3uLink = "http://pl.da-tv.vip/a71e77fa/835b3216/tv.m3u";
var programs = await M3UParser.DownloadM3UFromWebAsync(m3uLink); var result = await M3UParser.DownloadM3UFromWebAsync(m3uLink);
programsSubject.OnNext(result.programList);
programsSubject.OnNext(programs); var groupping = result.programList.GroupBy(item => item.GroupTitle)
var groupping = programs.GroupBy(item => item.GroupTitle)
.Select(group => new GroupInfo() { Name = group.Key, Count = group.Count() }) .Select(group => new GroupInfo() { Name = group.Key, Count = group.Count() })
.ToList(); .ToList();
groupsSubject.OnNext(groupping); 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");
}
}
}