feat: improve remote connect

This commit is contained in:
Tsiry Sandratraina
2023-01-05 01:09:48 +03:00
parent c00cb0bfdb
commit afebede8a3
25 changed files with 604 additions and 183 deletions

1
Cargo.lock generated
View File

@@ -3987,6 +3987,7 @@ dependencies = [
"music-player-settings",
"music-player-storage",
"music-player-tracklist",
"music-player-types",
"tokio",
"tokio-tungstenite",
"tonic",

View File

@@ -1,7 +1,7 @@
use anyhow::Error;
use async_trait::async_trait;
use music_player_types::types::{Album, Artist, Device, Track};
use music_player_types::types::{Album, Artist, Device, Playlist, Track};
use super::{Addon, Browseable, Player, StreamingAddon};
@@ -78,6 +78,10 @@ impl Browseable for Kodi {
todo!()
}
async fn playlists(&mut self, offset: i32, limit: i32) -> Result<Vec<Playlist>, Error> {
todo!()
}
async fn album(&mut self, id: &str) -> Result<Album, Error> {
todo!()
}
@@ -89,6 +93,10 @@ impl Browseable for Kodi {
async fn track(&mut self, id: &str) -> Result<Track, Error> {
todo!()
}
async fn playlist(&mut self, id: &str) -> Result<Playlist, Error> {
todo!()
}
}
#[async_trait]

View File

@@ -8,7 +8,7 @@ pub mod tononkira;
use anyhow::Error;
use async_trait::async_trait;
use music_player_types::types::{Album, Artist, Track};
use music_player_types::types::{Album, Artist, Playlist, Track};
pub trait Addon {
fn name(&self) -> &str;
@@ -32,9 +32,11 @@ pub trait Browseable {
async fn albums(&mut self, offset: i32, limit: i32) -> Result<Vec<Album>, Error>;
async fn artists(&mut self, offset: i32, limit: i32) -> Result<Vec<Artist>, Error>;
async fn tracks(&mut self, offset: i32, limit: i32) -> Result<Vec<Track>, Error>;
async fn playlists(&mut self, offset: i32, limit: i32) -> Result<Vec<Playlist>, Error>;
async fn album(&mut self, id: &str) -> Result<Album, Error>;
async fn artist(&mut self, id: &str) -> Result<Artist, Error>;
async fn track(&mut self, id: &str) -> Result<Track, Error>;
async fn playlist(&mut self, id: &str) -> Result<Playlist, Error>;
}
#[async_trait]

View File

@@ -5,7 +5,7 @@ use music_player_client::{
library::LibraryClient, playback::PlaybackClient, playlist::PlaylistClient,
tracklist::TracklistClient,
};
use music_player_types::types::{Album, Artist, Device, Track};
use music_player_types::types::{Album, Artist, Device, Playlist, Track};
pub struct Client {
pub library: LibraryClient,
@@ -107,6 +107,11 @@ impl Browseable for Local {
Ok(response.into_iter().map(Into::into).collect())
}
async fn playlists(&mut self, offset: i32, limit: i32) -> Result<Vec<Playlist>, Error> {
let response = self.client.as_mut().unwrap().playlist.list_all().await?;
Ok(response)
}
async fn album(&mut self, id: &str) -> Result<Album, Error> {
let response = self.client.as_mut().unwrap().library.album(id).await?;
match response {
@@ -130,6 +135,11 @@ impl Browseable for Local {
None => Err(Error::msg("Track not found")),
}
}
async fn playlist(&mut self, id: &str) -> Result<Playlist, Error> {
let response = self.client.as_mut().unwrap().playlist.find(id).await?;
Ok(response)
}
}
#[async_trait]

View File

@@ -19,6 +19,7 @@ rust_library(
"//playback:music_player_playback",
"//storage:music_player_storage",
"//tracklist:music_player_tracklist",
"//types:music_player_types",
"@crate_index//:tonic",
"@crate_index//:futures-util",
"@crate_index//:url",

View File

@@ -22,6 +22,10 @@ version = "0.1.9"
path = "../settings"
version = "0.1.1"
[dependencies.music-player-types]
path = "../types"
version = "0.1.1"
[dev-dependencies.music-player-playback]
path = "../playback"
version = "0.1.7"

View File

@@ -1,6 +1,9 @@
use anyhow::Error;
use music_player_server::api::music::v1alpha1::playlist_service_client::PlaylistServiceClient;
use anyhow::{Error, Ok};
use music_player_server::api::music::v1alpha1::{
playlist_service_client::PlaylistServiceClient, FindAllRequest, GetPlaylistDetailsRequest,
};
use music_player_settings::{read_settings, Settings};
use music_player_types::types::Playlist;
use tonic::transport::Channel;
pub struct PlaylistClient {
client: PlaylistServiceClient<Channel>,
@@ -13,21 +16,48 @@ impl PlaylistClient {
Ok(Self { client })
}
pub async fn add(&self, id: &str) {}
pub async fn find(&mut self, id: &str) -> Result<Playlist, Error> {
let request = tonic::Request::new(GetPlaylistDetailsRequest { id: id.to_string() });
let response = self.client.get_playlist_details(request).await?;
Ok(response.into_inner().into())
}
pub async fn list_songs(&self) {}
pub async fn add(&mut self, id: &str) {
todo!()
}
pub async fn clear(&self, id: &str) {}
pub async fn list_songs(&mut self) {
todo!()
}
pub async fn list_all(&self) {}
pub async fn clear(&mut self, id: &str) {
todo!()
}
pub async fn play(&self, id: &str) {}
pub async fn list_all(&mut self) -> Result<Vec<Playlist>, Error> {
let request = tonic::Request::new(FindAllRequest {});
let response = self.client.find_all(request).await?;
let playlists = response.into_inner().playlists;
Ok(playlists.into_iter().map(Into::into).collect())
}
pub async fn remove(&self, id: &str) {}
pub async fn play(&mut self, id: &str) {
todo!()
}
pub async fn shuffle(&self) {}
pub async fn remove(&mut self, id: &str) {
todo!()
}
pub async fn create(&self, name: &str) {}
pub async fn shuffle(&mut self) {
todo!()
}
pub async fn delete_playlist(&self, id: &str) {}
pub async fn create(&mut self, name: &str) {
todo!()
}
pub async fn delete_playlist(&mut self, id: &str) {
todo!()
}
}

View File

@@ -1,4 +1,4 @@
use music_player_types::types::{Album as AlbumType, Song};
use music_player_types::types::{Album as AlbumType, RemoteCoverUrl, Song};
use sea_orm::{entity::prelude::*, ActiveValue};
use serde::{Deserialize, Serialize};
@@ -65,7 +65,36 @@ impl From<AlbumType> for Model {
id: album.id.clone(),
title: album.title,
cover: album.cover,
..Default::default()
artist: album.artist,
artist_id: album.artist_id,
year: album.year,
tracks: album.tracks.into_iter().map(Into::into).collect(),
}
}
}
impl Into<AlbumType> for Model {
fn into(self) -> AlbumType {
AlbumType {
id: self.id,
title: self.title,
cover: self.cover,
artist: self.artist,
artist_id: self.artist_id,
year: self.year,
tracks: self.tracks.into_iter().map(Into::into).collect(),
}
}
}
impl RemoteCoverUrl for Model {
fn with_remote_cover_url(&self, base_url: &str) -> Self {
Self {
cover: self
.cover
.clone()
.map(|cover| format!("{}/covers/{}", base_url, cover)),
..self.clone()
}
}
}

View File

@@ -1,4 +1,4 @@
use music_player_types::types::{Artist as ArtistType, Song};
use music_player_types::types::{Artist as ArtistType, RemoteTrackUrl, Song, RemoteCoverUrl};
use sea_orm::{entity::prelude::*, ActiveValue};
use serde::{Deserialize, Serialize};
@@ -55,3 +55,39 @@ impl From<ArtistType> for Model {
}
}
}
impl Into<ArtistType> for Model {
fn into(self) -> ArtistType {
ArtistType {
id: self.id,
name: self.name,
..Default::default()
}
}
}
impl RemoteCoverUrl for Model {
fn with_remote_cover_url(&self, base_url: &str) -> Self {
Self {
albums: self
.albums
.iter()
.map(|album| album.with_remote_cover_url(base_url))
.collect(),
..self.clone()
}
}
}
impl RemoteTrackUrl for Model {
fn with_remote_track_url(&self, base_url: &str) -> Self {
Self {
tracks: self
.tracks
.iter()
.map(|track| track.with_remote_track_url(base_url))
.collect(),
..self.clone()
}
}
}

View File

@@ -1,3 +1,4 @@
use music_player_types::types::Playlist as PlaylistType;
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
@@ -47,3 +48,14 @@ impl Related<super::track::Entity> for Entity {
}
impl ActiveModelBehavior for ActiveModel {}
impl Into<PlaylistType> for Model {
fn into(self) -> PlaylistType {
PlaylistType {
id: self.id,
name: self.name,
description: self.description,
tracks: self.tracks.into_iter().map(Into::into).collect(),
}
}
}

View File

@@ -1,4 +1,4 @@
use music_player_types::types::{Song, Track as TrackType};
use music_player_types::types::{Song, Track as TrackType, RemoteTrackUrl};
use sea_orm::{entity::prelude::*, ActiveValue};
use serde::{Deserialize, Serialize};
@@ -192,3 +192,27 @@ impl From<TrackType> for Model {
}
}
}
impl Into<TrackType> for Model {
fn into(self) -> TrackType {
TrackType {
id: self.id,
title: self.title,
artist: self.artist,
uri: self.uri,
duration: self.duration,
album: Some(self.album.into()),
artists: self.artists.into_iter().map(Into::into).collect(),
..Default::default()
}
}
}
impl RemoteTrackUrl for Model {
fn with_remote_track_url(&self, base_url: &str) -> Self {
Self {
uri: format!("{}/tracks/{}", base_url, self.id),
..self.clone()
}
}
}

View File

@@ -1,13 +1,19 @@
#[cfg(test)]
mod tests;
use crate::simple_broker::SimpleBroker;
use async_graphql::Schema;
use futures_util::StreamExt;
use music_player_discovery::{discover, SERVICE_NAME, XBMC_SERVICE_NAME};
use music_player_entity::track as track_entity;
use music_player_playback::player::PlayerCommand;
use music_player_types::types::Device;
use rand::seq::SliceRandom;
use schema::{Mutation, Query, Subscription};
use std::{sync::Arc, thread};
use crate::simple_broker::SimpleBroker;
use std::{
sync::{Arc, Mutex},
thread,
};
use tokio::sync::mpsc::UnboundedSender;
pub mod schema;
pub mod simple_broker;
@@ -57,3 +63,23 @@ pub async fn scan_devices() -> Result<Arc<std::sync::Mutex<Vec<Device>>>, Box<dy
Ok(devices)
}
pub fn load_tracks(
player_cmd: &Arc<Mutex<UnboundedSender<PlayerCommand>>>,
mut tracks: Vec<track_entity::Model>,
position: Option<u32>,
shuffle: bool,
) {
if shuffle {
tracks.shuffle(&mut rand::thread_rng());
}
let player_cmd_tx = player_cmd.lock().unwrap();
player_cmd_tx.send(PlayerCommand::Stop).unwrap();
player_cmd_tx.send(PlayerCommand::Clear).unwrap();
player_cmd_tx
.send(PlayerCommand::LoadTracklist { tracks })
.unwrap();
player_cmd_tx
.send(PlayerCommand::PlayTrackAt(position.unwrap_or(0) as usize))
.unwrap();
}

View File

@@ -8,16 +8,11 @@ use music_player_storage::{
repo::{album::AlbumRepository, artist::ArtistRepository, track::TrackRepository},
Database,
};
use music_player_types::types;
use music_player_types::types::{self, RemoteCoverUrl, RemoteTrackUrl};
use sea_orm::{ActiveModelTrait, ActiveValue};
use tokio::sync::Mutex;
use super::objects::{
album::{Album, RemoteCoverUrl},
artist::Artist,
search_result::SearchResult,
track::{RemoteTrackUrl, Track},
};
use super::objects::{album::Album, artist::Artist, search_result::SearchResult, track::Track};
#[derive(Default)]
pub struct LibraryQuery;
@@ -69,6 +64,9 @@ impl LibraryQuery {
offset: Option<i32>,
limit: Option<i32>,
) -> Result<Vec<Artist>, Error> {
let connected_device = ctx
.data::<Arc<StdMutex<HashMap<String, types::Device>>>>()
.unwrap();
let current_device = ctx.data::<Arc<Mutex<CurrentDevice>>>().unwrap();
let mut device = current_device.lock().await;
@@ -77,7 +75,20 @@ impl LibraryQuery {
let artists = source
.artists(offset.unwrap_or(0), limit.unwrap_or(100))
.await?;
return Ok(artists.into_iter().map(Into::into).collect());
let device = connected_device.lock().unwrap();
let device = device.get("current_device").unwrap();
let base_url = device.base_url.as_ref().unwrap();
return Ok(artists
.into_iter()
.map(|artist| {
artist
.with_remote_cover_url(base_url.as_str())
.with_remote_track_url(base_url.as_str())
})
.map(Into::into)
.collect());
}
let db = ctx.data::<Arc<Mutex<Database>>>().unwrap();
@@ -115,7 +126,11 @@ impl LibraryQuery {
return Ok(result
.into_iter()
.map(|album| album.with_remote_cover_url(base_url.as_str()))
.map(|album| {
album
.with_remote_cover_url(base_url.as_str())
.with_remote_track_url(base_url.as_str())
})
.collect());
}
@@ -157,6 +172,9 @@ impl LibraryQuery {
}
async fn artist(&self, ctx: &Context<'_>, id: ID) -> Result<Artist, Error> {
let connected_device = ctx
.data::<Arc<StdMutex<HashMap<String, types::Device>>>>()
.unwrap();
let current_device = ctx.data::<Arc<Mutex<CurrentDevice>>>().unwrap();
let mut device = current_device.lock().await;
let id = id.to_string();
@@ -164,7 +182,12 @@ impl LibraryQuery {
if device.source.is_some() {
let source = device.source.as_mut().unwrap();
let artist = source.artist(&id).await?;
return Ok(artist.into());
let device = connected_device.lock().unwrap();
let device = device.get("current_device").unwrap();
let base_url = device.base_url.as_ref().unwrap();
return Ok(artist.with_remote_track_url(base_url.as_str()).into());
}
let db = ctx.data::<Arc<Mutex<Database>>>().unwrap();
@@ -193,7 +216,9 @@ impl LibraryQuery {
let device = device.get("current_device").unwrap();
let base_url = device.base_url.as_ref().unwrap();
return Ok(Album::from(album).with_remote_cover_url(base_url.as_str()));
return Ok(Album::from(album)
.with_remote_cover_url(base_url.as_str())
.with_remote_track_url(base_url.as_str()));
}
let db = ctx.data::<Arc<Mutex<Database>>>().unwrap();

View File

@@ -1,6 +1,6 @@
use async_graphql::*;
use music_player_entity::album::Model;
use music_player_types::types::Album as AlbumType;
use music_player_types::types::{Album as AlbumType, RemoteCoverUrl, RemoteTrackUrl};
use serde::Serialize;
use super::track::Track;
@@ -52,10 +52,6 @@ impl Album {
}
}
pub trait RemoteCoverUrl {
fn with_remote_cover_url(&self, base_url: &str) -> Self;
}
impl RemoteCoverUrl for Album {
fn with_remote_cover_url(&self, base_url: &str) -> Self {
Self {
@@ -68,6 +64,19 @@ impl RemoteCoverUrl for Album {
}
}
impl RemoteTrackUrl for Album {
fn with_remote_track_url(&self, base_url: &str) -> Self {
Self {
tracks: self
.tracks
.iter()
.map(|track| track.with_remote_track_url(base_url))
.collect(),
..self.clone()
}
}
}
impl From<Model> for Album {
fn from(model: Model) -> Self {
Self {

View File

@@ -1,7 +1,7 @@
use super::{album::Album, track::Track};
use async_graphql::*;
use music_player_entity::artist::Model;
use music_player_types::types::Artist as ArtistType;
use music_player_types::types::{Artist as ArtistType, RemoteTrackUrl};
use serde::Serialize;
#[derive(Default, Clone, Serialize)]
@@ -67,12 +67,29 @@ impl From<Model> for Artist {
}
}
}
impl From<ArtistType> for Artist {
fn from(artist: ArtistType) -> Self {
Self {
id: ID(artist.id),
name: artist.name,
picture: artist.picture.unwrap_or_default(),
albums: artist.albums.into_iter().map(Into::into).collect(),
songs: artist.songs.into_iter().map(Into::into).collect(),
..Default::default()
}
}
}
impl RemoteTrackUrl for Artist {
fn with_remote_track_url(&self, base_url: &str) -> Self {
Self {
songs: self
.songs
.iter()
.map(|track| track.with_remote_track_url(base_url))
.collect(),
..self.clone()
}
}
}

View File

@@ -1,6 +1,6 @@
use async_graphql::*;
use music_player_entity::{playlist::Model, select_result};
use music_player_types::types::Playlist as PlaylistType;
use music_player_types::types::{Playlist as PlaylistType, RemoteTrackUrl};
use super::track::Track;
@@ -66,3 +66,17 @@ impl From<PlaylistType> for Playlist {
}
}
}
impl RemoteTrackUrl for Playlist {
fn with_remote_track_url(&self, base_url: &str) -> Self {
Self {
tracks: self
.tracks
.clone()
.into_iter()
.map(|track| track.with_remote_track_url(base_url))
.collect(),
..self.clone()
}
}
}

View File

@@ -1,6 +1,6 @@
use async_graphql::*;
use music_player_entity::{select_result, track::Model};
use music_player_types::types;
use music_player_types::types::{self, RemoteTrackUrl};
use music_player_types::types::SimplifiedSong as TrackType;
use serde::Serialize;
@@ -88,10 +88,6 @@ impl Track {
}
}
pub trait RemoteTrackUrl {
fn with_remote_track_url(&self, base_url: &str) -> Self;
}
impl RemoteTrackUrl for Track {
fn with_remote_track_url(&self, base_url: &str) -> Self {
Self {

View File

@@ -3,6 +3,7 @@ use std::sync::Arc;
use async_graphql::*;
use cuid::cuid;
use futures_util::Stream;
use music_player_addons::CurrentDevice;
use music_player_entity::{
album as album_entity, artist as artist_entity, folder as folder_entity,
playlist as playlist_entity, playlist_tracks as playlist_tracks_entity, select_result,
@@ -31,6 +32,15 @@ impl PlaylistQuery {
let db = ctx.data::<Arc<Mutex<Database>>>().unwrap();
let db = db.lock().await;
let current_device = ctx.data::<Arc<Mutex<CurrentDevice>>>().unwrap();
let mut device = current_device.lock().await;
if device.source.is_some() {
let source = device.source.as_mut().unwrap();
let result = source.playlist(&id).await?;
return Ok(result.into());
}
let result = PlaylistRepository::new(db.get_connection())
.find(id.as_str())
.await?;
@@ -41,9 +51,18 @@ impl PlaylistQuery {
async fn playlists(&self, ctx: &Context<'_>) -> Result<Vec<Playlist>, Error> {
let db = ctx.data::<Arc<Mutex<Database>>>().unwrap();
let db = db.lock().await;
playlist_entity::Entity::find()
.order_by_asc(playlist_entity::Column::Name)
.all(db.get_connection())
let current_device = ctx.data::<Arc<Mutex<CurrentDevice>>>().unwrap();
let mut device = current_device.lock().await;
if device.source.is_some() {
let source = device.source.as_mut().unwrap();
let result = source.playlists(0, 10).await?;
return Ok(result.into_iter().map(Into::into).collect());
}
PlaylistRepository::new(db.get_connection())
.find_all()
.await
.map(|playlists| playlists.into_iter().map(Into::into).collect())
.map_err(|e| Error::new(e.to_string()))
@@ -52,10 +71,8 @@ impl PlaylistQuery {
async fn main_playlists(&self, ctx: &Context<'_>) -> Result<Vec<Playlist>, Error> {
let db = ctx.data::<Arc<Mutex<Database>>>().unwrap();
let db = db.lock().await;
playlist_entity::Entity::find()
.order_by_asc(playlist_entity::Column::Name)
.filter(playlist_entity::Column::FolderId.is_null())
.all(db.get_connection())
PlaylistRepository::new(db.get_connection())
.main_playlists()
.await
.map(|playlists| playlists.into_iter().map(Into::into).collect())
.map_err(|e| Error::new(e.to_string()))
@@ -64,10 +81,8 @@ impl PlaylistQuery {
async fn recent_playlists(&self, ctx: &Context<'_>) -> Result<Vec<Playlist>, Error> {
let db = ctx.data::<Arc<Mutex<Database>>>().unwrap();
let db = db.lock().await;
playlist_entity::Entity::find()
.order_by_desc(playlist_entity::Column::CreatedAt)
.limit(10)
.all(db.get_connection())
PlaylistRepository::new(db.get_connection())
.recent_playlists()
.await
.map(|playlists| playlists.into_iter().map(Into::into).collect())
.map_err(|e| Error::new(e.to_string()))

View File

@@ -12,7 +12,7 @@ use music_player_storage::repo::playlist::PlaylistRepository;
use music_player_storage::repo::track::TrackRepository;
use music_player_storage::Database;
use music_player_tracklist::Tracklist as TracklistState;
use music_player_types::types;
use music_player_types::types::{self, RemoteCoverUrl, RemoteTrackUrl};
use rand::seq::SliceRandom;
use sea_orm::{
ColumnTrait, EntityTrait, JoinType, ModelTrait, QueryFilter, QueryOrder, QuerySelect,
@@ -22,8 +22,10 @@ use std::sync::Mutex as StdMutex;
use std::{collections::HashMap, sync::Arc};
use tokio::sync::{mpsc::UnboundedSender, Mutex};
use crate::load_tracks;
use crate::simple_broker::SimpleBroker;
use super::objects::album::Album;
use super::{
objects::{
track::{Track, TrackInput},
@@ -234,6 +236,9 @@ impl TracklistMutation {
position: Option<u32>,
shuffle: bool,
) -> Result<bool, Error> {
let player_cmd = ctx
.data::<Arc<StdMutex<UnboundedSender<PlayerCommand>>>>()
.unwrap();
let db = ctx.data::<Arc<Mutex<Database>>>().unwrap();
let connected_device = ctx
.data::<Arc<StdMutex<HashMap<String, types::Device>>>>()
@@ -241,35 +246,29 @@ impl TracklistMutation {
let current_device = ctx.data::<Arc<Mutex<CurrentDevice>>>().unwrap();
let mut device = current_device.lock().await;
let id = id.to_string();
if device.source.is_some() {
let source = device.source.as_mut().unwrap();
// TODO: call grpc to play album
}
let album = source.album(&id).await?;
let id = id.to_string();
let device = connected_device.lock().unwrap();
let device = device.get("current_device").unwrap();
let base_url = device.base_url.as_ref().unwrap();
let album: album_entity::Model = album
.with_remote_cover_url(base_url.as_str())
.with_remote_track_url(base_url.as_str())
.into();
let tracks = album.tracks;
load_tracks(player_cmd, tracks, position, shuffle);
return Ok(true);
}
let result = AlbumRepository::new(db.lock().await.get_connection())
.find(&id)
.await?;
let player_cmd = ctx
.data::<Arc<StdMutex<UnboundedSender<PlayerCommand>>>>()
.unwrap();
let player_cmd_tx = player_cmd.lock().unwrap();
player_cmd_tx.send(PlayerCommand::Stop).unwrap();
player_cmd_tx.send(PlayerCommand::Clear).unwrap();
let mut tracks = result.tracks;
if shuffle {
tracks.shuffle(&mut rand::thread_rng());
}
player_cmd_tx
.send(PlayerCommand::LoadTracklist { tracks })
.unwrap();
player_cmd_tx
.send(PlayerCommand::PlayTrackAt(position.unwrap_or(0) as usize))
.unwrap();
load_tracks(player_cmd, result.tracks, position, shuffle);
Ok(true)
}
@@ -280,43 +279,36 @@ impl TracklistMutation {
position: Option<u32>,
shuffle: bool,
) -> Result<bool, Error> {
let player_cmd = ctx
.data::<Arc<StdMutex<UnboundedSender<PlayerCommand>>>>()
.unwrap();
let db = ctx.data::<Arc<Mutex<Database>>>().unwrap();
let connected_device = ctx
.data::<Arc<StdMutex<HashMap<String, types::Device>>>>()
.unwrap();
let current_device = ctx.data::<Arc<Mutex<CurrentDevice>>>().unwrap();
let mut device = current_device.lock().await;
let id = id.to_string();
if device.source.is_some() {
let source = device.source.as_mut().unwrap();
// TODO: call grpc to play artist tracks
let artist = source.artist(&id).await?;
let device = connected_device.lock().unwrap();
let device = device.get("current_device").unwrap();
let base_url = device.base_url.as_ref().unwrap();
let artist: artist_entity::Model =
artist.with_remote_track_url(base_url.as_str()).into();
load_tracks(player_cmd, artist.tracks, position, shuffle);
return Ok(true);
}
let id = id.to_string();
let mut artist = ArtistRepository::new(db.lock().await.get_connection())
let artist = ArtistRepository::new(db.lock().await.get_connection())
.find(&id)
.await?;
let player_cmd = ctx
.data::<Arc<StdMutex<UnboundedSender<PlayerCommand>>>>()
.unwrap();
let player_cmd_tx = player_cmd.lock().unwrap();
player_cmd_tx.send(PlayerCommand::Stop).unwrap();
player_cmd_tx.send(PlayerCommand::Clear).unwrap();
if shuffle {
artist.tracks.shuffle(&mut rand::thread_rng());
}
player_cmd_tx
.send(PlayerCommand::LoadTracklist {
tracks: artist.tracks,
})
.unwrap();
player_cmd_tx
.send(PlayerCommand::PlayTrackAt(position.unwrap_or(0) as usize))
.unwrap();
load_tracks(player_cmd, artist.tracks, position, shuffle);
Ok(true)
}
@@ -327,6 +319,9 @@ impl TracklistMutation {
position: Option<u32>,
shuffle: bool,
) -> Result<bool, Error> {
let player_cmd = ctx
.data::<Arc<std::sync::Mutex<UnboundedSender<PlayerCommand>>>>()
.unwrap();
let db = ctx.data::<Arc<Mutex<Database>>>().unwrap();
let db = db.lock().await;
let connected_device = ctx
@@ -339,32 +334,27 @@ impl TracklistMutation {
if device.source.is_some() {
let source = device.source.as_mut().unwrap();
// TODO: call grpc to play playlist
let result = source.playlist(&id).await?;
let device = connected_device.lock().unwrap();
let device = device.get("current_device").unwrap();
let base_url = device.base_url.as_ref().unwrap();
let tracks = result.with_remote_track_url(base_url.as_str()).tracks;
let tracks: Vec<track_entity::Model> = tracks.into_iter().map(Into::into).collect();
load_tracks(player_cmd, tracks, position, shuffle);
return Ok(true);
}
let playlist = PlaylistRepository::new(db.get_connection())
.find(id.as_str())
.await?;
let mut tracks: Vec<track_entity::Model> =
let tracks: Vec<track_entity::Model> =
playlist.tracks.into_iter().map(Into::into).collect();
if shuffle {
tracks.shuffle(&mut rand::thread_rng());
}
let player_cmd = ctx
.data::<Arc<std::sync::Mutex<UnboundedSender<PlayerCommand>>>>()
.unwrap();
let player_cmd_tx = player_cmd.lock().unwrap();
player_cmd_tx.send(PlayerCommand::Stop).unwrap();
player_cmd_tx.send(PlayerCommand::Clear).unwrap();
player_cmd_tx
.send(PlayerCommand::LoadTracklist { tracks })
.unwrap();
player_cmd_tx
.send(PlayerCommand::PlayTrackAt(position.unwrap_or(0) as usize))
.unwrap();
load_tracks(player_cmd, tracks, position, shuffle);
Ok(true)
}

View File

@@ -72,7 +72,8 @@ message GetPlaylistDetailsRequest { string id = 1; }
message GetPlaylistDetailsResponse {
string id = 1;
string name = 2;
repeated metadata.v1alpha1.Track tracks = 3;
string description = 3;
repeated metadata.v1alpha1.Track tracks = 4;
}
message CreateFolderRequest {

View File

@@ -3005,7 +3005,9 @@ pub struct GetPlaylistDetailsResponse {
pub id: ::prost::alloc::string::String,
#[prost(string, tag = "2")]
pub name: ::prost::alloc::string::String,
#[prost(message, repeated, tag = "3")]
#[prost(string, tag = "3")]
pub description: ::prost::alloc::string::String,
#[prost(message, repeated, tag = "4")]
pub tracks: ::prost::alloc::vec::Vec<super::super::metadata::v1alpha1::Track>,
}
#[allow(clippy::derive_partial_eq_without_eq)]

View File

@@ -15,8 +15,9 @@ pub mod api {
#[path = ""]
pub mod music {
use music_player_entity::folder;
use music_player_types::types::Playlist;
use self::v1alpha1::GetFolderDetailsResponse;
use self::v1alpha1::{GetFolderDetailsResponse, GetPlaylistDetailsResponse};
#[path = "music.v1alpha1.rs"]
pub mod v1alpha1;
@@ -31,6 +32,28 @@ pub mod api {
}
}
}
impl Into<Playlist> for GetPlaylistDetailsResponse {
fn into(self) -> Playlist {
Playlist {
id: self.id,
name: self.name,
description: Some(self.description),
tracks: self.tracks.into_iter().map(Into::into).collect(),
}
}
}
impl From<Playlist> for GetPlaylistDetailsResponse {
fn from(playlist: Playlist) -> Self {
Self {
id: playlist.id,
name: playlist.name,
description: playlist.description.unwrap_or_default(),
tracks: playlist.tracks.into_iter().map(Into::into).collect(),
}
}
}
}
#[path = ""]
@@ -130,6 +153,19 @@ pub mod api {
}
}
impl From<types::Track> for Song {
fn from(track: types::Track) -> Self {
Self {
id: track.id,
title: track.title,
duration: track.duration.unwrap_or_default(),
track_number: track.track_number.unwrap_or_default() as i32,
artists: track.artists.into_iter().map(Into::into).collect(),
..Default::default()
}
}
}
impl From<artist::Model> for SongArtist {
fn from(model: artist::Model) -> Self {
Self {
@@ -140,6 +176,16 @@ pub mod api {
}
}
impl From<types::Artist> for SongArtist {
fn from(artist: types::Artist) -> Self {
Self {
id: artist.id,
name: artist.name,
..Default::default()
}
}
}
impl From<track::Model> for ArtistSong {
fn from(model: track::Model) -> Self {
Self {
@@ -154,6 +200,20 @@ pub mod api {
}
}
impl Into<types::Track> for ArtistSong {
fn into(self) -> types::Track {
types::Track {
id: self.id,
title: self.title,
duration: Some(self.duration),
track_number: Some(u32::try_from(self.track_number).unwrap_or_default()),
disc_number: u32::try_from(self.disc_number).unwrap_or_default(),
artists: self.artists.into_iter().map(Into::into).collect(),
..Default::default()
}
}
}
impl Into<types::Track> for Track {
fn into(self) -> types::Track {
types::Track {
@@ -173,12 +233,45 @@ pub mod api {
}
}
impl From<types::Track> for Track {
fn from(track: types::Track) -> Self {
Self {
id: track.id,
title: track.title,
uri: track.uri,
duration: track.duration.unwrap_or_default(),
track_number: i32::try_from(track.track_number.unwrap_or_default()).unwrap(),
disc_number: i32::try_from(track.disc_number).unwrap(),
artists: track.artists.into_iter().map(Into::into).collect(),
artist: track.artist,
album: match track.album {
Some(album) => Some(album.into()),
None => None,
},
..Default::default()
}
}
}
impl Into<types::Artist> for Artist {
fn into(self) -> types::Artist {
types::Artist {
id: self.id,
name: self.name,
picture: Some(self.picture),
albums: self.albums.into_iter().map(Into::into).collect(),
songs: self.songs.into_iter().map(Into::into).collect(),
}
}
}
impl From<types::Artist> for Artist {
fn from(artist: types::Artist) -> Self {
Self {
id: artist.id,
name: artist.name,
picture: artist.picture.unwrap_or_default(),
..Default::default()
}
}
}
@@ -220,5 +313,19 @@ pub mod api {
}
}
}
impl From<types::Album> for Album {
fn from(album: types::Album) -> Self {
Self {
id: album.id,
title: album.title,
cover: album.cover.unwrap_or_default(),
artist: album.artist,
year: i32::try_from(album.year.unwrap_or_default()).unwrap_or_default(),
tracks: album.tracks.into_iter().map(Into::into).collect(),
..Default::default()
}
}
}
}
}

View File

@@ -1,5 +1,5 @@
use music_player_entity::{playlist, playlist_tracks, track};
use music_player_storage::Database;
use music_player_storage::{repo::playlist::PlaylistRepository, Database};
use sea_orm::{
ActiveModelTrait, ActiveValue, ColumnTrait, EntityTrait, ModelTrait, QueryFilter, Set,
};
@@ -203,71 +203,24 @@ impl PlaylistService for Playlist {
&self,
_request: tonic::Request<FindAllRequest>,
) -> Result<tonic::Response<FindAllResponse>, tonic::Status> {
playlist::Entity::find()
.all(self.db.lock().await.get_connection())
let result = PlaylistRepository::new(self.db.lock().await.get_connection())
.find_all()
.await
.map(|playlists| {
tonic::Response::new(FindAllResponse {
playlists: playlists
.into_iter()
.map(|playlist| GetPlaylistDetailsResponse {
id: playlist.id,
name: playlist.name,
..Default::default()
})
.collect(),
..Default::default()
})
})
.map_err(|e| tonic::Status::internal(e.to_string()))?;
let response = FindAllResponse {
..Default::default()
};
Ok(tonic::Response::new(response))
.map_err(|_| tonic::Status::internal("Failed to get playlist"))?;
Ok(tonic::Response::new(FindAllResponse {
playlists: result.into_iter().map(Into::into).collect(),
}))
}
async fn get_playlist_details(
&self,
request: tonic::Request<GetPlaylistDetailsRequest>,
) -> Result<tonic::Response<GetPlaylistDetailsResponse>, tonic::Status> {
let result = playlist::Entity::find_by_id(request.get_ref().id.clone())
.one(self.db.lock().await.get_connection())
.await;
match result {
Ok(playlist) => {
if playlist.is_none() {
return Err(tonic::Status::not_found("Playlist not found"));
}
playlist
.clone()
.unwrap()
.find_related(track::Entity)
.all(self.db.lock().await.get_connection())
.await
.map(|tracks| {
tonic::Response::new(GetPlaylistDetailsResponse {
id: playlist.clone().unwrap().id,
name: playlist.clone().unwrap().name,
tracks: tracks
.into_iter()
.map(|track| Track {
id: track.id,
title: track.title,
uri: track.uri,
duration: track.duration.unwrap_or_default(),
disc_number: i32::try_from(track.track.unwrap_or_default())
.unwrap(),
..Default::default()
})
.collect(),
..Default::default()
})
})
.map_err(|_| tonic::Status::internal("Failed to get playlist items"))
}
Err(_) => return Err(tonic::Status::internal("Failed to get playlist")),
}
let result = PlaylistRepository::new(self.db.lock().await.get_connection())
.find(&request.get_ref().id)
.await
.map_err(|_| tonic::Status::internal("Failed to get playlist"))?;
Ok(tonic::Response::new(result.into()))
}
async fn create_folder(

View File

@@ -5,7 +5,8 @@ use music_player_entity::{
};
use music_player_types::types::Playlist;
use sea_orm::{
ColumnTrait, DatabaseConnection, EntityTrait, JoinType, QueryFilter, QuerySelect, RelationTrait,
ColumnTrait, DatabaseConnection, EntityTrait, JoinType, QueryFilter, QueryOrder, QuerySelect,
RelationTrait,
};
pub struct PlaylistRepository {
@@ -90,6 +91,31 @@ impl PlaylistRepository {
}
pub async fn find_all(&self) -> Result<Vec<Playlist>, Error> {
todo!()
playlist_entity::Entity::find()
.order_by_asc(playlist_entity::Column::Name)
.all(&self.db)
.await
.map(|playlists| playlists.into_iter().map(Into::into).collect())
.map_err(|e| Error::msg(e.to_string()))
}
pub async fn main_playlists(&self) -> Result<Vec<Playlist>, Error> {
playlist_entity::Entity::find()
.order_by_asc(playlist_entity::Column::Name)
.filter(playlist_entity::Column::FolderId.is_null())
.all(&self.db)
.await
.map(|playlists| playlists.into_iter().map(Into::into).collect())
.map_err(|e| Error::msg(e.to_string()))
}
pub async fn recent_playlists(&self) -> Result<Vec<Playlist>, Error> {
playlist_entity::Entity::find()
.order_by_desc(playlist_entity::Column::CreatedAt)
.limit(10)
.all(&self.db)
.await
.map(|playlists| playlists.into_iter().map(Into::into).collect())
.map_err(|e| Error::msg(e.to_string()))
}
}

View File

@@ -55,6 +55,8 @@ pub struct Artist {
pub id: String,
pub name: String,
pub picture: Option<String>,
pub albums: Vec<Album>,
pub songs: Vec<Track>,
}
impl From<Document> for Album {
@@ -412,3 +414,84 @@ pub struct Folder {
pub name: String,
pub playlists: Vec<Playlist>,
}
pub trait RemoteTrackUrl {
fn with_remote_track_url(&self, base_url: &str) -> Self;
}
pub trait RemoteCoverUrl {
fn with_remote_cover_url(&self, base_url: &str) -> Self;
}
impl RemoteTrackUrl for Track {
fn with_remote_track_url(&self, base_url: &str) -> Self {
Self {
uri: format!("{}/tracks/{}", base_url, self.id),
..self.clone()
}
}
}
impl RemoteCoverUrl for Album {
fn with_remote_cover_url(&self, base_url: &str) -> Self {
Self {
cover: match self.cover {
Some(ref cover) => Some(format!("{}/covers/{}", base_url, cover)),
None => None,
},
..self.clone()
}
}
}
impl RemoteTrackUrl for Album {
fn with_remote_track_url(&self, base_url: &str) -> Self {
Self {
tracks: self
.tracks
.iter()
.map(|track| track.with_remote_track_url(base_url))
.collect(),
..self.clone()
}
}
}
impl RemoteCoverUrl for Artist {
fn with_remote_cover_url(&self, base_url: &str) -> Self {
Self {
albums: self
.albums
.iter()
.map(|album| album.with_remote_cover_url(base_url))
.collect(),
..self.clone()
}
}
}
impl RemoteTrackUrl for Artist {
fn with_remote_track_url(&self, base_url: &str) -> Self {
Self {
songs: self
.songs
.iter()
.map(|track| track.with_remote_track_url(base_url))
.collect(),
..self.clone()
}
}
}
impl RemoteTrackUrl for Playlist {
fn with_remote_track_url(&self, base_url: &str) -> Self {
Self {
tracks: self
.tracks
.iter()
.map(|track| track.with_remote_track_url(base_url))
.collect(),
..self.clone()
}
}
}