Initial scaffold: ytLive C# WPF project

- MVVM architecture (Models, ViewModels, Views, Services, Helpers)
- Models: Scene, Source, StreamConfig, StreamHealth, YouTubeChannel, ChatMessage
- Services: YouTubeAuthService (OAuth2), YouTubeStreamService (broadcasts/stream health),
  YouTubeChatService (live chat polling)
- ViewModels: MainViewModel with scene management, stream controls, chat
- MainWindow with dark theme: scene/source panel, preview area, chat panel, status bar
- Version roadmap: v0.1 (scenes, RTMP, OAuth2, chat, health) -> 1.0
This commit is contained in:
2026-08-04 09:09:52 -07:00
commit 9103ff1fb7
16 changed files with 943 additions and 0 deletions
+242
View File
@@ -0,0 +1,242 @@
<Window x:Class="ytLive.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:ytLive.ViewModels"
mc:Ignorable="d"
Title="ytLive" Height="720" Width="1280"
Background="#1a1a2e"
WindowStartupLocation="CenterScreen">
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
<!-- Base button style -->
<Style x:Key="YtButton" TargetType="Button">
<Setter Property="Background" Value="#e94560"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Padding" Value="16,8"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}"
CornerRadius="4"
Padding="{TemplateBinding Padding}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="YtButtonSecondary" TargetType="Button" BasedOn="{StaticResource YtButton}">
<Setter Property="Background" Value="#16213e"/>
</Style>
<Style x:Key="YtTextBox" TargetType="TextBox">
<Setter Property="Background" Value="#0f3460"/>
<Setter Property="Foreground" Value="#e0e0e0"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Padding" Value="8,6"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="CaretBrush" Value="White"/>
</Style>
<Style x:Key="YtLabel" TargetType="TextBlock">
<Setter Property="Foreground" Value="#a0a0b0"/>
<Setter Property="FontSize" Value="12"/>
</Style>
<Style x:Key="SectionHeader" TargetType="TextBlock">
<Setter Property="Foreground" Value="#e0e0e0"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Margin" Value="0,0,0,8"/>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- ═══ TOP BAR: Stream Controls ═══ -->
<Border Grid.Row="0" Background="#16213e" Padding="16,10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Logo / Brand -->
<TextBlock Grid.Column="0" Text="ytLive"
Foreground="#e94560" FontSize="22" FontWeight="Bold"
VerticalAlignment="Center" Margin="0,0,24,0"/>
<!-- Stream Title -->
<TextBox Grid.Column="1" Style="{StaticResource YtTextBox}"
Text="{Binding StreamTitle, UpdateSourceTrigger=PropertyChanged}"
Width="400" HorizontalAlignment="Left"
VerticalAlignment="Center"/>
<!-- Stream Status -->
<Border Grid.Column="2" Background="#0f3460" CornerRadius="4"
Padding="12,6" Margin="16,0" VerticalAlignment="Center">
<StackPanel Orientation="Horizontal">
<Ellipse Width="10" Height="10" Margin="0,0,8,0">
<Ellipse.Style>
<Style TargetType="Ellipse">
<Setter Property="Fill" Value="#555"/>
<Style.Triggers>
<DataTrigger Binding="{Binding StreamStatus}" Value="Streaming">
<Setter Property="Fill" Value="#00ff00"/>
</DataTrigger>
<DataTrigger Binding="{Binding StreamStatus}" Value="Connecting">
<Setter Property="Fill" Value="#ffaa00"/>
</DataTrigger>
<DataTrigger Binding="{Binding StreamStatus}" Value="Error">
<Setter Property="Fill" Value="#ff0000"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
<TextBlock Text="{Binding StatusDisplay}" Foreground="White"
FontSize="13" FontWeight="SemiBold" VerticalAlignment="Center"/>
</StackPanel>
</Border>
<!-- Stream Controls -->
<StackPanel Grid.Column="3" Orientation="Horizontal" VerticalAlignment="Center">
<Button Content="▶ GO LIVE" Style="{StaticResource YtButton}"
Command="{Binding StartStreamCommand}"
Visibility="{Binding StreamStatus, Converter={StaticResource BoolToVis},
ConverterParameter=Offline}"/>
<Button Content="■ STOP" Style="{StaticResource YtButton}"
Background="#333" Command="{Binding StopStreamCommand}"/>
<Button Content="YouTube" Style="{StaticResource YtButtonSecondary}"
Command="{Binding ConnectYouTubeCommand}" Margin="8,0,0,0"/>
</StackPanel>
</Grid>
</Border>
<!-- ═══ MIDDLE: Main Content ═══ -->
<Grid Grid.Row="1" Margin="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="220"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="300"/>
</Grid.ColumnDefinitions>
<!-- LEFT PANEL: Scenes & Sources -->
<Border Grid.Column="0" Background="#16213e" CornerRadius="6" Padding="12">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Scenes -->
<TextBlock Grid.Row="0" Text="SCENES" Style="{StaticResource SectionHeader}"/>
<ListBox Grid.Row="1" Background="Transparent" BorderThickness="0"
ItemsSource="{Binding Scenes}"
SelectedItem="{Binding ActiveScene}"
Foreground="#e0e0e0" FontSize="13">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" Padding="4,4"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Grid.Row="2" Orientation="Horizontal" Margin="0,8,0,0">
<Button Content="+" Style="{StaticResource YtButtonSecondary}"
Command="{Binding AddSceneCommand}" Padding="8,4" Margin="0,0,4,0"/>
<Button Content="" Style="{StaticResource YtButtonSecondary}"
Command="{Binding RemoveSceneCommand}" Padding="8,4"/>
</StackPanel>
<!-- Sources (for active scene) -->
<TextBlock Grid.Row="3" Text="SOURCES" Style="{StaticResource SectionHeader}" Margin="0,12,0,0"/>
</Grid>
</Border>
<!-- CENTER: Preview Area -->
<Border Grid.Column="1" Background="#0a0a1a" CornerRadius="6" Margin="8,0">
<Grid>
<TextBlock Text="Preview" Foreground="#333" FontSize="24"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
<!-- TODO: D3DImage or MediaElement for video preview -->
</Grid>
</Border>
<!-- RIGHT PANEL: Chat -->
<Border Grid.Column="2" Background="#16213e" CornerRadius="6" Padding="12">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="LIVE CHAT" Style="{StaticResource SectionHeader}"/>
<ListBox Grid.Row="1" Background="Transparent" BorderThickness="0"
ItemsSource="{Binding ChatMessages}"
Foreground="#e0e0e0" FontSize="12">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,2">
<TextBlock>
<Run Text="{Binding AuthorName, Mode=OneWay}" FontWeight="Bold" Foreground="#e94560"/>
<Run Text=": "/>
<Run Text="{Binding Message, Mode=OneWay}" Foreground="#e0e0e0"/>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Border>
</Grid>
<!-- ═══ BOTTOM BAR: Stream Health ═══ -->
<Border Grid.Row="2" Background="#0f3460" Padding="16,6">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Bitrate:" Style="{StaticResource YtLabel}" Margin="0,0,4,0"/>
<TextBlock Text="{Binding CurrentHealth.CurrentBitrate}" Style="{StaticResource YtLabel}"
Foreground="White" Margin="0,0,20,0"/>
<TextBlock Text="FPS:" Style="{StaticResource YtLabel}" Margin="0,0,4,0"/>
<TextBlock Text="{Binding CurrentHealth.FPS}" Style="{StaticResource YtLabel}"
Foreground="White" Margin="0,0,20,0"/>
<TextBlock Text="Dropped:" Style="{StaticResource YtLabel}" Margin="0,0,4,0"/>
<TextBlock Text="{Binding CurrentHealth.DroppedFrames}" Style="{StaticResource YtLabel}"
Foreground="White" Margin="0,0,20,0"/>
<TextBlock Text="Duration:" Style="{StaticResource YtLabel}" Margin="0,0,4,0"/>
<TextBlock Text="{Binding CurrentHealth.StreamDuration}" Style="{StaticResource YtLabel}"
Foreground="White"/>
<TextBlock Text="{Binding CurrentHealth.HealthMessage}" Style="{StaticResource YtLabel}"
Foreground="#e94560" Margin="20,0,0,0"/>
</StackPanel>
</Border>
</Grid>
</Window>