diff --git a/.gitmodules b/.gitmodules
index c7bfef07d9c523de8c8c4b80d41aaf1b92c2fdf9..4449e724ef3620f0d58908dc22a79b33f5a19a5c 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -2,5 +2,5 @@
 	path = cc-player
 	url = git@gitlab.fachschaften.org:spoccify/spoccify-cc-player.git
 [submodule "frontend"]
-	path = frontend
+	path = svelte-frontend
 	url = git@gitlab.fachschaften.org:spoccify/spoccify-frontend.git
diff --git a/Cargo.lock b/Cargo.lock
index 19e092329cb6d70e5f2bfdbd48a4fdb1e4528f77..0362cffa6f9babd52592cbf016fb4ff9defb8029 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -205,6 +205,12 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "futures-sink"
+version = "0.3.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9"
+
 [[package]]
 name = "futures-task"
 version = "0.3.25"
@@ -377,6 +383,16 @@ version = "0.3.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
 
+[[package]]
+name = "mime_guess"
+version = "2.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
+dependencies = [
+ "mime",
+ "unicase",
+]
+
 [[package]]
 name = "mio"
 version = "0.8.5"
@@ -656,6 +672,8 @@ dependencies = [
  "serde",
  "serde_json",
  "tokio",
+ "tower",
+ "tower-http",
  "tungstenite",
  "url",
  "uuid",
@@ -744,6 +762,19 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "tokio-util"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+]
+
 [[package]]
 name = "tower"
 version = "0.4.13"
@@ -773,10 +804,17 @@ dependencies = [
  "http",
  "http-body",
  "http-range-header",
+ "httpdate",
+ "mime",
+ "mime_guess",
+ "percent-encoding",
  "pin-project-lite",
+ "tokio",
+ "tokio-util",
  "tower",
  "tower-layer",
  "tower-service",
+ "tracing",
 ]
 
 [[package]]
@@ -843,6 +881,15 @@ version = "1.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
 
+[[package]]
+name = "unicase"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
+dependencies = [
+ "version_check",
+]
+
 [[package]]
 name = "unicode-bidi"
 version = "0.3.8"
diff --git a/Cargo.toml b/Cargo.toml
index 5ee89e754ad0e158c7fae2c170b1099769c03d47..b7b73d1cb0769c6b4352ad3b50317eef7b8afc76 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,6 +14,8 @@ rand = "0.8.5"
 serde = { version = "1.0.147", features = ["derive"] }
 serde_json = "1.0.88"
 tokio = { version = "1", features = ["full"] }
+tower = { version = "0.4.13", features = ["util"] }
+tower-http = { version = "0.3.4", features = ["fs", "trace"] }
 tungstenite = "0.17.3"
 url = { version = "2.3.1", features = ["serde"] }
 uuid = { version = "1.2.2", features = ["v4"] }
diff --git a/src/downloader.rs b/src/downloader.rs
index 903ec30a71dc39ac27128dcb062cac7164db5b95..14d373a3fabb70fbb5e85870f968f8fd7efce902 100644
--- a/src/downloader.rs
+++ b/src/downloader.rs
@@ -8,7 +8,6 @@ use url::Url;
 use uuid::Uuid;
 
 use crate::song::Song;
-use crate::util;
 
 const TMP_DOWNLOAD_DIR: &str = "./tmp";
 
@@ -167,25 +166,28 @@ pub async fn manage_downloads(playlist: &Mutable<Vec<Song>>) -> () {
 }
 
 fn fetch_info(playlist: Mutable<Vec<Song>>, uuid: &Uuid, url: &Url) {
-    println!("Fetching info for {}!", url);
+    println!("Fetching info for {}!", uuid);
 
-    let tmp_file_name = util::random_base64();
-    let tmp_file_path = Utf8Path::new(TMP_DOWNLOAD_DIR).join(tmp_file_name);
+    let base_path = Utf8Path::new(TMP_DOWNLOAD_DIR).join(uuid.to_string());
+    let info_json_path = base_path.join("audio");
+
+    std::fs::create_dir(&base_path)
+        .expect(&format!("Failed to create directory for song {}!", uuid));
 
     process::Command::new("yt-dlp")
         .args([
             "--output",
-            tmp_file_path.as_str(),
+            info_json_path.as_str(),
             "--write-info-json",
-            "--write-thumbnail",
             "--skip-download",
             url.as_str(),
         ])
         .output()
         .expect("Failed to fetch info!");
-    let info_json_file = format!("{}.info.json", tmp_file_path);
+    
+    let info_json_path = base_path.join("audio.info.json");
 
-    let data = std::fs::read_to_string(info_json_file).expect("Could not find info.json!");
+    let data = std::fs::read_to_string(&info_json_path).expect("Could not find info.json!");
 
     let info_json: Value = serde_json::from_str(&data).expect("Could not parse info.json!");
 
@@ -220,9 +222,27 @@ fn fetch_info(playlist: Mutable<Vec<Song>>, uuid: &Uuid, url: &Url) {
     } else {
         eprintln!("Could not fetch artist_url!");
     }
+
     println!("Fetched all info!");
 
-    let thumbnail_file = format!("{}.webm", tmp_file_path);
+    let thumbnail_path = base_path.join("thumbnail");
+
+    process::Command::new("yt-dlp")
+        .args([
+            "--load-info-json",
+            info_json_path.as_str(),
+            "--output",
+            thumbnail_path.as_str(),
+            "--write-thumbnail",
+            "--skip-download",
+            url.as_str(),
+        ])
+        .output()
+        .expect("Failed to download thumbnail!");
+
+    let thumbnail_path = base_path.join("thumbnail.webp");
+
+    println!("Downloaded thumbnail!");
 
     let mut playlist = playlist.lock_mut();
     if let Some(index) = (*playlist).iter().position(|song| song.equals_uuid(uuid)) {
@@ -233,7 +253,7 @@ fn fetch_info(playlist: Mutable<Vec<Song>>, uuid: &Uuid, url: &Url) {
             artist: artist,
             artist_url: artist_url,
             length: length,
-            thumbnail_file: thumbnail_file.to_owned(),
+            thumbnail_file: thumbnail_path.to_string(),
         };
     }
     drop(playlist);
@@ -249,20 +269,27 @@ fn download_song(
     length: &Option<u64>,
     thumbnail_file: &str,
 ) {
-    let audio_file_name = util::random_base64();
-    let audio_file_path = Utf8Path::new(TMP_DOWNLOAD_DIR).join(audio_file_name);
+    let base_path = Utf8Path::new(TMP_DOWNLOAD_DIR).join(uuid.to_string());
+    let info_json_path = base_path.join("audio.info.json");
+    let audio_file_path = base_path.join("audio");
 
     println!("Downloading from {} to {}!", url, audio_file_path);
 
     process::Command::new("yt-dlp")
-        .args(["--output", audio_file_path.as_str(), url.as_str()])
+        .args([
+            "--load-info-json",
+            info_json_path.as_str(),
+            "--output",
+            audio_file_path.as_str(),
+            url.as_str(),
+        ])
         .output()
         .expect("Failed to download audio!");
+    
+    let audio_file_path = base_path.join("audio.webm");
 
     println!("Download successful!");
 
-    let audio_file_path = format!("{}.webm", audio_file_path);
-
     let mut playlist = playlist.lock_mut();
     if let Some(index) = (*playlist).iter().position(|song| song.equals_uuid(uuid)) {
         playlist[index] = Song::Downloaded {
@@ -273,7 +300,7 @@ fn download_song(
             artist_url: artist_url.to_owned(),
             length: length.to_owned(),
             thumbnail_file: thumbnail_file.to_owned(),
-            audio_file: audio_file_path.to_owned(),
+            audio_file: audio_file_path.to_string(),
         };
     }
     drop(playlist);
@@ -290,17 +317,16 @@ fn convert_song(
     thumbnail_file: &str,
     audio_file: &str,
 ) {
-    let audio_file = Utf8Path::new(audio_file);
-
-    let raw_file_name = format!("{}.raw", util::random_base64());
-    let raw_file_path = Utf8Path::new(TMP_DOWNLOAD_DIR).join(raw_file_name);
+    let base_path = Utf8Path::new(TMP_DOWNLOAD_DIR).join(uuid.to_string());
+    let audio_file_path = base_path.join("audio.webm");
+    let raw_file_path = base_path.join("audio.raw");
 
     println!("Converting from {} to {}!", audio_file, raw_file_path);
 
     process::Command::new("ffmpeg")
         .args([
             "-i",
-            audio_file.as_str(),
+            audio_file_path.as_str(),
             "-f",
             "u8",
             "-acodec",
@@ -313,7 +339,8 @@ fn convert_song(
         ])
         .output()
         .expect("Failed to convert audio!");
-    std::fs::remove_file(audio_file).expect("Failed to remove audio file!");
+    
+    std::fs::remove_file(&audio_file_path).expect("Failed to remove audio file!");
 
     println!("Conversion successful!");
 
@@ -327,7 +354,7 @@ fn convert_song(
             artist_url: artist_url.to_owned(),
             length: length.to_owned(),
             thumbnail_file: thumbnail_file.to_owned(),
-            raw_file: raw_file_path.as_str().to_owned(),
+            raw_file: raw_file_path.to_string(),
         };
     }
     drop(playlist);
diff --git a/src/http.rs b/src/http.rs
index 9a9bfbfd1c1d2874a04d553e6c91de35d984eec4..5d2a982a2b8dd8d0b365be6a653193f781281197 100644
--- a/src/http.rs
+++ b/src/http.rs
@@ -1,11 +1,47 @@
+use axum::handler::HandlerWithoutStateExt;
+use axum::http::StatusCode;
+use axum::response::IntoResponse;
+use axum::routing::get_service;
 use axum::Router;
-use axum::routing::get;
+// use futures_signals::signal::Mutable;
+use std::io;
+use tower::util::ServiceExt;
+use tower_http::services::ServeDir;
 
-pub async fn handle_http(http_bind_address: &str) {
-    let app = Router::new().route("/", get(|| async { "Hello, World!" }));
+// use crate::song::Song;
 
-    axum::Server::bind(&http_bind_address.to_string().parse().expect("Invalid http bind address!"))
-        .serve(app.into_make_service())
-        .await
-        .expect("Failed to bind http port!");
+pub async fn handle_http(http_bind_address: &str, /* playlist_websocket: &Mutable<Vec<Song>> */) {
+    println!("Listening for http connections on {}!", http_bind_address);
+
+    async fn handle_404() -> (StatusCode, &'static str) {
+        (StatusCode::NOT_FOUND, "Not found")
+    }
+
+    let handle_404_service = handle_404
+        .into_service()
+        .map_err(|err| -> std::io::Error { match err {} });
+
+    let serve_frontend = ServeDir::new("frontend").not_found_service(handle_404_service.clone());
+    let serve_frontend = get_service(serve_frontend).handle_error(handle_error);
+
+    let serve_thumbnails = ServeDir::new("tmp").not_found_service(handle_404_service.clone());
+    let serve_thumbnails = get_service(serve_thumbnails).handle_error(handle_error);
+
+    let app = Router::new()
+        .nest_service("/", serve_frontend)
+        .nest_service("/thumbnail", serve_thumbnails);
+
+    axum::Server::bind(
+        &http_bind_address
+            .to_string()
+            .parse()
+            .expect("Invalid http bind address!"),
+    )
+    .serve(app.into_make_service())
+    .await
+    .expect("Failed to bind http port!");
+}
+
+async fn handle_error(_err: io::Error) -> impl IntoResponse {
+    (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error")
 }
diff --git a/src/main.rs b/src/main.rs
index 75839f57264eca9193b69fa3956757435803e5bc..d21526b6e5dfc2305236c77c27ec4e04bde1146a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -15,32 +15,30 @@ use song::Song;
 
 #[tokio::main]
 async fn main() {
-    let playlist = Mutable::new(Vec::<Song>::new());
-
     println!("Starting!");
 
-    let http_bind_host = env::var("SPOCCIFY_HTTP_BIND_HOST").unwrap_or("localhost".to_owned());
+    let playlist = Mutable::new(Vec::<Song>::new());
+    // let playlist_http = Mutable::clone(&playlist);
+    let playlist_websockets = Mutable::clone(&playlist);
+    let playlist_downloader = Mutable::clone(&playlist);
+
+    let http_bind_host = env::var("SPOCCIFY_HTTP_BIND_HOST").unwrap_or("127.0.0.1".to_owned());
     let http_bind_port = env::var("SPOCCIFY_HTTP_BIND_PORT").unwrap_or("9000".to_owned());
 
     let http_bind_address = format!("{}:{}", http_bind_host, http_bind_port);
 
-    // tokio::spawn(async move {
-    //     http::handle_http(&http_bind_address).await;
-    // });
-
     let player_token =
         Arc::new(env::var("SPOCCIFY_PLAYER_TOKEN").expect("No player token provided!"));
     let client_token =
         Arc::new(env::var("SPOCCIFY_CLIENT_TOKEN").expect("No client token provided!"));
 
     let websocket_bind_host =
-        env::var("SPOCCIFY_WEBSOCKET_BIND_HOST").unwrap_or("localhost".to_owned());
+        env::var("SPOCCIFY_WEBSOCKET_BIND_HOST").unwrap_or("127.0.0.1".to_owned());
     let websocket_bind_port = env::var("SPOCCIFY_WEBSOCKET_BIND_PORT").unwrap_or("9001".to_owned());
 
     let websocket_bind_address = format!("{}:{}", websocket_bind_host, websocket_bind_port);
-    let playlist_websockets = Mutable::clone(&playlist);
 
-    tokio::spawn(async move {
+    let websockets_task = tokio::task::spawn(async move {
         websocket::handle_websockets(
             &player_token,
             &client_token,
@@ -50,11 +48,20 @@ async fn main() {
         .await;
     });
 
-    let playlist_downloader = Mutable::clone(&playlist);
-
-    tokio::spawn(async move {
+    let downloader_task = tokio::task::spawn(async move {
         downloader::manage_downloads(&playlist_downloader).await;
     });
 
-    loop {}
+    tokio::join!(
+        http::handle_http(&http_bind_address /* , &playlist_http */),
+        /* websocket::handle_websockets(
+            &player_token,
+            &client_token,
+            &websocket_bind_address,
+            &playlist_websockets,
+        ), */
+        // websockets_task.await.unwrap(),
+        /* downloader::manage_downloads(&playlist_downloader) */
+        // downloader_task.await.unwrap(),
+    );
 }
diff --git a/src/util.rs b/src/util.rs
index 3cec313d2eeac7369266777fe937c387196b9361..70b1ca26a98f70f82d2ac3e1be229236a4e9b39a 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -1,6 +1,6 @@
 use rand::random;
 
-pub fn random_base64() -> String {
+pub fn _random_base64() -> String {
     base64::encode_config(
         random::<[u8; 8]>(),
         base64::Config::new(base64::CharacterSet::UrlSafe, false),
diff --git a/src/websocket.rs b/src/websocket.rs
index bcf0c626317e127e0ec1730d68a3d0b681efe0de..5e24b6aaef9d31ddfc0a67f2c85ed51e3b649c9f 100644
--- a/src/websocket.rs
+++ b/src/websocket.rs
@@ -12,9 +12,10 @@ use crate::client;
 use crate::player;
 
 pub async fn handle_websockets(player_token: &Arc<String>, client_token: &Arc<String>, websocket_bind_address: &str, playlist_websocket: &Mutable<Vec<Song>>) {
-    println!("Listening for websocket connections on {}!", websocket_bind_address);
-
     let server = TcpListener::bind(websocket_bind_address).expect("Failed to bind websocket port!");
+    
+    println!("Listening for websocket connections on {}!", websocket_bind_address);
+    
     for stream in server.incoming() {
         let playlist_clone = Mutable::clone(playlist_websocket);
         let player_token_clone = Arc::clone(player_token);