feat: panelden video izleme/indirme/silme + eşzamanlı yayın kapasitesi düzeltmesi

- streamIngest worker concurrency 3'ten sabit değildi, artık
  MAX_CONCURRENT_CAPTURES (varsayılan 10) ile ayarlanabiliyor. Eskiden
  3'ten fazla kanal aynı anda canlıya geçerse fazlası, ilk 3'ten biri
  bitene kadar (saatlerce) hiç kayda alınmıyordu.
- Python worker'larda (signal-detection, stt-scoring) bullmq varsayılan
  concurrency'si 1'di — WORKER_CONCURRENCY (varsayılan 5) ile
  paralelleştirildi, birden fazla kanal aynı anda segment/transkript
  üretirse sıraya takılmasın diye.
- Frontend artık shared-media volume'üne bağlı: /api/segments/[id]/video
  route'u Range destekli video stream/indirme sağlıyor. Segment ve
  kanal detay sayfalarına <video> oynatıcı, indirme linki ve onaylı
  silme (RawSegment + CandidateSegment, dosya dahil) eklendi.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-30 18:33:33 +03:00
co-authored by Claude Sonnet 5
parent fd21bbfb14
commit 8a92b9b019
11 changed files with 148 additions and 5 deletions
+16
View File
@@ -1,4 +1,6 @@
import { prisma } from "@streamclipper/db";
import { deleteRawSegment, deleteCandidateSegment } from "../actions";
import { DeleteButton } from "../components/DeleteButton";
export const dynamic = "force-dynamic";
@@ -40,6 +42,17 @@ export default async function SegmentsPage() {
{segment.duration}sn · {new Date(segment.startedAt).toLocaleString("tr-TR")}
</div>
<video controls preload="metadata" style={{ width: "100%", maxWidth: "480px", marginTop: "0.5rem", borderRadius: "6px" }}>
<source src={`/api/segments/${segment.id}/video`} type="video/mp4" />
</video>
<div style={{ display: "flex", gap: "0.5rem", alignItems: "center", marginTop: "0.5rem" }}>
<a className="badge muted" href={`/api/segments/${segment.id}/video?download=1`}>İndir</a>
<form action={deleteRawSegment.bind(null, segment.id)}>
<DeleteButton confirmText="Bu segmenti ve içindeki tüm aday klipleri silmek istediğine emin misin? Geri alınamaz." />
</form>
</div>
{segment.candidates.length === 0 ? (
<p className="empty" style={{ marginTop: "0.5rem" }}>
Bu segmentte aday klip bulunamadı.
@@ -58,6 +71,9 @@ export default async function SegmentsPage() {
{transcriptPreview(c.transcriptJson) && (
<p className="transcript-preview">{transcriptPreview(c.transcriptJson)}</p>
)}
<form action={deleteCandidateSegment.bind(null, c.id)} style={{ marginTop: "0.35rem" }}>
<DeleteButton confirmText="Bu aday klibi silmek istediğine emin misin?" />
</form>
</div>
))
)}