feat(streaming): frontend page for viewing and controlling stream #7

Merged
dylan merged 3 commits from feat/frontend-streaming-controls into master 2026-07-02 19:46:32 +01:00
5 changed files with 82 additions and 19 deletions
Showing only changes of commit fe735faba8 - Show all commits

View File

@@ -0,0 +1,16 @@
meta {
name: Delete Marker
type: http
seq: 4
}
delete {
url: {{base_url}}/markers/5
body: none
auth: inherit
}
settings {
encodeUrl: true
timeout: 0
}

View File

@@ -9,6 +9,7 @@ import {
getStreamHistory,
getMarkers,
createMarker,
deleteMarker,
saveSectionByMarkers,
clipSection,
getJob,
@@ -120,7 +121,7 @@ const StreamControl = () => {
const refreshMarkers = () => {
getMarkers()
.then((data) => setMarkers(data.filter((m) => m.streamId === streamStatus?.id)
.then((data) => setMarkers(data
.sort((a, b) => stringToDate(a.timestamp).getTime() - stringToDate(b.timestamp).getTime())))
.catch(() => {});
};
@@ -209,6 +210,17 @@ const StreamControl = () => {
}
};
const handleDeleteMarker = async (id: number) => {
try {
await deleteMarker(id);
if (startMarkerId === String(id)) setStartMarkerId("");
if (endMarkerId === String(id)) setEndMarkerId("");
refreshMarkers();
} catch (err) {
setMarkerError(err instanceof Error ? err.message : "Failed to delete marker");
}
};
const handleChipClick = (id: number) => {
const idStr = String(id);
setMarkerSaveError(null);
@@ -289,16 +301,16 @@ const StreamControl = () => {
{markerError && <p className="text-sm text-red-500">{markerError}</p>}
{markers.length > 0 && (
<div className="flex gap-2 overflow-x-auto pb-0.5 -mx-1 px-1">
<div className="flex gap-2 overflow-x-auto pb-0.5 -mx-1 px-1 pt-1.5">
{markers.map((m) => {
const isStart = startMarkerId === String(m.id);
const isEnd = endMarkerId === String(m.id);
return (
<div key={m.id} className="group relative flex-shrink-0">
<button
key={m.id}
onClick={() => handleChipClick(m.id)}
className={clsx(
"flex-shrink-0 flex flex-col items-start gap-0.5 rounded-lg border px-3 py-1.5 text-left transition-colors duration-150",
"flex flex-col items-start gap-0.5 rounded-lg border px-3 py-1.5 text-left transition-colors duration-150",
isStart && "border-primary bg-primary/5 ring-1 ring-primary",
isEnd && "border-accent bg-accent/5 ring-1 ring-accent",
!isStart && !isEnd && "border-gray-200 hover:border-gray-300 hover:bg-gray-50"
@@ -311,6 +323,14 @@ const StreamControl = () => {
{isEnd && <span className="text-accent font-medium"> · end</span>}
</span>
</button>
<button
onClick={(e) => { e.stopPropagation(); handleDeleteMarker(m.id); }}
title="Delete marker"
className="hidden group-hover:flex items-center justify-center absolute -top-1.5 -right-1.5 w-4 h-4 rounded-full bg-gray-400 hover:bg-red-500 text-white transition-colors duration-150"
>
<X size={10} />
</button>
</div>
);
})}
</div>

View File

@@ -263,6 +263,18 @@ const createMarker = async (message: string): Promise<Marker> => {
return result.data;
};
const deleteMarker = async (id: number): Promise<void> => {
const response = await fetch(API_URL + `/api/v1/markers/${id}`, {
method: 'DELETE',
credentials: 'include',
});
if (!response.ok) throw new Error(`Failed to delete marker: ${response.status}`);
const result: APIResponse = await response.json();
if (result.status === 'error') throw new Error(`Failed to delete marker: ${result.message}`);
};
const saveSection = async (startTime: string, endTime: string, title?: string, description?: string): Promise<string> => {
const response = await fetch(API_URL + '/api/v1/media/save', {
method: 'POST',
@@ -327,6 +339,7 @@ export {
getStreamHistory,
getMarkers,
createMarker,
deleteMarker,
saveSection,
saveSectionByMarkers,
clipSection,

View File

@@ -51,6 +51,15 @@ public class MarkerController {
);
}
@DeleteMapping("/{id}")
public ResponseEntity<APIResponse<String>> deleteMarker(@PathVariable Long id) {
markerService.deleteMarker(id);
return ResponseEntity.ok(
new APIResponse<>(SUCCESS, "Marker deleted successfully", "Marker " + id + " deleted.")
);
}
private MarkerResponse convertToDTO(Marker marker) {
return new MarkerResponse(
marker.getId(),

View File

@@ -72,6 +72,11 @@ public class MarkerService {
return markerRepository.saveAndFlush(marker);
}
public void deleteMarker(Long id) {
Marker marker = getMarkerById(id);
markerRepository.delete(marker);
}
@Scheduled(fixedDelay = 360_000)
@Transactional
public void deleteOldMarkers() {