Commit cd2d04af authored by Felix Häcker's avatar Felix Häcker

First commit

parents
.idea
./target
target/
target
**/*.rs.bk
Cargo.toml
\ No newline at end of file
This diff is collapsed.
# Gradio 8.0 - Rust
This is Gradio - completely rewritten in Rust. This is just a experiment. **Maybe** this version will replace the Vala version of Gradio.
It is splitted into two parts:
- **Rustio** - Backend
- Network communication
- radio-browser.info API acess
- Audio Playback
- **Gradio** - Frontend
- GTK Interface
- Desktop integration
- Library management
\ No newline at end of file
extern crate gio;
extern crate gtk;
use gtk::prelude::*;
use gio::{ApplicationExtManual, ApplicationExt};
extern crate rustio;
use rustio::{client::Client, audioplayer::AudioPlayer};
use std::str::FromStr;
use std::rc::Rc;
use std::cell::RefCell;
pub struct GradioApp{
pub player: Rc<RefCell<AudioPlayer>>,
pub client: Rc<Client>,
gtk_app: gtk::Application,
window: gtk::ApplicationWindow,
builder: gtk::Builder,
}
impl GradioApp {
pub fn new() -> GradioApp {
let player = Rc::new(RefCell::new(AudioPlayer::new()));
let client = Rc::new(Client::new());
let gtk_app = gtk::Application::new("de.haeckerfelix.Gradio", gio::ApplicationFlags::empty()).expect("Failed to initialize GtkApplication");
let builder = gtk::Builder::new_from_string(include_str!("window.ui"));
let window: gtk::ApplicationWindow = builder.get_object("main_window").unwrap();
GradioApp{player, client, gtk_app, window, builder}
}
pub fn run(&self){
self.connect_signals();
self.gtk_app.run(&[]);
}
fn connect_signals(&self) {
let get_button: gtk::Button = self.builder.get_object("get_button").unwrap();
let stop_playback_button: gtk::Button = self.builder.get_object("stop_playback_button").unwrap();
let start_playback_button: gtk::Button = self.builder.get_object("start_playback_button").unwrap();
let id_entry: gtk::Entry = self.builder.get_object("id_entry").unwrap();
let station_name_label: gtk::Label = self.builder.get_object("station_name_label").unwrap();
let station_language_label: gtk::Label = self.builder.get_object("station_language_label").unwrap();
let playable_url_label: gtk::Label = self.builder.get_object("playable_url_label").unwrap();
// GTK Application activate
let window_clone = self.window.clone();
self.gtk_app.connect_activate(move|app| {
app.add_window(&window_clone);
debug!("gtk application activate");
});
// get_button clicked
let player = self.player.clone();
let client = self.client.clone();
get_button.connect_clicked(move|_|{
let station_id: i32 = FromStr::from_str(&id_entry.get_text().unwrap()).unwrap();
let new_station = client.get_station_by_id(station_id);
let playable_url = client.get_playable_station_url(&new_station);
station_name_label.set_text(&new_station.name);
station_language_label.set_text(&new_station.language);
playable_url_label.set_text(&playable_url);
player.borrow_mut().set_station_url(playable_url);
//self.current_station = Some(new_station);
info!("button clicked");
});
// start_playback_button clicked
let player = self.player.clone();
start_playback_button.connect_clicked(move|_|{
player.borrow_mut().set_playback(true);
});
// stop_playback_button clicked
let player = self.player.clone();
stop_playback_button.connect_clicked(move|_|{
player.borrow_mut().set_playback(false);
});
}
}
\ No newline at end of file
#[macro_use] extern crate log;
extern crate pretty_env_logger;
extern crate gtk;
extern crate gio;
extern crate rustio;
mod app;
use app::GradioApp;
fn main() {
// Init Logger
pretty_env_logger::init();
// Init GTK
if gtk::init().is_err() {
error!("Failed to initialize GTK.");
return;
}
// Run App
let app = GradioApp::new();
app.run();
}
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.0 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkSizeGroup" id="content_sz"/>
<object class="GtkApplicationWindow" id="main_window">
<property name="width_request">600</property>
<property name="height_request">400</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="default_width">800</property>
<property name="default_height">500</property>
<property name="icon_name">de.haeckerfelix.Gradio</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkBox" id="sidebar_box">
<property name="width_request">230</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkStackSidebar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">12</property>
<property name="stack">page_stack</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSeparator">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="content_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkStack" id="page_stack">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">12</property>
<child>
<object class="GtkBox" id="test_page">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="column_spacing">12</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Station Name</property>
<style>
<class name="dim-label"/>
</style>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="station_name_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">-</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="station_language_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">-</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Current title</property>
<style>
<class name="dim-label"/>
</style>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Language</property>
<style>
<class name="dim-label"/>
</style>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Playable URL</property>
<style>
<class name="dim-label"/>
</style>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="current_title_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">-</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="playable_url_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">-</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="id_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="placeholder_text" translatable="yes">Enter a station id....</property>
<property name="input_purpose">number</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="get_button">
<property name="label" translatable="yes">Get station data</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="start_playback_button">
<property name="label" translatable="yes">Start playback</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkSeparator">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkButton" id="stop_playback_button">
<property name="label" translatable="yes">Stop playback</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
</object>
<packing>
<property name="name">test</property>
<property name="title" translatable="yes">Test Page</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
<child type="titlebar">
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkHeaderBar" id="sidebar_header">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSeparator">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHeaderBar" id="content_header">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="title">Gradio</property>
<property name="subtitle">Rust Preview</property>
<property name="show_close_button">True</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkSizeGroup" id="sidebar_sz">
<widgets>
<widget name="sidebar_box"/>
<widget name="sidebar_header"/>
</widgets>
</object>
</interface>
# rustio
A radio-browser.info API wrapper for rust, including audio playback using gstreamer.
Everything is WIP. Maybe this will be used for Gradio sometime.
## Todo
- [x] Server communication (JSON deserialize)
- [x] Basic elements (station, country, language, state, tag)
- [x] Server stats
- [x] Audio playback using gstreamer
- [ ] Error handling
- [ ] Editable stations
- [ ] Create new stations
- [ ] Advanced station search
- ...
\ No newline at end of file
extern crate gstreamer;
use gstreamer::{Element, ElementFactory, ElementExt};
use gstreamer::prelude::*;
pub struct AudioPlayer{
playbin: Element,
station_url: String,
}
impl AudioPlayer{
pub fn new() -> AudioPlayer{
gstreamer::init();
let playbin = ElementFactory::make("playbin", "playbin").unwrap();
AudioPlayer{
playbin,
station_url: "".to_string()
}
}
pub fn set_playback(&self, play: bool){
let ret = if play {
info!("Start playback");
self.playbin.set_state(gstreamer::State::Playing)
}else{
info!("Stop playback");
self.playbin.set_state(gstreamer::State::Null)
};
debug!("gstreamer state is \"{:?}\"", ret);
}
pub fn set_station_url(&mut self, url: String){
info!("Set station url: {}", url);
self.playbin.set_property("uri", &url);
}
}
\ No newline at end of file
extern crate serde;
extern crate serde_json;
extern crate reqwest;
use country::Country;
use station::Station;
#[derive(Deserialize)]
pub struct StationUrlResult{
ok: String,
url: String,
}
pub struct Client {
client: reqwest::Client,
server_url: String,
}
impl Client {
pub fn new() -> Client {
let client = reqwest::Client::new();
Client {
client: client,
server_url: "http://www.radio-browser.info/webservice/".to_string(),
}
}
pub fn get_all_languages(&self) -> Vec<Country>{
let url: String = format!("{}{}", self.server_url, "json/languages/");
self.client.get(&url).send().unwrap().json().unwrap()
}
pub fn get_all_countries(&self) -> Vec<Country>{
let url: String = format!("{}{}", self.server_url, "json/countries/");
self.client.get(&url).send().unwrap().json().unwrap()
}
pub fn get_all_states(&self) -> Vec<Country>{
let url: String = format!("{}{}", self.server_url, "json/states/");
self.client.get(&url).send().unwrap().json().unwrap()
}
pub fn get_all_tags(&self) -> Vec<Country>{
let url: String = format!("{}{}", self.server_url, "json/tags/");
self.client.get(&url).send().unwrap().json().unwrap()
}
pub fn get_station_by_id(&self, id: i32) -> Station{
let url: String = format!("{}{}{}", self.server_url, "json/stations/byid/", id);
let mut result: Vec<Station> = self.client.get(&url).send().unwrap().json().unwrap();
result.remove(0)
}
pub fn get_playable_station_url(&self, station: &Station) -> String{
let url: String = format!("{}{}{}", self.server_url, "v2/json/url/", station.id);
let mut result: StationUrlResult = self.client.get(&url).send().unwrap().json().unwrap();
result.url
}
}
\ No newline at end of file
#[derive(Deserialize)]
pub struct Country {
pub name: String,
pub value: String,
pub stationcount: String,
}
\ No newline at end of file
extern crate reqwest;
use serde_json::Value as JsonValue;
pub enum Error {
ReqwestError(reqwest::Error),
ServerError(JsonValue),
}
\ No newline at end of file
#[derive(Deserialize)]
pub struct Language {
pub name: String,
pub value: String,
pub stationcount: String,
}
\ No newline at end of file
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate log;
extern crate serde;
extern crate serde_json;
extern crate reqwest;
extern crate gstreamer;
pub mod error;
pub mod client;
pub mod audioplayer;
pub mod station;
pub mod country;
pub mod state;
pub mod language;
pub mod tag;
pub mod stats;
#[derive(Deserialize)]
pub struct State {
pub name: String,
pub value: String,
pub stationcount: String,
}
\ No newline at end of file
use serde_json::de::from_str;
#[derive(Deserialize)]
pub struct Station {
pub name: String,
pub language: String,
pub country: String,
pub state: String,
pub tags: String,
pub codec: String,
pub votes: String,
pub homepage: String,
pub favicon: String,
pub id: String,
pub changeuuid: String,
pub stationuuid: String,
pub url: String,
pub ip: String,
pub bitrate: String,
pub hls: String,
pub lastchangetime: String,
pub lastcheckok: String,
pub lastchecktime: String,
pub lastcheckoktime: String,
pub clicktimestamp: String,
pub clickcount: String,
pub clicktrend: String,
}
impl Station{
pub fn print(&self){
info!("#{} \"{}\" ({}/{})", self.id, self.name, self.country, self.state);
}
}
\ No newline at end of file
#[derive(Deserialize)]
pub struct Stats {
pub stations: String,
pub stations_broken: String,
pub tags: String,
pub clicks_last_hour: String,
pub clicks_last_day: String,
pub languages: String,
pub countries: String,
}
impl Stats{
pub fn print(&self){
println!("Stations: {} ({} broken)\nTags: {}\n\
Languages: {}\n\
Countries: {}\n\
Clicks: {} last hour / {} last day",
self.stations,
self.stations_broken,
self.tags,
self.languages,
self.countries,
self.clicks_last_hour,
self.clicks_last_day);
}
}
\ No newline at end of file
#[derive(Deserialize)]
pub struct Tag {
pub name: String,
pub value: String,
pub stationcount: String,
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment