feat(chromecast): added play_next feature

This commit is contained in:
Tsiry Sandratraina
2023-01-29 00:58:30 +03:00
parent b21b6ebc5e
commit eb2bae3c73
10 changed files with 182 additions and 14 deletions

2
Cargo.lock generated
View File

@@ -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",

View File

@@ -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"

View File

@@ -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!()
}

View File

@@ -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![],
});
}
}

View File

@@ -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!()
}

View File

@@ -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>;

View File

@@ -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!()
}

View File

@@ -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(());
}

View File

@@ -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> =

View File

@@ -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)]