mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-26 11:47:25 +01:00
Merge branch 'medium-tracks' into new-import-screen
This commit is contained in:
commit
3f5f751992
11 changed files with 327 additions and 539 deletions
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{Error, Result};
|
||||
use musicus_database::TrackSet;
|
||||
use musicus_database::Track;
|
||||
use glib::clone;
|
||||
use gstreamer_player::prelude::*;
|
||||
use std::cell::{Cell, RefCell};
|
||||
|
|
@ -10,21 +10,14 @@ use std::sync::Arc;
|
|||
#[cfg(target_os = "linux")]
|
||||
use mpris_player::{Metadata, MprisPlayer, PlaybackStatus};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PlaylistItem {
|
||||
pub track_set: TrackSet,
|
||||
pub indices: Vec<usize>,
|
||||
}
|
||||
|
||||
pub struct Player {
|
||||
music_library_path: PathBuf,
|
||||
player: gstreamer_player::Player,
|
||||
playlist: RefCell<Vec<PlaylistItem>>,
|
||||
current_item: Cell<Option<usize>>,
|
||||
playlist: RefCell<Vec<Track>>,
|
||||
current_track: Cell<Option<usize>>,
|
||||
playing: Cell<bool>,
|
||||
playlist_cbs: RefCell<Vec<Box<dyn Fn(Vec<PlaylistItem>)>>>,
|
||||
track_cbs: RefCell<Vec<Box<dyn Fn(usize, usize)>>>,
|
||||
playlist_cbs: RefCell<Vec<Box<dyn Fn(Vec<Track>)>>>,
|
||||
track_cbs: RefCell<Vec<Box<dyn Fn(usize)>>>,
|
||||
duration_cbs: RefCell<Vec<Box<dyn Fn(u64)>>>,
|
||||
playing_cbs: RefCell<Vec<Box<dyn Fn(bool)>>>,
|
||||
position_cbs: RefCell<Vec<Box<dyn Fn(u64)>>>,
|
||||
|
|
@ -47,7 +40,6 @@ impl Player {
|
|||
music_library_path,
|
||||
player: player.clone(),
|
||||
playlist: RefCell::new(Vec::new()),
|
||||
current_item: Cell::new(None),
|
||||
current_track: Cell::new(None),
|
||||
playing: Cell::new(false),
|
||||
playlist_cbs: RefCell::new(Vec::new()),
|
||||
|
|
@ -144,11 +136,11 @@ impl Player {
|
|||
result
|
||||
}
|
||||
|
||||
pub fn add_playlist_cb<F: Fn(Vec<PlaylistItem>) + 'static>(&self, cb: F) {
|
||||
pub fn add_playlist_cb<F: Fn(Vec<Track>) + 'static>(&self, cb: F) {
|
||||
self.playlist_cbs.borrow_mut().push(Box::new(cb));
|
||||
}
|
||||
|
||||
pub fn add_track_cb<F: Fn(usize, usize) + 'static>(&self, cb: F) {
|
||||
pub fn add_track_cb<F: Fn(usize) + 'static>(&self, cb: F) {
|
||||
self.track_cbs.borrow_mut().push(Box::new(cb));
|
||||
}
|
||||
|
||||
|
|
@ -168,14 +160,10 @@ impl Player {
|
|||
self.raise_cb.replace(Some(Box::new(cb)));
|
||||
}
|
||||
|
||||
pub fn get_playlist(&self) -> Vec<PlaylistItem> {
|
||||
pub fn get_playlist(&self) -> Vec<Track> {
|
||||
self.playlist.borrow().clone()
|
||||
}
|
||||
|
||||
pub fn get_current_item(&self) -> Option<usize> {
|
||||
self.current_item.get()
|
||||
}
|
||||
|
||||
pub fn get_current_track(&self) -> Option<usize> {
|
||||
self.current_track.get()
|
||||
}
|
||||
|
|
@ -188,41 +176,37 @@ impl Player {
|
|||
self.playing.get()
|
||||
}
|
||||
|
||||
pub fn add_item(&self, item: PlaylistItem) -> Result<()> {
|
||||
if item.indices.is_empty() {
|
||||
Err(Error::Other(String::from("Tried to add an empty playlist item!")))
|
||||
} else {
|
||||
let was_empty = {
|
||||
let mut playlist = self.playlist.borrow_mut();
|
||||
let was_empty = playlist.is_empty();
|
||||
pub fn add_item(&self, item: Track) -> Result<()> {
|
||||
let was_empty = {
|
||||
let mut playlist = self.playlist.borrow_mut();
|
||||
let was_empty = playlist.is_empty();
|
||||
|
||||
playlist.push(item);
|
||||
playlist.push(item);
|
||||
|
||||
was_empty
|
||||
};
|
||||
was_empty
|
||||
};
|
||||
|
||||
for cb in &*self.playlist_cbs.borrow() {
|
||||
cb(self.playlist.borrow().clone());
|
||||
}
|
||||
|
||||
if was_empty {
|
||||
self.set_track(0, 0)?;
|
||||
self.player.play();
|
||||
self.playing.set(true);
|
||||
|
||||
for cb in &*self.playing_cbs.borrow() {
|
||||
cb(true);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
self.mpris.set_can_play(true);
|
||||
self.mpris.set_playback_status(PlaybackStatus::Playing);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
for cb in &*self.playlist_cbs.borrow() {
|
||||
cb(self.playlist.borrow().clone());
|
||||
}
|
||||
|
||||
if was_empty {
|
||||
self.set_track(0)?;
|
||||
self.player.play();
|
||||
self.playing.set(true);
|
||||
|
||||
for cb in &*self.playing_cbs.borrow() {
|
||||
cb(true);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
self.mpris.set_can_play(true);
|
||||
self.mpris.set_playback_status(PlaybackStatus::Playing);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn play_pause(&self) {
|
||||
|
|
@ -254,79 +238,56 @@ impl Player {
|
|||
}
|
||||
|
||||
pub fn has_previous(&self) -> bool {
|
||||
if let Some(current_item) = self.current_item.get() {
|
||||
if let Some(current_track) = self.current_track.get() {
|
||||
current_track > 0 || current_item > 0
|
||||
} else {
|
||||
false
|
||||
}
|
||||
if let Some(current_track) = self.current_track.get() {
|
||||
current_track > 0
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn previous(&self) -> Result<()> {
|
||||
let mut current_item = self.current_item.get()
|
||||
.ok_or(Error::Other(String::from("Player tried to access non existant current item.")))?;
|
||||
|
||||
let mut current_track = self
|
||||
.current_track
|
||||
.get()
|
||||
.ok_or(Error::Other(String::from("Player tried to access non existant current track.")))?;
|
||||
|
||||
let playlist = self.playlist.borrow();
|
||||
if current_track > 0 {
|
||||
current_track -= 1;
|
||||
} else if current_item > 0 {
|
||||
current_item -= 1;
|
||||
current_track = playlist[current_item].indices.len() - 1;
|
||||
} else {
|
||||
return Err(Error::Other(String::from("No existing previous track.")));
|
||||
}
|
||||
|
||||
self.set_track(current_item, current_track)
|
||||
self.set_track(current_track)
|
||||
}
|
||||
|
||||
pub fn has_next(&self) -> bool {
|
||||
if let Some(current_item) = self.current_item.get() {
|
||||
if let Some(current_track) = self.current_track.get() {
|
||||
let playlist = self.playlist.borrow();
|
||||
let item = &playlist[current_item];
|
||||
|
||||
current_track + 1 < item.indices.len() || current_item + 1 < playlist.len()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
if let Some(current_track) = self.current_track.get() {
|
||||
let playlist = self.playlist.borrow();
|
||||
current_track + 1 < playlist.len()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&self) -> Result<()> {
|
||||
let mut current_item = self.current_item.get()
|
||||
.ok_or(Error::Other(String::from("Player tried to access non existant current item.")))?;
|
||||
let mut current_track = self
|
||||
.current_track
|
||||
.get()
|
||||
.ok_or(Error::Other(String::from("Player tried to access non existant current track.")))?;
|
||||
|
||||
let playlist = self.playlist.borrow();
|
||||
let item = &playlist[current_item];
|
||||
if current_track + 1 < item.indices.len() {
|
||||
|
||||
if current_track + 1 < playlist.len() {
|
||||
current_track += 1;
|
||||
} else if current_item + 1 < playlist.len() {
|
||||
current_item += 1;
|
||||
current_track = 0;
|
||||
} else {
|
||||
return Err(Error::Other(String::from("No existing previous track.")));
|
||||
return Err(Error::Other(String::from("No existing next track.")));
|
||||
}
|
||||
|
||||
self.set_track(current_item, current_track)
|
||||
self.set_track(current_track)
|
||||
}
|
||||
|
||||
pub fn set_track(&self, current_item: usize, current_track: usize) -> Result<()> {
|
||||
let item = &self.playlist.borrow()[current_item];
|
||||
let track = &item.track_set.tracks[current_track];
|
||||
pub fn set_track(&self, current_track: usize) -> Result<()> {
|
||||
let track = &self.playlist.borrow()[current_track];
|
||||
|
||||
let path = self.music_library_path.join(track.path.clone())
|
||||
.into_os_string().into_string().unwrap();
|
||||
|
|
@ -340,26 +301,25 @@ impl Player {
|
|||
self.player.play();
|
||||
}
|
||||
|
||||
self.current_item.set(Some(current_item));
|
||||
self.current_track.set(Some(current_track));
|
||||
|
||||
for cb in &*self.track_cbs.borrow() {
|
||||
cb(current_item, current_track);
|
||||
cb(current_track);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let mut parts = Vec::<String>::new();
|
||||
for part in &track.work_parts {
|
||||
parts.push(item.track_set.recording.work.parts[*part].title.clone());
|
||||
parts.push(track.recording.work.parts[*part].title.clone());
|
||||
}
|
||||
|
||||
let mut title = item.track_set.recording.work.get_title();
|
||||
let mut title = track.recording.work.get_title();
|
||||
if !parts.is_empty() {
|
||||
title = format!("{}: {}", title, parts.join(", "));
|
||||
}
|
||||
|
||||
let subtitle = item.track_set.recording.get_performers();
|
||||
let subtitle = track.recording.get_performers();
|
||||
|
||||
let mut metadata = Metadata::new();
|
||||
metadata.artist = Some(vec![title]);
|
||||
|
|
@ -379,7 +339,7 @@ impl Player {
|
|||
}
|
||||
|
||||
for cb in &*self.track_cbs.borrow() {
|
||||
cb(self.current_item.get().unwrap(), self.current_track.get().unwrap());
|
||||
cb(self.current_track.get().unwrap());
|
||||
}
|
||||
|
||||
for cb in &*self.duration_cbs.borrow() {
|
||||
|
|
@ -394,7 +354,6 @@ impl Player {
|
|||
pub fn clear(&self) {
|
||||
self.player.stop();
|
||||
self.playing.set(false);
|
||||
self.current_item.set(None);
|
||||
self.current_track.set(None);
|
||||
self.playlist.replace(Vec::new());
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,5 @@ DROP TABLE "ensembles";
|
|||
DROP TABLE "recordings";
|
||||
DROP TABLE "performances";
|
||||
DROP TABLE "mediums";
|
||||
DROP TABLE "track_sets";
|
||||
DROP TABLE "tracks";
|
||||
|
||||
|
|
|
|||
|
|
@ -60,17 +60,11 @@ CREATE TABLE "mediums" (
|
|||
"discid" TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE "track_sets" (
|
||||
CREATE TABLE "tracks" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"medium" TEXT NOT NULL REFERENCES "mediums"("id") ON DELETE CASCADE,
|
||||
"index" INTEGER NOT NULL,
|
||||
"recording" TEXT NOT NULL REFERENCES "recordings"("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "tracks" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"track_set" TEXT NOT NULL REFERENCES "track_sets"("id") ON DELETE CASCADE,
|
||||
"index" INTEGER NOT NULL,
|
||||
"recording" TEXT NOT NULL REFERENCES "recordings"("id"),
|
||||
"work_parts" TEXT NOT NULL,
|
||||
"path" TEXT NOT NULL
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use super::generate_id;
|
||||
use super::schema::{ensembles, mediums, performances, persons, recordings, track_sets, tracks};
|
||||
use super::schema::{ensembles, mediums, performances, persons, recordings, tracks};
|
||||
use super::{Database, Error, Recording, Result};
|
||||
use diesel::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -18,32 +18,17 @@ pub struct Medium {
|
|||
/// If applicable, the MusicBrainz DiscID.
|
||||
pub discid: Option<String>,
|
||||
|
||||
/// The tracks of the medium, grouped by recording.
|
||||
pub tracks: Vec<TrackSet>,
|
||||
}
|
||||
|
||||
impl Medium {
|
||||
/// Get an iterator that iterates through all tracks within this medium in order.
|
||||
pub fn track_iter<'a>(&'a self) -> TrackIter<'a> {
|
||||
TrackIter::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of tracks of one recording within a medium.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TrackSet {
|
||||
/// The recording to which the tracks belong.
|
||||
pub recording: Recording,
|
||||
|
||||
/// The actual tracks.
|
||||
/// The tracks of the medium.
|
||||
pub tracks: Vec<Track>,
|
||||
}
|
||||
|
||||
/// A track within a recording on a medium.
|
||||
/// A track on a medium.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Track {
|
||||
/// The recording on this track.
|
||||
pub recording: Recording,
|
||||
|
||||
/// The work parts that are played on this track. They are indices to the
|
||||
/// work parts of the work that is associated with the recording.
|
||||
pub work_parts: Vec<usize>,
|
||||
|
|
@ -63,23 +48,14 @@ struct MediumRow {
|
|||
pub discid: Option<String>,
|
||||
}
|
||||
|
||||
/// Table data for a [`TrackSet`].
|
||||
#[derive(Insertable, Queryable, Debug, Clone)]
|
||||
#[table_name = "track_sets"]
|
||||
struct TrackSetRow {
|
||||
pub id: String,
|
||||
pub medium: String,
|
||||
pub index: i32,
|
||||
pub recording: String,
|
||||
}
|
||||
|
||||
/// Table data for a [`Track`].
|
||||
#[derive(Insertable, Queryable, Debug, Clone)]
|
||||
#[table_name = "tracks"]
|
||||
struct TrackRow {
|
||||
pub id: String,
|
||||
pub track_set: String,
|
||||
pub medium: String,
|
||||
pub index: i32,
|
||||
pub recording: String,
|
||||
pub work_parts: String,
|
||||
pub path: String,
|
||||
}
|
||||
|
|
@ -135,7 +111,7 @@ impl Database {
|
|||
self.connection.transaction::<(), Error, _>(|| {
|
||||
let medium_id = &medium.id;
|
||||
|
||||
// This will also delete the track sets and tracks.
|
||||
// This will also delete the tracks.
|
||||
self.delete_medium(medium_id)?;
|
||||
|
||||
// Add the new medium.
|
||||
|
|
@ -150,49 +126,34 @@ impl Database {
|
|||
.values(medium_row)
|
||||
.execute(&self.connection)?;
|
||||
|
||||
for (index, track_set) in medium.tracks.iter().enumerate() {
|
||||
// Add associated items from the server, if they don't already
|
||||
// exist.
|
||||
for (index, track) in medium.tracks.iter().enumerate() {
|
||||
// Add associated items from the server, if they don't already exist.
|
||||
|
||||
if self.get_recording(&track_set.recording.id)?.is_none() {
|
||||
self.update_recording(track_set.recording.clone())?;
|
||||
if self.get_recording(&track.recording.id)?.is_none() {
|
||||
self.update_recording(track.recording.clone())?;
|
||||
}
|
||||
|
||||
// Add the actual track set data.
|
||||
// Add the actual track data.
|
||||
|
||||
let track_set_id = generate_id();
|
||||
let work_parts = track
|
||||
.work_parts
|
||||
.iter()
|
||||
.map(|part_index| part_index.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(",");
|
||||
|
||||
let track_set_row = TrackSetRow {
|
||||
id: track_set_id.clone(),
|
||||
let track_row = TrackRow {
|
||||
id: generate_id(),
|
||||
medium: medium_id.to_owned(),
|
||||
index: index as i32,
|
||||
recording: track_set.recording.id.clone(),
|
||||
recording: track.recording.id.clone(),
|
||||
work_parts,
|
||||
path: track.path.clone(),
|
||||
};
|
||||
|
||||
diesel::insert_into(track_sets::table)
|
||||
.values(track_set_row)
|
||||
diesel::insert_into(tracks::table)
|
||||
.values(track_row)
|
||||
.execute(&self.connection)?;
|
||||
|
||||
for (index, track) in track_set.tracks.iter().enumerate() {
|
||||
let work_parts = track
|
||||
.work_parts
|
||||
.iter()
|
||||
.map(|part_index| part_index.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(",");
|
||||
|
||||
let track_row = TrackRow {
|
||||
id: generate_id(),
|
||||
track_set: track_set_id.clone(),
|
||||
index: index as i32,
|
||||
work_parts,
|
||||
path: track.path.clone(),
|
||||
};
|
||||
|
||||
diesel::insert_into(tracks::table)
|
||||
.values(track_row)
|
||||
.execute(&self.connection)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -238,8 +199,8 @@ impl Database {
|
|||
let mut mediums: Vec<Medium> = Vec::new();
|
||||
|
||||
let rows = mediums::table
|
||||
.inner_join(track_sets::table.on(track_sets::medium.eq(mediums::id)))
|
||||
.inner_join(recordings::table.on(recordings::id.eq(track_sets::recording)))
|
||||
.inner_join(tracks::table.on(tracks::medium.eq(mediums::id)))
|
||||
.inner_join(recordings::table.on(recordings::id.eq(tracks::recording)))
|
||||
.inner_join(performances::table.on(performances::recording.eq(recordings::id)))
|
||||
.inner_join(persons::table.on(persons::id.nullable().eq(performances::person)))
|
||||
.filter(persons::id.eq(person_id))
|
||||
|
|
@ -260,8 +221,8 @@ impl Database {
|
|||
let mut mediums: Vec<Medium> = Vec::new();
|
||||
|
||||
let rows = mediums::table
|
||||
.inner_join(track_sets::table.on(track_sets::medium.eq(mediums::id)))
|
||||
.inner_join(recordings::table.on(recordings::id.eq(track_sets::recording)))
|
||||
.inner_join(tracks::table.on(tracks::medium.eq(tracks::id)))
|
||||
.inner_join(recordings::table.on(recordings::id.eq(tracks::recording)))
|
||||
.inner_join(performances::table.on(performances::recording.eq(recordings::id)))
|
||||
.inner_join(ensembles::table.on(ensembles::id.nullable().eq(performances::ensemble)))
|
||||
.filter(ensembles::id.eq(ensemble_id))
|
||||
|
|
@ -284,93 +245,81 @@ impl Database {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Get all available track sets for a recording.
|
||||
pub fn get_track_sets(&self, recording_id: &str) -> Result<Vec<TrackSet>> {
|
||||
let mut track_sets: Vec<TrackSet> = Vec::new();
|
||||
/// Get all available tracks for a recording.
|
||||
pub fn get_tracks(&self, recording_id: &str) -> Result<Vec<Track>> {
|
||||
let mut tracks: Vec<Track> = Vec::new();
|
||||
|
||||
let rows = track_sets::table
|
||||
.inner_join(recordings::table.on(recordings::id.eq(track_sets::recording)))
|
||||
let rows = tracks::table
|
||||
.inner_join(recordings::table.on(recordings::id.eq(tracks::recording)))
|
||||
.filter(recordings::id.eq(recording_id))
|
||||
.select(track_sets::table::all_columns())
|
||||
.load::<TrackSetRow>(&self.connection)?;
|
||||
.select(tracks::table::all_columns())
|
||||
.load::<TrackRow>(&self.connection)?;
|
||||
|
||||
for row in rows {
|
||||
let track_set = self.get_track_set_from_row(row)?;
|
||||
track_sets.push(track_set);
|
||||
let track = self.get_track_from_row(row)?;
|
||||
tracks.push(track);
|
||||
}
|
||||
|
||||
Ok(track_sets)
|
||||
Ok(tracks)
|
||||
}
|
||||
|
||||
/// Retrieve all available information on a medium from related tables.
|
||||
fn get_medium_data(&self, row: MediumRow) -> Result<Medium> {
|
||||
let track_set_rows = track_sets::table
|
||||
.filter(track_sets::medium.eq(&row.id))
|
||||
.order_by(track_sets::index)
|
||||
.load::<TrackSetRow>(&self.connection)?;
|
||||
|
||||
let mut track_sets = Vec::new();
|
||||
|
||||
for track_set_row in track_set_rows {
|
||||
let track_set = self.get_track_set_from_row(track_set_row)?;
|
||||
track_sets.push(track_set);
|
||||
}
|
||||
|
||||
let medium = Medium {
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
discid: row.discid,
|
||||
tracks: track_sets,
|
||||
};
|
||||
|
||||
Ok(medium)
|
||||
}
|
||||
|
||||
/// Convert a track set row from the database to an actual track set.
|
||||
fn get_track_set_from_row(&self, row: TrackSetRow) -> Result<TrackSet> {
|
||||
let recording_id = row.recording;
|
||||
|
||||
let recording = self
|
||||
.get_recording(&recording_id)?
|
||||
.ok_or(Error::Other(format!(
|
||||
"Failed to get recording ({}) for track set ({}).",
|
||||
recording_id,
|
||||
row.id,
|
||||
)))?;
|
||||
|
||||
let track_rows = tracks::table
|
||||
.filter(tracks::track_set.eq(row.id))
|
||||
.filter(tracks::medium.eq(&row.id))
|
||||
.order_by(tracks::index)
|
||||
.load::<TrackRow>(&self.connection)?;
|
||||
|
||||
let mut tracks = Vec::new();
|
||||
|
||||
for track_row in track_rows {
|
||||
let mut part_indices = Vec::new();
|
||||
|
||||
let work_parts = track_row
|
||||
.work_parts
|
||||
.split(',');
|
||||
|
||||
for part_index in work_parts {
|
||||
if !part_index.is_empty() {
|
||||
let index = str::parse(part_index)
|
||||
.or(Err(Error::Other(format!("Failed to parse part index from '{}'.", track_row.work_parts))))?;
|
||||
|
||||
part_indices.push(index);
|
||||
}
|
||||
}
|
||||
|
||||
let track = Track {
|
||||
work_parts: part_indices,
|
||||
path: track_row.path,
|
||||
};
|
||||
|
||||
let track = self.get_track_from_row(track_row)?;
|
||||
tracks.push(track);
|
||||
}
|
||||
|
||||
let track_set = TrackSet { recording, tracks };
|
||||
let medium = Medium {
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
discid: row.discid,
|
||||
tracks,
|
||||
};
|
||||
|
||||
Ok(track_set)
|
||||
Ok(medium)
|
||||
}
|
||||
|
||||
/// Convert a track row from the database to an actual track.
|
||||
fn get_track_from_row(&self, row: TrackRow) -> Result<Track> {
|
||||
let recording_id = row.recording;
|
||||
|
||||
let recording = self
|
||||
.get_recording(&recording_id)?
|
||||
.ok_or(Error::Other(format!(
|
||||
"Failed to get recording ({}) for track ({}).",
|
||||
recording_id,
|
||||
row.id,
|
||||
)))?;
|
||||
|
||||
let mut part_indices = Vec::new();
|
||||
|
||||
let work_parts = row
|
||||
.work_parts
|
||||
.split(',');
|
||||
|
||||
for part_index in work_parts {
|
||||
if !part_index.is_empty() {
|
||||
let index = str::parse(part_index)
|
||||
.or(Err(Error::Other(format!("Failed to parse part index from '{}'.", row.work_parts))))?;
|
||||
|
||||
part_indices.push(index);
|
||||
}
|
||||
}
|
||||
|
||||
let track = Track {
|
||||
recording,
|
||||
work_parts: part_indices,
|
||||
path: row.path,
|
||||
};
|
||||
|
||||
Ok(track)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,19 +55,11 @@ table! {
|
|||
}
|
||||
|
||||
table! {
|
||||
track_sets (id) {
|
||||
tracks (id) {
|
||||
id -> Text,
|
||||
medium -> Text,
|
||||
index -> Integer,
|
||||
recording -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
tracks (id) {
|
||||
id -> Text,
|
||||
track_set -> Text,
|
||||
index -> Integer,
|
||||
work_parts -> Text,
|
||||
path -> Text,
|
||||
}
|
||||
|
|
@ -106,9 +98,8 @@ joinable!(performances -> instruments (role));
|
|||
joinable!(performances -> persons (person));
|
||||
joinable!(performances -> recordings (recording));
|
||||
joinable!(recordings -> works (work));
|
||||
joinable!(track_sets -> mediums (medium));
|
||||
joinable!(track_sets -> recordings (recording));
|
||||
joinable!(tracks -> track_sets (track_set));
|
||||
joinable!(tracks -> mediums (medium));
|
||||
joinable!(tracks -> recordings (recording));
|
||||
joinable!(work_parts -> works (work));
|
||||
joinable!(work_sections -> works (work));
|
||||
joinable!(works -> persons (composer));
|
||||
|
|
@ -121,7 +112,6 @@ allow_tables_to_appear_in_same_query!(
|
|||
performances,
|
||||
persons,
|
||||
recordings,
|
||||
track_sets,
|
||||
tracks,
|
||||
work_parts,
|
||||
work_sections,
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ pub enum Action {
|
|||
GetMediumsForPerson(String, Sender<Result<Vec<Medium>>>),
|
||||
GetMediumsForEnsemble(String, Sender<Result<Vec<Medium>>>),
|
||||
DeleteMedium(String, Sender<Result<()>>),
|
||||
GetTrackSets(String, Sender<Result<Vec<TrackSet>>>),
|
||||
GetTracks(String, Sender<Result<Vec<Track>>>),
|
||||
Stop(Sender<()>),
|
||||
}
|
||||
|
||||
|
|
@ -145,8 +145,8 @@ impl DbThread {
|
|||
DeleteMedium(id, sender) => {
|
||||
sender.send(db.delete_medium(&id)).unwrap();
|
||||
}
|
||||
GetTrackSets(recording_id, sender) => {
|
||||
sender.send(db.get_track_sets(&recording_id)).unwrap();
|
||||
GetTracks(recording_id, sender) => {
|
||||
sender.send(db.get_tracks(&recording_id)).unwrap();
|
||||
}
|
||||
Stop(sender) => {
|
||||
sender.send(()).unwrap();
|
||||
|
|
@ -371,10 +371,10 @@ impl DbThread {
|
|||
receiver.await?
|
||||
}
|
||||
|
||||
/// Get all track sets for a recording.
|
||||
pub async fn get_track_sets(&self, recording_id: &str) -> Result<Vec<TrackSet>> {
|
||||
/// Get all tracks for a recording.
|
||||
pub async fn get_tracks(&self, recording_id: &str) -> Result<Vec<Track>> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
self.action_sender.send(GetTrackSets(recording_id.to_owned(), sender))?;
|
||||
self.action_sender.send(GetTracks(recording_id.to_owned(), sender))?;
|
||||
receiver.await?
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use glib::prelude::*;
|
|||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use libadwaita::prelude::*;
|
||||
use musicus_backend::db::{generate_id, Medium, Track, TrackSet};
|
||||
use musicus_backend::db::{generate_id, Medium};
|
||||
use musicus_backend::import::ImportSession;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
|
@ -24,7 +24,7 @@ pub struct MediumEditor {
|
|||
status_page: libadwaita::StatusPage,
|
||||
disc_status_page: libadwaita::StatusPage,
|
||||
track_set_list: Rc<List>,
|
||||
track_sets: RefCell<Vec<TrackSetData>>,
|
||||
// track_sets: RefCell<Vec<TrackSetData>>,
|
||||
}
|
||||
|
||||
impl Screen<Arc<ImportSession>, Medium> for MediumEditor {
|
||||
|
|
@ -59,7 +59,7 @@ impl Screen<Arc<ImportSession>, Medium> for MediumEditor {
|
|||
status_page,
|
||||
disc_status_page,
|
||||
track_set_list: list,
|
||||
track_sets: RefCell::new(Vec::new()),
|
||||
// track_sets: RefCell::new(Vec::new()),
|
||||
});
|
||||
|
||||
// Connect signals and callbacks
|
||||
|
|
@ -83,43 +83,43 @@ impl Screen<Arc<ImportSession>, Medium> for MediumEditor {
|
|||
|
||||
add_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
spawn!(@clone this, async move {
|
||||
if let Some(track_set) = push!(this.handle, TrackSetEditor, Arc::clone(&this.session)).await {
|
||||
let length = {
|
||||
let mut track_sets = this.track_sets.borrow_mut();
|
||||
track_sets.push(track_set);
|
||||
track_sets.len()
|
||||
};
|
||||
// if let Some(track_set) = push!(this.handle, TrackSetEditor, Arc::clone(&this.session)).await {
|
||||
// let length = {
|
||||
// let mut track_sets = this.track_sets.borrow_mut();
|
||||
// track_sets.push(track_set);
|
||||
// track_sets.len()
|
||||
// };
|
||||
|
||||
this.track_set_list.update(length);
|
||||
}
|
||||
// this.track_set_list.update(length);
|
||||
// }
|
||||
});
|
||||
}));
|
||||
|
||||
this.track_set_list.set_make_widget_cb(clone!(@weak this => move |index| {
|
||||
let track_set = &this.track_sets.borrow()[index];
|
||||
// this.track_set_list.set_make_widget_cb(clone!(@weak this => move |index| {
|
||||
// let track_set = &this.track_sets.borrow()[index];
|
||||
|
||||
let title = track_set.recording.work.get_title();
|
||||
let subtitle = track_set.recording.get_performers();
|
||||
// let title = track_set.recording.work.get_title();
|
||||
// let subtitle = track_set.recording.get_performers();
|
||||
|
||||
let edit_image = gtk::Image::from_icon_name(Some("document-edit-symbolic"));
|
||||
let edit_button = gtk::Button::new();
|
||||
edit_button.set_has_frame(false);
|
||||
edit_button.set_valign(gtk::Align::Center);
|
||||
edit_button.set_child(Some(&edit_image));
|
||||
// let edit_image = gtk::Image::from_icon_name(Some("document-edit-symbolic"));
|
||||
// let edit_button = gtk::Button::new();
|
||||
// edit_button.set_has_frame(false);
|
||||
// edit_button.set_valign(gtk::Align::Center);
|
||||
// edit_button.set_child(Some(&edit_image));
|
||||
|
||||
let row = libadwaita::ActionRow::new();
|
||||
row.set_activatable(true);
|
||||
row.set_title(Some(&title));
|
||||
row.set_subtitle(Some(&subtitle));
|
||||
row.add_suffix(&edit_button);
|
||||
row.set_activatable_widget(Some(&edit_button));
|
||||
// let row = libadwaita::ActionRow::new();
|
||||
// row.set_activatable(true);
|
||||
// row.set_title(Some(&title));
|
||||
// row.set_subtitle(Some(&subtitle));
|
||||
// row.add_suffix(&edit_button);
|
||||
// row.set_activatable_widget(Some(&edit_button));
|
||||
|
||||
edit_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
// edit_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
// TODO: Implement editing.
|
||||
}));
|
||||
// }));
|
||||
|
||||
row.upcast()
|
||||
}));
|
||||
// row.upcast()
|
||||
// }));
|
||||
|
||||
try_again_button.connect_clicked(clone!(@weak this => move |_| {
|
||||
this.widget.set_visible_child_name("content");
|
||||
|
|
@ -136,68 +136,68 @@ impl Screen<Arc<ImportSession>, Medium> for MediumEditor {
|
|||
impl MediumEditor {
|
||||
/// Save the medium and possibly upload it to the server.
|
||||
async fn save(&self) -> Result<Medium> {
|
||||
let name = self.name_entry.get_text().to_string();
|
||||
// let name = self.name_entry.get_text().to_string();
|
||||
|
||||
// Create a new directory in the music library path for the imported medium.
|
||||
|
||||
let mut path = self.handle.backend.get_music_library_path().unwrap().clone();
|
||||
path.push(&name);
|
||||
std::fs::create_dir(&path)?;
|
||||
// let mut path = self.handle.backend.get_music_library_path().unwrap().clone();
|
||||
// path.push(&name);
|
||||
// std::fs::create_dir(&path)?;
|
||||
|
||||
// Convert the track set data to real track sets.
|
||||
|
||||
let mut track_sets = Vec::new();
|
||||
let import_tracks = self.session.tracks();
|
||||
// let mut track_sets = Vec::new();
|
||||
// let import_tracks = self.session.tracks();
|
||||
|
||||
for track_set_data in &*self.track_sets.borrow() {
|
||||
let mut tracks = Vec::new();
|
||||
// for track_set_data in &*self.track_sets.borrow() {
|
||||
// let mut tracks = Vec::new();
|
||||
|
||||
for track_data in &track_set_data.tracks {
|
||||
// for track_data in &track_set_data.tracks {
|
||||
// Copy the corresponding audio file to the music library.
|
||||
|
||||
let import_track = &import_tracks[track_data.track_source];
|
||||
// let import_track = &import_tracks[track_data.track_source];
|
||||
|
||||
let mut track_path = path.clone();
|
||||
track_path.push(import_track.path.file_name().unwrap());
|
||||
// let mut track_path = path.clone();
|
||||
// track_path.push(import_track.path.file_name().unwrap());
|
||||
|
||||
std::fs::copy(&import_track.path, &track_path)?;
|
||||
// std::fs::copy(&import_track.path, &track_path)?;
|
||||
|
||||
// Create the real track.
|
||||
|
||||
let track = Track {
|
||||
work_parts: track_data.work_parts.clone(),
|
||||
path: track_path.to_str().unwrap().to_owned(),
|
||||
};
|
||||
// let track = Track {
|
||||
// work_parts: track_data.work_parts.clone(),
|
||||
// path: track_path.to_str().unwrap().to_owned(),
|
||||
// };
|
||||
|
||||
tracks.push(track);
|
||||
}
|
||||
// tracks.push(track);
|
||||
// }
|
||||
|
||||
let track_set = TrackSet {
|
||||
recording: track_set_data.recording.clone(),
|
||||
tracks,
|
||||
};
|
||||
// let track_set = TrackSet {
|
||||
// recording: track_set_data.recording.clone(),
|
||||
// tracks,
|
||||
// };
|
||||
|
||||
track_sets.push(track_set);
|
||||
}
|
||||
// track_sets.push(track_set);
|
||||
// }
|
||||
|
||||
let medium = Medium {
|
||||
id: generate_id(),
|
||||
name: self.name_entry.get_text().to_string(),
|
||||
discid: Some(self.session.source_id().to_owned()),
|
||||
tracks: track_sets,
|
||||
};
|
||||
// let medium = Medium {
|
||||
// id: generate_id(),
|
||||
// name: self.name_entry.get_text().to_string(),
|
||||
// discid: Some(self.session.source_id().to_owned()),
|
||||
// tracks: track_sets,
|
||||
// };
|
||||
|
||||
let upload = self.publish_switch.get_active();
|
||||
if upload {
|
||||
self.handle.backend.cl().post_medium(&medium).await?;
|
||||
}
|
||||
// let upload = self.publish_switch.get_active();
|
||||
// if upload {
|
||||
// self.handle.backend.cl().post_medium(&medium).await?;
|
||||
// }
|
||||
|
||||
self.handle.backend
|
||||
.db()
|
||||
.update_medium(medium.clone())
|
||||
.await?;
|
||||
// self.handle.backend
|
||||
// .db()
|
||||
// .update_medium(medium.clone())
|
||||
// .await?;
|
||||
|
||||
self.handle.backend.library_changed();
|
||||
// self.handle.backend.library_changed();
|
||||
|
||||
Ok(medium)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,53 +5,21 @@ use gettextrs::gettext;
|
|||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use libadwaita::prelude::*;
|
||||
use musicus_backend::PlaylistItem;
|
||||
use musicus_backend::db::Medium;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// Elements for visually representing the contents of the medium.
|
||||
enum ListItem {
|
||||
/// A header shown on top of a track set. The value is the index of the corresponding track set
|
||||
/// within the medium.
|
||||
Header(usize),
|
||||
|
||||
/// A track. The indices are from the track set and the track.
|
||||
Track(usize, usize),
|
||||
|
||||
/// A separator shown between track sets.
|
||||
Separator,
|
||||
}
|
||||
|
||||
/// A screen for showing the contents of a medium.
|
||||
pub struct MediumScreen {
|
||||
handle: NavigationHandle<()>,
|
||||
medium: Medium,
|
||||
widget: widgets::Screen,
|
||||
list: Rc<List>,
|
||||
items: Vec<ListItem>,
|
||||
}
|
||||
|
||||
impl Screen<Medium, ()> for MediumScreen {
|
||||
/// Create a new medium screen for the specified medium and load the
|
||||
/// contents asynchronously.
|
||||
fn new(medium: Medium, handle: NavigationHandle<()>) -> Rc<Self> {
|
||||
let mut items = Vec::new();
|
||||
let mut first = true;
|
||||
|
||||
for (track_set_index, track_set) in medium.tracks.iter().enumerate() {
|
||||
if !first {
|
||||
items.push(ListItem::Separator);
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
|
||||
items.push(ListItem::Header(track_set_index));
|
||||
|
||||
for (track_index, _) in track_set.tracks.iter().enumerate() {
|
||||
items.push(ListItem::Track(track_set_index, track_index));
|
||||
}
|
||||
}
|
||||
|
||||
let widget = widgets::Screen::new();
|
||||
widget.set_title(&medium.name);
|
||||
|
||||
|
|
@ -65,7 +33,6 @@ impl Screen<Medium, ()> for MediumScreen {
|
|||
medium,
|
||||
widget,
|
||||
list,
|
||||
items,
|
||||
});
|
||||
|
||||
this.widget.set_back_cb(clone!(@weak this => move || {
|
||||
|
|
@ -82,63 +49,35 @@ impl Screen<Medium, ()> for MediumScreen {
|
|||
}));
|
||||
|
||||
section.add_action("media-playback-start-symbolic", clone!(@weak this => move || {
|
||||
for track_set in &this.medium.tracks {
|
||||
let indices = (0..track_set.tracks.len()).collect();
|
||||
|
||||
let playlist_item = PlaylistItem {
|
||||
track_set: track_set.clone(),
|
||||
indices,
|
||||
};
|
||||
|
||||
this.handle.backend.pl().add_item(playlist_item).unwrap();
|
||||
for track in &this.medium.tracks {
|
||||
this.handle.backend.pl().add_item(track.clone()).unwrap();
|
||||
}
|
||||
}));
|
||||
|
||||
this.list.set_make_widget_cb(clone!(@weak this => move |index| {
|
||||
match this.items[index] {
|
||||
ListItem::Header(index) => {
|
||||
let track_set = &this.medium.tracks[index];
|
||||
let recording = &track_set.recording;
|
||||
let track = &this.medium.tracks[index];
|
||||
|
||||
let row = libadwaita::ActionRow::new();
|
||||
row.set_activatable(false);
|
||||
row.set_selectable(false);
|
||||
row.set_title(Some(&recording.work.get_title()));
|
||||
row.set_subtitle(Some(&recording.get_performers()));
|
||||
|
||||
row.upcast()
|
||||
}
|
||||
ListItem::Track(track_set_index, track_index) => {
|
||||
let track_set = &this.medium.tracks[track_set_index];
|
||||
let track = &track_set.tracks[track_index];
|
||||
|
||||
let mut parts = Vec::<String>::new();
|
||||
for part in &track.work_parts {
|
||||
parts.push(track_set.recording.work.parts[*part].title.clone());
|
||||
}
|
||||
|
||||
let title = if parts.is_empty() {
|
||||
gettext("Unknown")
|
||||
} else {
|
||||
parts.join(", ")
|
||||
};
|
||||
|
||||
let row = libadwaita::ActionRow::new();
|
||||
row.set_selectable(false);
|
||||
row.set_activatable(false);
|
||||
row.set_title(Some(&title));
|
||||
row.set_margin_start(12);
|
||||
|
||||
row.upcast()
|
||||
}
|
||||
ListItem::Separator => {
|
||||
let separator = gtk::Separator::new(gtk::Orientation::Horizontal);
|
||||
separator.upcast()
|
||||
}
|
||||
let mut parts = Vec::<String>::new();
|
||||
for part in &track.work_parts {
|
||||
parts.push(track.recording.work.parts[*part].title.clone());
|
||||
}
|
||||
|
||||
let title = if parts.is_empty() {
|
||||
gettext("Unknown")
|
||||
} else {
|
||||
parts.join(", ")
|
||||
};
|
||||
|
||||
let row = libadwaita::ActionRow::new();
|
||||
row.set_selectable(false);
|
||||
row.set_activatable(false);
|
||||
row.set_title(Some(&title));
|
||||
row.set_margin_start(12);
|
||||
|
||||
row.upcast()
|
||||
}));
|
||||
|
||||
this.list.update(this.items.len());
|
||||
this.list.update(this.medium.tracks.len());
|
||||
|
||||
this
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,21 +5,25 @@ use glib::clone;
|
|||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use libadwaita::prelude::*;
|
||||
use musicus_backend::PlaylistItem;
|
||||
use musicus_backend::db::Track;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::Rc;
|
||||
|
||||
/// Elements for visually representing the playlist.
|
||||
enum ListItem {
|
||||
/// A header shown on top of a track set. This contains an index
|
||||
/// referencing the playlist item containing this track set.
|
||||
Header(usize),
|
||||
/// A playable track.
|
||||
Track {
|
||||
/// Index within the playlist.
|
||||
index: usize,
|
||||
|
||||
/// A playable track. This contains an index to the playlist item, an
|
||||
/// index to the track and whether it is the currently played one.
|
||||
Track(usize, usize, bool),
|
||||
/// Whether this is the first track of the recording.
|
||||
first: bool,
|
||||
|
||||
/// A separator shown between track sets.
|
||||
/// Whether this is the currently played track.
|
||||
playing: bool,
|
||||
},
|
||||
|
||||
/// A separator shown between recordings.
|
||||
Separator,
|
||||
}
|
||||
|
||||
|
|
@ -37,10 +41,9 @@ pub struct PlayerScreen {
|
|||
play_image: gtk::Image,
|
||||
pause_image: gtk::Image,
|
||||
list: Rc<List>,
|
||||
playlist: RefCell<Vec<PlaylistItem>>,
|
||||
playlist: RefCell<Vec<Track>>,
|
||||
items: RefCell<Vec<ListItem>>,
|
||||
seeking: Cell<bool>,
|
||||
current_item: Cell<usize>,
|
||||
current_track: Cell<usize>,
|
||||
}
|
||||
|
||||
|
|
@ -87,7 +90,6 @@ impl Screen<(), ()> for PlayerScreen {
|
|||
items: RefCell::new(Vec::new()),
|
||||
playlist: RefCell::new(Vec::new()),
|
||||
seeking: Cell::new(false),
|
||||
current_item: Cell::new(0),
|
||||
current_track: Cell::new(0),
|
||||
});
|
||||
|
||||
|
|
@ -102,28 +104,26 @@ impl Screen<(), ()> for PlayerScreen {
|
|||
this.show_playlist();
|
||||
}));
|
||||
|
||||
player.add_track_cb(clone!(@weak this, @weak player => @default-return (), move |current_item, current_track| {
|
||||
player.add_track_cb(clone!(@weak this, @weak player => @default-return (), move |current_track| {
|
||||
this.previous_button.set_sensitive(this.handle.backend.pl().has_previous());
|
||||
this.next_button.set_sensitive(this.handle.backend.pl().has_next());
|
||||
|
||||
let item = &this.playlist.borrow()[current_item];
|
||||
let track = &item.track_set.tracks[current_track];
|
||||
let track = &this.playlist.borrow()[current_track];
|
||||
|
||||
let mut parts = Vec::<String>::new();
|
||||
for part in &track.work_parts {
|
||||
parts.push(item.track_set.recording.work.parts[*part].title.clone());
|
||||
parts.push(track.recording.work.parts[*part].title.clone());
|
||||
}
|
||||
|
||||
let mut title = item.track_set.recording.work.get_title();
|
||||
let mut title = track.recording.work.get_title();
|
||||
if !parts.is_empty() {
|
||||
title = format!("{}: {}", title, parts.join(", "));
|
||||
}
|
||||
|
||||
this.title_label.set_text(&title);
|
||||
this.subtitle_label.set_text(&item.track_set.recording.get_performers());
|
||||
this.subtitle_label.set_text(&track.recording.get_performers());
|
||||
this.position_label.set_text("0:00");
|
||||
|
||||
this.current_item.set(current_item);
|
||||
this.current_track.set(current_track);
|
||||
|
||||
this.show_playlist();
|
||||
|
|
@ -205,29 +205,17 @@ impl Screen<(), ()> for PlayerScreen {
|
|||
|
||||
this.list.set_make_widget_cb(clone!(@weak this => move |index| {
|
||||
let widget = match this.items.borrow()[index] {
|
||||
ListItem::Header(item_index) => {
|
||||
let playlist_item = &this.playlist.borrow()[item_index];
|
||||
let recording = &playlist_item.track_set.recording;
|
||||
|
||||
let row = libadwaita::ActionRow::new();
|
||||
row.set_activatable(false);
|
||||
row.set_selectable(false);
|
||||
row.set_title(Some(&recording.work.get_title()));
|
||||
row.set_subtitle(Some(&recording.get_performers()));
|
||||
|
||||
row.upcast()
|
||||
}
|
||||
ListItem::Track(item_index, track_index, playing) => {
|
||||
let playlist_item = &this.playlist.borrow()[item_index];
|
||||
let index = playlist_item.indices[track_index];
|
||||
let track = &playlist_item.track_set.tracks[index];
|
||||
ListItem::Track {index, first, playing} => {
|
||||
let track = &this.playlist.borrow()[index];
|
||||
|
||||
let mut parts = Vec::<String>::new();
|
||||
for part in &track.work_parts {
|
||||
parts.push(playlist_item.track_set.recording.work.parts[*part].title.clone());
|
||||
parts.push(track.recording.work.parts[*part].title.clone());
|
||||
}
|
||||
|
||||
let title = if parts.is_empty() {
|
||||
let title = if first {
|
||||
track.recording.work.get_title()
|
||||
} else if parts.is_empty() {
|
||||
gettext("Unknown")
|
||||
} else {
|
||||
parts.join(", ")
|
||||
|
|
@ -238,8 +226,18 @@ impl Screen<(), ()> for PlayerScreen {
|
|||
row.set_activatable(true);
|
||||
row.set_title(Some(&title));
|
||||
|
||||
if first {
|
||||
let subtitle = if !parts.is_empty() {
|
||||
format!("{}\n{}", track.recording.get_performers(), parts.join(", "))
|
||||
} else {
|
||||
track.recording.get_performers()
|
||||
};
|
||||
|
||||
row.set_subtitle(Some(&subtitle));
|
||||
}
|
||||
|
||||
row.connect_activated(clone!(@weak this => move |_| {
|
||||
this.handle.backend.pl().set_track(item_index, track_index).unwrap();
|
||||
this.handle.backend.pl().set_track(index).unwrap();
|
||||
}));
|
||||
|
||||
let icon = if playing {
|
||||
|
|
@ -272,25 +270,35 @@ impl PlayerScreen {
|
|||
/// Update the user interface according to the playlist.
|
||||
fn show_playlist(&self) {
|
||||
let playlist = self.playlist.borrow();
|
||||
let current_item = self.current_item.get();
|
||||
let current_track = self.current_track.get();
|
||||
|
||||
let mut first = true;
|
||||
let mut items = Vec::new();
|
||||
|
||||
for (item_index, playlist_item) in playlist.iter().enumerate() {
|
||||
if !first {
|
||||
items.push(ListItem::Separator);
|
||||
let mut last_recording_id = "";
|
||||
|
||||
for (index, track) in playlist.iter().enumerate() {
|
||||
let first_track = if track.recording.id != last_recording_id {
|
||||
last_recording_id = &track.recording.id;
|
||||
|
||||
if !first {
|
||||
items.push(ListItem::Separator);
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
|
||||
true
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
false
|
||||
};
|
||||
|
||||
items.push(ListItem::Header(item_index));
|
||||
let item = ListItem::Track {
|
||||
index,
|
||||
first: first_track,
|
||||
playing: index == current_track,
|
||||
};
|
||||
|
||||
for (index, _) in playlist_item.indices.iter().enumerate() {
|
||||
let playing = current_item == item_index && current_track == index;
|
||||
items.push(ListItem::Track(item_index, index, playing));
|
||||
}
|
||||
items.push(item);
|
||||
}
|
||||
|
||||
let length = items.len();
|
||||
|
|
|
|||
|
|
@ -6,29 +6,17 @@ use gettextrs::gettext;
|
|||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use libadwaita::prelude::*;
|
||||
use musicus_backend::PlaylistItem;
|
||||
use musicus_backend::db::{Recording, TrackSet};
|
||||
use musicus_backend::db::{Recording, Track};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// Representation of one entry within the track list.
|
||||
enum ListItem {
|
||||
/// A track row. This hold an index to the track set and an index to the
|
||||
/// track within the track set.
|
||||
Track(usize, usize),
|
||||
|
||||
/// A separator intended for use between track sets.
|
||||
Separator,
|
||||
}
|
||||
|
||||
/// A screen for showing a recording.
|
||||
pub struct RecordingScreen {
|
||||
handle: NavigationHandle<()>,
|
||||
recording: Recording,
|
||||
widget: widgets::Screen,
|
||||
list: Rc<List>,
|
||||
track_sets: RefCell<Vec<TrackSet>>,
|
||||
items: RefCell<Vec<ListItem>>,
|
||||
tracks: RefCell<Vec<Track>>,
|
||||
}
|
||||
|
||||
impl Screen<Recording, ()> for RecordingScreen {
|
||||
|
|
@ -48,22 +36,12 @@ impl Screen<Recording, ()> for RecordingScreen {
|
|||
recording,
|
||||
widget,
|
||||
list,
|
||||
track_sets: RefCell::new(Vec::new()),
|
||||
items: RefCell::new(Vec::new()),
|
||||
tracks: RefCell::new(Vec::new()),
|
||||
});
|
||||
|
||||
section.add_action("media-playback-start-symbolic", clone!(@weak this => move || {
|
||||
if let Some(player) = this.handle.backend.get_player() {
|
||||
if let Some(track_set) = this.track_sets.borrow().get(0).cloned() {
|
||||
let indices = (0..track_set.tracks.len()).collect();
|
||||
|
||||
let playlist_item = PlaylistItem {
|
||||
track_set,
|
||||
indices,
|
||||
};
|
||||
|
||||
player.add_item(playlist_item).unwrap();
|
||||
}
|
||||
for track in &*this.tracks.borrow() {
|
||||
this.handle.backend.pl().add_item(track.clone()).unwrap();
|
||||
}
|
||||
}));
|
||||
|
||||
|
|
@ -86,47 +64,36 @@ impl Screen<Recording, ()> for RecordingScreen {
|
|||
}));
|
||||
|
||||
this.list.set_make_widget_cb(clone!(@weak this => move |index| {
|
||||
let widget = match this.items.borrow()[index] {
|
||||
ListItem::Track(track_set_index, track_index) => {
|
||||
let track_set = &this.track_sets.borrow()[track_set_index];
|
||||
let track = &track_set.tracks[track_index];
|
||||
let track = &this.tracks.borrow()[index];
|
||||
|
||||
let mut title_parts = Vec::<String>::new();
|
||||
for part in &track.work_parts {
|
||||
title_parts.push(this.recording.work.parts[*part].title.clone());
|
||||
}
|
||||
let mut title_parts = Vec::<String>::new();
|
||||
for part in &track.work_parts {
|
||||
title_parts.push(this.recording.work.parts[*part].title.clone());
|
||||
}
|
||||
|
||||
let title = if title_parts.is_empty() {
|
||||
gettext("Unknown")
|
||||
} else {
|
||||
title_parts.join(", ")
|
||||
};
|
||||
|
||||
let row = libadwaita::ActionRow::new();
|
||||
row.set_title(Some(&title));
|
||||
|
||||
row.upcast()
|
||||
}
|
||||
ListItem::Separator => {
|
||||
let separator = gtk::Separator::new(gtk::Orientation::Horizontal);
|
||||
separator.upcast()
|
||||
}
|
||||
let title = if title_parts.is_empty() {
|
||||
gettext("Unknown")
|
||||
} else {
|
||||
title_parts.join(", ")
|
||||
};
|
||||
|
||||
widget
|
||||
let row = libadwaita::ActionRow::new();
|
||||
row.set_title(Some(&title));
|
||||
|
||||
row.upcast()
|
||||
}));
|
||||
|
||||
// Load the content asynchronously.
|
||||
|
||||
spawn!(@clone this, async move {
|
||||
let track_sets = this.handle
|
||||
let tracks = this.handle
|
||||
.backend
|
||||
.db()
|
||||
.get_track_sets(&this.recording.id)
|
||||
.get_tracks(&this.recording.id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
this.show_track_sets(track_sets);
|
||||
this.show_tracks(tracks);
|
||||
this.widget.ready();
|
||||
});
|
||||
|
||||
|
|
@ -135,26 +102,10 @@ impl Screen<Recording, ()> for RecordingScreen {
|
|||
}
|
||||
|
||||
impl RecordingScreen {
|
||||
/// Update the track sets variable as well as the user interface.
|
||||
fn show_track_sets(&self, track_sets: Vec<TrackSet>) {
|
||||
let mut first = true;
|
||||
let mut items = Vec::new();
|
||||
|
||||
for (track_set_index, track_set) in track_sets.iter().enumerate() {
|
||||
if !first {
|
||||
items.push(ListItem::Separator);
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
|
||||
for (track_index, _) in track_set.tracks.iter().enumerate() {
|
||||
items.push(ListItem::Track(track_set_index, track_index));
|
||||
}
|
||||
}
|
||||
|
||||
let length = items.len();
|
||||
self.items.replace(items);
|
||||
self.track_sets.replace(track_sets);
|
||||
/// Update the tracks variable as well as the user interface.
|
||||
fn show_tracks(&self, tracks: Vec<Track>) {
|
||||
let length = tracks.len();
|
||||
self.tracks.replace(tracks);
|
||||
self.list.update(length);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use musicus_backend::{Player, PlaylistItem};
|
||||
use musicus_backend::Player;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
|
|
@ -83,7 +83,7 @@ impl PlayerBar {
|
|||
self.player.replace(player.clone());
|
||||
|
||||
if let Some(player) = player {
|
||||
let playlist = Rc::new(RefCell::new(Vec::<PlaylistItem>::new()));
|
||||
let playlist = Rc::new(RefCell::new(Vec::new()));
|
||||
|
||||
player.add_playlist_cb(clone!(
|
||||
@strong player,
|
||||
|
|
@ -107,25 +107,24 @@ impl PlayerBar {
|
|||
@strong self.title_label as title_label,
|
||||
@strong self.subtitle_label as subtitle_label,
|
||||
@strong self.position_label as position_label
|
||||
=> move |current_item, current_track| {
|
||||
=> move |current_track| {
|
||||
previous_button.set_sensitive(player.has_previous());
|
||||
next_button.set_sensitive(player.has_next());
|
||||
|
||||
let item = &playlist.borrow()[current_item];
|
||||
let track = &item.track_set.tracks[current_track];
|
||||
let track = &playlist.borrow()[current_track];
|
||||
|
||||
let mut parts = Vec::<String>::new();
|
||||
for part in &track.work_parts {
|
||||
parts.push(item.track_set.recording.work.parts[*part].title.clone());
|
||||
parts.push(track.recording.work.parts[*part].title.clone());
|
||||
}
|
||||
|
||||
let mut title = item.track_set.recording.work.get_title();
|
||||
let mut title = track.recording.work.get_title();
|
||||
if !parts.is_empty() {
|
||||
title = format!("{}: {}", title, parts.join(", "));
|
||||
}
|
||||
|
||||
title_label.set_text(&title);
|
||||
subtitle_label.set_text(&item.track_set.recording.get_performers());
|
||||
subtitle_label.set_text(&track.recording.get_performers());
|
||||
position_label.set_text("0:00");
|
||||
}
|
||||
));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue