Update app.py
Browse files
app.py
CHANGED
@@ -1,111 +1,107 @@
|
|
1 |
-
import time
|
2 |
-
import cv2
|
3 |
-
import numpy as np
|
4 |
import streamlit as st
|
5 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6 |
|
7 |
-
#
|
8 |
-
|
|
|
|
|
9 |
|
10 |
-
|
11 |
-
|
12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
13 |
"""
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
</style>
|
25 |
-
""",
|
26 |
-
unsafe_allow_html=True,
|
27 |
-
)
|
28 |
-
# -----------------------------------------------------------------------------
|
29 |
-
st.title("Live Streaming Space")
|
30 |
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
|
|
39 |
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
if now - self.last_flush_time >= self.buffer_duration:
|
46 |
-
# Here you could (for example) encode/store the buffered frames.
|
47 |
-
# For this demo we just clear the buffer every 3 seconds.
|
48 |
-
self.buffer = []
|
49 |
-
self.last_flush_time = now
|
50 |
-
# Return the image unchanged for display
|
51 |
-
return img
|
52 |
|
53 |
-
#
|
54 |
-
|
55 |
-
|
56 |
-
password = st.sidebar.text_input("Enter broadcasting password", type="password")
|
57 |
-
if password == "test123":
|
58 |
-
st.sidebar.success("Authenticated for broadcasting!")
|
59 |
-
broadcast_mode = True
|
60 |
-
else:
|
61 |
-
broadcast_mode = False
|
62 |
|
63 |
-
#
|
64 |
-
|
65 |
-
st.sidebar.header("Broadcast Settings")
|
66 |
-
# (In a real app you might enumerate the actual connected cameras.
|
67 |
-
# Here we simply provide a dummy list of choices.)
|
68 |
-
camera_options = ["Camera 0", "Camera 1", "Camera 2"]
|
69 |
-
camera_choice = st.sidebar.selectbox("Select Camera", camera_options)
|
70 |
-
camera_index = int(camera_choice.split(" ")[-1])
|
71 |
|
72 |
-
|
73 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
74 |
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
"controls": True,
|
85 |
-
"style": {
|
86 |
-
"width": "100%",
|
87 |
-
"border": "2px solid #ccc",
|
88 |
-
"border-radius": "10px",
|
89 |
-
},
|
90 |
-
},
|
91 |
-
rtc_configuration={"iceServers": [{"urls": ["stun:stun.l.google.com:19302"]}]},
|
92 |
-
)
|
93 |
-
else:
|
94 |
-
st.write("### Viewing Broadcast")
|
95 |
-
st.info("If you have the broadcasting password, enter it in the sidebar to broadcast your own stream.")
|
96 |
-
# In viewing mode, join the same “room” in RECVONLY mode so you can see the active stream.
|
97 |
-
webrtc_ctx = webrtc_streamer(
|
98 |
-
key="live_stream",
|
99 |
-
mode=WebRtcMode.RECVONLY,
|
100 |
-
media_stream_constraints={"video": True, "audio": False},
|
101 |
-
video_transformer_factory=BufferingTransformer,
|
102 |
-
video_html_attrs={
|
103 |
-
"controls": True,
|
104 |
-
"style": {
|
105 |
-
"width": "100%",
|
106 |
-
"border": "2px solid #ccc",
|
107 |
-
"border-radius": "10px",
|
108 |
-
},
|
109 |
-
},
|
110 |
-
rtc_configuration={"iceServers": [{"urls": ["stun:stun.l.google.com:19302"]}]},
|
111 |
-
)
|
|
|
|
|
|
|
|
|
1 |
import streamlit as st
|
2 |
+
import cv2
|
3 |
+
import time
|
4 |
+
import threading
|
5 |
+
import os
|
6 |
+
from streamlit_autorefresh import st_autorefresh
|
7 |
+
|
8 |
+
# Initialize session state for broadcasting if not already set
|
9 |
+
if "broadcasting" not in st.session_state:
|
10 |
+
st.session_state.broadcasting = False
|
11 |
+
if "broadcaster_thread" not in st.session_state:
|
12 |
+
st.session_state.broadcaster_thread = None
|
13 |
+
|
14 |
+
def broadcast_loop(camera_index):
|
15 |
+
"""
|
16 |
+
Capture 3-second segments from the selected camera and write them to 'latest.mp4'.
|
17 |
+
This loop runs in a background thread while st.session_state.broadcasting is True.
|
18 |
+
"""
|
19 |
+
cap = cv2.VideoCapture(int(camera_index))
|
20 |
+
if not cap.isOpened():
|
21 |
+
st.error("Could not open camera.")
|
22 |
+
return
|
23 |
|
24 |
+
# Try to determine FPS; default to 20 if not available.
|
25 |
+
fps = cap.get(cv2.CAP_PROP_FPS)
|
26 |
+
if fps is None or fps < 1:
|
27 |
+
fps = 20
|
28 |
|
29 |
+
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
30 |
+
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
31 |
+
|
32 |
+
while st.session_state.broadcasting:
|
33 |
+
# Open a VideoWriter to record a 3-second segment.
|
34 |
+
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
|
35 |
+
out = cv2.VideoWriter("latest.mp4", fourcc, fps, (width, height))
|
36 |
+
segment_start = time.time()
|
37 |
+
|
38 |
+
while time.time() - segment_start < 3 and st.session_state.broadcasting:
|
39 |
+
ret, frame = cap.read()
|
40 |
+
if not ret:
|
41 |
+
break
|
42 |
+
out.write(frame)
|
43 |
+
time.sleep(1.0 / fps)
|
44 |
+
out.release() # Close the file so that it can be served.
|
45 |
+
cap.release()
|
46 |
+
|
47 |
+
def start_broadcast(password, camera_index):
|
48 |
+
"""
|
49 |
+
Start broadcasting if the correct password is entered.
|
50 |
"""
|
51 |
+
if password != "test123":
|
52 |
+
return "Incorrect password! Broadcast not started."
|
53 |
+
if not st.session_state.broadcasting:
|
54 |
+
st.session_state.broadcasting = True
|
55 |
+
thread = threading.Thread(target=broadcast_loop, args=(camera_index,), daemon=True)
|
56 |
+
st.session_state.broadcaster_thread = thread
|
57 |
+
thread.start()
|
58 |
+
return "Broadcasting started."
|
59 |
+
else:
|
60 |
+
return "Already broadcasting."
|
|
|
|
|
|
|
|
|
|
|
|
|
61 |
|
62 |
+
def stop_broadcast():
|
63 |
+
"""
|
64 |
+
Stop broadcasting by setting the broadcasting flag to False.
|
65 |
+
"""
|
66 |
+
if st.session_state.broadcasting:
|
67 |
+
st.session_state.broadcasting = False
|
68 |
+
return "Broadcast stopped."
|
69 |
+
else:
|
70 |
+
return "Not broadcasting."
|
71 |
|
72 |
+
def get_latest_video_path():
|
73 |
+
"""
|
74 |
+
Return the path to the latest video if it exists.
|
75 |
+
"""
|
76 |
+
return "latest.mp4" if os.path.exists("latest.mp4") else None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
77 |
|
78 |
+
# Set page configuration
|
79 |
+
st.set_page_config(page_title="Temporary File‑Based Live Stream", layout="wide")
|
80 |
+
st.title("Temporary File‑Based Live Stream with Streamlit")
|
|
|
|
|
|
|
|
|
|
|
|
|
81 |
|
82 |
+
# Create two tabs: one for broadcasting and one for viewing.
|
83 |
+
tabs = st.tabs(["Broadcast", "View"])
|
|
|
|
|
|
|
|
|
|
|
|
|
84 |
|
85 |
+
with tabs[0]:
|
86 |
+
st.header("Broadcasting Controls")
|
87 |
+
password_input = st.text_input("Enter broadcast password:", type="password")
|
88 |
+
camera_index_input = st.number_input("Camera Index", min_value=0, value=0, step=1)
|
89 |
+
col1, col2 = st.columns(2)
|
90 |
+
with col1:
|
91 |
+
if st.button("Start Broadcasting"):
|
92 |
+
status = start_broadcast(password_input, camera_index_input)
|
93 |
+
st.info(status)
|
94 |
+
with col2:
|
95 |
+
if st.button("Stop Broadcasting"):
|
96 |
+
status = stop_broadcast()
|
97 |
+
st.info(status)
|
98 |
|
99 |
+
with tabs[1]:
|
100 |
+
st.header("Live View")
|
101 |
+
video_path = get_latest_video_path()
|
102 |
+
if video_path:
|
103 |
+
# Automatically refresh the app every 3 seconds to load the latest file.
|
104 |
+
st_autorefresh(interval=3000, key="video_autorefresh")
|
105 |
+
st.video(video_path)
|
106 |
+
else:
|
107 |
+
st.write("No broadcast available yet.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|