using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using ytLive.Models;
namespace ytLive.Services;
///
/// Manages YouTube live stream lifecycle — create broadcasts,
/// bind stream keys, monitor health.
///
public class YouTubeStreamService
{
private readonly YouTubeAuthService _auth;
private readonly HttpClient _http = new();
private const string ApiBase = "https://www.googleapis.com/youtube/v3";
public YouTubeStreamService(YouTubeAuthService auth)
{
_auth = auth;
}
private async Task EnsureToken()
{
if (_auth.CurrentChannel == null) return false;
if (_auth.CurrentChannel.TokenExpiry <= DateTime.UtcNow.AddMinutes(5))
return await _auth.RefreshToken();
return true;
}
public async Task CreateBroadcast(string title, string description, DateTime scheduledStartTime)
{
if (!await EnsureToken()) return null;
var broadcast = new
{
snippet = new
{
title,
description,
scheduledStartTime = scheduledStartTime.ToString("o"),
categoryId = "22" // People & Blogs
},
status = new
{
privacyStatus = "public",
selfDeclaredMadeForKids = false
},
contentDetails = new
{
enableAutoStart = true,
enableAutoStop = true
}
};
_http.DefaultRequestHeaders.Authorization = new("Bearer", _auth.CurrentChannel!.AccessToken);
var response = await _http.PostAsJsonAsync(
$"{ApiBase}/liveBroadcasts?part=snippet,status,contentDetails", broadcast);
if (!response.IsSuccessStatusCode) return null;
var json = await response.Content.ReadAsStringAsync();
var data = JsonSerializer.Deserialize(json);
return data.GetProperty("id").GetString();
}
public async Task BindStream(string broadcastId, string streamKey)
{
if (!await EnsureToken()) return null;
// Create stream resource
var stream = new
{
snippet = new { title = $"stream-{broadcastId}" },
contentDetails = new
{
ingestionType = "rtmp",
frameRate = "60fps",
resolution = "1080p"
}
};
_http.DefaultRequestHeaders.Authorization = new("Bearer", _auth.CurrentChannel!.AccessToken);
var response = await _http.PostAsJsonAsync(
$"{ApiBase}/liveStreams?part=snippet,contentDetails", stream);
if (!response.IsSuccessStatusCode) return null;
var json = await response.Content.ReadAsStringAsync();
var data = JsonSerializer.Deserialize(json);
var streamId = data.GetProperty("id").GetString();
// Bind stream to broadcast
var bindResponse = await _http.PutAsJsonAsync(
$"{ApiBase}/liveBroadcasts?part=id,contentDetails&id={broadcastId}",
new { contentDetails = new { streamId } });
return bindResponse.IsSuccessStatusCode ? streamId : null;
}
public async Task GetStreamHealth(string broadcastId)
{
if (!await EnsureToken()) return null;
_http.DefaultRequestHeaders.Authorization = new("Bearer", _auth.CurrentChannel!.AccessToken);
var response = await _http.GetAsync(
$"{ApiBase}/liveBroadcasts?part=contentDetails,status&id={broadcastId}");
if (!response.IsSuccessStatusCode) return null;
var json = await response.Content.ReadAsStringAsync();
var data = JsonSerializer.Deserialize(json);
var items = data.GetProperty("items");
if (items.GetArrayLength() == 0) return null;
var status = items[0].GetProperty("status").GetProperty("lifeCycleStatus").GetString();
return new StreamHealth
{
HealthStatus = status switch
{
"live" => "good",
"ready" => "ok",
"created" => "ok",
_ => "bad"
},
HealthMessage = status
};
}
}