Compare commits
No commits in common. "fb3218ff587e442e7e17243991adbe86c4b34619" and "ec13842fe4ef756f1182ea73e76f2e477de77fbc" have entirely different histories.
fb3218ff58
...
ec13842fe4
|
@ -9,10 +9,3 @@ export interface TrackInfo {
|
||||||
|
|
||||||
export const getTracks = (): Promise<TrackInfo[]> =>
|
export const getTracks = (): Promise<TrackInfo[]> =>
|
||||||
fetch("/api/v1/tracks").then((r) => r.json());
|
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 }),
|
|
||||||
});
|
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,7 +8,7 @@ export class TrackCard extends HTMLElement {
|
||||||
durationContainer: TextField;
|
durationContainer: TextField;
|
||||||
|
|
||||||
static get observedAttributes() {
|
static get observedAttributes() {
|
||||||
return ["trackId", "trackNumber", "name", "album", "artist", "duration"];
|
return ["id", "trackNumber", "name", "album", "artist", "duration"];
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -29,18 +29,6 @@ export class TrackCard extends HTMLElement {
|
||||||
this.durationContainer.classList.add("track-card__duration");
|
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 {
|
get name(): string | null {
|
||||||
return this.getAttribute("name");
|
return this.getAttribute("name");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
import * as _ from "lodash";
|
import * as _ from "lodash";
|
||||||
import { TrackInfo, getTracks, playTrack } from "./client";
|
import { TrackInfo, getTracks } from "./client";
|
||||||
import { DataCard } from "./components/DataCard";
|
import { DataCard } from "./components/DataCard";
|
||||||
import { NowPlaying } from "./components/NowPlaying";
|
import { NowPlaying } from "./components/NowPlaying";
|
||||||
import { TextField } from "./components/TextField";
|
import { TextField } from "./components/TextField";
|
||||||
import { TrackCard } from "./components/TrackCard";
|
import { TrackCard } from "./components/TrackCard";
|
||||||
import { PlaylistRow } from "./components/PlaylistRow";
|
|
||||||
|
|
||||||
window.customElements.define("data-card", DataCard);
|
window.customElements.define("data-card", DataCard);
|
||||||
window.customElements.define("now-playing", NowPlaying);
|
window.customElements.define("now-playing", NowPlaying);
|
||||||
window.customElements.define("text-field", TextField);
|
window.customElements.define("text-field", TextField);
|
||||||
window.customElements.define("track-card", TrackCard);
|
window.customElements.define("track-card", TrackCard);
|
||||||
window.customElements.define("playlist-row", PlaylistRow);
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
|
@ -18,30 +16,25 @@ declare global {
|
||||||
"now-playing": NowPlaying;
|
"now-playing": NowPlaying;
|
||||||
"text-field": TextField;
|
"text-field": TextField;
|
||||||
"track-card": TrackCard;
|
"track-card": TrackCard;
|
||||||
"playlist-row": PlaylistRow;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateTrackList = (tracks: TrackInfo[]) => {
|
const updateTrackList = (tracks: TrackInfo[]) => {
|
||||||
const playlist = document.querySelector(".track-list__tracks");
|
const track_list = document.querySelector(".track-list__tracks");
|
||||||
if (playlist) {
|
if (track_list) {
|
||||||
_.map(tracks, (info) => {
|
let track_formats = _.map(tracks, (info) => {
|
||||||
let card: TrackCard = document.createElement("track-card");
|
let card: TrackCard = document.createElement("track-card");
|
||||||
let listItem: PlaylistRow = document.createElement("playlist-row");
|
|
||||||
|
|
||||||
card.trackId = info.id;
|
|
||||||
card.name = info.name || null;
|
card.name = info.name || null;
|
||||||
card.album = info.album || null;
|
card.album = info.album || null;
|
||||||
card.artist = info.artist || null;
|
card.artist = info.artist || null;
|
||||||
card.duration = (info.duration && `${info.duration}`) || null;
|
card.duration = (info.duration && `${info.duration}`) || null;
|
||||||
|
return card;
|
||||||
listItem.appendChild(card);
|
});
|
||||||
listItem.trackId = info.id;
|
_.map(track_formats, (trackCard) => {
|
||||||
listItem.onPlay = (id: string) => {
|
let listItem = document.createElement("li");
|
||||||
console.log("time to play ", id);
|
listItem.classList.add("track-list__row");
|
||||||
playTrack(id);
|
listItem.appendChild(trackCard);
|
||||||
};
|
track_list.appendChild(listItem);
|
||||||
playlist.appendChild(listItem);
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log("track_list does not exist");
|
console.log("track_list does not exist");
|
||||||
|
|
|
@ -143,11 +143,6 @@ body {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.playlist-row {
|
|
||||||
display: flex;
|
|
||||||
margin-top: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.track-list__row {
|
.track-list__row {
|
||||||
margin-top: 32px;
|
margin-top: 32px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,12 +19,6 @@ dependencies = [
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anyhow"
|
|
||||||
version = "1.0.69"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
@ -74,15 +68,6 @@ version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
|
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]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
@ -193,28 +178,6 @@ version = "0.3.25"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac"
|
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]]
|
[[package]]
|
||||||
name = "futures-sink"
|
name = "futures-sink"
|
||||||
version = "0.3.25"
|
version = "0.3.25"
|
||||||
|
@ -234,7 +197,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6"
|
checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-macro",
|
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
@ -263,113 +225,6 @@ dependencies = [
|
||||||
"wasi",
|
"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]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.3.15"
|
version = "0.3.15"
|
||||||
|
@ -432,12 +287,6 @@ dependencies = [
|
||||||
"http",
|
"http",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "heck"
|
|
||||||
version = "0.4.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.2.6"
|
version = "0.2.6"
|
||||||
|
@ -629,12 +478,6 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "muldiv"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "956787520e75e9bd233246045d19f42fb73242759cc57fba9611d940ae96d4b0"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "multipart"
|
name = "multipart"
|
||||||
version = "0.18.0"
|
version = "0.18.0"
|
||||||
|
@ -658,7 +501,6 @@ name = "music-player"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"flow",
|
"flow",
|
||||||
"gstreamer",
|
|
||||||
"id3",
|
"id3",
|
||||||
"mime",
|
"mime",
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
|
@ -667,41 +509,10 @@ dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"url",
|
"url",
|
||||||
"urlencoding",
|
|
||||||
"uuid",
|
"uuid",
|
||||||
"warp",
|
"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]]
|
[[package]]
|
||||||
name = "num_cpus"
|
name = "num_cpus"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
|
@ -718,15 +529,6 @@ version = "1.17.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
|
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]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
|
@ -750,12 +552,6 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "paste"
|
|
||||||
version = "1.0.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.2.0"
|
version = "2.2.0"
|
||||||
|
@ -806,46 +602,6 @@ version = "0.2.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
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]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.50"
|
version = "1.0.50"
|
||||||
|
@ -1075,19 +831,6 @@ dependencies = [
|
||||||
"unicode-ident",
|
"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]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.3.0"
|
version = "3.3.0"
|
||||||
|
@ -1205,32 +948,6 @@ dependencies = [
|
||||||
"tracing",
|
"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]]
|
[[package]]
|
||||||
name = "tower-service"
|
name = "tower-service"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
|
@ -1339,12 +1056,6 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "urlencoding"
|
|
||||||
version = "2.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf-8"
|
name = "utf-8"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
|
@ -1366,12 +1077,6 @@ version = "0.2.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "version-compare"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
|
@ -1503,12 +1208,3 @@ name = "windows_x86_64_msvc"
|
||||||
version = "0.42.1"
|
version = "0.42.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
|
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winnow"
|
|
||||||
version = "0.3.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ee7b2c67f962bf5042bfd8b6a916178df33a26eec343ae064cb8e069f638fa6f"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
flow = { path = "../../flow" }
|
flow = { path = "../../flow" }
|
||||||
gstreamer = { version = "0.19" }
|
|
||||||
id3 = { version = "1.6" }
|
id3 = { version = "1.6" }
|
||||||
mime_guess = { version = "2.0" }
|
mime_guess = { version = "2.0" }
|
||||||
mime = { version = "0.3" }
|
mime = { version = "0.3" }
|
||||||
|
@ -18,6 +17,5 @@ tokio = { version = "1.24", features = ["full"] }
|
||||||
url = { version = "2.3" }
|
url = { version = "2.3" }
|
||||||
uuid = { version = "1", features = ["v4"] }
|
uuid = { version = "1", features = ["v4"] }
|
||||||
warp = { version = "0.3" }
|
warp = { version = "0.3" }
|
||||||
urlencoding = { version = "2.1" }
|
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
|
|
@ -1,23 +1,17 @@
|
||||||
use flow::Flow;
|
use flow::Flow;
|
||||||
use music_player::{
|
|
||||||
core::{ControlMsg, Core},
|
|
||||||
database::{MemoryIndex, MusicIndex},
|
|
||||||
media::{TrackId, TrackInfo},
|
|
||||||
scanner::FileScanner,
|
|
||||||
};
|
|
||||||
use serde::Deserialize;
|
|
||||||
use std::{
|
use std::{
|
||||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
thread,
|
|
||||||
};
|
};
|
||||||
use warp::Filter;
|
use warp::Filter;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
use music_player::{
|
||||||
struct TrackRequest {
|
core::Core,
|
||||||
id: String,
|
database::{MemoryIndex, MusicIndex},
|
||||||
}
|
media::TrackInfo,
|
||||||
|
scanner::FileScanner,
|
||||||
|
};
|
||||||
|
|
||||||
fn tracks(index: &Arc<impl MusicIndex>) -> Vec<TrackInfo> {
|
fn tracks(index: &Arc<impl MusicIndex>) -> Vec<TrackInfo> {
|
||||||
match index.list_tracks() {
|
match index.list_tracks() {
|
||||||
|
@ -31,6 +25,15 @@ struct Static(PathBuf);
|
||||||
|
|
||||||
impl Static {
|
impl Static {
|
||||||
fn read(self, root: PathBuf) -> String {
|
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;
|
let mut path = root;
|
||||||
path.push(self.0);
|
path.push(self.0);
|
||||||
println!("path: {:?}", path);
|
println!("path: {:?}", path);
|
||||||
|
@ -52,15 +55,13 @@ pub async fn main() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let index = Arc::new(MemoryIndex::new());
|
let index = Arc::new(MemoryIndex::new());
|
||||||
let scanner = Arc::new(FileScanner::new(vec![music_root.clone()]));
|
let scanner = FileScanner::new(vec![music_root.clone()]);
|
||||||
let (core, api) = match Core::new(index.clone(), scanner) {
|
let _core = match Core::new(index.clone(), scanner) {
|
||||||
Flow::Ok((core, api)) => (core, api),
|
Flow::Ok(core) => core,
|
||||||
Flow::Err(error) => panic!("error: {}", error),
|
Flow::Err(error) => panic!("error: {}", error),
|
||||||
Flow::Fatal(error) => panic!("fatal: {}", error),
|
Flow::Fatal(error) => panic!("fatal: {}", error),
|
||||||
};
|
};
|
||||||
|
|
||||||
let _handle = thread::spawn(move || core.start());
|
|
||||||
|
|
||||||
println!("config: {:?} {:?} {:?}", dev, bundle_root, music_root);
|
println!("config: {:?} {:?} {:?}", dev, bundle_root, music_root);
|
||||||
|
|
||||||
let root = warp::path!().and(warp::get()).map({
|
let root = warp::path!().and(warp::get()).map({
|
||||||
|
@ -101,18 +102,6 @@ pub async fn main() {
|
||||||
move || warp::reply::json(&tracks(&index))
|
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)
|
let tracks_for_artist = warp::path!("api" / "v1" / "artist" / String)
|
||||||
.and(warp::get())
|
.and(warp::get())
|
||||||
|
@ -139,7 +128,7 @@ pub async fn main() {
|
||||||
.or(queue)
|
.or(queue)
|
||||||
.or(playing_status);
|
.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);
|
let server = warp::serve(routes);
|
||||||
server
|
server
|
||||||
.run(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8002))
|
.run(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8002))
|
||||||
|
|
|
@ -1,16 +1,9 @@
|
||||||
use crate::{
|
use crate::{database::MusicIndex, media::TrackInfo, scanner::MusicScanner, Error, FatalError};
|
||||||
database::MusicIndex,
|
use flow::{ok, Flow};
|
||||||
media::{TrackId, TrackInfo},
|
|
||||||
playback::{Playback, PlaybackControl, PlaybackStatus},
|
|
||||||
scanner::MusicScanner,
|
|
||||||
Error, FatalError,
|
|
||||||
};
|
|
||||||
use flow::{ok, return_error, Flow};
|
|
||||||
use gstreamer::{format::ClockTime, prelude::*, MessageView};
|
|
||||||
use std::{
|
use std::{
|
||||||
sync::{
|
sync::{
|
||||||
mpsc::{channel, Receiver, RecvTimeoutError, Sender},
|
mpsc::{channel, Receiver, RecvTimeoutError, Sender},
|
||||||
Arc, Mutex,
|
Arc,
|
||||||
},
|
},
|
||||||
thread,
|
thread,
|
||||||
thread::JoinHandle,
|
thread::JoinHandle,
|
||||||
|
@ -21,9 +14,7 @@ fn scan_frequency() -> Duration {
|
||||||
Duration::from_secs(60)
|
Duration::from_secs(60)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum ControlMsg {
|
pub enum ControlMsg {
|
||||||
PlayTrack(TrackId),
|
|
||||||
Exit,
|
Exit,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,119 +32,74 @@ pub enum PlaybackMsg {
|
||||||
|
|
||||||
pub struct Core {
|
pub struct Core {
|
||||||
db: Arc<dyn MusicIndex>,
|
db: Arc<dyn MusicIndex>,
|
||||||
scanner: Arc<dyn MusicScanner>,
|
_track_handle: JoinHandle<()>,
|
||||||
control_rx: Receiver<ControlMsg>,
|
_track_rx: Receiver<TrackMsg>,
|
||||||
|
_playback_handle: JoinHandle<()>,
|
||||||
playback_controller: Playback,
|
_playback_rx: Receiver<PlaybackMsg>,
|
||||||
|
control_tx: Sender<ControlMsg>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Core {
|
impl Core {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
db: Arc<dyn MusicIndex>,
|
db: Arc<dyn MusicIndex>,
|
||||||
scanner: Arc<dyn MusicScanner>,
|
scanner: impl MusicScanner + 'static,
|
||||||
) -> Flow<(Core, CoreAPI), FatalError, Error> {
|
) -> Flow<Core, FatalError, Error> {
|
||||||
let (control_tx, control_rx) = channel::<ControlMsg>();
|
let (control_tx, control_rx) = channel::<ControlMsg>();
|
||||||
let db = db;
|
let db = db;
|
||||||
|
|
||||||
let playback_controller = Playback::new();
|
let (_track_handle, _track_rx) = {
|
||||||
|
let (track_tx, track_rx) = channel();
|
||||||
ok((
|
let db = db.clone();
|
||||||
Core {
|
let track_handle = thread::spawn(move || {
|
||||||
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();
|
let mut next_scan = Instant::now();
|
||||||
loop {
|
loop {
|
||||||
if Instant::now() >= next_scan {
|
if Instant::now() >= next_scan {
|
||||||
let scan_start = Instant::now();
|
let _ = track_tx.send(TrackMsg::UpdateInProgress);
|
||||||
let _ = scanner_tx.send(TrackMsg::UpdateInProgress);
|
for track in scanner.scan() {
|
||||||
for track in self.scanner.scan() {
|
|
||||||
match track {
|
match track {
|
||||||
Ok(track) => self.db.add_track(track),
|
Ok(track) => db.add_track(track),
|
||||||
Err(_) => ok(()),
|
Err(_) => ok(()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
let _ = scanner_tx.send(TrackMsg::UpdateComplete);
|
let _ = track_tx.send(TrackMsg::UpdateComplete);
|
||||||
next_scan = Instant::now() + scan_frequency();
|
next_scan = Instant::now() + scan_frequency();
|
||||||
println!("scan duration: {:?}", Instant::now() - scan_start);
|
|
||||||
}
|
}
|
||||||
match self.control_rx.recv_timeout(Duration::from_millis(1000)) {
|
match control_rx.recv_timeout(Duration::from_millis(1000)) {
|
||||||
Ok(ControlMsg::PlayTrack(id)) => {
|
Ok(ControlMsg::Exit) => return,
|
||||||
let _ = self.play_track(id);
|
|
||||||
}
|
|
||||||
Ok(ControlMsg::Exit) => return ok(()),
|
|
||||||
Err(RecvTimeoutError::Timeout) => (),
|
Err(RecvTimeoutError::Timeout) => (),
|
||||||
Err(RecvTimeoutError::Disconnected) => return ok(()),
|
Err(RecvTimeoutError::Disconnected) => return,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
(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> {
|
pub fn list_tracks<'a>(&'a self) -> Flow<Vec<TrackInfo>, FatalError, Error> {
|
||||||
self.db.list_tracks().map_err(Error::DatabaseError)
|
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());
|
self.track_handle.join();
|
||||||
let pipeline = return_error!(Flow::from(
|
self.playback_handle.join();
|
||||||
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.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 index = MemoryIndex::new();
|
||||||
let scanner = MockScanner::new();
|
let scanner = MockScanner::new();
|
||||||
match Core::new(Arc::new(index), Arc::new(scanner)) {
|
match Core::new(Arc::new(index), scanner) {
|
||||||
Flow::Ok((core, api)) => {
|
Flow::Ok(core) => {
|
||||||
thread::sleep(Duration::from_millis(10));
|
thread::sleep(Duration::from_millis(10));
|
||||||
f(core)
|
f(core)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
pub mod core;
|
pub mod core;
|
||||||
pub mod database;
|
pub mod database;
|
||||||
pub mod media;
|
pub mod media;
|
||||||
pub mod playback;
|
|
||||||
pub mod scanner;
|
pub mod scanner;
|
||||||
use database::DatabaseError;
|
use database::DatabaseError;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
@ -10,15 +9,6 @@ use thiserror::Error;
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Database error: {0}")]
|
#[error("Database error: {0}")]
|
||||||
DatabaseError(DatabaseError),
|
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 {
|
impl From<DatabaseError> for Error {
|
||||||
|
|
|
@ -59,12 +59,6 @@ impl From<url::ParseError> for AudioError {
|
||||||
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize)]
|
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize)]
|
||||||
pub struct TrackId(String);
|
pub struct TrackId(String);
|
||||||
|
|
||||||
impl TrackId {
|
|
||||||
pub fn as_str(&self) -> &str {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for TrackId {
|
impl Default for TrackId {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self(uuid::Uuid::new_v4().as_hyphenated().to_string())
|
Self(uuid::Uuid::new_v4().as_hyphenated().to_string())
|
||||||
|
|
|
@ -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(())
|
|
||||||
}
|
|
||||||
*/
|
|
|
@ -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>;
|
fn scan<'a>(&'a self) -> Box<dyn Iterator<Item = Result<TrackInfo, ScannerError>> + 'a>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,6 +58,11 @@ pub struct FileIterator {
|
||||||
|
|
||||||
impl FileIterator {
|
impl FileIterator {
|
||||||
fn scan_file(&self, path: PathBuf) -> Result<TrackInfo, ScannerError> {
|
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())
|
let mimetype = mime_guess::from_path(path.clone())
|
||||||
.first()
|
.first()
|
||||||
.ok_or(ScannerError::CannotScan)?;
|
.ok_or(ScannerError::CannotScan)?;
|
||||||
|
|
|
@ -34,7 +34,7 @@ fn main() {
|
||||||
thread::spawn(move || loop {
|
thread::spawn(move || loop {
|
||||||
let position: Option<ClockTime> = pipeline.query_position();
|
let position: Option<ClockTime> = pipeline.query_position();
|
||||||
let duration: Option<ClockTime> = pipeline.query_duration();
|
let duration: Option<ClockTime> = pipeline.query_duration();
|
||||||
println!("Position {:?} {:?}", position, duration);
|
println!("{:?} {:?}", position, duration);
|
||||||
thread::sleep(Duration::from_millis(100));
|
thread::sleep(Duration::from_millis(100));
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue