From 20f557c31a2889f6ff7f41985cdbed52c784fee4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 17 Sep 2020 19:03:30 +0300 Subject: [PATCH] Implement basic web app Lists all currently available rooms and has some play/pause logic. WebRTC integration is still missing. --- common/src/api.rs | 8 +- server/src/api.rs | 43 +++++++++++ server/src/main.rs | 1 + server/src/server.rs | 6 +- server/src/subscriber.rs | 5 +- server/static/index.html | 30 ++++++- server/static/scripts.js | 163 +++++++++++++++++++++++++++++++++++++++ server/static/style.css | 3 + 8 files changed, 250 insertions(+), 9 deletions(-) create mode 100644 server/src/api.rs create mode 100644 server/static/scripts.js create mode 100644 server/static/style.css diff --git a/common/src/api.rs b/common/src/api.rs index e159a24..ca523b2 100644 --- a/common/src/api.rs +++ b/common/src/api.rs @@ -7,13 +7,13 @@ use serde::{Deserialize, Serialize}; /// Response of the `rooms` endpoint. #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] -pub struct Rooms(Vec); +pub struct Rooms(pub Vec); /// Information for one `Room #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub struct Room { - id: uuid::Uuid, - name: String, - description: Option, + pub id: uuid::Uuid, + pub name: String, + pub description: Option, } diff --git a/server/src/api.rs b/server/src/api.rs new file mode 100644 index 0000000..d57e25d --- /dev/null +++ b/server/src/api.rs @@ -0,0 +1,43 @@ +// Copyright (C) 2020 Sebastian Dröge +// +// Licensed under the MIT license, see the LICENSE file or + +use crate::rooms::{self, Rooms}; + +use actix::Addr; +use actix_web::{web, HttpResponse}; + +use log::{error, trace}; + +use webrtc_audio_publishing::api; + +pub async fn rooms(rooms: web::Data>) -> Result { + let room_addrs = rooms.send(rooms::ListRoomsMessage).await.map_err(|err| { + error!("Failed to list rooms: {}", err); + HttpResponse::InternalServerError() + })?; + + let mut room_descs = Vec::with_capacity(room_addrs.len()); + for room in room_addrs { + let room_desc = match room.send(rooms::RoomInformationMessage).await { + Err(err) => { + error!( + "Failed to retrieve room information for {:?}: {:?}", + room, err + ); + continue; + } + Ok(desc) => desc, + }; + + room_descs.push(api::Room { + id: room_desc.id.0, + name: room_desc.name.clone(), + description: room_desc.description.clone(), + }); + } + + trace!("Returning room descriptions {:?}", room_descs); + + Ok(HttpResponse::Ok().json(api::Rooms(room_descs))) +} diff --git a/server/src/main.rs b/server/src/main.rs index ff8cf63..19b5030 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -2,6 +2,7 @@ // // Licensed under the MIT license, see the LICENSE file or +mod api; mod config; mod publisher; mod rooms; diff --git a/server/src/server.rs b/server/src/server.rs index 6c50321..4dd43f7 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -2,6 +2,7 @@ // // Licensed under the MIT license, see the LICENSE file or +use crate::api; use crate::config::Config; use crate::publisher::Publisher; use crate::rooms::Rooms; @@ -9,7 +10,7 @@ use crate::subscriber::Subscriber; use actix::{Actor, Addr}; use actix_files::NamedFile; -use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer, Responder}; +use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer}; use actix_web_actors::ws; use log::error; @@ -30,7 +31,7 @@ async fn ws( path: web::Path, req: HttpRequest, stream: web::Payload, -) -> impl Responder { +) -> Result { match path.as_str() { "publish" => { let publisher = Publisher::new( @@ -90,6 +91,7 @@ pub async fn run(cfg: Config) -> Result<(), anyhow::Error> { .route("/", web::get().to(index)) .route("/ws/{mode:(publish|subscribe)}", web::get().to(ws)) .route("/static/{filename:.*}", web::get().to(static_file)) + .route("/api/rooms", web::get().to(api::rooms)) }); let server = if cfg.use_tls { diff --git a/server/src/subscriber.rs b/server/src/subscriber.rs index 495c729..b1b9773 100644 --- a/server/src/subscriber.rs +++ b/server/src/subscriber.rs @@ -13,7 +13,7 @@ use actix::{Actor, Addr, Handler, Message, StreamHandler, WeakAddr}; use actix_web::dev::ConnectionInfo; use actix_web_actors::ws; -use log::{debug, trace}; +use log::{debug, trace, warn}; /// Actor that represents a WebRTC subscriber. #[derive(Debug)] @@ -59,7 +59,8 @@ impl Actor for Subscriber { } impl StreamHandler> for Subscriber { - fn handle(&mut self, _msg: Result, _ctx: &mut Self::Context) { + fn handle(&mut self, msg: Result, _ctx: &mut Self::Context) { + warn!("received {:?}", msg); // TODO } } diff --git a/server/static/index.html b/server/static/index.html index cd08755..8749f5b 100644 --- a/server/static/index.html +++ b/server/static/index.html @@ -1 +1,29 @@ -Hello world! + + + + + WebRTC Audio Publishing + + + + + +

Available rooms

+ + + + + + + + + + +
NameDescription
+ + + + + diff --git a/server/static/scripts.js b/server/static/scripts.js new file mode 100644 index 0000000..3593b27 --- /dev/null +++ b/server/static/scripts.js @@ -0,0 +1,163 @@ +const rooms_table = document.getElementById('rooms'); +var websocket = connectWebsocket(); + +var playing_room = null; + +function connectWebsocket() { + var scheme; + if (window.location.protocol == 'https:') { + scheme = 'wss:'; + } else if (window.location.protocol == 'http:') { + scheme = 'ws:'; + } + const server = window.location.hostname; + const port = window.location.port || 80; + const ws_url = scheme + '//' + server + ':' + port + '/ws/subscribe'; + + const ws = new WebSocket(ws_url); + + ws.addEventListener('error', onServerError); + ws.addEventListener('message', onServerMessage); + ws.addEventListener('close', onServerClose); + + return ws; +} + +function onLoad() { + updateRooms(); + setInterval(updateRooms, 5000); +} + +function updateRooms() { + var request = new XMLHttpRequest(); + request.open('GET', '/api/rooms', true); + request.onload = function () { + if (request.status >= 200 && request.status < 400) { + var rooms = JSON.parse(this.response); + + const tbody = document.createElement('tbody'); + var found_playing_room = false; + rooms.forEach(room => { + const tr = document.createElement('tr'); + tr.id = room.id; + + const td_name = document.createElement('td'); + td_name.textContent = room.name; + tr.appendChild(td_name); + + const td_description = document.createElement('td'); + td_description.textContent = room.description; + tr.appendChild(td_description); + + const td_play = document.createElement('td'); + const play_button = document.createElement('button'); + play_button.type = 'button'; + play_button.id = 'playButton-' + room.id; + play_button.className = 'playButton'; + + if (playing_room == room.id) { + play_button.textContent = 'Pause'; + tr.classList.add('playing'); + } else { + play_button.textContent = 'Play'; + } + + play_button.onclick = function() { + if (playing_room == room.id) { + pauseRoom(); + return; + } + + if (playing_room != null) { + pauseRoom(); + } + + playRoom(room.id); + }; + td_play.appendChild(play_button); + tr.appendChild(td_play); + + tbody.appendChild(tr); + + if (playing_room != null && room.id == playing_room) { + found_playing_room = true; + } + }); + + if (playing_room && !found_playing_room) { + console.debug('playing room disappeared'); + pauseRoom(); + } + + rooms_table.replaceChild(tbody, rooms_table.tBodies[0]); + } + } + + request.send(); +} + +function playRoom(id) { + if (playing_room != null && playing_room != id) { + pauseRoom(); + } + console.debug('playing ' + id); + + websocket.send(JSON.stringify({ + 'join_room': { + 'id': id + } + })); + + const play_button = document.getElementById('playButton-' + id); + play_button.textContent = 'Pause'; + + const play_row = document.getElementById(id); + play_row.classList.add('playing'); + + playing_room = id; +} + +function pauseRoom() { + if (playing_room == null) { + return; + } + + console.debug('pausing ' + playing_room); + + // TODO: Stop playback + + websocket.send(JSON.stringify('leave_room')); + + const play_button = document.getElementById('playButton-' + playing_room); + play_button.textContent = 'Play'; + + const play_row = document.getElementById(playing_room); + play_row.classList.remove('playing'); + + playing_room = null; +} + +function onServerMessage(event) { + console.log("Received " + event.data); + + // TODO: Handle messages + +} + +function onServerClose(event) { + console.log("Disconnected"); + + pauseRoom(); + window.setTimeout(function() { + websocket = connectWebsocket(); + }, 1000); +} + +function onServerError(event) { + console.log("server error " + event.data); + + pauseRoom(); + window.setTimeout(function() { + websocket = connectWebsocket(); + }, 3000); +} diff --git a/server/static/style.css b/server/static/style.css new file mode 100644 index 0000000..3174022 --- /dev/null +++ b/server/static/style.css @@ -0,0 +1,3 @@ +.playing { + color: blue; +} -- GitLab