fix: egress 수명을 세션 수명으로 분리해 트랙 교체 시 송출 단절 해소 - #86
Merged
Conversation
egress가 트랙 수명(trackCtx)에 묶여 있어 카메라 전환 시 함께 종료됐다. 새 트랙의 OnTrack이 egress를 다시 만들어 복구되긴 했으나 그 사이 RTMP 연결이 끊기고 FFmpeg가 재기동되어 송출이 단절됐다. egress를 세션 수명으로 옮기고 트랙 교체 시 기존 egress를 재전달해 연결을 유지한다. 세션 종료와 별개로 egress만 멈추는 경로(#83의 명시적 stream/stop)를 위해 전용 cancel을 분리해 뒀다. 수명이 트랙과 분리되면서 사라진 해상도 재측정 지점은 프레임 메타데이터 비교로 대체했다. 해상도 변경은 송출 실패가 아니므로 백오프·재연결 집계 없이 재측정 후 재스폰한다. 생성 시점에 idle로 고정된 채 갱신되지 않던 StreamState는 egress 상태 스냅샷으로 합성한다. StopReason은 명시적 중지 개념이 생기는 #83에서 채운다. NewRTMPEgress와 RunTrack의 시그니처는 바꾸지 않아 기존 호출부와 테스트는 영향받지 않는다.
hjbin-25
approved these changes
Aug 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #84
문제
egress의 수명이 세션이 아니라 트랙에 묶여 있었다.
manager.go에서 트랙마다trackCtx를 새로 만들고(:493) egress를 거기에 묶는데(:531), 새 트랙이 도착하면 기존trackCancel()을 호출한다(:486-491).따라서 발행자가 방송 중 카메라를 전환하면 egress가 종료된다. 새 트랙의
OnTrack이 egress를 다시 만들어 결과적으로 복구되지만, 그 사이 RTMP 연결이 끊기고 FFmpeg가 재기동되므로 시청자 쪽에서는 스트림이 끊겼다 다시 붙는 것으로 보인다.현재는
YOUTUBE_STREAM_KEY가 2026-08-02 유실 후 미복구라manager.go:526에서 egress 생성 자체가 스킵되어 이 결함이 드러나지 않는다. 키가 있던 시점에는 실제로 발생하던 문제다.변경
egress 수명을 세션 수명으로 이동
Session에egress참조와egressCancel추가. 세션 종료와 별개로 egress만 멈추는 경로(#83의 명시적 stream/stop)를 위해 전용 cancel을 분리했다OnTrack에서CreateForUser로 이동.Run은 첫 프레임 수집 전에는 FFmpeg를 스폰하지 않으므로 트랙 없는 세션에서는 고루틴 하나가 대기할 뿐이다OnTrack은s.mu하에 기존 egress를 읽어RunTrack에 재전달한다.RunTrack은Enqueue만 호출하고 egress를 종료시키지 않으므로(track.go:351-352) 트랙 교체에도 연결이 유지된다해상도 재기동
수명이 트랙과 분리되면서 기존에 egress 재생성으로 우연히 얻어지던 해상도 재측정 지점이 사라졌다.
writeFrames가 프레임 메타데이터의width/height를 현재 스폰 해상도와 비교해, 불일치 프레임을 쓰지 않고 반환하면 그 프레임을 시드로 재측정(30프레임) 후 재스폰한다.ffmpeg.go:136, H264h264.go:48·241-242). JPEG 헤더 재파싱 없이 메타데이터 비교만으로 정확하다ReconnectAttempts·재연결 메트릭에 집계하지 않는다StreamState 실배선
생성 시
idle로 설정된 뒤 갱신 코드가 전혀 없던StreamState를 egress 상태 스냅샷으로 합성한다. egress 고루틴이 세션 락을 잡는 역방향 결합을 피하려 pull 모델(전용 뮤텍스 +Status()게터)을 썼다. 락 순서는session.mu → egress.statusMu단방향이다.StopReason은 명시적 중지 개념이 생기는 #83에서 채운다.범위 밖 (의도적)
YOUTUBE_STREAM_KEY전역 키 트리거를 그대로 유지했고, 사용자별 송출 로직과handleStartStream/handleStopStream실배선은 넣지 않았다. 전부 #83에서 처리한다. 동작 보존이 이 PR의 리뷰 가치다.NewRTMPEgress와RunTrack의 시그니처를 바꾸지 않아 기존 호출부와 egress 테스트 6종은 영향받지 않는다.검증
go test -race -count=1 ./...전 패키지 통과 (캐시 우회)TestBypassWebRTCEndToEnd격리 race 실행 통과 (알려진 flaky 대응)-race -count=3통과 — 640x360 스폰 → 해상도 변경 감지 → 1280x720 재스폰, ffprobe로 최종 출력 확인,ReconnectAttempts=0확인StartedAt보존 포함) / 시드 수집 / 취소 시 수집 중단 / 해상도 판정 표 테스트 / 스폰 전 취소 종료 / 세션 수명 egress 생성·삭제 /TargetURL스트림 키 비노출 / 키 없을 때 기존 동작 보존배포 영향
없다.
YOUTUBE_STREAM_KEY가 비어 있어 egress 생성 경로가 실행되지 않으므로 현재 프로덕션 동작은 변하지 않는다.관계
#83 (사용자별 RTMP 송출 대상)의 선행 작업. 명시적 방송 시작 구조가 성립하려면 egress가 트랙보다 오래 살아야 한다.