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
+143
View File
@@ -0,0 +1,143 @@
using System.Collections.ObjectModel;
using System.Windows.Input;
using ytLive.Helpers;
using ytLive.Models;
using ytLive.Services;
namespace ytLive.ViewModels;
public class MainViewModel : ViewModelBase
{
private readonly YouTubeAuthService _youtubeAuth;
private readonly YouTubeStreamService _youtubeStream;
private readonly YouTubeChatService _youtubeChat;
private Scene? _activeScene;
private StreamStatus _streamStatus = StreamStatus.Offline;
private StreamHealth _currentHealth = new();
private string _streamTitle = string.Empty;
private string _streamKey = string.Empty;
private bool _isYouTubeConnected;
public ObservableCollection<Scene> Scenes { get; } = new();
public ObservableCollection<ChatMessage> ChatMessages { get; } = new();
public Scene? ActiveScene
{
get => _activeScene;
set => SetProperty(ref _activeScene, value);
}
public StreamStatus StreamStatus
{
get => _streamStatus;
set => SetProperty(ref _streamStatus, value);
}
public StreamHealth CurrentHealth
{
get => _currentHealth;
set => SetProperty(ref _currentHealth, value);
}
public string StreamTitle
{
get => _streamTitle;
set => SetProperty(ref _streamTitle, value);
}
public string StreamKey
{
get => _streamKey;
set => SetProperty(ref _streamKey, value);
}
public bool IsYouTubeConnected
{
get => _isYouTubeConnected;
set => SetProperty(ref _isYouTubeConnected, value);
}
public string StatusDisplay => StreamStatus switch
{
StreamStatus.Offline => "OFFLINE",
StreamStatus.Connecting => "CONNECTING...",
StreamStatus.Streaming => "LIVE",
StreamStatus.Error => "ERROR",
_ => "UNKNOWN"
};
// Commands
public ICommand AddSceneCommand { get; }
public ICommand RemoveSceneCommand { get; }
public ICommand StartStreamCommand { get; }
public ICommand StopStreamCommand { get; }
public ICommand ConnectYouTubeCommand { get; }
public MainViewModel()
{
_youtubeAuth = new YouTubeAuthService("", ""); // TODO: load from config
_youtubeStream = new YouTubeStreamService(_youtubeAuth);
_youtubeChat = new YouTubeChatService(_youtubeAuth);
_youtubeChat.MessageReceived += OnChatMessageReceived;
AddSceneCommand = new RelayCommand(_ => AddScene());
RemoveSceneCommand = new RelayCommand(scene => RemoveScene(scene as Scene));
StartStreamCommand = new RelayCommand(_ => StartStream(), _ => StreamStatus == StreamStatus.Offline);
StopStreamCommand = new RelayCommand(_ => StopStream(), _ => StreamStatus == StreamStatus.Streaming);
ConnectYouTubeCommand = new RelayCommand(_ => ConnectYouTube());
// Start with a default scene
AddScene("Scene 1");
}
private void AddScene(string? name = null)
{
var scene = new Scene { Name = name ?? $"Scene {Scenes.Count + 1}" };
Scenes.Add(scene);
ActiveScene = scene;
}
private void RemoveScene(Scene? scene)
{
if (scene == null) return;
Scenes.Remove(scene);
if (ActiveScene == scene)
ActiveScene = Scenes.FirstOrDefault();
}
private void OnChatMessageReceived(ChatMessage message)
{
System.Windows.Application.Current.Dispatcher.Invoke(() =>
{
ChatMessages.Add(message);
if (ChatMessages.Count > 500)
ChatMessages.RemoveAt(0);
});
}
private async void StartStream()
{
StreamStatus = StreamStatus.Connecting;
OnPropertyChanged(nameof(StatusDisplay));
// TODO: Initialize capture pipeline, encode, and push to RTMP
// For now, simulate connection
StreamStatus = StreamStatus.Streaming;
OnPropertyChanged(nameof(StatusDisplay));
}
private void StopStream()
{
StreamStatus = StreamStatus.Offline;
OnPropertyChanged(nameof(StatusDisplay));
}
private void ConnectYouTube()
{
// TODO: Launch OAuth2 flow in browser
// For now, this is a stub
IsYouTubeConnected = false;
}
}