Compare commits

..

No commits in common. "fb3218ff587e442e7e17243991adbe86c4b34619" and "ec13842fe4ef756f1182ea73e76f2e477de77fbc" have entirely different histories.

14 changed files with 94 additions and 672 deletions

View File

@ -9,10 +9,3 @@ export interface TrackInfo {
export const getTracks = (): Promise<TrackInfo[]> =>
fetch("/api/v1/tracks").then((r) => r.json());
export const playTrack = (id: string): Promise<Response> =>
fetch("/api/v1/play", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ id: id }),
});

View File

@ -1,34 +0,0 @@
export class PlaylistRow extends HTMLElement {
onPlay: (trackId: string) => void;
static get observedAttributes() {
return ["trackId"];
}
constructor() {
super();
this.onPlay = (_) => {};
}
get trackId(): string | null {
return this.getAttribute("trackId");
}
set trackId(id: string | null) {
id ? this.setAttribute("trackId", id) : this.removeAttribute("trackId");
}
connectedCallback() {
this.classList.add("playlist-row");
const playButton = document.createElement("button");
playButton.innerHTML = "Play";
playButton.addEventListener("click", (_) => {
if (this.trackId) {
this.onPlay(this.trackId);
}
});
this.appendChild(playButton);
}
}

View File

@ -8,7 +8,7 @@ export class TrackCard extends HTMLElement {
durationContainer: TextField;
static get observedAttributes() {
return ["trackId", "trackNumber", "name", "album", "artist", "duration"];
return ["id", "trackNumber", "name", "album", "artist", "duration"];
}
constructor() {
@ -29,18 +29,6 @@ export class TrackCard extends HTMLElement {
this.durationContainer.classList.add("track-card__duration");
}
get trackId(): string | null {
return this.getAttribute("id");
}
set trackId(id: string | null) {
if (id) {
this.setAttribute("trackId", id);
} else {
this.removeAttribute("trackId");
}
}
get name(): string | null {
return this.getAttribute("name");
}

View File

@ -1,16 +1,14 @@
import * as _ from "lodash";
import { TrackInfo, getTracks, playTrack } from "./client";
import { TrackInfo, getTracks } from "./client";
import { DataCard } from "./components/DataCard";
import { NowPlaying } from "./components/NowPlaying";
import { TextField } from "./components/TextField";
import { TrackCard } from "./components/TrackCard";
import { PlaylistRow } from "./components/PlaylistRow";
window.customElements.define("data-card", DataCard);
window.customElements.define("now-playing", NowPlaying);
window.customElements.define("text-field", TextField);
window.customElements.define("track-card", TrackCard);
window.customElements.define("playlist-row", PlaylistRow);
declare global {
interface HTMLElementTagNameMap {
@ -18,30 +16,25 @@ declare global {
"now-playing": NowPlaying;
"text-field": TextField;
"track-card": TrackCard;
"playlist-row": PlaylistRow;
}
}
const updateTrackList = (tracks: TrackInfo[]) => {
const playlist = document.querySelector(".track-list__tracks");
if (playlist) {
_.map(tracks, (info) => {
const track_list = document.querySelector(".track-list__tracks");
if (track_list) {
let track_formats = _.map(tracks, (info) => {
let card: TrackCard = document.createElement("track-card");
let listItem: PlaylistRow = document.createElement("playlist-row");
card.trackId = info.id;
card.name = info.name || null;
card.album = info.album || null;
card.artist = info.artist || null;
card.duration = (info.duration && `${info.duration}`) || null;
listItem.appendChild(card);
listItem.trackId = info.id;
listItem.onPlay = (id: string) => {
console.log("time to play ", id);
playTrack(id);
};
playlist.appendChild(listItem);
return card;
});
_.map(track_formats, (trackCard) => {
let listItem = document.createElement("li");
listItem.classList.add("track-list__row");
listItem.appendChild(trackCard);
track_list.appendChild(listItem);
});
} else {
console.log("track_list does not exist");

View File

@ -143,11 +143,6 @@ body {
list-style: none;
}
.playlist-row {
display: flex;
margin-top: 32px;
}
.track-list__row {
margin-top: 32px;
}

View File

@ -19,12 +19,6 @@ dependencies = [
"version_check",
]
[[package]]
name = "anyhow"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
[[package]]
name = "autocfg"
version = "1.1.0"
@ -74,15 +68,6 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
[[package]]
name = "cfg-expr"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0357a6402b295ca3a86bc148e84df46c02e41f41fef186bda662557ef6328aa"
dependencies = [
"smallvec",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
@ -193,28 +178,6 @@ version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac"
[[package]]
name = "futures-executor"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-macro"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-sink"
version = "0.3.25"
@ -234,7 +197,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6"
dependencies = [
"futures-core",
"futures-macro",
"futures-sink",
"futures-task",
"pin-project-lite",
@ -263,113 +225,6 @@ dependencies = [
"wasi",
]
[[package]]
name = "gio-sys"
version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9b693b8e39d042a95547fc258a7b07349b1f0b48f4b2fa3108ba3c51c0b5229"
dependencies = [
"glib-sys",
"gobject-sys",
"libc",
"system-deps",
"winapi",
]
[[package]]
name = "glib"
version = "0.16.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd4df61a866ed7259d6189b8bcb1464989a77f1d85d25d002279bbe9dd38b2f"
dependencies = [
"bitflags",
"futures-channel",
"futures-core",
"futures-executor",
"futures-task",
"futures-util",
"gio-sys",
"glib-macros",
"glib-sys",
"gobject-sys",
"libc",
"once_cell",
"smallvec",
"thiserror",
]
[[package]]
name = "glib-macros"
version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e084807350b01348b6d9dbabb724d1a0bb987f47a2c85de200e98e12e30733bf"
dependencies = [
"anyhow",
"heck",
"proc-macro-crate",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "glib-sys"
version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61a4f46316d06bfa33a7ac22df6f0524c8be58e3db2d9ca99ccb1f357b62a65"
dependencies = [
"libc",
"system-deps",
]
[[package]]
name = "gobject-sys"
version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3520bb9c07ae2a12c7f2fbb24d4efc11231c8146a86956413fb1a79bb760a0f1"
dependencies = [
"glib-sys",
"libc",
"system-deps",
]
[[package]]
name = "gstreamer"
version = "0.19.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85fc926d081923c840403ec5ec3b2157a7cd236a2587c3031a4f0206f13ed500"
dependencies = [
"bitflags",
"cfg-if",
"futures-channel",
"futures-core",
"futures-util",
"glib",
"gstreamer-sys",
"libc",
"muldiv",
"num-integer",
"num-rational",
"once_cell",
"option-operations",
"paste",
"pretty-hex",
"thiserror",
]
[[package]]
name = "gstreamer-sys"
version = "0.19.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "545f52ad8a480732cc4290fd65dfe42952c8ae374fe581831ba15981fedf18a4"
dependencies = [
"glib-sys",
"gobject-sys",
"libc",
"system-deps",
]
[[package]]
name = "h2"
version = "0.3.15"
@ -432,12 +287,6 @@ dependencies = [
"http",
]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.2.6"
@ -629,12 +478,6 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "muldiv"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "956787520e75e9bd233246045d19f42fb73242759cc57fba9611d940ae96d4b0"
[[package]]
name = "multipart"
version = "0.18.0"
@ -658,7 +501,6 @@ name = "music-player"
version = "0.1.0"
dependencies = [
"flow",
"gstreamer",
"id3",
"mime",
"mime_guess",
@ -667,41 +509,10 @@ dependencies = [
"thiserror",
"tokio",
"url",
"urlencoding",
"uuid",
"warp",
]
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.15.0"
@ -718,15 +529,6 @@ version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
[[package]]
name = "option-operations"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c26d27bb1aeab65138e4bf7666045169d1717febcc9ff870166be8348b223d0"
dependencies = [
"paste",
]
[[package]]
name = "parking_lot"
version = "0.12.1"
@ -750,12 +552,6 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "paste"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79"
[[package]]
name = "percent-encoding"
version = "2.2.0"
@ -806,46 +602,6 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "pretty-hex"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6fa0831dd7cc608c38a5e323422a0077678fa5744aa2be4ad91c4ece8eec8d5"
[[package]]
name = "proc-macro-crate"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
dependencies = [
"once_cell",
"toml_edit",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.50"
@ -1075,19 +831,6 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "system-deps"
version = "6.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2955b1fe31e1fa2fbd1976b71cc69a606d7d4da16f6de3333d0c92d51419aeff"
dependencies = [
"cfg-expr",
"heck",
"pkg-config",
"toml",
"version-compare",
]
[[package]]
name = "tempfile"
version = "3.3.0"
@ -1205,32 +948,6 @@ dependencies = [
"tracing",
]
[[package]]
name = "toml"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
dependencies = [
"serde",
]
[[package]]
name = "toml_datetime"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
[[package]]
name = "toml_edit"
version = "0.19.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a1eb0622d28f4b9c90adc4ea4b2b46b47663fde9ac5fafcb14a1369d5508825"
dependencies = [
"indexmap",
"toml_datetime",
"winnow",
]
[[package]]
name = "tower-service"
version = "0.3.2"
@ -1339,12 +1056,6 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "urlencoding"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9"
[[package]]
name = "utf-8"
version = "0.7.6"
@ -1366,12 +1077,6 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version-compare"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
[[package]]
name = "version_check"
version = "0.9.4"
@ -1503,12 +1208,3 @@ name = "windows_x86_64_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
[[package]]
name = "winnow"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee7b2c67f962bf5042bfd8b6a916178df33a26eec343ae064cb8e069f638fa6f"
dependencies = [
"memchr",
]

View File

@ -7,7 +7,6 @@ edition = "2021"
[dependencies]
flow = { path = "../../flow" }
gstreamer = { version = "0.19" }
id3 = { version = "1.6" }
mime_guess = { version = "2.0" }
mime = { version = "0.3" }
@ -18,6 +17,5 @@ tokio = { version = "1.24", features = ["full"] }
url = { version = "2.3" }
uuid = { version = "1", features = ["v4"] }
warp = { version = "0.3" }
urlencoding = { version = "2.1" }
[lib]

View File

@ -1,23 +1,17 @@
use flow::Flow;
use music_player::{
core::{ControlMsg, Core},
database::{MemoryIndex, MusicIndex},
media::{TrackId, TrackInfo},
scanner::FileScanner,
};
use serde::Deserialize;
use std::{
net::{IpAddr, Ipv4Addr, SocketAddr},
path::PathBuf,
sync::Arc,
thread,
};
use warp::Filter;
#[derive(Clone, Debug, Deserialize)]
struct TrackRequest {
id: String,
}
use music_player::{
core::Core,
database::{MemoryIndex, MusicIndex},
media::TrackInfo,
scanner::FileScanner,
};
fn tracks(index: &Arc<impl MusicIndex>) -> Vec<TrackInfo> {
match index.list_tracks() {
@ -31,6 +25,15 @@ struct Static(PathBuf);
impl Static {
fn read(self, root: PathBuf) -> String {
/*
let mut path = root;
match self {
Bundle::Index => path.push(PathBuf::from("index.html")),
Bundle::App => path.push(PathBuf::from("bundle.js")),
Bundle::Styles => path.push(PathBuf::from("styles.css")),
};
std::fs::read_to_string(path).expect("to find the file")
*/
let mut path = root;
path.push(self.0);
println!("path: {:?}", path);
@ -52,15 +55,13 @@ pub async fn main() {
.unwrap();
let index = Arc::new(MemoryIndex::new());
let scanner = Arc::new(FileScanner::new(vec![music_root.clone()]));
let (core, api) = match Core::new(index.clone(), scanner) {
Flow::Ok((core, api)) => (core, api),
let scanner = FileScanner::new(vec![music_root.clone()]);
let _core = match Core::new(index.clone(), scanner) {
Flow::Ok(core) => core,
Flow::Err(error) => panic!("error: {}", error),
Flow::Fatal(error) => panic!("fatal: {}", error),
};
let _handle = thread::spawn(move || core.start());
println!("config: {:?} {:?} {:?}", dev, bundle_root, music_root);
let root = warp::path!().and(warp::get()).map({
@ -101,18 +102,6 @@ pub async fn main() {
move || warp::reply::json(&tracks(&index))
});
let play_track = warp::path!("api" / "v1" / "play")
.and(warp::post())
.and(warp::body::json())
.map({
let api = api.clone();
move |body: TrackRequest| {
let result = api.play_track(TrackId::from(body.id));
println!("Play result: {:?}", result);
warp::reply::json(&("ok".to_owned()))
}
});
/*
let tracks_for_artist = warp::path!("api" / "v1" / "artist" / String)
.and(warp::get())
@ -139,7 +128,7 @@ pub async fn main() {
.or(queue)
.or(playing_status);
*/
let routes = root.or(assets).or(track_list).or(play_track);
let routes = root.or(assets).or(track_list);
let server = warp::serve(routes);
server
.run(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8002))

View File

@ -1,16 +1,9 @@
use crate::{
database::MusicIndex,
media::{TrackId, TrackInfo},
playback::{Playback, PlaybackControl, PlaybackStatus},
scanner::MusicScanner,
Error, FatalError,
};
use flow::{ok, return_error, Flow};
use gstreamer::{format::ClockTime, prelude::*, MessageView};
use crate::{database::MusicIndex, media::TrackInfo, scanner::MusicScanner, Error, FatalError};
use flow::{ok, Flow};
use std::{
sync::{
mpsc::{channel, Receiver, RecvTimeoutError, Sender},
Arc, Mutex,
Arc,
},
thread,
thread::JoinHandle,
@ -21,9 +14,7 @@ fn scan_frequency() -> Duration {
Duration::from_secs(60)
}
#[derive(Clone)]
pub enum ControlMsg {
PlayTrack(TrackId),
Exit,
}
@ -41,119 +32,74 @@ pub enum PlaybackMsg {
pub struct Core {
db: Arc<dyn MusicIndex>,
scanner: Arc<dyn MusicScanner>,
control_rx: Receiver<ControlMsg>,
playback_controller: Playback,
_track_handle: JoinHandle<()>,
_track_rx: Receiver<TrackMsg>,
_playback_handle: JoinHandle<()>,
_playback_rx: Receiver<PlaybackMsg>,
control_tx: Sender<ControlMsg>,
}
impl Core {
pub fn new(
db: Arc<dyn MusicIndex>,
scanner: Arc<dyn MusicScanner>,
) -> Flow<(Core, CoreAPI), FatalError, Error> {
scanner: impl MusicScanner + 'static,
) -> Flow<Core, FatalError, Error> {
let (control_tx, control_rx) = channel::<ControlMsg>();
let db = db;
let playback_controller = Playback::new();
ok((
Core {
db,
scanner,
control_rx,
playback_controller,
},
CoreAPI {
control_tx: Arc::new(Mutex::new(control_tx)),
},
))
}
pub fn start(&self) -> Flow<(), FatalError, Error> {
gstreamer::init();
let (scanner_tx, _scanner_rx) = channel();
let mut next_scan = Instant::now();
loop {
if Instant::now() >= next_scan {
let scan_start = Instant::now();
let _ = scanner_tx.send(TrackMsg::UpdateInProgress);
for track in self.scanner.scan() {
match track {
Ok(track) => self.db.add_track(track),
Err(_) => ok(()),
};
let (_track_handle, _track_rx) = {
let (track_tx, track_rx) = channel();
let db = db.clone();
let track_handle = thread::spawn(move || {
let mut next_scan = Instant::now();
loop {
if Instant::now() >= next_scan {
let _ = track_tx.send(TrackMsg::UpdateInProgress);
for track in scanner.scan() {
match track {
Ok(track) => db.add_track(track),
Err(_) => ok(()),
};
}
let _ = track_tx.send(TrackMsg::UpdateComplete);
next_scan = Instant::now() + scan_frequency();
}
match control_rx.recv_timeout(Duration::from_millis(1000)) {
Ok(ControlMsg::Exit) => return,
Err(RecvTimeoutError::Timeout) => (),
Err(RecvTimeoutError::Disconnected) => return,
}
}
let _ = scanner_tx.send(TrackMsg::UpdateComplete);
next_scan = Instant::now() + scan_frequency();
println!("scan duration: {:?}", Instant::now() - scan_start);
}
match self.control_rx.recv_timeout(Duration::from_millis(1000)) {
Ok(ControlMsg::PlayTrack(id)) => {
let _ = self.play_track(id);
}
Ok(ControlMsg::Exit) => return ok(()),
Err(RecvTimeoutError::Timeout) => (),
Err(RecvTimeoutError::Disconnected) => return ok(()),
}
}
});
(track_handle, track_rx)
};
let (_playback_handle, _playback_rx) = {
let (_playback_tx, playback_rx) = channel();
let playback_handle = thread::spawn(move || {});
(playback_handle, playback_rx)
};
ok(Core {
db,
_track_handle,
_track_rx,
_playback_handle,
_playback_rx,
control_tx,
})
}
pub fn list_tracks<'a>(&'a self) -> Flow<Vec<TrackInfo>, FatalError, Error> {
self.db.list_tracks().map_err(Error::DatabaseError)
}
pub fn play_track<'a>(&'a self, id: TrackId) -> Flow<(), FatalError, Error> {
pub fn exit(&self) {
let _ = self.control_tx.send(ControlMsg::Exit);
/*
println!("play_track: {}", id.as_ref());
let pipeline = return_error!(Flow::from(
gstreamer::parse_launch(&format!("playbin uri={}", id.as_str()))
.map_err(|err| Error::CannotPlay(err.to_string()),)
));
return_error!(Flow::from(
pipeline
.set_state(gstreamer::State::Playing)
.map_err(|err| Error::CannotPlay(err.to_string()))
));
{
let pipeline = pipeline.clone();
thread::spawn(move || {
println!("starting");
let bus = pipeline.bus().unwrap();
for msg in bus.iter_timed(gstreamer::ClockTime::NONE) {
match msg.view() {
MessageView::Eos(_) => (),
MessageView::Error(err) => {
println!(
"Error from {:?}: {} ({:?})",
err.src().map(|s| s.path_string()),
err.error(),
err.debug()
);
}
msg => println!("{:?}", msg),
}
}
});
}
self.track_handle.join();
self.playback_handle.join();
*/
self.playback_controller.play_track(id);
ok(())
}
}
#[derive(Clone)]
pub struct CoreAPI {
control_tx: Arc<Mutex<Sender<ControlMsg>>>,
}
impl CoreAPI {
pub fn play_track(&self, id: TrackId) -> () {
self.control_tx
.lock()
.unwrap()
.send(ControlMsg::PlayTrack(id))
.unwrap()
}
}
@ -169,8 +115,8 @@ mod test {
{
let index = MemoryIndex::new();
let scanner = MockScanner::new();
match Core::new(Arc::new(index), Arc::new(scanner)) {
Flow::Ok((core, api)) => {
match Core::new(Arc::new(index), scanner) {
Flow::Ok(core) => {
thread::sleep(Duration::from_millis(10));
f(core)
}

View File

@ -1,7 +1,6 @@
pub mod core;
pub mod database;
pub mod media;
pub mod playback;
pub mod scanner;
use database::DatabaseError;
use thiserror::Error;
@ -10,15 +9,6 @@ use thiserror::Error;
pub enum Error {
#[error("Database error: {0}")]
DatabaseError(DatabaseError),
#[error("Cannot play track")]
CannotPlay,
#[error("Cannot stop playback")]
CannotStop,
#[error("Unmatched glib error: {0}")]
GlibError(gstreamer::glib::Error),
}
impl From<DatabaseError> for Error {

View File

@ -59,12 +59,6 @@ impl From<url::ParseError> for AudioError {
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize)]
pub struct TrackId(String);
impl TrackId {
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Default for TrackId {
fn default() -> Self {
Self(uuid::Uuid::new_v4().as_hyphenated().to_string())

View File

@ -1,131 +0,0 @@
use crate::{media::TrackId, Error, FatalError};
use flow::{ok, return_error, Flow};
use gstreamer::{format::ClockTime, prelude::*, MessageView, StateChangeError};
use std::{
path::PathBuf,
sync::mpsc::{channel, Receiver, Sender},
thread::{self, JoinHandle},
time::Duration,
};
use urlencoding::encode;
pub enum PlaybackControl {
PlayTrack(TrackId),
Stop,
Exit,
}
pub enum PlaybackStatus {
Stopped,
}
pub struct Playback {
handle: JoinHandle<Flow<(), FatalError, Error>>,
control_tx: Sender<PlaybackControl>,
}
impl Playback {
pub fn new() -> Playback {
let (control_tx, control_rx) = channel::<PlaybackControl>();
let handle = thread::spawn(move || {
let mut pipeline = None;
loop {
match control_rx.recv().unwrap() {
PlaybackControl::PlayTrack(id) => match play_track(id) {
Flow::Ok(pipeline_) => pipeline = Some(pipeline_),
Flow::Fatal(err) => panic!("fatal error: {:?}", err),
Flow::Err(err) => panic!("playback error: {:?}", err),
},
PlaybackControl::Stop => {
if let Some(ref pipeline) = pipeline {
return_error!(Flow::from(
pipeline
.set_state(gstreamer::State::Paused)
.map_err(|_| Error::CannotStop)
));
}
}
PlaybackControl::Exit => return ok(()),
}
}
});
Self { handle, control_tx }
}
pub fn play_track(&self, id: TrackId) {
self.control_tx
.send(PlaybackControl::PlayTrack(id))
.unwrap();
}
}
fn play_track(id: TrackId) -> Flow<gstreamer::Element, FatalError, Error> {
let pb = PathBuf::from(id.as_ref());
let path = pb
.iter()
.skip(1)
.map(|component| encode(&component.to_string_lossy()).into_owned())
.collect::<PathBuf>();
let playbin = format!("playbin uri=file:///{}", path.display());
println!("setting up to play {}", playbin);
let pipeline = return_error!(Flow::from(
gstreamer::parse_launch(&playbin).map_err(|err| Error::GlibError(err))
));
println!("ready to play");
return_error!(Flow::from(
pipeline
.set_state(gstreamer::State::Playing)
.map_err(|_| Error::CannotPlay)
));
println!("playing started");
ok(pipeline)
}
/*
fn play_track(id: TrackId) -> Flow<(), FatalError, Error> {
let playbin = format!("playbin uri=file://{}", id.as_ref());
let pipeline = return_error!(Flow::from(
gstreamer::parse_launch(&playbin).map_err(|err| Error::GlibError(err))
));
return_error!(Flow::from(
pipeline
.set_state(gstreamer::State::Playing)
.map_err(|_| Error::CannotPlay)
));
let message_handler = {
let pipeline = pipeline.clone();
thread::spawn(move || {
let bus = pipeline.bus().unwrap();
for msg in bus.iter_timed(gstreamer::ClockTime::NONE) {
match msg.view() {
MessageView::Eos(_) => (),
MessageView::Error(err) => {
println!(
"Error from {:?}: {} ({:?})",
err.src().map(|s| s.path_string()),
err.error(),
err.debug()
);
}
msg => println!("{:?}", msg),
}
}
})
};
let query_handler = {
let pipeline = pipeline.clone();
thread::spawn(move || loop {
let position: Option<ClockTime> = pipeline.query_position();
let duration: Option<ClockTime> = pipeline.query_duration();
println!("Position {:?} {:?}", position, duration);
thread::sleep(Duration::from_millis(100));
})
};
ok(())
}
*/

View File

@ -37,7 +37,7 @@ impl From<id3::Error> for ScannerError {
}
}
pub trait MusicScanner: Sync + Send {
pub trait MusicScanner: Send {
fn scan<'a>(&'a self) -> Box<dyn Iterator<Item = Result<TrackInfo, ScannerError>> + 'a>;
}
@ -58,6 +58,11 @@ pub struct FileIterator {
impl FileIterator {
fn scan_file(&self, path: PathBuf) -> Result<TrackInfo, ScannerError> {
println!(
"[{:?}] {}",
mime_guess::from_path(path.clone()).first(),
path.to_str().unwrap()
);
let mimetype = mime_guess::from_path(path.clone())
.first()
.ok_or(ScannerError::CannotScan)?;

View File

@ -34,7 +34,7 @@ fn main() {
thread::spawn(move || loop {
let position: Option<ClockTime> = pipeline.query_position();
let duration: Option<ClockTime> = pipeline.query_duration();
println!("Position {:?} {:?}", position, duration);
println!("{:?} {:?}", position, duration);
thread::sleep(Duration::from_millis(100));
})
};