feat: plays radio stream from url

feat:  api structure
This commit is contained in:
Ahmet Kaan GÜMÜŞ 2024-03-07 04:07:11 +03:00
parent c908fdc097
commit 8f2b6f1e3f
17 changed files with 3534 additions and 0 deletions

13
back/Cargo.toml Normal file
View file

@ -0,0 +1,13 @@
[package]
name = "back"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
axum = "0.7.4"
serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.114"
tokio = { version = "1.36.0", features = ["full"] }
tower-http = { version = "0.5.2", features = ["full"] }

6
back/src/lib.rs Normal file
View file

@ -0,0 +1,6 @@
pub mod routing;
#[derive(Debug, Clone)]
pub struct AppState{
}

23
back/src/main.rs Normal file
View file

@ -0,0 +1,23 @@
use back::{routing, AppState};
use std::env::{self};
fn take_args() -> String{
let mut bind_address:String = String::new();
for element in env::args(){
bind_address = element
}
println!("\n\n\tOn Air -> http://{}\n\n", bind_address);
bind_address
}
#[tokio::main]
async fn main() {
println!("Hello, world!");
let state = AppState{
};
let app = routing::routing(axum::extract::State(state)).await;
let listener = tokio::net::TcpListener::bind(take_args()).await.unwrap();
axum::serve(listener, app).await.unwrap();
}

20
back/src/routing.rs Normal file
View file

@ -0,0 +1,20 @@
use crate::AppState;
use axum::{extract::State, http::StatusCode, response::IntoResponse, routing::get, Json, Router};
use tower_http::cors::CorsLayer;
pub async fn routing(State(state): State<AppState>) -> Router{
Router::new()
.route("/", get(alive))
.layer(CorsLayer::permissive())
.with_state(state.clone())
}
async fn alive() -> impl IntoResponse{
let alive_json = serde_json::json!({
"status":"alive",
});
println!("Alive");
(StatusCode::OK, Json(alive_json))
}

10
front/Cargo.toml Normal file
View file

@ -0,0 +1,10 @@
[package]
name = "front"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dioxus = { version = "0.5.0-alpha.0", features = ["web"] }
reqwest = { version = "0.11.24", features = ["json"] }

74
front/Dioxus.toml Normal file
View file

@ -0,0 +1,74 @@
[application]
# dioxus project name
name = "Radioxide"
# default platfrom
# you can also use `dx serve/build --platform XXX` to use other platform
# value: web | desktop
default_platform = "web"
# Web `build` & `serve` dist path
out_dir = "dist"
# resource (static) file folder
asset_dir = "public"
[web.app]
# HTML title tag content
title = "Radioxide | Fully Oxidised Radio"
[web.watcher]
index_on_404 = true
watch_path = ["src", "examples"]
# include `assets` in web platform
[web.resource]
# CSS style file
style = []
# Javascript code file
script = []
[web.resource.dev]
# Javascript code file
# serve: [dev-server] only
script = []
[application.plugins]
available = true
required = []
[bundler]
# Bundle identifier
identifier = "io.github.Radioxide"
# Bundle publisher
publisher = "Radioxide"
# Bundle icon
icon = ["icons/icon.png"]
# Bundle resources
resources = ["public/*"]
# Bundle copyright
copyright = ""
# Bundle category
category = "Utility"
# Bundle short description
short_description = "Online Radio Streaming."
# Bundle long description
long_description = """
Online Radio Streaming Platform.
"""

1418
front/dist/assets/dioxus/Radioxide.js vendored Normal file

File diff suppressed because it is too large Load diff

Binary file not shown.

1418
front/dist/assets/dioxus/name.js vendored Normal file

File diff suppressed because it is too large Load diff

BIN
front/dist/assets/dioxus/name_bg.wasm vendored Normal file

Binary file not shown.

View file

@ -0,0 +1,345 @@
let m,p,ls,d,t,op,i,e,z,metaflags;
export function setAttributeInner(node, field, value, ns) {
const name = field;
if (ns === "style") {
// ????? why do we need to do this
if (node.style === undefined) {
node.style = {};
}
node.style[name] = value;
} else if (!!ns) {
node.setAttributeNS(ns, name, value);
} else {
switch (name) {
case "value":
if (value !== node.value) {
node.value = value;
}
break;
case "initial_value":
node.defaultValue = value;
break;
case "checked":
node.checked = truthy(value);
break;
case "initial_checked":
node.defaultChecked = truthy(value);
break;
case "selected":
node.selected = truthy(value);
break;
case "initial_selected":
node.defaultSelected = truthy(value);
break;
case "dangerous_inner_html":
node.innerHTML = value;
break;
default:
// https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364
if (!truthy(value) && bool_attrs.hasOwnProperty(name)) {
node.removeAttribute(name);
} else {
node.setAttribute(name, value);
}
}
}
}
const bool_attrs = {
allowfullscreen: true,
allowpaymentrequest: true,
async: true,
autofocus: true,
autoplay: true,
checked: true,
controls: true,
default: true,
defer: true,
disabled: true,
formnovalidate: true,
hidden: true,
ismap: true,
itemscope: true,
loop: true,
multiple: true,
muted: true,
nomodule: true,
novalidate: true,
open: true,
playsinline: true,
readonly: true,
required: true,
reversed: true,
selected: true,
truespeed: true,
webkitdirectory: true,
};
function truthy(val) {
return val === "true" || val === true;
}
class ListenerMap {
constructor(root) {
// bubbling events can listen at the root element
this.global = {};
// non bubbling events listen at the element the listener was created at
this.local = {};
this.root = null;
this.handler = null;
}
create(event_name, element, bubbles) {
if (bubbles) {
if (this.global[event_name] === undefined) {
this.global[event_name] = {};
this.global[event_name].active = 1;
this.root.addEventListener(event_name, this.handler);
} else {
this.global[event_name].active++;
}
}
else {
const id = element.getAttribute("data-dioxus-id");
if (!this.local[id]) {
this.local[id] = {};
}
element.addEventListener(event_name, this.handler);
}
}
remove(element, event_name, bubbles) {
if (bubbles) {
this.global[event_name].active--;
if (this.global[event_name].active === 0) {
this.root.removeEventListener(event_name, this.global[event_name].callback);
delete this.global[event_name];
}
}
else {
const id = element.getAttribute("data-dioxus-id");
delete this.local[id][event_name];
if (this.local[id].length === 0) {
delete this.local[id];
}
element.removeEventListener(event_name, this.handler);
}
}
removeAllNonBubbling(element) {
const id = element.getAttribute("data-dioxus-id");
delete this.local[id];
}
}
function LoadChild(ptr, len) {
// iterate through each number and get that child
node = stack[stack.length - 1];
ptr_end = ptr + len;
for (; ptr < ptr_end; ptr++) {
end = m.getUint8(ptr);
for (node = node.firstChild; end > 0; end--) {
node = node.nextSibling;
}
}
return node;
}
const listeners = new ListenerMap();
let nodes = [];
let stack = [];
let root;
const templates = {};
let node, els, end, ptr_end, k;
export function save_template(nodes, tmpl_id) {
templates[tmpl_id] = nodes;
}
export function hydrate(ids) {
const hydrateNodes = document.querySelectorAll('[data-node-hydration]');
for (let i = 0; i < hydrateNodes.length; i++) {
const hydrateNode = hydrateNodes[i];
const hydration = hydrateNode.getAttribute('data-node-hydration');
const split = hydration.split(',');
const id = ids[parseInt(split[0])];
nodes[id] = hydrateNode;
if (split.length > 1) {
hydrateNode.listening = split.length - 1;
hydrateNode.setAttribute('data-dioxus-id', id);
for (let j = 1; j < split.length; j++) {
const listener = split[j];
const split2 = listener.split(':');
const event_name = split2[0];
const bubbles = split2[1] === '1';
listeners.create(event_name, hydrateNode, bubbles);
}
}
}
const treeWalker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_COMMENT,
);
let currentNode = treeWalker.nextNode();
while (currentNode) {
const id = currentNode.textContent;
const split = id.split('node-id');
if (split.length > 1) {
nodes[ids[parseInt(split[1])]] = currentNode.nextSibling;
}
currentNode = treeWalker.nextNode();
}
}
export function get_node(id) {
return nodes[id];
}
export function initialize(root, handler) {
listeners.handler = handler;
nodes = [root];
stack = [root];
listeners.root = root;
}
function AppendChildren(id, many){
root = nodes[id];
els = stack.splice(stack.length-many);
for (k = 0; k < many; k++) {
root.appendChild(els[k]);
}
}
let s = "";let lsp,sp,sl; let c = new TextDecoder();let u16buf,u16bufp;const evt = [];
let evt_cache_hit, evt_cache_idx;
function get_evt() {
evt_cache_idx = u8buf[u8bufp++];
if(evt_cache_idx & 128){
evt_cache_hit=s.substring(sp,sp+=u8buf[u8bufp++]);
evt[evt_cache_idx&4294967167]=evt_cache_hit;
return evt_cache_hit;
}
else{
return evt[evt_cache_idx&4294967167];
}
}let u8buf,u8bufp;const ns_cache = [];
let ns_cache_cache_hit, ns_cache_cache_idx;
function get_ns_cache() {
ns_cache_cache_idx = u8buf[u8bufp++];
if(ns_cache_cache_idx & 128){
ns_cache_cache_hit=s.substring(sp,sp+=u8buf[u8bufp++]);
ns_cache[ns_cache_cache_idx&4294967167]=ns_cache_cache_hit;
return ns_cache_cache_hit;
}
else{
return ns_cache[ns_cache_cache_idx&4294967167];
}
}const attr = [];
let attr_cache_hit, attr_cache_idx;
function get_attr() {
attr_cache_idx = u8buf[u8bufp++];
if(attr_cache_idx & 128){
attr_cache_hit=s.substring(sp,sp+=u8buf[u8bufp++]);
attr[attr_cache_idx&4294967167]=attr_cache_hit;
return attr_cache_hit;
}
else{
return attr[attr_cache_idx&4294967167];
}
}let u32buf,u32bufp;
let ns,event_name,value,id,ptr,bubbles,len,field;
export function create(r){
d=r;
}
export function update_memory(b){
m=new DataView(b.buffer)
}
export function run(){
metaflags=m.getUint32(d,true);
if((metaflags>>>6)&1){
ls=m.getUint32(d+6*4,true);
}
p=ls;
if (metaflags&1){
lsp = m.getUint32(d+1*4,true);
}
if ((metaflags>>>2)&1) {
sl = m.getUint32(d+2*4,true);
if ((metaflags>>>1)&1) {
sp = lsp;
s = "";
e = sp + ((sl / 4) | 0) * 4;
while (sp < e) {
t = m.getUint32(sp, true);
s += String.fromCharCode(
t & 255,
(t & 65280) >> 8,
(t & 16711680) >> 16,
t >> 24
);
sp += 4;
}
while (sp < lsp + sl) {
s += String.fromCharCode(m.getUint8(sp++));
}
} else {
s = c.decode(new DataView(m.buffer, lsp, sl));
}
}
sp=0;if ((metaflags>>>4)&1){
t = m.getUint32(d+4*4,true);
u16buf=new Uint16Array(m.buffer,t,((m.buffer.byteLength-t)-(m.buffer.byteLength-t)%2)/2);
}
u16bufp=0;if ((metaflags>>>5)&1){
t = m.getUint32(d+5*4,true);
u8buf=new Uint8Array(m.buffer,t,((m.buffer.byteLength-t)-(m.buffer.byteLength-t)%1)/1);
}
u8bufp=0;if ((metaflags>>>3)&1){
t = m.getUint32(d+3*4,true);
u32buf=new Uint32Array(m.buffer,t,((m.buffer.byteLength-t)-(m.buffer.byteLength-t)%4)/4);
}
u32bufp=0;
for(;;){
op=m.getUint32(p,true);
p+=4;
z=0;
while(z++<4){
switch(op&255){
case 0:{AppendChildren(root, stack.length-1);}break;case 1:{stack.push(nodes[u32buf[u32bufp++]]);}break;case 2:{AppendChildren(u32buf[u32bufp++], u16buf[u16bufp++]);}break;case 3:{stack.pop();}break;case 4:{root = nodes[u32buf[u32bufp++]]; els = stack.splice(stack.length-u16buf[u16bufp++]); if (root.listening) { listeners.removeAllNonBubbling(root); } root.replaceWith(...els);}break;case 5:{nodes[u32buf[u32bufp++]].after(...stack.splice(stack.length-u16buf[u16bufp++]));}break;case 6:{nodes[u32buf[u32bufp++]].before(...stack.splice(stack.length-u16buf[u16bufp++]));}break;case 7:{node = nodes[u32buf[u32bufp++]]; if (node !== undefined) { if (node.listening) { listeners.removeAllNonBubbling(node); } node.remove(); }}break;case 8:{stack.push(document.createTextNode(s.substring(sp,sp+=u32buf[u32bufp++])));}break;case 9:{node = document.createTextNode(s.substring(sp,sp+=u32buf[u32bufp++])); nodes[u32buf[u32bufp++]] = node; stack.push(node);}break;case 10:{node = document.createElement('pre'); node.hidden = true; stack.push(node); nodes[u32buf[u32bufp++]] = node;}break;case 11:event_name=get_evt();id=u32buf[u32bufp++];bubbles=u8buf[u8bufp++];node = nodes[id]; if(node.listening){node.listening += 1;}else{node.listening = 1;} node.setAttribute('data-dioxus-id', `${id}`); listeners.create(event_name, node, bubbles);break;case 12:{node = nodes[u32buf[u32bufp++]]; node.listening -= 1; node.removeAttribute('data-dioxus-id'); listeners.remove(node, get_evt(), u8buf[u8bufp++]);}break;case 13:{nodes[u32buf[u32bufp++]].textContent = s.substring(sp,sp+=u32buf[u32bufp++]);}break;case 14:{node = nodes[u32buf[u32bufp++]]; setAttributeInner(node, get_attr(), s.substring(sp,sp+=u32buf[u32bufp++]), get_ns_cache());}break;case 15:id=u32buf[u32bufp++];field=get_attr();ns=get_ns_cache();{
node = nodes[id];
if (!ns) {
switch (field) {
case "value":
node.value = "";
break;
case "checked":
node.checked = false;
break;
case "selected":
node.selected = false;
break;
case "dangerous_inner_html":
node.innerHTML = "";
break;
default:
node.removeAttribute(field);
break;
}
} else if (ns == "style") {
node.style.removeProperty(name);
} else {
node.removeAttributeNS(ns, field);
}
}break;case 16:{nodes[u32buf[u32bufp++]] = LoadChild(u32buf[u32bufp++], u8buf[u8bufp++]);}break;case 17:ptr=u32buf[u32bufp++];len=u8buf[u8bufp++];value=s.substring(sp,sp+=u32buf[u32bufp++]);id=u32buf[u32bufp++];{
node = LoadChild(ptr, len);
if (node.nodeType == Node.TEXT_NODE) {
node.textContent = value;
} else {
let text = document.createTextNode(value);
node.replaceWith(text);
node = text;
}
nodes[id] = node;
}break;case 18:{els = stack.splice(stack.length - u16buf[u16bufp++]); node = LoadChild(u32buf[u32bufp++], u8buf[u8bufp++]); node.replaceWith(...els);}break;case 19:{node = templates[u16buf[u16bufp++]][u16buf[u16bufp++]].cloneNode(true); nodes[u32buf[u32bufp++]] = node; stack.push(node);}break;case 20:return true;
}
op>>>=8;
}
}
}
export function run_from_bytes(bytes){
d = 0;
update_memory(new Uint8Array(bytes))
run()
}

View file

@ -0,0 +1,79 @@
export function setAttributeInner(node, field, value, ns) {
const name = field;
if (ns === "style") {
// ????? why do we need to do this
if (node.style === undefined) {
node.style = {};
}
node.style[name] = value;
} else if (!!ns) {
node.setAttributeNS(ns, name, value);
} else {
switch (name) {
case "value":
if (value !== node.value) {
node.value = value;
}
break;
case "initial_value":
node.defaultValue = value;
break;
case "checked":
node.checked = truthy(value);
break;
case "initial_checked":
node.defaultChecked = truthy(value);
break;
case "selected":
node.selected = truthy(value);
break;
case "initial_selected":
node.defaultSelected = truthy(value);
break;
case "dangerous_inner_html":
node.innerHTML = value;
break;
default:
// https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364
if (!truthy(value) && bool_attrs.hasOwnProperty(name)) {
node.removeAttribute(name);
} else {
node.setAttribute(name, value);
}
}
}
}
const bool_attrs = {
allowfullscreen: true,
allowpaymentrequest: true,
async: true,
autofocus: true,
autoplay: true,
checked: true,
controls: true,
default: true,
defer: true,
disabled: true,
formnovalidate: true,
hidden: true,
ismap: true,
itemscope: true,
loop: true,
multiple: true,
muted: true,
nomodule: true,
novalidate: true,
open: true,
playsinline: true,
readonly: true,
required: true,
reversed: true,
selected: true,
truespeed: true,
webkitdirectory: true,
};
function truthy(val) {
return val === "true" || val === true;
}

View file

@ -0,0 +1,11 @@
export function get_form_data(form) {
let values = new Map();
const formData = new FormData(form);
for (let name of formData.keys()) {
values.set(name, formData.getAll(name));
}
return values;
}

View file

@ -0,0 +1,12 @@
export function get_select_data(select) {
let values = [];
for (let i = 0; i < select.options.length; i++) {
let option = select.options[i];
if (option.selected) {
values.push(option.value.toString());
}
}
return values;
}

View file

@ -0,0 +1,41 @@
export class Dioxus {
constructor(sendCallback, returnCallback) {
this.sendCallback = sendCallback;
this.returnCallback = returnCallback;
this.promiseResolve = null;
this.received = [];
}
// Receive message from Rust
recv() {
return new Promise((resolve, _reject) => {
// If data already exists, resolve immediately
let data = this.received.shift();
if (data) {
resolve(data);
return;
}
// Otherwise set a resolve callback
this.promiseResolve = resolve;
});
}
// Send message to rust.
send(data) {
this.sendCallback(data);
}
// Internal rust send
rustSend(data) {
// If a promise is waiting for data, resolve it, and clear the resolve callback
if (this.promiseResolve) {
this.promiseResolve(data);
this.promiseResolve = null;
return;
}
// Otherwise add the data to a queue
this.received.push(data);
}
}

46
front/dist/index.html vendored Normal file
View file

@ -0,0 +1,46 @@
<!DOCTYPE html>
<html>
<head>
<title>Radioxide | Fully Oxidised Radio</title>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8" />
</head>
<body>
<div id="main"></div>
<script type="module">
import init from "/./assets/dioxus/Radioxide.js";
init("/./assets/dioxus/Radioxide_bg.wasm").then(wasm => {
if (wasm.__wbindgen_start == undefined) {
wasm.main();
}
});
</script>
</body>
</html><script>// Dioxus-CLI
// https://github.com/DioxusLabs/cli
(function () {
var protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
var url = protocol + '//' + window.location.host + '/_dioxus/ws';
var poll_interval = 8080;
var reload_upon_connect = () => {
window.setTimeout(
() => {
var ws = new WebSocket(url);
ws.onopen = () => window.location.reload();
ws.onclose = reload_upon_connect;
},
poll_interval);
};
var ws = new WebSocket(url);
ws.onmessage = (ev) => {
if (ev.data == "reload") {
window.location.reload();
}
};
ws.onclose = reload_upon_connect;
})()</script>

18
front/src/main.rs Normal file
View file

@ -0,0 +1,18 @@
use dioxus::prelude::*;
fn main() {
println!("Hello, world!");
launch(app);
}
fn app() -> Element {
rsx!{
h1 {
"Radioxide"
}
div {
audio{ autoplay:true, controls:true, muted:false, src:"https://playerservices.streamtheworld.com/api/livestream-redirect/METRO_FM128AAC.aac?/;stream.mp3" }
}
}
}