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:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user