Compare commits
52 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 60ed10ffef | |||
| d62139afc9 | |||
| 2b8ed17f52 | |||
| 1505218706 | |||
| 085a904444 | |||
| 30ac4ede6c | |||
| 59ab798b74 | |||
| 0da4161fa9 | |||
| 732ce0ce78 | |||
| 84203db798 | |||
| 282e5c3f5a | |||
| 2ac99425af | |||
| 75526a7d43 | |||
| b0e9e1022f | |||
| b9b5d1c923 | |||
| 4880b25e90 | |||
| dbd7aad30d | |||
| 951e02f0fb | |||
| d033411dfc | |||
| 4db5dd6f23 | |||
| b3d1e2e851 | |||
| 5bf1f06bfe | |||
| faa852016d | |||
| 5e8014c127 | |||
| 6e10f0b979 | |||
| 6249c6d149 | |||
| 27b5b6805f | |||
| 58ffc7300b | |||
| 5e211cc038 | |||
| d0f9df0a64 | |||
| d22808497d | |||
| 15e7182c22 | |||
| d2eeffd200 | |||
| c1e832e08d | |||
| 362050820b | |||
| c435c0fbe2 | |||
| 499c731bf6 | |||
| 58711836e6 | |||
| a3e7fb780e | |||
| 083c1e0e20 | |||
| 449f69f835 | |||
| 33acf44d58 | |||
| 236cdcfc74 | |||
| 49adc5e15d | |||
| 72bafbcba6 | |||
| ab1954e28c | |||
| 6e172484f8 | |||
| c91944e965 | |||
| c978d66c56 | |||
| 735ab0b489 | |||
| a129f9b98e | |||
| 7702330c9a |
16 changed files with 1217 additions and 235 deletions
60
Assets/online-sources.json
Normal file
60
Assets/online-sources.json
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
{
|
||||||
|
"_comment": "Online-Streams fuer HomeStream. URLs Stand Mai 2026, Quelle: harryshomepage.de + rundfunkforum.",
|
||||||
|
"tv": [
|
||||||
|
{ "name": "Das Erste HD", "url": "https://daserste-live.ard-mcdn.de/daserste/live/hls/de/master.m3u8", "kind": "TvHd" },
|
||||||
|
{ "name": "ZDF HD", "url": "https://zdf-hls-15.akamaized.net/hls/live/2016498/de/veryhigh/master.m3u8", "kind": "TvHd" },
|
||||||
|
{ "name": "3sat HD", "url": "https://zdf-hls-18.akamaized.net/hls/live/2016501/dach/veryhigh/master.m3u8", "kind": "TvHd" },
|
||||||
|
{ "name": "ARTE HD", "url": "https://artesimulcast.akamaized.net/hls/live/2030993/artelive_de/master.m3u8", "kind": "TvHd" },
|
||||||
|
{ "name": "ARD-alpha HD", "url": "https://mcdn.br.de/br/fs/ard_alpha/hls/de/master.m3u8", "kind": "TvHd" },
|
||||||
|
{ "name": "ONE HD", "url": "https://mcdn-one.ard.de/ardone/hls/master.m3u8", "kind": "TvHd" },
|
||||||
|
{ "name": "ZDFneo HD", "url": "https://zdf-hls-16.akamaized.net/hls/live/2016499/de/veryhigh/master.m3u8", "kind": "TvHd" },
|
||||||
|
{ "name": "ZDFinfo HD", "url": "https://zdf-hls-17.akamaized.net/hls/live/2016500/de/veryhigh/master.m3u8", "kind": "TvHd" },
|
||||||
|
{ "name": "Tagesschau24 HD", "url": "https://tagesschau.akamaized.net/hls/live/2020117/tagesschau/tagesschau_3/master.m3u8", "kind": "TvHd" },
|
||||||
|
{ "name": "Phoenix HD", "url": "https://zdf-hls-19.akamaized.net/hls/live/2016502/de/veryhigh/master.m3u8", "kind": "TvHd" },
|
||||||
|
{ "name": "BR Sued HD", "url": "https://livestreams.br.de/i/bfssued_germany@119890/master.m3u8", "kind": "TvHd" },
|
||||||
|
{ "name": "WDR HD", "url": "https://wdr-wdrfernsehen-koeln.icecast.wdr.de/wdr/wdrfernsehen/koeln/hls/master.m3u8", "kind": "TvHd" },
|
||||||
|
{ "name": "NDR HD", "url": "https://ndrfs-lh.akamaihd.net/i/ndrfs_nds@430233/master.m3u8", "kind": "TvHd" },
|
||||||
|
{ "name": "HR HD", "url": "https://hr-hrfernsehen-live.akamaized.net/hls/live/2016105/hrfernsehen/master.m3u8", "kind": "TvHd" },
|
||||||
|
{ "name": "MDR Sachsen HD", "url": "https://mdrsnhls-lh.akamaihd.net/i/livetvmdr_sachsen@439432/master.m3u8", "kind": "TvHd" },
|
||||||
|
{ "name": "RBB Berlin HD", "url": "https://rbb_berlin-lh.akamaihd.net/i/rbbBerlin_Live@380294/master.m3u8", "kind": "TvHd" },
|
||||||
|
{ "name": "SWR BW HD", "url": "https://swrbw-lh.akamaihd.net/i/swrbw_live@196738/master.m3u8", "kind": "TvHd" },
|
||||||
|
{ "name": "SR Fernsehen HD", "url": "https://live2_sr-lh.akamaihd.net/i/sr_universal02@107595/master.m3u8", "kind": "TvHd" },
|
||||||
|
{ "name": "KiKA HD", "url": "https://kikageohls.akamaized.net/hls/live/2022693/livetvkika_de/master.m3u8", "kind": "TvHd" },
|
||||||
|
{ "name": "Deutsche Welle", "url": "https://dwamdstream102.akamaized.net/hls/live/2015525/dwstream102/index.m3u8", "kind": "TvHd" }
|
||||||
|
],
|
||||||
|
"radio": [
|
||||||
|
{ "name": "Bayern 1", "url": "https://dispatcher.rndfnk.com/br/br1/obb/mp3/high" },
|
||||||
|
{ "name": "Bayern 2", "url": "https://dispatcher.rndfnk.com/br/br2/sued/mp3/high" },
|
||||||
|
{ "name": "Bayern 3", "url": "https://dispatcher.rndfnk.com/br/br3/live/mp3/high" },
|
||||||
|
{ "name": "BR Klassik", "url": "https://dispatcher.rndfnk.com/br/brklassik/live/mp3/high" },
|
||||||
|
{ "name": "B5 aktuell", "url": "https://dispatcher.rndfnk.com/br/br5/live/mp3/high" },
|
||||||
|
{ "name": "Deutschlandfunk", "url": "https://st01.sslstream.dlf.de/dlf/01/128/mp3/stream.mp3" },
|
||||||
|
{ "name": "Deutschlandfunk Kultur", "url": "https://st02.sslstream.dlf.de/dlf/02/128/mp3/stream.mp3" },
|
||||||
|
{ "name": "Deutschlandfunk Nova", "url": "https://st03.sslstream.dlf.de/dlf/03/128/mp3/stream.mp3" },
|
||||||
|
{ "name": "WDR 2", "url": "https://wdr-wdr2-rheinland.icecastssl.wdr.de/wdr/wdr2/rheinland/mp3/128/stream.mp3" },
|
||||||
|
{ "name": "WDR 4", "url": "https://wdr-wdr4-live.icecastssl.wdr.de/wdr/wdr4/live/mp3/128/stream.mp3" },
|
||||||
|
{ "name": "1LIVE", "url": "https://wdr-1live-live.icecastssl.wdr.de/wdr/1live/live/mp3/128/stream.mp3" },
|
||||||
|
{ "name": "NDR 2", "url": "https://icecast.ndr.de/ndr/ndr2/niedersachsen/mp3/128/stream.mp3" },
|
||||||
|
{ "name": "N-JOY", "url": "https://icecast.ndr.de/ndr/njoy/live/mp3/128/stream.mp3" },
|
||||||
|
{ "name": "SWR1 BW", "url": "https://liveradio.swr.de/sw282p3/swr1bw/play.mp3" },
|
||||||
|
{ "name": "SWR3", "url": "https://liveradio.swr.de/sw282p3/swr3/play.mp3" },
|
||||||
|
{ "name": "HR1", "url": "https://hr-hr1-live.cast.addradio.de/hr/hr1/live/mp3/128/stream.mp3" },
|
||||||
|
{ "name": "HR3", "url": "https://hr-hr3-live.cast.addradio.de/hr/hr3/live/mp3/128/stream.mp3" },
|
||||||
|
{ "name": "MDR Sachsen", "url": "https://mdr-284350-0.cast.mdr.de/mdr/284350/0/mp3/high/stream.mp3" },
|
||||||
|
{ "name": "RBB Radio Eins", "url": "https://rbb-radioeins-live.cast.addradio.de/rbb/radioeins/live/mp3/128/stream.mp3" },
|
||||||
|
{ "name": "Antenne Bayern", "url": "https://stream.antenne.de/antenne/stream/mp3" }
|
||||||
|
],
|
||||||
|
"web": [
|
||||||
|
{ "name": "YouTube", "url": "https://www.youtube.com" },
|
||||||
|
{ "name": "ARD Mediathek", "url": "https://www.ardmediathek.de" },
|
||||||
|
{ "name": "ZDF Mediathek", "url": "https://www.zdf.de/" },
|
||||||
|
{ "name": "Netflix", "url": "https://www.netflix.com" },
|
||||||
|
{ "name": "Disney+", "url": "https://www.disneyplus.com" },
|
||||||
|
{ "name": "Joyn", "url": "https://www.joyn.de" },
|
||||||
|
{ "name": "RTL+", "url": "https://plus.rtl.de" },
|
||||||
|
{ "name": "ARTE", "url": "https://www.arte.tv/de" },
|
||||||
|
{ "name": "Apple TV+", "url": "https://tv.apple.com" },
|
||||||
|
{ "name": "Amazon Prime Video", "url": "https://www.amazon.de/gp/video/storefront" },
|
||||||
|
{ "name": "n-tv", "url": "https://www.n-tv.de" }
|
||||||
|
]
|
||||||
|
}
|
||||||
16
ClockWindow.xaml
Normal file
16
ClockWindow.xaml
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<Window x:Class="FritzTV.ClockWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
WindowStyle="None"
|
||||||
|
AllowsTransparency="True"
|
||||||
|
Background="Transparent"
|
||||||
|
ShowInTaskbar="False"
|
||||||
|
IsHitTestVisible="False"
|
||||||
|
Topmost="False"
|
||||||
|
SizeToContent="WidthAndHeight">
|
||||||
|
<Border Background="#AA000000" CornerRadius="8" Padding="20,10">
|
||||||
|
<TextBlock x:Name="TxtClock"
|
||||||
|
Foreground="White" FontSize="48" FontWeight="Light"
|
||||||
|
FontFamily="Consolas"/>
|
||||||
|
</Border>
|
||||||
|
</Window>
|
||||||
13
ClockWindow.xaml.cs
Normal file
13
ClockWindow.xaml.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace FritzTV;
|
||||||
|
|
||||||
|
public partial class ClockWindow : Window
|
||||||
|
{
|
||||||
|
public ClockWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateTime(string time) => TxtClock.Text = time;
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="LibVLCSharp.WPF" Version="3.9.7.1" />
|
<PackageReference Include="LibVLCSharp.WPF" Version="3.9.7.1" />
|
||||||
|
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3967.48" />
|
||||||
<PackageReference Include="VideoLAN.LibVLC.Windows" Version="3.0.23.1" />
|
<PackageReference Include="VideoLAN.LibVLC.Windows" Version="3.0.23.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
@ -27,4 +28,11 @@
|
||||||
<Resource Include="Assets\logo.svg" />
|
<Resource Include="Assets\logo.svg" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<!-- Online-Stream-Liste wird zur Laufzeit gelesen, daher Content (kein Resource) -->
|
||||||
|
<Content Include="Assets\online-sources.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<Window x:Class="FritzTV.MainWindow"
|
<Window x:Class="FritzTV.MainWindow"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
|
||||||
xmlns:vlc="clr-namespace:LibVLCSharp.WPF;assembly=LibVLCSharp.WPF"
|
xmlns:vlc="clr-namespace:LibVLCSharp.WPF;assembly=LibVLCSharp.WPF"
|
||||||
Title="HomeStream"
|
Title="HomeStream"
|
||||||
Height="720" Width="1280"
|
Height="720" Width="1280"
|
||||||
|
|
@ -137,13 +138,22 @@
|
||||||
ToolTip="Kategorien einklappen"/>
|
ToolTip="Kategorien einklappen"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<StackPanel DockPanel.Dock="Top">
|
<StackPanel DockPanel.Dock="Top">
|
||||||
<Button x:Name="BtnAll" Content="📺 Alle Sender" Style="{StaticResource SidebarButton}" Click="BtnCategory_Click" Tag="all"/>
|
<Button x:Name="BtnAll" Content="📺 Alle Sender" Style="{StaticResource SidebarButton}" Click="BtnCategory_Click" Tag="all"/>
|
||||||
<Button x:Name="BtnTv" Content="🎬 TV" Style="{StaticResource SidebarButton}" Click="BtnCategory_Click" Tag="tv"/>
|
|
||||||
<Button x:Name="BtnRadio" Content="📡 Radio" Style="{StaticResource SidebarButton}" Click="BtnCategory_Click" Tag="radio"/>
|
|
||||||
<Separator Margin="8" Background="#333"/>
|
<Separator Margin="8" Background="#333"/>
|
||||||
<Button x:Name="BtnFav" Content="⭐ Favoriten" Style="{StaticResource SidebarButton}" Click="BtnCategory_Click" Tag="fav"/>
|
<Button x:Name="BtnTvFritz" Content="🎬 TV (FritzBox)" Style="{StaticResource SidebarButton}" Click="BtnCategory_Click" Tag="tv-fritz"
|
||||||
|
ToolTip="DVB-C über die FritzBox"/>
|
||||||
|
<Button x:Name="BtnTvOnline" Content="🎬 TV (Online)" Style="{StaticResource SidebarButton}" Click="BtnCategory_Click" Tag="tv-online"
|
||||||
|
ToolTip="ÖR-TV über Internet (HLS)"/>
|
||||||
|
<Button x:Name="BtnRadioFritz" Content="📡 Radio (FritzBox)" Style="{StaticResource SidebarButton}" Click="BtnCategory_Click" Tag="radio-fritz"
|
||||||
|
ToolTip="DVB-C-Radio über die FritzBox"/>
|
||||||
|
<Button x:Name="BtnRadioOnline" Content="📡 Radio (Online)" Style="{StaticResource SidebarButton}" Click="BtnCategory_Click" Tag="radio-online"
|
||||||
|
ToolTip="Webradio (ÖR-Streams)"/>
|
||||||
|
<Button x:Name="BtnWeb" Content="🌐 Streaming" Style="{StaticResource SidebarButton}" Click="BtnCategory_Click" Tag="web"
|
||||||
|
ToolTip="YouTube, Netflix, Mediatheken etc. (Browser)"/>
|
||||||
<Separator Margin="8" Background="#333"/>
|
<Separator Margin="8" Background="#333"/>
|
||||||
<Button x:Name="BtnEpgGrid" Content="📅 Programm" Style="{StaticResource SidebarButton}" Click="BtnEpgGrid_Click"
|
<Button x:Name="BtnFav" Content="⭐ Favoriten" Style="{StaticResource SidebarButton}" Click="BtnCategory_Click" Tag="fav"/>
|
||||||
|
<Separator Margin="8" Background="#333"/>
|
||||||
|
<Button x:Name="BtnEpgGrid" Content="📅 Programm" Style="{StaticResource SidebarButton}" Click="BtnEpgGrid_Click"
|
||||||
ToolTip="Programmübersicht aller Sender"/>
|
ToolTip="Programmübersicht aller Sender"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
|
@ -217,49 +227,50 @@
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<!-- VideoView -->
|
<!-- VideoView + WebView2 + EPG-Overlay: alle im gleichen Grid-Cell -->
|
||||||
<Grid Grid.Row="0">
|
<Grid Grid.Row="0">
|
||||||
|
|
||||||
|
<!-- VLC-Player fuer TV/Radio -->
|
||||||
<vlc:VideoView x:Name="VideoView" Background="Black">
|
<vlc:VideoView x:Name="VideoView" Background="Black">
|
||||||
<Border x:Name="VideoOverlay"
|
<Border x:Name="VideoOverlay"
|
||||||
Background="#01000000"
|
Background="#01000000"
|
||||||
PreviewMouseLeftButtonDown="VideoClickCatcher_DoubleClick">
|
PreviewMouseLeftButtonDown="VideoClickCatcher_DoubleClick">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Button x:Name="BtnSidebarToggleOverlay"
|
<!-- Digitale Uhr (optional, Strg+U) -->
|
||||||
HorizontalAlignment="Left" VerticalAlignment="Top"
|
<Border x:Name="ClockOverlay" Visibility="Collapsed"
|
||||||
Width="36" Height="36" Margin="8"
|
HorizontalAlignment="Right" VerticalAlignment="Top"
|
||||||
Background="#80000000" Foreground="White" BorderThickness="0"
|
Background="#AA000000" CornerRadius="8"
|
||||||
Content="☰" FontSize="16" Cursor="Hand"
|
Padding="20,10" Margin="16"
|
||||||
Visibility="Collapsed"
|
IsHitTestVisible="False">
|
||||||
Panel.ZIndex="1000"
|
<TextBlock x:Name="TxtClock"
|
||||||
Click="BtnSidebarToggle_Click"
|
Foreground="White" FontSize="48" FontWeight="Light"
|
||||||
ToolTip="Seitenleiste wieder einblenden (Strg+B)"/>
|
FontFamily="Consolas"/>
|
||||||
|
</Border>
|
||||||
|
|
||||||
<!-- Radio-Cover bei Audio-Streams — INNERHALB VideoView damit es die native HWND überdeckt -->
|
<!-- Radio-Cover bei Audio-Streams -->
|
||||||
<Border x:Name="RadioCover" Visibility="Collapsed"
|
<Border x:Name="RadioCover" Visibility="Collapsed" Background="#1A1A1A">
|
||||||
Background="#1A1A1A">
|
|
||||||
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
|
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||||
<Image x:Name="RadioLogo" Width="200" Height="200" Stretch="Uniform"
|
<Image x:Name="RadioLogo" Width="200" Height="200" Stretch="Uniform"
|
||||||
RenderOptions.BitmapScalingMode="HighQuality"/>
|
RenderOptions.BitmapScalingMode="HighQuality"/>
|
||||||
<TextBlock x:Name="RadioFallbackIcon" Text="📻"
|
<TextBlock x:Name="RadioFallbackIcon" Text="📻"
|
||||||
FontSize="120" Foreground="#444"
|
FontSize="120" Foreground="#444"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center" Visibility="Collapsed"/>
|
||||||
Visibility="Collapsed"/>
|
|
||||||
<TextBlock x:Name="TxtRadioName"
|
<TextBlock x:Name="TxtRadioName"
|
||||||
FontSize="24" FontWeight="Bold" Foreground="White"
|
FontSize="24" FontWeight="Bold" Foreground="White"
|
||||||
HorizontalAlignment="Center" Margin="0,24,0,0"/>
|
HorizontalAlignment="Center" Margin="0,24,0,0"/>
|
||||||
<TextBlock x:Name="TxtRadioText"
|
<TextBlock x:Name="TxtRadioText"
|
||||||
FontSize="16" Foreground="#0078D4" FontWeight="SemiBold"
|
FontSize="16" Foreground="#0078D4" FontWeight="SemiBold"
|
||||||
HorizontalAlignment="Center" Margin="24,12,24,0"
|
HorizontalAlignment="Center" Margin="24,12,24,0"
|
||||||
TextWrapping="Wrap" TextAlignment="Center"
|
TextWrapping="Wrap" TextAlignment="Center" MaxWidth="600"/>
|
||||||
MaxWidth="600"/>
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- EPG-Overlay (Joyn-Style): liegt INNERHALB der VideoView damit Klicks ber HWND funktionieren -->
|
<!-- EPG-Overlay: INNERHALB VideoView (HWND-Grenze)
|
||||||
|
VideoView wird beim EPG-Oeffnen immer sichtbar gemacht,
|
||||||
|
auch wenn WebView aktiv war -->
|
||||||
<Border x:Name="EpgOverlay" Visibility="Collapsed"
|
<Border x:Name="EpgOverlay" Visibility="Collapsed"
|
||||||
Background="#D8000000">
|
Background="#D8000000">
|
||||||
<DockPanel>
|
<DockPanel>
|
||||||
<!-- Header: Programm + Datum/Zeit + Schließen -->
|
|
||||||
<Grid DockPanel.Dock="Top" Background="#0A0A0A" Height="60">
|
<Grid DockPanel.Dock="Top" Background="#0A0A0A" Height="60">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="Auto"/>
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
|
@ -275,20 +286,11 @@
|
||||||
<Button x:Name="BtnCloseEpg" Content="✕" FontSize="18"
|
<Button x:Name="BtnCloseEpg" Content="✕" FontSize="18"
|
||||||
Width="40" Height="40"
|
Width="40" Height="40"
|
||||||
Background="Transparent" Foreground="White" BorderThickness="0"
|
Background="Transparent" Foreground="White" BorderThickness="0"
|
||||||
Cursor="Hand"
|
Cursor="Hand" Click="BtnCloseEpg_Click" ToolTip="Schließen (Esc)"/>
|
||||||
Click="BtnCloseEpg_Click"
|
|
||||||
ToolTip="Schließen (Esc)"/>
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<!-- Status-Zeile (Lade EPG… etc.) -->
|
|
||||||
<TextBlock x:Name="TxtEpgOverlayStatus" DockPanel.Dock="Bottom"
|
<TextBlock x:Name="TxtEpgOverlayStatus" DockPanel.Dock="Bottom"
|
||||||
Foreground="#888" FontSize="11" Padding="24,8"
|
Foreground="#888" FontSize="11" Padding="24,8" Background="#0A0A0A"/>
|
||||||
Background="#0A0A0A"/>
|
|
||||||
|
|
||||||
<!-- Scrollbares EPG-Grid (Canvas) -->
|
|
||||||
<!-- ScrollBar-Style explizit hier weil vlc:VideoView eine eigene HWND hat
|
|
||||||
und Window.Resources-Styles dort nicht greifen -->
|
|
||||||
<ScrollViewer x:Name="EpgScrollViewer"
|
<ScrollViewer x:Name="EpgScrollViewer"
|
||||||
HorizontalScrollBarVisibility="Auto"
|
HorizontalScrollBarVisibility="Auto"
|
||||||
VerticalScrollBarVisibility="Auto"
|
VerticalScrollBarVisibility="Auto"
|
||||||
|
|
@ -343,6 +345,10 @@
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
</vlc:VideoView>
|
</vlc:VideoView>
|
||||||
|
|
||||||
|
<!-- WebView2 fuer Web-Sender -->
|
||||||
|
<wv2:WebView2 x:Name="WebView" Visibility="Collapsed" Panel.ZIndex="5"/>
|
||||||
|
|
||||||
<TextBlock x:Name="TxtNoChannel"
|
<TextBlock x:Name="TxtNoChannel"
|
||||||
Text="Wähle einen Sender aus der Liste"
|
Text="Wähle einen Sender aus der Liste"
|
||||||
Foreground="#666" FontSize="18"
|
Foreground="#666" FontSize="18"
|
||||||
|
|
@ -361,8 +367,13 @@
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<StackPanel Grid.Column="0">
|
<StackPanel Grid.Column="0">
|
||||||
<TextBlock x:Name="TxtCurrentChannel"
|
<StackPanel Orientation="Horizontal">
|
||||||
Foreground="White" FontSize="18" FontWeight="Bold"/>
|
<TextBlock x:Name="TxtCurrentChannel"
|
||||||
|
Foreground="White" FontSize="18" FontWeight="Bold"/>
|
||||||
|
<TextBlock x:Name="TxtChannelSource"
|
||||||
|
Foreground="#888" FontSize="11" FontWeight="SemiBold"
|
||||||
|
VerticalAlignment="Center" Margin="10,0,0,0"/>
|
||||||
|
</StackPanel>
|
||||||
<TextBlock x:Name="TxtEpgNow"
|
<TextBlock x:Name="TxtEpgNow"
|
||||||
Foreground="#DDD" FontSize="12" Margin="0,4,0,0"
|
Foreground="#DDD" FontSize="12" Margin="0,4,0,0"
|
||||||
TextWrapping="Wrap"/>
|
TextWrapping="Wrap"/>
|
||||||
|
|
@ -381,6 +392,12 @@
|
||||||
<Slider x:Name="SldVolume" Width="100" Minimum="0" Maximum="100" Value="80"
|
<Slider x:Name="SldVolume" Width="100" Minimum="0" Maximum="100" Value="80"
|
||||||
VerticalAlignment="Center" Margin="8,0"
|
VerticalAlignment="Center" Margin="8,0"
|
||||||
ValueChanged="SldVolume_ValueChanged"/>
|
ValueChanged="SldVolume_ValueChanged"/>
|
||||||
|
<Button x:Name="BtnClock" Content="🕒" FontSize="14" Width="40" Height="40"
|
||||||
|
Background="Transparent" Foreground="#888" BorderThickness="0"
|
||||||
|
Click="BtnClock_Click" Cursor="Hand" ToolTip="Uhr einblenden (Strg+U)"/>
|
||||||
|
<Button x:Name="BtnAlwaysOnTop" Content="📌" FontSize="14" Width="40" Height="40"
|
||||||
|
Background="Transparent" Foreground="#888" BorderThickness="0"
|
||||||
|
Click="BtnAlwaysOnTop_Click" Cursor="Hand" ToolTip="Immer im Vordergrund (Strg+T)"/>
|
||||||
<Button x:Name="BtnFullscreen" Content="⛶" FontSize="16" Width="40" Height="40"
|
<Button x:Name="BtnFullscreen" Content="⛶" FontSize="16" Width="40" Height="40"
|
||||||
Background="Transparent" Foreground="White" BorderThickness="0"
|
Background="Transparent" Foreground="White" BorderThickness="0"
|
||||||
Click="BtnFullscreen_Click" Cursor="Hand" ToolTip="Vollbild (F11)"/>
|
Click="BtnFullscreen_Click" Cursor="Hand" ToolTip="Vollbild (F11)"/>
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -3,7 +3,10 @@ using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace FritzTV.Models;
|
namespace FritzTV.Models;
|
||||||
|
|
||||||
public enum ChannelKind { TvSd, TvHd, Radio }
|
public enum ChannelKind { TvSd, TvHd, Radio, Web }
|
||||||
|
|
||||||
|
/// <summary>Woher kommt der Sender? FritzBox (DVB-C/lokal) oder Online (HLS-Stream)</summary>
|
||||||
|
public enum ChannelSource { FritzBox, Online }
|
||||||
|
|
||||||
public class Channel : INotifyPropertyChanged
|
public class Channel : INotifyPropertyChanged
|
||||||
{
|
{
|
||||||
|
|
@ -12,6 +15,9 @@ public class Channel : INotifyPropertyChanged
|
||||||
public required ChannelKind Kind { get; init; }
|
public required ChannelKind Kind { get; init; }
|
||||||
public int Number { get; set; }
|
public int Number { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Quelle: lokale FritzBox oder Online-Stream. Default ist FritzBox (Abwärtskompatibilität).</summary>
|
||||||
|
public ChannelSource Source { get; init; } = ChannelSource.FritzBox;
|
||||||
|
|
||||||
private bool _isFavorite;
|
private bool _isFavorite;
|
||||||
public bool IsFavorite
|
public bool IsFavorite
|
||||||
{
|
{
|
||||||
|
|
|
||||||
16
Models/Reminder.cs
Normal file
16
Models/Reminder.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
namespace FritzTV.Models;
|
||||||
|
|
||||||
|
public class Reminder
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
public required string ChannelName { get; set; }
|
||||||
|
public required string Title { get; set; }
|
||||||
|
public string Description { get; set; } = "";
|
||||||
|
public DateTime StartTime { get; set; }
|
||||||
|
public DateTime EndTime { get; set; }
|
||||||
|
public int MinutesBefore { get; set; } = 5;
|
||||||
|
public bool Fired { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>Zeitpunkt zu dem die Benachrichtigung ausgelöst wird</summary>
|
||||||
|
public DateTime NotifyAt => StartTime.AddMinutes(-MinutesBefore);
|
||||||
|
}
|
||||||
|
|
@ -35,4 +35,6 @@ public static class AppPaths
|
||||||
public static string Logos => Path.Combine(Root, "logos");
|
public static string Logos => Path.Combine(Root, "logos");
|
||||||
public static string Epg => Path.Combine(Root, "epg");
|
public static string Epg => Path.Combine(Root, "epg");
|
||||||
public static string CrashLog => Path.Combine(Root, "crash.log");
|
public static string CrashLog => Path.Combine(Root, "crash.log");
|
||||||
|
public static string WebView2Profile => Path.Combine(Root, "webview2");
|
||||||
|
public static string Reminders => Path.Combine(Root, "reminders.json");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,12 @@ public class AppSettings
|
||||||
public string LastChannel { get; set; } = "";
|
public string LastChannel { get; set; } = "";
|
||||||
public double Volume { get; set; } = 80;
|
public double Volume { get; set; } = 80;
|
||||||
|
|
||||||
|
/// <summary>Online-Sender (ÖR-TV + Webradio aus Assets\online-sources.json) zusätzlich zur FritzBox-Liste anzeigen</summary>
|
||||||
|
public bool ShowOnlineSources { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>Startkategorie beim App-Start (default: Favoriten)</summary>
|
||||||
|
public string StartCategory { get; set; } = "fav";
|
||||||
|
|
||||||
private static readonly string ConfigPath = AppPaths.Settings;
|
private static readonly string ConfigPath = AppPaths.Settings;
|
||||||
|
|
||||||
public static AppSettings Load()
|
public static AppSettings Load()
|
||||||
|
|
|
||||||
98
Services/OnlineSourceClient.cs
Normal file
98
Services/OnlineSourceClient.cs
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.Json;
|
||||||
|
using FritzTV.Models;
|
||||||
|
|
||||||
|
namespace FritzTV.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lädt eine kuratierte Liste von Online-Streams (öffentlich-rechtliche TV + Webradio)
|
||||||
|
/// aus Assets\online-sources.json. Diese Datei wird beim Build neben die .exe kopiert.
|
||||||
|
/// </summary>
|
||||||
|
public class OnlineSourceClient
|
||||||
|
{
|
||||||
|
private static readonly string JsonPath = Path.Combine(
|
||||||
|
Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)!,
|
||||||
|
"Assets", "online-sources.json");
|
||||||
|
|
||||||
|
public Task<List<Channel>> LoadAllAsync()
|
||||||
|
{
|
||||||
|
// Synchron lesen ist OK, die Datei liegt lokal und ist klein (<10 KB)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!File.Exists(JsonPath))
|
||||||
|
return Task.FromResult(new List<Channel>());
|
||||||
|
|
||||||
|
var json = File.ReadAllText(JsonPath);
|
||||||
|
var doc = JsonDocument.Parse(json);
|
||||||
|
var root = doc.RootElement;
|
||||||
|
|
||||||
|
var channels = new List<Channel>();
|
||||||
|
int number = 1000; // Online-Sender bekommen Nummern ab 1000 damit sie nicht mit FritzBox-Nummern kollidieren
|
||||||
|
|
||||||
|
if (root.TryGetProperty("tv", out var tvArr))
|
||||||
|
{
|
||||||
|
foreach (var item in tvArr.EnumerateArray())
|
||||||
|
{
|
||||||
|
var name = item.GetProperty("name").GetString();
|
||||||
|
var url = item.GetProperty("url").GetString();
|
||||||
|
var kindStr = item.TryGetProperty("kind", out var k) ? k.GetString() : "TvHd";
|
||||||
|
if (string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(url)) continue;
|
||||||
|
|
||||||
|
channels.Add(new Channel
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
Url = url,
|
||||||
|
Kind = Enum.TryParse<ChannelKind>(kindStr, out var kind) ? kind : ChannelKind.TvHd,
|
||||||
|
Number = number++,
|
||||||
|
Source = ChannelSource.Online
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.TryGetProperty("radio", out var radioArr))
|
||||||
|
{
|
||||||
|
foreach (var item in radioArr.EnumerateArray())
|
||||||
|
{
|
||||||
|
var name = item.GetProperty("name").GetString();
|
||||||
|
var url = item.GetProperty("url").GetString();
|
||||||
|
if (string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(url)) continue;
|
||||||
|
|
||||||
|
channels.Add(new Channel
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
Url = url,
|
||||||
|
Kind = ChannelKind.Radio,
|
||||||
|
Number = number++,
|
||||||
|
Source = ChannelSource.Online
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.TryGetProperty("web", out var webArr))
|
||||||
|
{
|
||||||
|
foreach (var item in webArr.EnumerateArray())
|
||||||
|
{
|
||||||
|
var name = item.GetProperty("name").GetString();
|
||||||
|
var url = item.GetProperty("url").GetString();
|
||||||
|
if (string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(url)) continue;
|
||||||
|
|
||||||
|
channels.Add(new Channel
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
Url = url,
|
||||||
|
Kind = ChannelKind.Web,
|
||||||
|
Number = number++,
|
||||||
|
Source = ChannelSource.Online
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(channels);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Bei Parse-Fehler einfach leere Liste — App soll trotzdem laufen
|
||||||
|
return Task.FromResult(new List<Channel>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
107
Services/ReminderService.cs
Normal file
107
Services/ReminderService.cs
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
using FritzTV.Models;
|
||||||
|
|
||||||
|
namespace FritzTV.Services;
|
||||||
|
|
||||||
|
public class ReminderService
|
||||||
|
{
|
||||||
|
private static readonly string RemindersPath = AppPaths.Reminders;
|
||||||
|
private readonly DispatcherTimer _checkTimer;
|
||||||
|
private List<Reminder> _reminders = new();
|
||||||
|
|
||||||
|
/// <summary>Feuert wenn eine Erinnerung faellig ist. Callback laeuft auf UI-Thread.</summary>
|
||||||
|
public event Action<Reminder>? ReminderDue;
|
||||||
|
|
||||||
|
public IReadOnlyList<Reminder> Reminders => _reminders.AsReadOnly();
|
||||||
|
|
||||||
|
public ReminderService()
|
||||||
|
{
|
||||||
|
Load();
|
||||||
|
_checkTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(30) };
|
||||||
|
_checkTimer.Tick += (_, _) => CheckReminders();
|
||||||
|
_checkTimer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(Reminder reminder)
|
||||||
|
{
|
||||||
|
_reminders.Add(reminder);
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Remove(Guid id)
|
||||||
|
{
|
||||||
|
_reminders.RemoveAll(r => r.Id == id);
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasReminder(string channelName, DateTime startTime)
|
||||||
|
=> _reminders.Any(r => r.ChannelName == channelName && r.StartTime == startTime);
|
||||||
|
|
||||||
|
private void CheckReminders()
|
||||||
|
{
|
||||||
|
var now = DateTime.Now;
|
||||||
|
var due = _reminders
|
||||||
|
.Where(r => !r.Fired && r.NotifyAt <= now && r.StartTime > now.AddMinutes(-30))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var r in due)
|
||||||
|
{
|
||||||
|
r.Fired = true;
|
||||||
|
FireNotification(r);
|
||||||
|
ReminderDue?.Invoke(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
_reminders.RemoveAll(r => r.Fired && r.StartTime < now.AddHours(-1));
|
||||||
|
if (due.Any()) Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FireNotification(Reminder r)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var minLeft = (int)(r.StartTime - DateTime.Now).TotalMinutes;
|
||||||
|
var when = minLeft <= 0 ? "Jetzt" : $"In {minLeft} Minute{(minLeft == 1 ? "" : "n")}";
|
||||||
|
var msg = $"{when}: {r.Title}\n{r.ChannelName} {r.StartTime:HH:mm}\u2013{r.EndTime:HH:mm}";
|
||||||
|
|
||||||
|
var result = System.Windows.MessageBox.Show(
|
||||||
|
msg + "\n\nJetzt einschalten?",
|
||||||
|
"\u23f0 HomeStream-Erinnerung",
|
||||||
|
System.Windows.MessageBoxButton.YesNo,
|
||||||
|
System.Windows.MessageBoxImage.Information);
|
||||||
|
|
||||||
|
if (result == System.Windows.MessageBoxResult.Yes)
|
||||||
|
SwitchToChannel?.Invoke(r.ChannelName);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Wird aufgerufen wenn User bei Erinnerung "Jetzt einschalten" klickt.</summary>
|
||||||
|
public Action<string>? SwitchToChannel { get; set; }
|
||||||
|
|
||||||
|
private void Load()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(RemindersPath))
|
||||||
|
{
|
||||||
|
var json = File.ReadAllText(RemindersPath);
|
||||||
|
_reminders = JsonSerializer.Deserialize<List<Reminder>>(json) ?? new();
|
||||||
|
_reminders.RemoveAll(r => r.StartTime < DateTime.Now.AddHours(-1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { _reminders = new(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Save()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(RemindersPath)!);
|
||||||
|
var json = JsonSerializer.Serialize(_reminders, new JsonSerializerOptions { WriteIndented = true });
|
||||||
|
File.WriteAllText(RemindersPath, json);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
<Window x:Class="FritzTV.SettingsWindow"
|
<Window x:Class="FritzTV.SettingsWindow"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
Title="Einstellungen" Height="240" Width="420"
|
Title="Einstellungen" Height="320" Width="440"
|
||||||
Background="#1A1A1A" Foreground="White"
|
Background="#1A1A1A" Foreground="White"
|
||||||
WindowStartupLocation="CenterOwner" ResizeMode="NoResize">
|
WindowStartupLocation="CenterOwner" ResizeMode="NoResize">
|
||||||
<Grid Margin="24">
|
<Grid Margin="24">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
|
|
@ -19,7 +22,16 @@
|
||||||
<TextBlock Grid.Row="2" Margin="0,4,0,0" FontSize="11" Foreground="#666"
|
<TextBlock Grid.Row="2" Margin="0,4,0,0" FontSize="11" Foreground="#666"
|
||||||
Text="z.B. 192.168.178.1 oder fritz.box · DVB-C muss aktiv sein"/>
|
Text="z.B. 192.168.178.1 oder fritz.box · DVB-C muss aktiv sein"/>
|
||||||
|
|
||||||
<StackPanel Grid.Row="4" Orientation="Horizontal" HorizontalAlignment="Right">
|
<Separator Grid.Row="3" Margin="0,16,0,16" Background="#333"/>
|
||||||
|
|
||||||
|
<CheckBox Grid.Row="4" x:Name="ChkOnlineSources"
|
||||||
|
Content="Online-Sender anzeigen (ÖR-TV + Webradio)"
|
||||||
|
Foreground="White" FontSize="13"/>
|
||||||
|
<TextBlock Grid.Row="5" Margin="22,4,0,0" FontSize="11" Foreground="#666"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
Text="Zusätzliche Sender aus dem Internet (ARD, ZDF, 3sat, arte, Webradios). Funktioniert auch ohne FritzBox."/>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="7" Orientation="Horizontal" HorizontalAlignment="Right">
|
||||||
<Button Content="Abbrechen" Width="100" Height="32" Margin="0,0,8,0"
|
<Button Content="Abbrechen" Width="100" Height="32" Margin="0,0,8,0"
|
||||||
Click="BtnCancel_Click" Background="#333" Foreground="White" BorderThickness="0"/>
|
Click="BtnCancel_Click" Background="#333" Foreground="White" BorderThickness="0"/>
|
||||||
<Button Content="Speichern" Width="100" Height="32"
|
<Button Content="Speichern" Width="100" Height="32"
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ public partial class SettingsWindow : Window
|
||||||
DarkTitleBar.EnableFor(this);
|
DarkTitleBar.EnableFor(this);
|
||||||
_settings = settings;
|
_settings = settings;
|
||||||
TxtIp.Text = settings.FritzBoxIp;
|
TxtIp.Text = settings.FritzBoxIp;
|
||||||
|
ChkOnlineSources.IsChecked = settings.ShowOnlineSources;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BtnSave_Click(object sender, RoutedEventArgs e)
|
private void BtnSave_Click(object sender, RoutedEventArgs e)
|
||||||
|
|
@ -25,6 +26,7 @@ public partial class SettingsWindow : Window
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_settings.FritzBoxIp = ip;
|
_settings.FritzBoxIp = ip;
|
||||||
|
_settings.ShowOnlineSources = ChkOnlineSources.IsChecked == true;
|
||||||
DialogResult = true;
|
DialogResult = true;
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
291
_wiki_home.md
Normal file
291
_wiki_home.md
Normal file
|
|
@ -0,0 +1,291 @@
|
||||||
|
# HomeStream – Benutzerhandbuch
|
||||||
|
|
||||||
|
HomeStream ist ein DVB-C Streaming-Client für Windows, der AVM FRITZ!Box-Router als Quelle nutzt und zusätzlich öffentlich-rechtliche Online-Streams sowie Streaming-Dienste (YouTube, Netflix, Mediatheken) integriert.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Inhaltsverzeichnis
|
||||||
|
|
||||||
|
1. [Systemvoraussetzungen](#1-systemvoraussetzungen)
|
||||||
|
2. [Installation](#2-installation)
|
||||||
|
3. [Erster Start](#3-erster-start)
|
||||||
|
4. [Oberfläche](#4-oberfläche)
|
||||||
|
5. [Sender & Kategorien](#5-sender--kategorien)
|
||||||
|
6. [Wiedergabe & Steuerung](#6-wiedergabe--steuerung)
|
||||||
|
7. [Favoriten](#7-favoriten)
|
||||||
|
8. [EPG – Programmführer](#8-epg--programmführer)
|
||||||
|
9. [Erinnerungen](#9-erinnerungen)
|
||||||
|
10. [Streaming-Browser](#10-streaming-browser)
|
||||||
|
11. [Einstellungen](#11-einstellungen)
|
||||||
|
12. [Tastenkürzel](#12-tastenkürzel)
|
||||||
|
13. [Häufige Probleme](#13-häufige-probleme)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Systemvoraussetzungen
|
||||||
|
|
||||||
|
| Anforderung | Details |
|
||||||
|
|---|---|
|
||||||
|
| Betriebssystem | Windows 10 oder Windows 11 (64-Bit) |
|
||||||
|
| .NET-Runtime | Nicht erforderlich – selbst-enthaltener Build |
|
||||||
|
| FRITZ!Box | DVB-C muss aktiviert und konfiguriert sein |
|
||||||
|
| Netzwerk | App und FRITZ!Box im gleichen Heimnetzwerk |
|
||||||
|
| WebView2 Runtime | Für Streaming-Browser (auf Win10/11 meist vorinstalliert) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Installation
|
||||||
|
|
||||||
|
1. Neueste Version von der [Release-Seite](https://www.dimedtec.net/dimedtec/HomeStream/releases) herunterladen
|
||||||
|
2. ZIP-Datei entpacken (z.B. nach `C:\Programme\HomeStream\`)
|
||||||
|
3. `HomeStream.exe` starten – keine Installation erforderlich
|
||||||
|
|
||||||
|
> **Hinweis:** Windows SmartScreen kann beim ersten Start eine Warnung anzeigen. „Weitere Informationen" → „Trotzdem ausführen" wählen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Erster Start
|
||||||
|
|
||||||
|
Beim ersten Start lädt HomeStream automatisch:
|
||||||
|
- Die Senderliste von der FRITZ!Box (`192.168.178.1` ist der Standard)
|
||||||
|
- Online-Sender (ÖR-TV und Webradio) aus der integrierten Quellenliste
|
||||||
|
- EPG-Daten von iptv-epg.org (~6 MB, einmalig pro Tag)
|
||||||
|
|
||||||
|
Falls die FRITZ!Box-Adresse abweicht, unter **Einstellungen** die korrekte IP eintragen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Oberfläche
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────┬──────────────────────┬────────────────────────────────┐
|
||||||
|
│ Kategorien │ Senderliste │ Videobild │
|
||||||
|
│ (Sidebar) │ │ │
|
||||||
|
│ │ ▶ Das Erste HD │ │
|
||||||
|
│ 📺 TV Fritz │ ZDF HD │ │
|
||||||
|
│ 📺 TV Online│ 3sat HD │ │
|
||||||
|
│ 📡 Radio │ ... │ │
|
||||||
|
│ 🌐 Streaming│ │ │
|
||||||
|
│ ⭐ Favoriten│ │ │
|
||||||
|
│ 📅 Programm │ │ │
|
||||||
|
├─────────────┴──────────────────────┴────────────────────────────────┤
|
||||||
|
│ Das Erste HD ● FritzBox ▶ Jetzt: Tagesschau │
|
||||||
|
│ 🔊 ────────────── 📌 ⛶ │
|
||||||
|
└─────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Bereiche:**
|
||||||
|
- **Linke Spalte:** Kategorie-Buttons zur Filterung
|
||||||
|
- **Mittlere Spalte:** Senderliste mit Suchfeld oben
|
||||||
|
- **Hauptbereich:** Videobild / Streaming-Browser
|
||||||
|
- **Untere Leiste:** Aktueller Sender, EPG-Info, Lautstärke, Buttons
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Sender & Kategorien
|
||||||
|
|
||||||
|
### Kategorien
|
||||||
|
|
||||||
|
| Kategorie | Inhalt |
|
||||||
|
|---|---|
|
||||||
|
| 📺 TV (FritzBox) | DVB-C Fernsehen über die FRITZ!Box |
|
||||||
|
| 📺 TV (Online) | Öffentlich-rechtliche TV-Streams (ARD, ZDF, 3sat, ARTE …) |
|
||||||
|
| 📡 Radio (FritzBox) | DVB-C Radiosender über die FRITZ!Box |
|
||||||
|
| 📡 Radio (Online) | Webradio (Bayern, DLF, WDR, NDR, SWR …) |
|
||||||
|
| 🌐 Streaming | Browser-basierte Streaming-Dienste |
|
||||||
|
| ⭐ Favoriten | Alle als Favorit markierten Sender |
|
||||||
|
|
||||||
|
> Die App startet immer mit der **Favoriten**-Ansicht.
|
||||||
|
|
||||||
|
### Sendersuche
|
||||||
|
|
||||||
|
Im Suchfeld oben in der Senderliste tippen – die Liste filtert in Echtzeit.
|
||||||
|
|
||||||
|
### Sidebar ein-/ausblenden
|
||||||
|
|
||||||
|
**Strg+B** blendet die beiden linken Spalten ein oder aus. Wenn ausgeblendet, erscheint ein Hinweis in der Titelleiste.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Wiedergabe & Steuerung
|
||||||
|
|
||||||
|
### Sender wechseln
|
||||||
|
|
||||||
|
Sender in der Liste anklicken – nach einer kurzen Verzögerung (250 ms Debounce) startet die Wiedergabe.
|
||||||
|
|
||||||
|
### Lautstärke
|
||||||
|
|
||||||
|
- Schieberegler in der Statusleiste unten
|
||||||
|
- **🔊-Button** für Stummschalten (Toggle)
|
||||||
|
- Tastenkürzel: **M** für Stummschalten
|
||||||
|
|
||||||
|
### Vollbild
|
||||||
|
|
||||||
|
- **F11** oder Doppelklick auf das Video
|
||||||
|
- **ESC** beendet den Vollbildmodus
|
||||||
|
|
||||||
|
### Immer im Vordergrund
|
||||||
|
|
||||||
|
- **📌-Button** (Pin) in der Statusleiste
|
||||||
|
- Oder **Strg+T**
|
||||||
|
- Aktiv: Button leuchtet blau
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Favoriten
|
||||||
|
|
||||||
|
- **⭐-Button** in der Statusleiste neben dem Sendernamen klicken
|
||||||
|
- Favorit ist gesetzt wenn der Stern gefüllt ist (★)
|
||||||
|
- Favoriten sind **medienübergreifend** – FritzBox-, Online- und Streaming-Sender können gleichermaßen als Favorit gespeichert werden
|
||||||
|
- Favoriten werden in `%APPDATA%\HomeStream\settings.json` gespeichert
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. EPG – Programmführer
|
||||||
|
|
||||||
|
### Aktuelles Programm
|
||||||
|
|
||||||
|
Unter dem Sendernamen in der Statusleiste wird angezeigt:
|
||||||
|
- **▶ Jetzt:** Aktuelle Sendung (FritzBox: aus EIT-Signal; Online: aus EPG-Daten)
|
||||||
|
- Danach: Die nächsten 2–3 Sendungen mit Uhrzeit
|
||||||
|
|
||||||
|
Diese Anzeige aktualisiert sich automatisch alle 60 Sekunden.
|
||||||
|
|
||||||
|
### Programmübersicht (Grid)
|
||||||
|
|
||||||
|
Klick auf **📅 Programm** in der Sidebar öffnet ein Joyn-ähnliches EPG-Grid:
|
||||||
|
|
||||||
|
- Alle TV-Sender in einer Übersicht
|
||||||
|
- 8 Stunden Programmvorschau
|
||||||
|
- Aktuelle Sendung rot hervorgehoben
|
||||||
|
- **Linksklick** auf eine Sendung: sofort einschalten
|
||||||
|
- **Rechtsklick** auf eine Sendung: Erinnerung setzen
|
||||||
|
- Tooltip zeigt Titel, Uhrzeit und Beschreibung
|
||||||
|
|
||||||
|
**Navigation im Grid:**
|
||||||
|
- Horizontales Scrollen: Mausrad oder Scrollbar
|
||||||
|
- Vertikales Scrollen: Mausrad oder Scrollbar
|
||||||
|
|
||||||
|
> EPG-Daten werden täglich frisch von [iptv-epg.org](https://iptv-epg.org) geladen und lokal in `%APPDATA%\HomeStream\epg\` gecacht.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Erinnerungen
|
||||||
|
|
||||||
|
Im EPG-Grid eine zukünftige Sendung **rechts klicken** → „⏰ 5 min vorher erinnern".
|
||||||
|
|
||||||
|
- Die Sendung zeigt ein ⏰-Symbol im Grid
|
||||||
|
- 5 Minuten vor Sendungsstart erscheint eine Benachrichtigung
|
||||||
|
- Auf **„Ja"** klicken schaltet automatisch auf den Sender um und schließt das EPG
|
||||||
|
- Erinnerungen werden in `%APPDATA%\HomeStream\reminders.json` gespeichert und überleben App-Neustarts
|
||||||
|
|
||||||
|
Zum Entfernen: Rechtsklick auf die Sendung → „⏰ Erinnerung entfernen".
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Streaming-Browser
|
||||||
|
|
||||||
|
Unter **🌐 Streaming** sind Browser-basierte Dienste verfügbar:
|
||||||
|
|
||||||
|
| Dienst | URL |
|
||||||
|
|---|---|
|
||||||
|
| YouTube | youtube.com |
|
||||||
|
| ARD Mediathek | ardmediathek.de |
|
||||||
|
| ZDF Mediathek | zdf.de |
|
||||||
|
| Netflix | netflix.com |
|
||||||
|
| Disney+ | disneyplus.com |
|
||||||
|
| Joyn | joyn.de |
|
||||||
|
| RTL+ | plus.rtl.de |
|
||||||
|
| ARTE | arte.tv/de |
|
||||||
|
| Apple TV+ | tv.apple.com |
|
||||||
|
| Amazon Prime Video | amazon.de/gp/video |
|
||||||
|
| n-tv | n-tv.de |
|
||||||
|
|
||||||
|
### Login speichern
|
||||||
|
|
||||||
|
Der Streaming-Browser verwendet ein **persistentes WebView2-Profil** unter `%APPDATA%\HomeStream\webview2\`. Einmal einloggen genügt – der Login bleibt beim nächsten App-Start erhalten.
|
||||||
|
|
||||||
|
### Steuerung im Browser-Modus
|
||||||
|
|
||||||
|
- Normale Browser-Navigation per Maus
|
||||||
|
- **Strg+B** öffnet die Sidebar wieder wenn sie eingeklappt ist
|
||||||
|
- **🔊-Button** (Stummschalten) funktioniert auch im Browser-Modus
|
||||||
|
- **📌-Button** (Immer im Vordergrund) funktioniert auch im Browser-Modus
|
||||||
|
|
||||||
|
### Wechsel zurück zu TV
|
||||||
|
|
||||||
|
Einfach einen TV- oder Radio-Sender in der Senderliste anklicken – der Browser wird automatisch gestoppt und der VLC-Player übernimmt.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Einstellungen
|
||||||
|
|
||||||
|
Erreichbar über den **⚙-Button** in der Statusleiste.
|
||||||
|
|
||||||
|
| Einstellung | Beschreibung |
|
||||||
|
|---|---|
|
||||||
|
| FritzBox-Adresse | IP oder Hostname der FRITZ!Box (Standard: 192.168.178.1) |
|
||||||
|
| Online-Sender anzeigen | ÖR-TV und Webradio ein-/ausschalten |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Tastenkürzel
|
||||||
|
|
||||||
|
| Taste | Funktion |
|
||||||
|
|---|---|
|
||||||
|
| **Strg+B** | Sidebar ein-/ausblenden |
|
||||||
|
| **Strg+T** | Immer im Vordergrund an/aus |
|
||||||
|
| **F11** | Vollbild an/aus |
|
||||||
|
| **ESC** | Vollbild beenden / EPG schließen |
|
||||||
|
| **M** | Stummschalten |
|
||||||
|
| **↑ / ↓** | Sender in der Liste wechseln |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Häufige Probleme
|
||||||
|
|
||||||
|
### Senderliste leer / FRITZ!Box nicht erreichbar
|
||||||
|
|
||||||
|
- FRITZ!Box-Adresse in den Einstellungen prüfen
|
||||||
|
- DVB-C in der FRITZ!Box aktiviert? → FRITZ!Box-Oberfläche → Heimnetz → Multimedia → DVB-C
|
||||||
|
- App und FRITZ!Box im gleichen Netzwerk?
|
||||||
|
- Online-Sender funktionieren auch ohne FRITZ!Box
|
||||||
|
|
||||||
|
### Bild friert ein / ruckelt
|
||||||
|
|
||||||
|
- FRITZ!Box neu starten
|
||||||
|
- Prüfen ob ein anderes Gerät gleichzeitig streamt (FRITZ!Box erlaubt nur einen Stream pro IP-Adresse)
|
||||||
|
- Im originalen VLC-Player testen: wenn dort auch Probleme auftreten, liegt es am DVB-Signal
|
||||||
|
|
||||||
|
### EPG zeigt falsche Zeiten oder keine Daten
|
||||||
|
|
||||||
|
- EPG-Cache löschen: `%APPDATA%\HomeStream\epg\` leeren
|
||||||
|
- App neu starten – EPG wird automatisch neu geladen
|
||||||
|
|
||||||
|
### Streaming-Browser startet nicht / WebView2 fehlt
|
||||||
|
|
||||||
|
- WebView2 Runtime installieren: [Microsoft WebView2 Runtime](https://developer.microsoft.com/de-de/microsoft-edge/webview2/)
|
||||||
|
- Auf Windows 10 Version 1803 oder neuer aktualisieren
|
||||||
|
|
||||||
|
### Login im Streaming-Browser geht verloren
|
||||||
|
|
||||||
|
- Das WebView2-Profil liegt in `%APPDATA%\HomeStream\webview2\`
|
||||||
|
- Wird dieser Ordner gelöscht, gehen alle gespeicherten Logins verloren
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Datenspeicherung
|
||||||
|
|
||||||
|
Alle Benutzerdaten werden lokal gespeichert unter `%APPDATA%\HomeStream\`:
|
||||||
|
|
||||||
|
| Datei/Ordner | Inhalt |
|
||||||
|
|---|---|
|
||||||
|
| `settings.json` | Einstellungen, Favoriten, letzter Sender |
|
||||||
|
| `reminders.json` | Gespeicherte EPG-Erinnerungen |
|
||||||
|
| `epg\` | Gecachte EPG-Daten (täglich aktualisiert) |
|
||||||
|
| `logos\` | Gecachte Sender-Logos |
|
||||||
|
| `webview2\` | Browser-Profil (Logins, Cookies) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*HomeStream wird entwickelt von [dimedtec GmbH](https://www.dimedtec.net) · [Quellcode & Releases](https://www.dimedtec.net/dimedtec/HomeStream)*
|
||||||
19
publish.ps1
19
publish.ps1
|
|
@ -27,8 +27,10 @@ if ($Tag) {
|
||||||
throw "Tag muss Format v1.2.3 haben"
|
throw "Tag muss Format v1.2.3 haben"
|
||||||
}
|
}
|
||||||
Write-Host "→ Tag $Tag setzen..."
|
Write-Host "→ Tag $Tag setzen..."
|
||||||
git -C $ScriptDir tag $Tag 2>&1 | Out-Null
|
# Git schreibt Status auf stderr; mit try-catch abfangen damit PS nicht abbricht.
|
||||||
git -C $ScriptDir push origin $Tag 2>&1 | Out-Null
|
# Tag existiert evtl. schon — das ist OK, wir ignorieren den Fehler.
|
||||||
|
try { git -C $ScriptDir tag $Tag 2>$null | Out-Null } catch { }
|
||||||
|
try { git -C $ScriptDir push origin $Tag 2>$null | Out-Null } catch { }
|
||||||
Write-Host " OK"
|
Write-Host " OK"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,16 +77,17 @@ if (-not $Tag) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host "→ Forgejo-Release $Tag erstellen..."
|
Write-Host "→ Forgejo-Release $Tag erstellen..."
|
||||||
$headers = @{ 'Authorization' = "token $Token"; 'Content-Type' = 'application/json' }
|
$headers = @{ 'Authorization' = "token $Token"; 'Content-Type' = 'application/json; charset=utf-8' }
|
||||||
$body = @{
|
$bodyObj = @{
|
||||||
tag_name = $Tag
|
tag_name = $Tag
|
||||||
name = "HomeStream $version"
|
name = "HomeStream $version"
|
||||||
body = "Self-contained Release für Windows 10/11 (x64). Keine .NET-Installation nötig."
|
body = "Self-contained Release fuer Windows 10/11 (x64). Keine .NET-Installation noetig."
|
||||||
draft = $false
|
draft = $false
|
||||||
prerelease = $false
|
prerelease = $false
|
||||||
} | ConvertTo-Json
|
}
|
||||||
|
# Explizit UTF-8 kodieren damit Umlaute korrekt uebertragen werden
|
||||||
$release = Invoke-RestMethod -Uri "$ApiBase/releases" -Method Post -Headers $headers -Body $body
|
$bodyBytes = [System.Text.Encoding]::UTF8.GetBytes(($bodyObj | ConvertTo-Json))
|
||||||
|
$release = Invoke-RestMethod -Uri "$ApiBase/releases" -Method Post -Headers $headers -Body $bodyBytes
|
||||||
Write-Host " Release-ID: $($release.id)"
|
Write-Host " Release-ID: $($release.id)"
|
||||||
|
|
||||||
# ── 5. ZIP als Asset hochladen ────────────────────────────────────────────
|
# ── 5. ZIP als Asset hochladen ────────────────────────────────────────────
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue