You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

5.6 KiB

Live Stop Phase Gate Design

Date: 2026-09-02
Status: Approved for implementation planning

Problem

Stopping a live stream currently clears local state immediately after POST /v1/live/{dockId}/stop. The UI then allows another POST /v1/live/{dockId}/sessions while the previous session may still be stopping. That can race the backend and produce join failures (business code 57007) or stale session polling against an old streamSessionId.

Scope

In scope:

  • Monitor live stop/reopen flow in useMonitorLive
  • Monitor detail panel / LivePlayer button disable + phase text
  • Join retries for business code 57007 in Monitor and MediaView

Out of scope:

  • Changing backend stop/join contracts
  • Treating viewer leave (DELETE .../viewers/me) as a stop-phase gate
  • Redesigning MediaView stop-push controls (MediaView has no stop-push button)

Goals

  1. After clicking stop, do not allow re-open until the old session reports phase=stopped.
  2. Keep the old streamSessionId and poll it until stopped.
  3. On reopen, always use the newly returned streamSessionId for phase poll and play-url.
  4. If join returns 57007, wait ~1.5s and retry a few times instead of immediate failure toast.
  5. Keep heartbeat independent of play retries; stop path clears playback and stops heartbeat for the stopping session.

Non-Goals

  • Global dock cooldown timers unrelated to an actual stopping session
  • Auto-stop on viewer leave
  • Changing HLS 404 startup retry behavior

Approach

Use a local stopping gate owned by useMonitorLive.

Recommended over pure 57007 retries because the UI can disable controls before the user clicks again, and over a dock-wide cooldown because the gate is tied to a concrete previous session.

State Machine

States relevant to this change:

Local condition UI phase text Play toggle Stop button
stop request in flight / stopping 正在停止 disabled disabled
old session stopped (gate cleared) 已停止 enabled hidden
join in flight (opening) 正在启动 disabled hidden
session starting / reconnecting 启动中 / 重连中 enabled (leave viewer) enabled
session streaming 直播中 enabled (leave viewer) enabled

Stop path (stopLiveStream)

  1. Confirm dialog unchanged.
  2. Set local stopping=true, clear playUrl and timers/heartbeat.
  3. Keep dockId + old session (streamSessionId) for polling.
  4. Call POST /v1/live/{dockId}/stop.
  5. Optionally leave viewer for the old session.
  6. Poll GET /v1/live/{dockId}/sessions/{oldSessionId} until:
    • phase === 'stopped' → clear stopping gate, allow reopen
    • timeout / hard failure → toast, keep controls conservative (do not silently reopen)
  7. While gate is active, openLive() and toggleLive() open-path are no-ops / disabled.

Reopen path (openLive)

  1. Reject if disposed, opening, stopping, or an active non-stopped session exists.
  2. POST /v1/live/{dockId}/sessions.
  3. On success, replace local session with the new result.session only.
  4. If new session phase === 'streaming', request play-url with the new session id.
  5. Otherwise start phase polling with the new session id.
  6. On business code 57007:
    • wait 1500ms
    • retry up to 3 additional attempts (4 total tries)
    • if still failing, toast and exit opening

Leave viewer path (closeLive / play toggle off)

Unchanged: invalidate generation, clear local state, leave viewer. No stop-phase polling.

API Error Handling

Code Meaning for frontend Action
57006 lease invalid existing rejoin flow
57007 previous session still stopping / not ready to create wait 1.5s and retry join a few times
other normal failure toast

http.js already attaches business code onto rejected errors; reuse that.

UI Binding Changes

  • liveView.opening true for join-in-flight
  • add liveView.stopping (or derive from session phase + local flag) to disable LivePlayer control and hide/disable stop button
  • livePhaseText:
    • local stop request / phase stopping正在停止
    • phase stopped / idle after gate clear → 已停止
    • opening with no session yet → 正在启动 / 打开中

MediaView

No stop-push gate. Only add the same 57007 join retry helper so a dock still stopping from Monitor does not hard-fail MediaView open.

Risks

  • Stop poll timeout leaves user blocked: mitigate with explicit toast and a manual clear/reset on dock switch / dispose.
  • Accidental reuse of old session id after reopen: mitigate by replacing live.session only from join response and scoping all polls/play-url/heartbeat to that id.
  • Race: user switches selected dock while stopping: gate must be dock/session scoped and disposed with page leave.

Acceptance Criteria

  1. Click stop → play and stop controls disabled; UI shows 正在停止.
  2. Frontend keeps polling the old streamSessionId until stopped.
  3. Only after stopped can play start a new join.
  4. New join uses new streamSessionId for subsequent poll and play-url.
  5. Join 57007 auto-retries with short delay instead of immediate failure.
  6. Viewer leave still clears immediately and does not enter the stopping gate.
  7. MediaView join also retries on 57007.

Implementation Touchpoints

  • src/composables/useMonitorLive.js
  • src/components/MonitorDetailPanel.vue (button disable bindings)
  • src/components/LivePlayer.vue (disable control while stopping)
  • src/views/MonitorView/MonitorView.vue (pass stopping into liveView)
  • src/views/MediaView/MediaView.vue (57007 retry on open)
  • optionally tiny shared helper for join-with-57007-retry near src/api/live.js