Stream State across browser
This commit is contained in:
1 parent
391fc9ecd9
commit
2d98ea2d17
2 files changed
+85
-11
No files matched your search
+26
-1
@@ -42,6 +42,7 @@ _FILENAME_RE = re.compile(r"batch[_-](\d+)[_-](\d{8})[_-]\d{6}\.mp4", re.IGNOREC
|
|||||||
_streaming_mp4 = None
|
_streaming_mp4 = None
|
||||||
_streaming_lock = threading.Lock()
|
_streaming_lock = threading.Lock()
|
||||||
_streaming_proc = None
|
_streaming_proc = None
|
||||||
|
_current_file = None
|
||||||
|
|
||||||
|
|
||||||
def _parse_filename(filename):
|
def _parse_filename(filename):
|
||||||
@@ -115,7 +116,7 @@ def api_mp4_files():
|
|||||||
|
|
||||||
@app.route("/api/start-recount", methods=["POST"])
|
@app.route("/api/start-recount", methods=["POST"])
|
||||||
def start_recount():
|
def start_recount():
|
||||||
global _streaming_mp4, _streaming_proc
|
global _streaming_mp4, _streaming_proc, _current_file
|
||||||
data = request.get_json(force=True) or {}
|
data = request.get_json(force=True) or {}
|
||||||
mp4_path = data.get("path", "")
|
mp4_path = data.get("path", "")
|
||||||
if not mp4_path:
|
if not mp4_path:
|
||||||
@@ -125,6 +126,7 @@ def start_recount():
|
|||||||
|
|
||||||
with _streaming_lock:
|
with _streaming_lock:
|
||||||
_stop_ffmpeg_locked()
|
_stop_ffmpeg_locked()
|
||||||
|
_current_file = mp4_path
|
||||||
cmd = [
|
cmd = [
|
||||||
"ffmpeg", "-re", "-i", mp4_path,
|
"ffmpeg", "-re", "-i", mp4_path,
|
||||||
"-vf", "scale=640:480",
|
"-vf", "scale=640:480",
|
||||||
@@ -139,16 +141,39 @@ def start_recount():
|
|||||||
"success": True,
|
"success": True,
|
||||||
"stream_url": f"{RECOUNT_API_URL}/api/live-video",
|
"stream_url": f"{RECOUNT_API_URL}/api/live-video",
|
||||||
"file": Path(mp4_path).name,
|
"file": Path(mp4_path).name,
|
||||||
|
"path": mp4_path,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/stop-recount", methods=["POST"])
|
@app.route("/api/stop-recount", methods=["POST"])
|
||||||
def stop_recount():
|
def stop_recount():
|
||||||
|
global _current_file
|
||||||
with _streaming_lock:
|
with _streaming_lock:
|
||||||
_stop_ffmpeg_locked()
|
_stop_ffmpeg_locked()
|
||||||
|
_current_file = None
|
||||||
return jsonify({"success": True})
|
return jsonify({"success": True})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/state")
|
||||||
|
def api_state():
|
||||||
|
with _streaming_lock:
|
||||||
|
streaming = _streaming_proc is not None and _streaming_proc.poll() is None
|
||||||
|
mp4 = _streaming_mp4
|
||||||
|
current_file = _current_file
|
||||||
|
result = {
|
||||||
|
"streaming": streaming,
|
||||||
|
}
|
||||||
|
if current_file:
|
||||||
|
result["file"] = current_file
|
||||||
|
result["file_name"] = Path(current_file).name
|
||||||
|
result["stream_url"] = f"{RECOUNT_API_URL}/api/live-video"
|
||||||
|
batch_num, date_str = _parse_filename(Path(current_file).name)
|
||||||
|
if batch_num is not None:
|
||||||
|
result["batch_number"] = batch_num
|
||||||
|
result["batch_date"] = date_str
|
||||||
|
return jsonify(result)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/download-mp4")
|
@app.route("/api/download-mp4")
|
||||||
def api_download_mp4():
|
def api_download_mp4():
|
||||||
fpath = request.args.get("file", "")
|
fpath = request.args.get("file", "")
|
||||||
|
|||||||
+59
-10
@@ -434,11 +434,56 @@ let streamUrl = '';
|
|||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
loadRecountProgress();
|
loadRecountProgress();
|
||||||
loadFiles();
|
loadFiles();
|
||||||
|
syncState();
|
||||||
|
|
||||||
setInterval(loadRecountProgress, 500);
|
setInterval(loadRecountProgress, 500);
|
||||||
setInterval(loadFiles, 30000);
|
setInterval(loadFiles, 30000);
|
||||||
|
setInterval(syncState, 1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function syncState() {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/state');
|
||||||
|
const data = await res.json();
|
||||||
|
applyState(data);
|
||||||
|
} catch (err) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyState(data) {
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
if (data.file) {
|
||||||
|
selectedFile = data.file;
|
||||||
|
document.getElementById('btn-start').disabled = data.streaming;
|
||||||
|
document.getElementById('btn-download').disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.streaming && data.file) {
|
||||||
|
streaming = true;
|
||||||
|
streamUrl = data.stream_url;
|
||||||
|
const img = document.getElementById('stream-img');
|
||||||
|
const placeholder = document.getElementById('video-placeholder');
|
||||||
|
if (img.src.indexOf(streamUrl) === -1) {
|
||||||
|
img.src = streamUrl + '?' + Date.now();
|
||||||
|
img.style.display = 'block';
|
||||||
|
if (placeholder) placeholder.style.display = 'none';
|
||||||
|
}
|
||||||
|
document.getElementById('btn-stop').disabled = false;
|
||||||
|
document.getElementById('btn-start').disabled = true;
|
||||||
|
document.getElementById('btn-start').textContent = 'STREAMING';
|
||||||
|
document.getElementById('btn-start').classList.add('btn-ghost');
|
||||||
|
document.getElementById('video-status').innerHTML =
|
||||||
|
'<span style="width:6px;height:6px;border-radius:50%;background:var(--accent4);box-shadow:0 0 6px var(--accent4);animation:blink 1.5s ease-in-out infinite;"></span> streaming ' + (data.file_name || '');
|
||||||
|
document.getElementById('video-status').className = 'video-status connected';
|
||||||
|
|
||||||
|
loadBatchResult(data.file);
|
||||||
|
} else if (!data.streaming && data.file) {
|
||||||
|
loadBatchResult(data.file);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadFiles();
|
||||||
|
}
|
||||||
|
|
||||||
// --- File list ---
|
// --- File list ---
|
||||||
|
|
||||||
async function loadFiles() {
|
async function loadFiles() {
|
||||||
@@ -485,8 +530,8 @@ function selectFile(path, name) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
selectedFile = path;
|
selectedFile = path;
|
||||||
document.getElementById('btn-start').disabled = false;
|
|
||||||
document.getElementById('btn-download').disabled = false;
|
document.getElementById('btn-download').disabled = false;
|
||||||
|
if (!streaming) document.getElementById('btn-start').disabled = false;
|
||||||
loadFiles();
|
loadFiles();
|
||||||
loadBatchResult(path);
|
loadBatchResult(path);
|
||||||
}
|
}
|
||||||
@@ -557,7 +602,6 @@ function downloadFile() {
|
|||||||
async function startRecount() {
|
async function startRecount() {
|
||||||
if (!selectedFile) return;
|
if (!selectedFile) return;
|
||||||
const btnStart = document.getElementById('btn-start');
|
const btnStart = document.getElementById('btn-start');
|
||||||
const btnStop = document.getElementById('btn-stop');
|
|
||||||
btnStart.disabled = true;
|
btnStart.disabled = true;
|
||||||
btnStart.textContent = 'CONNECTING...';
|
btnStart.textContent = 'CONNECTING...';
|
||||||
|
|
||||||
@@ -576,6 +620,7 @@ async function startRecount() {
|
|||||||
}
|
}
|
||||||
streamUrl = data.stream_url;
|
streamUrl = data.stream_url;
|
||||||
streaming = true;
|
streaming = true;
|
||||||
|
selectedFile = data.path;
|
||||||
|
|
||||||
const img = document.getElementById('stream-img');
|
const img = document.getElementById('stream-img');
|
||||||
const placeholder = document.getElementById('video-placeholder');
|
const placeholder = document.getElementById('video-placeholder');
|
||||||
@@ -583,15 +628,9 @@ async function startRecount() {
|
|||||||
img.style.display = 'block';
|
img.style.display = 'block';
|
||||||
if (placeholder) placeholder.style.display = 'none';
|
if (placeholder) placeholder.style.display = 'none';
|
||||||
|
|
||||||
btnStop.disabled = false;
|
document.getElementById('btn-stop').disabled = false;
|
||||||
btnStart.textContent = 'STREAMING';
|
btnStart.textContent = 'STREAMING';
|
||||||
btnStart.classList.add('btn-ghost');
|
btnStart.classList.add('btn-ghost');
|
||||||
|
|
||||||
document.getElementById('video-status').innerHTML =
|
|
||||||
'<span style="width:6px;height:6px;border-radius:50%;background:var(--accent4);box-shadow:0 0 6px var(--accent4);animation:blink 1.5s ease-in-out infinite;"></span> streaming ' + data.file;
|
|
||||||
document.getElementById('video-status').className = 'video-status connected';
|
|
||||||
|
|
||||||
loadFiles();
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alert('Error: ' + err.message);
|
alert('Error: ' + err.message);
|
||||||
btnStart.disabled = false;
|
btnStart.disabled = false;
|
||||||
@@ -605,6 +644,8 @@ async function stopRecount() {
|
|||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
streaming = false;
|
streaming = false;
|
||||||
streamUrl = '';
|
streamUrl = '';
|
||||||
|
selectedFile = null;
|
||||||
|
batchResultLoaded = false;
|
||||||
|
|
||||||
const img = document.getElementById('stream-img');
|
const img = document.getElementById('stream-img');
|
||||||
const placeholder = document.getElementById('video-placeholder');
|
const placeholder = document.getElementById('video-placeholder');
|
||||||
@@ -614,7 +655,7 @@ async function stopRecount() {
|
|||||||
|
|
||||||
const btnStart = document.getElementById('btn-start');
|
const btnStart = document.getElementById('btn-start');
|
||||||
const btnStop = document.getElementById('btn-stop');
|
const btnStop = document.getElementById('btn-stop');
|
||||||
btnStart.disabled = selectedFile ? false : true;
|
btnStart.disabled = true;
|
||||||
btnStart.textContent = '▶ START RECOUNT';
|
btnStart.textContent = '▶ START RECOUNT';
|
||||||
btnStart.classList.remove('btn-ghost');
|
btnStart.classList.remove('btn-ghost');
|
||||||
btnStop.disabled = true;
|
btnStop.disabled = true;
|
||||||
@@ -623,6 +664,14 @@ async function stopRecount() {
|
|||||||
'<span style="width:6px;height:6px;border-radius:50%;background:var(--text-secondary);"></span> stopped';
|
'<span style="width:6px;height:6px;border-radius:50%;background:var(--text-secondary);"></span> stopped';
|
||||||
document.getElementById('video-status').className = 'video-status stopped';
|
document.getElementById('video-status').className = 'video-status stopped';
|
||||||
|
|
||||||
|
document.getElementById('live-title').innerHTML = '◉ Live Counter';
|
||||||
|
document.getElementById('live-count').textContent = '--';
|
||||||
|
document.getElementById('live-label').textContent = 'Objects Counted';
|
||||||
|
document.getElementById('live-batch').textContent = '--';
|
||||||
|
document.getElementById('live-time').textContent = '--';
|
||||||
|
document.getElementById('live-status').textContent = 'no file selected';
|
||||||
|
document.getElementById('live-status').className = 'counter-status disconnected';
|
||||||
|
|
||||||
loadFiles();
|
loadFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user