+
+
+
+
+
diff --git a/examples/streaming_web/frontend/public/js/index.js b/examples/streaming_web/frontend/public/js/index.js
new file mode 100644
index 0000000..2641014
--- /dev/null
+++ b/examples/streaming_web/frontend/public/js/index.js
@@ -0,0 +1,37 @@
+const API_URL = '/api'; // Backend API base path
+
+// Execute when the document's DOM is fully loaded
+document.addEventListener('DOMContentLoaded', async () => {
+ const cameraGrid = document.getElementById('camera-grid'); // Reference to the camera grid container
+
+ try {
+ // Fetch the list of labels from the backend
+ const response = await fetch(`${API_URL}/labels`);
+ if (!response.ok) throw new Error('Failed to fetch labels'); // Throw an error if the response is not OK
+ const data = await response.json(); // Parse the JSON response
+
+ // Render the fetched labels onto the page
+ const labels = data.labels || []; // Default to an empty array if no labels are returned
+ labels.forEach(label => {
+ const cameraDiv = document.createElement('div'); // Create a container for each label
+ cameraDiv.className = 'camera'; // Assign a class for styling
+
+ const link = document.createElement('a'); // Create a clickable link
+ link.href = `/label.html?label=${encodeURIComponent(label)}`; // Encode the label to ensure URL safety
+
+ const title = document.createElement('h2'); // Create a title for the label
+ title.textContent = label; // Set the label text
+
+ const description = document.createElement('p'); // Create a description under the title
+ description.textContent = `View ${label}`; // Set the descriptive text
+
+ link.appendChild(title); // Add the title to the link
+ link.appendChild(description); // Add the description to the link
+ cameraDiv.appendChild(link); // Add the link to the camera container
+ cameraGrid.appendChild(cameraDiv); // Add the camera container to the grid
+ });
+ } catch (error) {
+ // Log an error message if fetching or processing labels fails
+ console.error('Error fetching labels:', error);
+ }
+});
diff --git a/examples/streaming_web/frontend/public/js/label.js b/examples/streaming_web/frontend/public/js/label.js
new file mode 100644
index 0000000..df1012f
--- /dev/null
+++ b/examples/streaming_web/frontend/public/js/label.js
@@ -0,0 +1,117 @@
+let socket; // Define the WebSocket globally to manage the connection throughout the script
+
+// Execute when the document's DOM is fully loaded
+document.addEventListener('DOMContentLoaded', () => {
+ const labelTitle = document.getElementById('label-title'); // Reference to the label title element
+ const cameraGrid = document.getElementById('camera-grid'); // Reference to the camera grid container
+ const urlParams = new URLSearchParams(window.location.search); // Extract query parameters from the URL
+ const label = urlParams.get('label'); // Retrieve the 'label' parameter
+
+ // If the label is missing from the URL, log an error and terminate further execution
+ if (!label) {
+ console.error('Label parameter is missing in the URL');
+ return;
+ }
+
+ // Set the page's title to the label name
+ labelTitle.textContent = label;
+
+ // Initialise the WebSocket connection for the given label
+ initializeWebSocket(label);
+
+ // Ensure the WebSocket connection is closed when the page is unloaded
+ window.addEventListener('beforeunload', () => {
+ if (socket) {
+ socket.close(); // Close the WebSocket connection gracefully
+ }
+ });
+});
+
+/**
+ * Initialise the WebSocket connection for live updates.
+ *
+ * @param {string} label - The label used to establish the WebSocket connection.
+ */
+function initializeWebSocket(label) {
+ // Determine the appropriate WebSocket protocol based on the page's protocol
+ const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
+ socket = new WebSocket(`${protocol}${window.location.host}/api/ws/labels/${encodeURIComponent(label)}`);
+
+ // Handle WebSocket connection establishment
+ socket.onopen = () => {
+ console.log('WebSocket connected!');
+
+ // Set up a heartbeat mechanism to keep the connection alive
+ setInterval(() => {
+ if (socket.readyState === WebSocket.OPEN) {
+ socket.send(JSON.stringify({ type: 'ping' })); // Send a ping message every 30 seconds
+ }
+ }, 30000);
+ };
+
+ // Handle incoming messages from the WebSocket server
+ socket.onmessage = (event) => {
+ const data = JSON.parse(event.data); // Parse the received JSON data
+ handleUpdate(data, label); // Process the update based on the current label
+ };
+
+ // Handle WebSocket errors
+ socket.onerror = (error) => console.error('WebSocket error:', error);
+
+ // Handle WebSocket closure
+ socket.onclose = () => console.log('WebSocket closed');
+}
+
+/**
+ * Handle updates received from the WebSocket server.
+ *
+ * @param {Object} data - The data received from the WebSocket server.
+ * @param {string} currentLabel - The label currently being displayed.
+ */
+function handleUpdate(data, currentLabel) {
+ if (data.label === currentLabel) {
+ console.log('Received update for current label:', data.label);
+ updateCameraGrid(data.images); // Update the camera grid with new images
+ } else {
+ console.log('Received update for different label:', data.label);
+ }
+}
+
+/**
+ * Update the camera grid with new images.
+ *
+ * @param {Array} images - An array of image data, each containing a key and base64-encoded image.
+ */
+function updateCameraGrid(images) {
+ const cameraGrid = document.getElementById('camera-grid'); // Reference to the camera grid container
+ images.forEach(({ key, image }) => {
+ // Check if a camera div for the given key already exists
+ const existingCameraDiv = document.querySelector(`.camera[data-key="${key}"]`);
+ if (existingCameraDiv) {
+ // Update the existing image source
+ const img = existingCameraDiv.querySelector('img');
+ img.src = `data:image/png;base64,${image}`;
+ } else {
+ // Create a new camera div if it doesn't exist
+ const cameraDiv = document.createElement('div');
+ cameraDiv.className = 'camera'; // Add a class for styling
+ cameraDiv.dataset.key = key; // Set a custom data attribute with the key
+
+ // Create a title for the camera
+ const title = document.createElement('h2');
+ title.textContent = key.split('_').pop(); // Extract and display a simplified key
+
+ // Create an image element for the camera
+ const img = document.createElement('img');
+ img.src = `data:image/png;base64,${image}`; // Set the base64-encoded image as the source
+ img.alt = `${key} image`; // Add an alternative text for accessibility
+
+ // Append the title and image to the camera div
+ cameraDiv.appendChild(title);
+ cameraDiv.appendChild(img);
+
+ // Append the camera div to the grid
+ cameraGrid.appendChild(cameraDiv);
+ }
+ });
+}
diff --git a/examples/streaming_web/frontend/public/label.html b/examples/streaming_web/frontend/public/label.html
new file mode 100644
index 0000000..67cd243
--- /dev/null
+++ b/examples/streaming_web/frontend/public/label.html
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+ Label Details
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/streaming_web/frontend/vite.config.js b/examples/streaming_web/frontend/vite.config.js
new file mode 100644
index 0000000..23b6108
--- /dev/null
+++ b/examples/streaming_web/frontend/vite.config.js
@@ -0,0 +1,31 @@
+import { defineConfig } from 'vite';
+
+// Export the Vite configuration object
+export default defineConfig({
+ // Set the root directory for the project to 'public'
+ root: 'public',
+
+ server: {
+ // Define the port for the development server
+ port: 8888,
+
+ proxy: {
+ // Configure proxy settings for the '/api' prefix
+ '/api': {
+ // The backend server to forward API requests to
+ target: 'http://127.0.0.1:8000',
+
+ // Enable changing the origin of the host header to the target URL
+ changeOrigin: true,
+
+ // Enable WebSocket proxying
+ ws: true,
+ },
+ },
+ },
+
+ build: {
+ // Specify the output directory for the build process
+ outDir: '../dist',
+ },
+});
diff --git a/examples/streaming_web/static/js/camera.js b/examples/streaming_web/static/js/camera.js
deleted file mode 100644
index c4cdd87..0000000
--- a/examples/streaming_web/static/js/camera.js
+++ /dev/null
@@ -1,7 +0,0 @@
-$(document).ready(function(){
- function updateImage() {
- var src = $("#camera-image").attr("src").split('?')[0]; // Remove any existing query string
- $("#camera-image").attr("src", src + '?' + new Date().getTime());
- }
- setInterval(updateImage, 5000); // Update every 5 seconds
-});
diff --git a/examples/streaming_web/static/js/index.js b/examples/streaming_web/static/js/index.js
deleted file mode 100644
index 8c8a360..0000000
--- a/examples/streaming_web/static/js/index.js
+++ /dev/null
@@ -1,8 +0,0 @@
-$(document).ready(function(){
- setInterval(function(){
- $('img').each(function(){
- var src = $(this).attr('src').split('?')[0]; // Remove any existing query string
- $(this).attr('src', src + '?' + new Date().getTime());
- });
- }, 5000); // Update every 5 seconds
-});
diff --git a/examples/streaming_web/static/js/label.js b/examples/streaming_web/static/js/label.js
deleted file mode 100644
index 44761d6..0000000
--- a/examples/streaming_web/static/js/label.js
+++ /dev/null
@@ -1,87 +0,0 @@
-$(document).ready(() => {
- initializeWebSocket();
-});
-
-/**
- * Initialize the WebSocket connection and set up event handlers.
- */
-function initializeWebSocket() {
- const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
- const currentPageLabel = getCurrentPageLabel();
-
- const socket = new WebSocket(`${protocol}${window.location.host}/ws/label/${currentPageLabel}`);
-
- socket.onopen = () => {
- console.log('WebSocket connected!');
- };
-
- socket.onmessage = (event) => {
- const data = JSON.parse(event.data);
- handleUpdate(data, currentPageLabel);
- };
-
- socket.onerror = (error) => {
- console.error('WebSocket error:', error);
- };
-
- socket.onclose = () => {
- console.log('WebSocket closed');
- };
-}
-
-/**
- * Get the label of the current page.
- * @returns {string} The label of the current page.
- */
-function getCurrentPageLabel() {
- return $('h1').text();
-}
-
-/**
- * Handle WebSocket updates for multiple images.
- * @param {Object} data - The received data
- * @param {string} currentPageLabel - The label of the current page
- */
-function handleUpdate(data, currentPageLabel) {
- if (data.label === currentPageLabel) {
- console.log('Received update for current label:', data.label);
- updateCameraGrid(data.images); // 更新為處理多鏡頭影像
- } else {
- console.log('Received update for different label:', data.label);
- }
-}
-
-/**
- * Update the camera grid with new images for multiple cameras.
- * @param {Array} images - The array of images with key and base64 data.
- */
-function updateCameraGrid(images) {
- images.forEach((cameraData) => {
- // 檢查是否已經存在相同的 image_name
- const existingCameraDiv = $(`.camera h2:contains(${cameraData.key.split('_').pop()})`).closest('.camera');
-
- if (existingCameraDiv.length > 0) {
- // 更新現有的圖像
- existingCameraDiv.find('img').attr('src', `data:image/png;base64,${cameraData.image}`);
- } else {
- // 如果沒有相同的 image_name,則創建新的圖區
- const cameraDiv = createCameraDiv(cameraData);
- $('.camera-grid').append(cameraDiv);
- }
- });
-}
-
-/**
- * Create a camera div element.
- * @param {Object} cameraData - The data for creating the camera div
- * @returns {HTMLElement} - The div element containing the image and title
- */
-function createCameraDiv(cameraData) {
- const cameraDiv = $('