Handler.cs 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. using Application.Abstractions.Data;
  2. using Application.Abstractions.Hub;
  3. using Application.Abstractions.Messaging;
  4. using Application.Abstractions.YouTube;
  5. using Domain.Entities.Members;
  6. using Domain.Entities.Members.ValueObject;
  7. using Microsoft.EntityFrameworkCore;
  8. using SharedKernel.Results;
  9. namespace Application.Features.Admin.Channel.YouTubePubSub.ForceLive;
  10. internal sealed class Handler(
  11. IAppDbContext db,
  12. IYouTubeLiveStateStore liveStateStore,
  13. IChannelStatusBroadcaster broadcaster
  14. ) : ICommandHandler<Command, Result>
  15. {
  16. public async Task<Result> Handle(Command request, CancellationToken ct)
  17. {
  18. if (string.IsNullOrWhiteSpace(request.YouTubeChannelID) || string.IsNullOrWhiteSpace(request.VideoID))
  19. {
  20. return Result.Failure(Error.Problem("ForceLive.Invalid", "YouTube Channel ID 와 Video ID 가 필요합니다."));
  21. }
  22. var channel = await db.Channel.AsNoTracking()
  23. .FirstOrDefaultAsync(c => c.YouTubeChannelID == request.YouTubeChannelID && c.IsActive, ct);
  24. if (channel is null)
  25. {
  26. return Result.Failure(Error.NotFound("ForceLive.ChannelNotFound", "내부 Channel 을 찾을 수 없습니다."));
  27. }
  28. var title = string.IsNullOrWhiteSpace(request.Title) ? "(수동 등록 라이브)" : request.Title!;
  29. var now = DateTime.UtcNow;
  30. // YouTube API 호출 없이 LiveStreamInfo 수동 생성 — quota 0
  31. var liveInfo = new YouTubeLiveStreamInfo(
  32. VideoId: request.VideoID,
  33. Title: title,
  34. ChannelId: request.YouTubeChannelID,
  35. LiveBroadcastContent: "live",
  36. ActiveLiveChatId: null,
  37. ScheduledStartTime: null,
  38. ActualStartTime: now,
  39. ViewerCount: 0,
  40. ViewerCountUpdatedAt: now
  41. );
  42. // Redis 라이브 상태 저장
  43. await liveStateStore.SetLiveAsync(request.YouTubeChannelID, liveInfo);
  44. // DB BroadcastSession 생성 (없으면)
  45. var existing = await db.BroadcastSession
  46. .FirstOrDefaultAsync(b => b.Platform == BroadcastPlatform.YouTube && b.VideoID == request.VideoID, ct);
  47. if (existing is null)
  48. {
  49. var session = BroadcastSession.Create(channel.ID, BroadcastPlatform.YouTube, request.VideoID);
  50. session.UpdateStats(title, now, null, 0, 0);
  51. db.BroadcastSession.Add(session);
  52. await db.SaveChangesAsync(ct);
  53. }
  54. // SignalR 브로드캐스트 → ChannelSidebar / WatchView 실시간 반영
  55. await broadcaster.BroadcastAsync(channel.SID, isLive: true, viewerCount: 0, videoId: request.VideoID, ct);
  56. return Result.Success();
  57. }
  58. }