mirror of
https://github.com/tsirysndr/music-player.git
synced 2026-01-10 05:37:57 -05:00
feat(chromecast): added play_next feature
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -5773,7 +5773,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rust_cast"
|
||||
version = "0.18.1"
|
||||
source = "git+https://github.com/tsirysndr/rust-cast.git?rev=ee415c8#ee415c8a6eecf8b0e7f5b0b956633d9d765b76e9"
|
||||
source = "git+https://github.com/tsirysndr/rust-cast.git?rev=3a36506#3a365060f94e0db4e930e75ec5ccce9cb59dda71"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"log",
|
||||
|
||||
@@ -20,7 +20,7 @@ version = "0.1.2"
|
||||
surf = { version = "2.3.2", features = ["h1-client-rustls"], default-features = false}
|
||||
async-trait = "0.1.59"
|
||||
anyhow = "1.0.67"
|
||||
rust_cast = { git = "https://github.com/tsirysndr/rust-cast.git", rev = "ee415c8", features = ["thread_safe"] }
|
||||
rust_cast = { git = "https://github.com/tsirysndr/rust-cast.git", rev = "3a36506", features = ["thread_safe"] }
|
||||
jsonrpsee = { version = "0.16.2", features = ["jsonrpsee-ws-client", "jsonrpsee-http-client", "jsonrpsee-client-transport", "async-client", "client"] }
|
||||
url = "2.3.1"
|
||||
md5 = "0.7.0"
|
||||
|
||||
@@ -80,7 +80,7 @@ impl Player for Airplay {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn load_tracks(&mut self, tracks: Vec<Track>) -> Result<(), Error> {
|
||||
async fn load_tracks(&mut self, tracks: Vec<Track>, start_index: Option<i32>) -> Result<(), Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
|
||||
@@ -185,7 +185,11 @@ impl<'a> Player for Chromecast<'a> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn load_tracks(&mut self, tracks: Vec<Track>) -> Result<(), Error> {
|
||||
async fn load_tracks(
|
||||
&mut self,
|
||||
tracks: Vec<Track>,
|
||||
start_index: Option<i32>,
|
||||
) -> Result<(), Error> {
|
||||
if let Some(cast_device) = &self.client {
|
||||
let medias = tracks
|
||||
.iter()
|
||||
@@ -217,7 +221,7 @@ impl<'a> Player for Chromecast<'a> {
|
||||
cast_device.media.queue_load(
|
||||
self.transport_id.as_ref().unwrap(),
|
||||
medias,
|
||||
Some(0),
|
||||
Some(start_index.unwrap_or(0)),
|
||||
None,
|
||||
)?;
|
||||
|
||||
@@ -227,8 +231,58 @@ impl<'a> Player for Chromecast<'a> {
|
||||
}
|
||||
|
||||
async fn play_next(&mut self, track: Track) -> Result<(), Error> {
|
||||
self.current_app_session()?;
|
||||
todo!()
|
||||
if let Some(_) = &self.client {
|
||||
let items = vec![Media {
|
||||
content_id: track.uri.clone(),
|
||||
content_type: "".to_string(),
|
||||
stream_type: StreamType::Buffered,
|
||||
metadata: Some(Metadata::MusicTrack(MusicTrackMediaMetadata {
|
||||
title: Some(track.title.clone()),
|
||||
artist: Some(track.artists.first().unwrap().name.clone()),
|
||||
album_name: Some(track.album.as_ref().unwrap().title.clone()),
|
||||
album_artist: Some(track.artists.first().unwrap().name.clone()),
|
||||
track_number: track.track_number,
|
||||
disc_number: Some(track.disc_number),
|
||||
images: match &track.album.as_ref().unwrap().cover {
|
||||
Some(cover) => vec![Image {
|
||||
url: cover.clone(),
|
||||
dimensions: None,
|
||||
}],
|
||||
None => vec![],
|
||||
},
|
||||
release_date: None,
|
||||
composer: None,
|
||||
})),
|
||||
duration: None,
|
||||
}];
|
||||
let playback = self.get_current_playback().await?;
|
||||
|
||||
let tracklist = playback.items;
|
||||
let mut tracklist = tracklist.iter();
|
||||
let mut before: Option<i32> = None;
|
||||
loop {
|
||||
let cursor = tracklist.next();
|
||||
if cursor.is_none() {
|
||||
break;
|
||||
}
|
||||
let (_, item_id) = cursor.unwrap();
|
||||
if *item_id == playback.current_item_id.unwrap() {
|
||||
let cursor = tracklist.next();
|
||||
before = cursor.map(|(_, item_id)| *item_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let (cast_device, transport_id, media_session_id, _) = self.current_app_session()?;
|
||||
cast_device.media.queue_insert(
|
||||
transport_id.as_str(),
|
||||
media_session_id,
|
||||
items,
|
||||
before,
|
||||
)?;
|
||||
return Ok(());
|
||||
}
|
||||
Err(Error::msg("Cast device is not connected"))
|
||||
}
|
||||
|
||||
async fn load(&mut self, track: Track) -> Result<(), Error> {
|
||||
@@ -276,6 +330,56 @@ impl<'a> Player for Chromecast<'a> {
|
||||
Some(status) => {
|
||||
let media = status.media.as_ref().unwrap();
|
||||
let metadata = media.metadata.as_ref().unwrap();
|
||||
let items = status.items.as_ref().unwrap();
|
||||
let items = items
|
||||
.iter()
|
||||
.map(|item| {
|
||||
let media = item.media.as_ref().unwrap();
|
||||
let metadata = media.metadata.as_ref().unwrap();
|
||||
let cover = metadata.images.first().map(|x| x.url.clone());
|
||||
(
|
||||
Track {
|
||||
id: media
|
||||
.content_id
|
||||
.clone()
|
||||
.split("/")
|
||||
.last()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
uri: media.content_id.clone(),
|
||||
title: metadata.title.clone().unwrap(),
|
||||
artists: vec![Artist {
|
||||
id: format!(
|
||||
"{:x}",
|
||||
md5::compute(metadata.artist.clone().unwrap())
|
||||
),
|
||||
name: metadata.artist.clone().unwrap(),
|
||||
..Default::default()
|
||||
}],
|
||||
album: Some(Album {
|
||||
id: cover
|
||||
.clone()
|
||||
.map(|x| {
|
||||
x.split("/")
|
||||
.last()
|
||||
.map(|x| x.split(".").next().unwrap())
|
||||
.unwrap()
|
||||
.to_string()
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
title: metadata.album_name.clone().unwrap(),
|
||||
cover,
|
||||
..Default::default()
|
||||
}),
|
||||
track_number: metadata.track_number,
|
||||
disc_number: metadata.disc_number.unwrap_or(0),
|
||||
duration: media.duration,
|
||||
..Default::default()
|
||||
},
|
||||
item.item_id,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<(Track, i32)>>();
|
||||
|
||||
match metadata {
|
||||
Metadata::MusicTrack(metadata) => {
|
||||
@@ -326,6 +430,8 @@ impl<'a> Player for Chromecast<'a> {
|
||||
.map(|x| (x * 1000.0) as u32)
|
||||
.unwrap_or(0),
|
||||
is_playing: true,
|
||||
items,
|
||||
current_item_id: status.current_item_id,
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
@@ -345,6 +451,8 @@ impl<'a> Player for Chromecast<'a> {
|
||||
index: 0,
|
||||
position_ms: status.current_time.map(|x| x as u32).unwrap_or(0),
|
||||
is_playing: true,
|
||||
current_item_id: status.current_item_id,
|
||||
items,
|
||||
});
|
||||
}
|
||||
None => {
|
||||
@@ -353,6 +461,8 @@ impl<'a> Player for Chromecast<'a> {
|
||||
index: 0,
|
||||
position_ms: 0,
|
||||
is_playing: false,
|
||||
current_item_id: None,
|
||||
items: vec![],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +138,11 @@ impl Player for Kodi {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn load_tracks(&mut self, tracks: Vec<Track>) -> Result<(), Error> {
|
||||
async fn load_tracks(
|
||||
&mut self,
|
||||
tracks: Vec<Track>,
|
||||
start_index: Option<i32>,
|
||||
) -> Result<(), Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ pub trait Player {
|
||||
async fn next(&mut self) -> Result<(), Error>;
|
||||
async fn previous(&mut self) -> Result<(), Error>;
|
||||
async fn seek(&mut self, position: u32) -> Result<(), Error>;
|
||||
async fn load_tracks(&mut self, tracks: Vec<Track>) -> Result<(), Error>;
|
||||
async fn load_tracks(&mut self, tracks: Vec<Track>, start_index: Option<i32>) -> Result<(), Error>;
|
||||
async fn play_next(&mut self, track: Track) -> Result<(), Error>;
|
||||
async fn load(&mut self, track: Track) -> Result<(), Error>;
|
||||
async fn get_current_playback(&mut self) -> Result<Playback, Error>;
|
||||
|
||||
@@ -179,7 +179,11 @@ impl Player for Local {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn load_tracks(&mut self, tracks: Vec<Track>) -> Result<(), Error> {
|
||||
async fn load_tracks(
|
||||
&mut self,
|
||||
tracks: Vec<Track>,
|
||||
start_index: Option<i32>,
|
||||
) -> Result<(), Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
|
||||
@@ -151,7 +151,10 @@ pub async fn load_tracks(
|
||||
}
|
||||
}
|
||||
player
|
||||
.load_tracks(tracks.clone().into_iter().map(Into::into).collect())
|
||||
.load_tracks(
|
||||
tracks.clone().into_iter().map(Into::into).collect(),
|
||||
Some(0),
|
||||
)
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ use sea_orm::{
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex as StdMutex;
|
||||
use tokio::sync::{mpsc::UnboundedSender, Mutex};
|
||||
use url::Url;
|
||||
|
||||
use crate::load_tracks;
|
||||
use crate::simple_broker::SimpleBroker;
|
||||
@@ -245,6 +246,7 @@ impl TracklistMutation {
|
||||
|
||||
if device.source.is_some() {
|
||||
let source = device.source.as_mut().unwrap();
|
||||
let source_ip = source.device_ip();
|
||||
let result = source.track(&id).await?;
|
||||
|
||||
let base_url = device
|
||||
@@ -259,6 +261,24 @@ impl TracklistMutation {
|
||||
.with_remote_track_url(base_url.as_str())
|
||||
.with_remote_cover_url(base_url.as_str())
|
||||
.into();
|
||||
|
||||
let receiver = device.receiver.as_mut().unwrap();
|
||||
let will_play_on_chromecast = receiver.device_type() == "chromecast";
|
||||
if will_play_on_chromecast {
|
||||
let url = Url::parse(track.uri.as_str()).unwrap();
|
||||
let host = url.host_str().unwrap();
|
||||
track.uri = track.uri.to_lowercase().replace(host, source_ip.as_str());
|
||||
let cover = match track.clone().album.cover {
|
||||
Some(cover) => Url::parse(cover.as_str()).ok().map(|url| {
|
||||
let host = url.host_str().unwrap();
|
||||
cover.to_lowercase().replace(host, source_ip.as_str())
|
||||
}),
|
||||
None => None,
|
||||
};
|
||||
track.album.cover = cover;
|
||||
}
|
||||
receiver.play_next(track.into()).await?;
|
||||
return Ok(true);
|
||||
} else {
|
||||
track = TrackRepository::new(db.lock().await.get_connection())
|
||||
.find(&id)
|
||||
@@ -267,7 +287,12 @@ impl TracklistMutation {
|
||||
|
||||
if device.receiver.is_some() {
|
||||
let receiver = device.receiver.as_mut().unwrap();
|
||||
track = update_track_url(devices, track)?;
|
||||
let will_play_on_chromecast = receiver.device_type() == "chromecast";
|
||||
track = update_track_url(devices.clone(), track)?;
|
||||
let t: types::Track = track.into();
|
||||
track = update_cover_url(devices.clone(), t.clone(), will_play_on_chromecast)
|
||||
.unwrap_or_else(|_| t.clone())
|
||||
.into();
|
||||
receiver.play_next(track.into()).await?;
|
||||
return Ok(true);
|
||||
}
|
||||
@@ -418,7 +443,17 @@ impl TracklistMutation {
|
||||
if device.receiver.is_some() {
|
||||
let receiver = device.receiver.as_mut().unwrap();
|
||||
let will_play_on_chromecast = receiver.device_type() == "chromecast";
|
||||
artist = update_tracks_url(devices, artist, will_play_on_chromecast)?;
|
||||
artist = update_tracks_url(devices.clone(), artist, will_play_on_chromecast)?;
|
||||
artist.tracks = artist
|
||||
.tracks
|
||||
.into_iter()
|
||||
.map(|track| {
|
||||
let t: types::Track = track.into();
|
||||
update_cover_url(devices.clone(), t.clone(), will_play_on_chromecast)
|
||||
.unwrap_or_else(|_| t.clone())
|
||||
.into()
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
load_tracks(
|
||||
@@ -489,7 +524,17 @@ impl TracklistMutation {
|
||||
if device.receiver.is_some() {
|
||||
let receiver = device.receiver.as_mut().unwrap();
|
||||
let will_play_on_chromecast = receiver.device_type() == "chromecast";
|
||||
playlist = update_tracks_url(devices, playlist, will_play_on_chromecast)?;
|
||||
playlist = update_tracks_url(devices.clone(), playlist, will_play_on_chromecast)?;
|
||||
playlist.tracks = playlist
|
||||
.tracks
|
||||
.into_iter()
|
||||
.map(|track| {
|
||||
let t: types::Track = track.into();
|
||||
update_cover_url(devices.clone(), t.clone(), will_play_on_chromecast)
|
||||
.unwrap_or_else(|_| t.clone())
|
||||
.into()
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
let tracks: Vec<track_entity::Model> =
|
||||
|
||||
@@ -16,8 +16,10 @@ pub const AIRPLAY_SERVICE_NAME: &str = "_raop._tcp.local.";
|
||||
pub struct Playback {
|
||||
pub current_track: Option<Track>,
|
||||
pub index: u32,
|
||||
pub current_item_id: Option<i32>,
|
||||
pub position_ms: u32,
|
||||
pub is_playing: bool,
|
||||
pub items: Vec<(Track, i32)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
|
||||
Reference in New Issue
Block a user