useDonationAlert.ts 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. 'use client';
  2. import { useEffect, useRef, useState, useCallback } from 'react';
  3. import * as signalR from '@microsoft/signalr';
  4. import { DonationAlertData, DonationRemoteState } from '@/types/donation';
  5. type AlertQueueItem = DonationAlertData & { status: 'queued'|'playing'|'done' };
  6. export function useDonationAlert(widgetToken: string, hubUrl: string) {
  7. const connectionRef = useRef<signalR.HubConnection|null>(null);
  8. const [connected, setConnected] = useState(false);
  9. const [queue, setQueue] = useState<AlertQueueItem[]>([]);
  10. const [current, setCurrent] = useState<AlertQueueItem|null>(null);
  11. const [remoteState, setRemoteState] = useState<DonationRemoteState>({
  12. isPaused: false,
  13. isAccepting: true,
  14. isAudioOnly: false,
  15. isVideoOnly: false
  16. });
  17. const [skipSignal, setSkipSignal] = useState(0);
  18. // SignalR 연결
  19. useEffect(() => {
  20. const conn = new signalR.HubConnectionBuilder()
  21. .withUrl(hubUrl)
  22. .withAutomaticReconnect()
  23. .build();
  24. conn.on('ReceiveAlert', (data: DonationAlertData) => {
  25. setQueue(prev => [...prev, { ...data, status: 'queued' }]);
  26. });
  27. conn.on('ReceiveSkip', () => {
  28. setSkipSignal(prev => prev + 1);
  29. });
  30. conn.on('ReceivePause', (isPaused: boolean) => {
  31. setRemoteState(prev => ({ ...prev, isPaused }));
  32. });
  33. conn.on('ReceiveState', (state: DonationRemoteState) => {
  34. setRemoteState(state);
  35. });
  36. conn.start().then(() => {
  37. conn.invoke('JoinChannel', widgetToken);
  38. setConnected(true);
  39. }).catch(err => {
  40. console.error('[DonationHub] Connect failed:', err);
  41. });
  42. conn.onreconnected(() => {
  43. conn.invoke('JoinChannel', widgetToken);
  44. setConnected(true);
  45. });
  46. conn.onclose(() => setConnected(false));
  47. connectionRef.current = conn;
  48. return () => {
  49. conn.stop();
  50. };
  51. }, [widgetToken, hubUrl]);
  52. // 큐 처리 — 일시정지가 아닐 때 다음 알림 꺼내기
  53. useEffect(() => {
  54. if (current || remoteState.isPaused || queue.length === 0) {
  55. return;
  56. }
  57. const next = queue[0];
  58. setQueue(prev => prev.slice(1));
  59. setCurrent({ ...next, status: 'playing' });
  60. // 알림 재생 시작 보고
  61. connectionRef.current?.invoke('AlertDelivered', next.alertID).catch(() => {});
  62. }, [queue, current, remoteState.isPaused]);
  63. // 스킵 시그널 처리
  64. useEffect(() => {
  65. if (skipSignal > 0 && current) {
  66. setCurrent(null);
  67. }
  68. }, [skipSignal]);
  69. // 알림 완료 콜백
  70. const onAlertComplete = useCallback(() => {
  71. setCurrent(null);
  72. }, []);
  73. return {
  74. connected,
  75. current,
  76. queue,
  77. remoteState,
  78. skipSignal,
  79. onAlertComplete
  80. };
  81. }