라이브 녹화 도구에 “일시정지” 기능 붙이기 — 기존 감시 루프에 조건문 한 줄 끼워 넣기

자체 개발한 라이브 스트림 녹화 도구(Python/FastAPI + yt-dlp 기반)에 작은 기능 하나를 추가한 이야기다. 새로운 스케줄러나 백그라운드 작업을 새로 만들지 않고, 기존에 이미 돌고 있던 감시 루프를 그대로 활용해서 구현했다.

문제

이 도구는 채널을 등록해두면 방송이 시작되는 걸 자동으로 감지해서 녹화를 시작한다. 그런데 녹화 도중 Stop 버튼을 눌러 수동으로 멈춰도, 몇 초 뒤 감시 루프가 다시 그 채널을 체크하면서 “아직 라이브네” 하고 자동으로 녹화를 재시작해버렸다. 방송을 잠깐 쉬게 하고 싶어서 멈췄는데, 계속 다시 켜지는 셈이었다.

원인

감시 루프는 대략 이런 구조였다.

async def monitor_loop():
    while True:
        await asyncio.sleep(interval)  # 기본 60초
        ids = [cid for cid, c in channels.items() if c.get("monitoring", True)]
        # 각 채널마다 라이브 체크

각 채널을 체크할 때는 아래 로직으로 자동 녹화가 트리거됐다.

is_live = await check_is_live(ch["url"], ...)
if is_live and not channels[ch_id].get("recording_id"):
    rec_id = await _start_recording_for_channel(ch_id)

“방송 중” + “지금 녹화 중이 아님” 두 조건만 맞으면 무조건 자동으로 녹화가 시작되는 구조였다. Stop 버튼은 그 순간 돌고 있던 녹화 프로세스를 죽이는 것뿐이라, 그다음 감시 주기가 돌아오면 위 조건이 다시 참이 되어 녹화가 재시작됐다.

해결 방향

가장 먼저 떠오른 방법은 “N분 뒤에 다시 감시를 켜는 예약 작업”을 새로 만드는 것이었다. 하지만 이미 감시 루프가 몇십 초 간격으로 계속 돌고 있는 상태라, 굳이 별도의 타이머를 하나 더 만들 필요가 없었다. 대신 “이 시각까지는 자동 녹화를 시작하지 마라”는 미래 시각 하나를 채널 데이터에 저장해두고, 기존 감시 루프가 매번 그 시각을 지났는지만 확인하도록 조건문 한 줄을 끼워 넣는 방식을 택했다.

paused_until = channels[ch_id].get("monitoring_paused_until", 0)
if is_live and paused_until and time.time() < paused_until:
    return  # 방송 중이어도 자동 녹화는 건너뜀
if is_live and not channels[ch_id].get("recording_id"):
    rec_id = await _start_recording_for_channel(ch_id)

라이브 감지 자체(상태 갱신, 마지막 체크 시각 표시 등)는 그대로 동작하고, 딱 “자동으로 녹화를 시작하는” 한 줄만 건너뛰게 만들었다. 별도의 카운트다운을 어딘가에서 계속 돌리는 게 아니라, 목표 시각 하나만 기억해두고 매번 “지금이 그 시각을 지났나?”만 비교하는 방식이라 구현이 단순하다.

API 확장

기존 정지 엔드포인트에 “몇 분간 정지할지”를 받는 파라미터를 추가했다.

@app.post("/api/channels/{ch_id}/stop")
async def stop_channel(ch_id: str, pause_minutes: int = 0):
    ...
    _stop_rec(recordings[rec_id], force=False)  # 기존 정지 로직
    if pause_minutes > 0:
        channels[ch_id]["monitoring_paused_until"] = time.time() + pause_minutes * 60
        _save_state()
    return {"ok": True, "paused_until": channels[ch_id].get("monitoring_paused_until", 0)}

“지금 시각 + N분”을 계산해서 저장하는 게 전부다.

UI

  • Stop 버튼을 누르면 바로 정지하는 대신, “1시간 / 3시간 / 6시간 / 12시간 / 직접 입력(분)” 중 고를 수 있는 작은 선택 창을 띄우도록 바꿨다.
  • 채널 목록 카드에 정지 중인 채널은 “⏸ N시간 N분 남음” 배지를 표시했다. 이 값은 서버에서 받아오는 값을 그대로 계산해서 보여주는 거라, 기존에 몇 초 간격으로 돌던 화면 갱신 주기에 맞춰 자연스럽게 최신 값으로 다시 그려진다. 별도의 초 단위 실시간 카운트다운은 만들지 않았다 — 몇 초 단위로만 갱신돼도 “얼마나 남았는지” 확인하는 용도로는 충분하고, 매초 화면을 다시 그리는 건 자원 낭비였다.

놓쳤던 부분: 수동으로 다시 시작하면?

기능을 붙이고 나서 테스트하다가, 정지해둔 채널을 수동으로 Record 버튼 눌러서 다시 녹화를 시작했는데도 “⏸ N분 남음” 배지가 그대로 남아있는 걸 발견했다. 원인은 간단했다 — 이 배지는 저장된 “정지 목표 시각” 값이 남아있으면 그냥 계산해서 보여주는 거라, 수동으로 녹화를 시작한 것과 별개로 그 값 자체는 지워지지 않았기 때문이다.

수동으로 Record를 누른다는 건 “이제 정지할 필요 없다”는 의사표시이므로, 그 시점에 저장된 값을 지우도록 한 줄 추가했다.

if ch.get("monitoring_paused_until"):
    ch["monitoring_paused_until"] = 0
    _save_state()

사소하지만 유용했던 것: 마지막 입력값 기억하기

“직접 입력(분)”으로 시간을 넣을 때마다 매번 새로 타이핑하는 게 번거로워서, 브라우저의 로컬 저장소에 마지막으로 입력한 값을 저장해뒀다가 다음에 창을 열 때 자동으로 채워지도록 했다. 서버와는 무관한, 순수하게 그 브라우저에서만 기억되는 편의 기능이다.

정리

  • 새 기능이라고 해서 항상 새 인프라(스케줄러, 큐, 타이머)가 필요한 건 아니다. 이미 주기적으로 도는 루프가 있다면, 그 루프가 확인하는 조건 하나를 추가하는 것만으로 “예약된 동작”을 흉내 낼 수 있다.
  • 새 상태값을 하나 추가할 때는, 그 값이 “누가 언제 지우는지”까지 같이 설계해야 한다. 저장만 하고 지우는 경로를 안 만들면, 사용자 입장에서는 화면에 남은 흔적이 왜 안 없어지는지 헷갈리는 상황이 생긴다.