diff --git a/.gitignore b/.gitignore index da719a0..cacc1a2 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ target/ .vscode/ dist/ certificates/ +audios/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html diff --git a/back/Cargo.toml b/back/Cargo.toml index 30e8c07..5d80e63 100644 --- a/back/Cargo.toml +++ b/back/Cargo.toml @@ -12,4 +12,6 @@ rand = "0.8.5" serde = { version = "1.0.197", features = ["derive"] } serde_json = "1.0.114" tokio = { version = "1.36.0", features = ["full"] } +#tokio-stream = { version = "0.1.15", features = ["full"] } +tokio-util = { version = "0.7.10", features = ["full"] } tower-http = { version = "0.5.2", features = ["full"] } diff --git a/back/src/lib.rs b/back/src/lib.rs index d9c68af..317af45 100644 --- a/back/src/lib.rs +++ b/back/src/lib.rs @@ -1,7 +1,6 @@ use serde::{Deserialize, Serialize}; pub mod routing; -pub mod read; #[derive(Debug, Clone)] pub struct AppState{ diff --git a/back/src/routing.rs b/back/src/routing.rs index 8caef3c..3892e00 100644 --- a/back/src/routing.rs +++ b/back/src/routing.rs @@ -1,5 +1,7 @@ use crate::{AppState, ServerStatus, CoinStatus}; -use axum::{extract::State, http::StatusCode, response::IntoResponse, routing::get, Json, Router}; +use axum::{body::Body, extract::State, http::StatusCode, response::IntoResponse, routing::get, Json, Router}; +use tokio::fs::File; +use tokio_util::io::ReaderStream; use tower_http::cors::CorsLayer; use rand::prelude::*; @@ -7,6 +9,7 @@ pub async fn routing(State(state): State) -> Router { Router::new() .route("/", get(alive)) .route("/coin", get(flip_coin)) + .route("/stream", get(stream)) .layer(CorsLayer::permissive()) .with_state(state.clone()) } @@ -32,3 +35,9 @@ async fn flip_coin() -> impl IntoResponse { println!("{}", coin_json); (StatusCode::OK, Json(coin_json)) } + +async fn stream() -> impl IntoResponse { + let file = File::open("audios/audio.mp3").await.unwrap(); + let stream = ReaderStream::new(file); + Body::from_stream(stream) +} \ No newline at end of file diff --git a/front/Cargo.toml b/front/Cargo.toml index 890291c..81e1b9c 100644 --- a/front/Cargo.toml +++ b/front/Cargo.toml @@ -6,9 +6,11 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -async-std = "1.12.0" +anyhow = "1.0.81" +cpal = { version = "0.15.3", features = ["wasm-bindgen"] } dioxus = { version = "0.5.0-alpha.0", features = ["web"] } log = "0.4.21" reqwest = { version = "0.11.24", features = ["json"] } serde = { version = "1.0.197", features = ["derive"] } +tokio_with_wasm = "0.4.3" wasm-logger = "0.2.0" diff --git a/front/src/main.rs b/front/src/main.rs index dca26e4..b539b41 100644 --- a/front/src/main.rs +++ b/front/src/main.rs @@ -1,8 +1,11 @@ use std::time::Duration; -use async_std::task; +use tokio_with_wasm::tokio; use dioxus::prelude::*; +use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; +use cpal::{FromSample, Sample, SizedSample}; use serde::Deserialize; -const SERVER_ADDRESS: &str = "https://localhost:2323"; + +const SERVER_ADDRESS: &str = "https://tahinli.com.tr:2323"; #[derive(Debug, Clone, PartialEq, Deserialize)] enum Server{ @@ -75,6 +78,14 @@ async fn coin_status_check() -> Result { fn app() -> Element { rsx! { page_base {} + audio_stream_renderer {} + div { + button { + onclick: move |_| record(), + "style":"width: 80px; height: 50px;", + "Sinusoidal" + } + } coin_status_renderer {} server_status_renderer {} } @@ -95,6 +106,101 @@ fn page_base() ->Element { } } } + +pub async fn run(device: &cpal::Device, config: &cpal::StreamConfig) -> Result<(), anyhow::Error> +where + T: SizedSample + FromSample, +{ + let sample_rate = config.sample_rate.0 as f32; + let channels = config.channels as usize; + + // Produce a sinusoid of maximum amplitude. + let mut sample_clock = 0f32; + let mut next_value = move || { + sample_clock = (sample_clock + 1.0) % sample_rate; + (sample_clock * 440.0 * 2.0 * std::f32::consts::PI / sample_rate).sin() + }; + + let err_fn = |err| eprintln!("an error occurred on stream: {}", err); + + let stream = device.build_output_stream( + config, + move |data: &mut [T], _: &cpal::OutputCallbackInfo| { + write_data(data, channels, &mut next_value) + }, + err_fn, + None, + )?; + stream.play()?; + + tokio::time::sleep(Duration::from_secs(3)).await; + + Ok(()) +} + +fn write_data(output: &mut [T], channels: usize, next_sample: &mut dyn FnMut() -> f32) +where + T: Sample + FromSample, +{ + for frame in output.chunks_mut(channels) { + let value: T = T::from_sample(next_sample()); + for sample in frame.iter_mut() { + *sample = value; + } + } +} + + + +pub async fn record() { + log::info!("mic"); + let host = cpal::default_host(); + let devices = host.devices().unwrap(); + for (_derive_index, device) in devices.enumerate() { + log::info!("{:?}", device.name()); + } + let device = host.default_output_device().unwrap(); + + let mut supported_config = device.supported_output_configs().unwrap(); + let config = supported_config.next().unwrap().with_max_sample_rate(); + log::info!("{:?}", config); + match config.sample_format() { + cpal::SampleFormat::I8 => {log::info!("i8")}, + cpal::SampleFormat::I16 => {log::info!("i16")}, + //cpal::SampleFormat::I24 => {log::info!("i24")}, + cpal::SampleFormat::I32 => {log::info!("i32")}, + //cpal::SampleFormat::I48 => {log::info!("i48")}, + cpal::SampleFormat::I64 => {log::info!("i64")}, + cpal::SampleFormat::U8 => {log::info!("u8")}, + cpal::SampleFormat::U16 => {log::info!("u16")}, + //cpal::SampleFormat::U24 => {log::info!("u24")}, + cpal::SampleFormat::U32 => {log::info!("u32")}, + //cpal::SampleFormat::U48 => {log::info!("u48")}, + cpal::SampleFormat::U64 => {log::info!("u64")}, + cpal::SampleFormat::F32 => {log::info!("f32"); + run::(&device, &config.clone().into()).await.unwrap();}, + cpal::SampleFormat::F64 => {log::info!("f64")}, + sample_format => panic!("Unsupported sample format '{sample_format}'"), + } + /*let config:StreamConfig = config.into(); + let stream = device.build_output_stream( + &config, + move |data: &mut [f32], _: &cpal::OutputCallbackInfo| { + + log::info!("{:?}", data); + //I need to do something here, I think + }, + move |_err| { + + }, + None).unwrap(); + + stream.play().unwrap(); + tokio::time::sleep(Duration::from_secs(10)).await; + stream.pause().unwrap();*/ + + +} fn server_status_renderer() -> Element { let server_check_time = 1_u64; let mut server_status = use_signal(move || ServerStatus{status:Server::Unstable,}); @@ -102,7 +208,7 @@ fn server_status_renderer() -> Element { let mut server_status_unstable = use_signal(move|| false); let _server_status_task:Coroutine<()> = use_coroutine(|_| async move { loop { - task::sleep(Duration::from_secs(server_check_time)).await; + tokio::time::sleep(Duration::from_secs(server_check_time)).await; *server_status_watchdog.write() = true; *server_status.write() = server_status_check(server_status).await; *server_status_watchdog.write() = false; @@ -111,13 +217,13 @@ fn server_status_renderer() -> Element { let _server_status_watchdog_timer:Coroutine<()> = use_coroutine(|_| async move { let mut watchdog_counter = 0_i8; loop { - task::sleep(Duration::from_secs(2*server_check_time+1)).await; + tokio::time::sleep(Duration::from_secs(2*server_check_time+1)).await; if !server_status_watchdog() { *server_status_unstable.write() = false; } if server_status_watchdog() { for _i in 0..5 { - task::sleep(Duration::from_secs(1)).await; + tokio::time::sleep(Duration::from_secs(1)).await; if server_status_watchdog() { watchdog_counter += 1; } @@ -179,6 +285,19 @@ fn coin_status_renderer() -> Element { } } } +fn audio_stream_renderer() -> Element { + rsx! { + div { + audio{ + src:"https://tahinli.com.tr:2323/stream", + controls:true, + autoplay: true, + muted:false, + r#loop:true, + } + } + } +} #[component] fn ShowServerStatus(server_status: ServerStatus) -> Element { rsx! {