mirror of
https://github.com/librespot-org/librespot.git
synced 2025-10-03 09:49:31 +02:00
Merge f5cf187142
into a407beaa45
This commit is contained in:
commit
545a3d17e7
9 changed files with 1640 additions and 15 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -1861,8 +1861,11 @@ dependencies = [
|
||||||
"sha1",
|
"sha1",
|
||||||
"sysinfo",
|
"sysinfo",
|
||||||
"thiserror 2.0.16",
|
"thiserror 2.0.16",
|
||||||
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
"url",
|
"url",
|
||||||
|
"zbus",
|
||||||
|
"zvariant",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -37,7 +37,7 @@ repository = "https://github.com/librespot-org/librespot"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["native-tls", "rodio-backend", "with-libmdns"]
|
default = ["native-tls", "rodio-backend", "with-libmdns", "with-mpris"]
|
||||||
|
|
||||||
# TLS backends (mutually exclusive - compile-time checks in oauth/src/lib.rs)
|
# TLS backends (mutually exclusive - compile-time checks in oauth/src/lib.rs)
|
||||||
# Note: Feature validation is in oauth crate since it's compiled first in the dependency tree.
|
# Note: Feature validation is in oauth crate since it's compiled first in the dependency tree.
|
||||||
|
@ -133,6 +133,10 @@ with-dns-sd = ["librespot-discovery/with-dns-sd"]
|
||||||
# data.
|
# data.
|
||||||
passthrough-decoder = ["librespot-playback/passthrough-decoder"]
|
passthrough-decoder = ["librespot-playback/passthrough-decoder"]
|
||||||
|
|
||||||
|
# MPRIS: Allow external tool to have access to playback
|
||||||
|
# status, metadata and to control the player.
|
||||||
|
with-mpris = ["dep:zbus", "dep:zvariant"]
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "librespot"
|
name = "librespot"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
@ -181,7 +185,10 @@ tokio = { version = "1", features = [
|
||||||
"sync",
|
"sync",
|
||||||
"process",
|
"process",
|
||||||
] }
|
] }
|
||||||
|
time = { version = "0.3", features = ["formatting"] }
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
|
zbus = { version = "5", default-features = false, features = ["tokio"], optional = true }
|
||||||
|
zvariant = { version = "5", default-features = false, optional = true }
|
||||||
|
|
||||||
[package.metadata.deb]
|
[package.metadata.deb]
|
||||||
maintainer = "Librespot Organization <noreply@github.com>"
|
maintainer = "Librespot Organization <noreply@github.com>"
|
||||||
|
|
|
@ -126,6 +126,7 @@ enum SpircCommand {
|
||||||
RepeatTrack(bool),
|
RepeatTrack(bool),
|
||||||
Disconnect { pause: bool },
|
Disconnect { pause: bool },
|
||||||
SetPosition(u32),
|
SetPosition(u32),
|
||||||
|
SeekOffset(i32),
|
||||||
SetVolume(u16),
|
SetVolume(u16),
|
||||||
Activate,
|
Activate,
|
||||||
Load(LoadRequest),
|
Load(LoadRequest),
|
||||||
|
@ -139,6 +140,7 @@ const VOLUME_UPDATE_DELAY: Duration = Duration::from_millis(500);
|
||||||
const UPDATE_STATE_DELAY: Duration = Duration::from_millis(200);
|
const UPDATE_STATE_DELAY: Duration = Duration::from_millis(200);
|
||||||
|
|
||||||
/// The spotify connect handle
|
/// The spotify connect handle
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Spirc {
|
pub struct Spirc {
|
||||||
commands: mpsc::UnboundedSender<SpircCommand>,
|
commands: mpsc::UnboundedSender<SpircCommand>,
|
||||||
}
|
}
|
||||||
|
@ -384,6 +386,13 @@ impl Spirc {
|
||||||
Ok(self.commands.send(SpircCommand::Load(command))?)
|
Ok(self.commands.send(SpircCommand::Load(command))?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Seek to given offset.
|
||||||
|
///
|
||||||
|
/// Does nothing if we are not the active device.
|
||||||
|
pub fn seek_offset(&self, offset_ms: i32) -> Result<(), Error> {
|
||||||
|
Ok(self.commands.send(SpircCommand::SeekOffset(offset_ms))?)
|
||||||
|
}
|
||||||
|
|
||||||
/// Disconnects the current device and pauses the playback according the value.
|
/// Disconnects the current device and pauses the playback according the value.
|
||||||
///
|
///
|
||||||
/// Does nothing if we are not the active device.
|
/// Does nothing if we are not the active device.
|
||||||
|
@ -650,6 +659,7 @@ impl SpircTask {
|
||||||
SpircCommand::Repeat(repeat) => self.handle_repeat_context(repeat)?,
|
SpircCommand::Repeat(repeat) => self.handle_repeat_context(repeat)?,
|
||||||
SpircCommand::RepeatTrack(repeat) => self.handle_repeat_track(repeat),
|
SpircCommand::RepeatTrack(repeat) => self.handle_repeat_track(repeat),
|
||||||
SpircCommand::SetPosition(position) => self.handle_seek(position),
|
SpircCommand::SetPosition(position) => self.handle_seek(position),
|
||||||
|
SpircCommand::SeekOffset(offset) => self.handle_seek_offset(offset),
|
||||||
SpircCommand::SetVolume(volume) => self.set_volume(volume),
|
SpircCommand::SetVolume(volume) => self.set_volume(volume),
|
||||||
SpircCommand::Load(command) => self.handle_load(command, None).await?,
|
SpircCommand::Load(command) => self.handle_load(command, None).await?,
|
||||||
};
|
};
|
||||||
|
@ -1460,6 +1470,25 @@ impl SpircTask {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_seek_offset(&mut self, offset_ms: i32) {
|
||||||
|
let position_ms = match self.play_status {
|
||||||
|
SpircPlayStatus::Stopped => return,
|
||||||
|
SpircPlayStatus::LoadingPause { position_ms }
|
||||||
|
| SpircPlayStatus::LoadingPlay { position_ms }
|
||||||
|
| SpircPlayStatus::Paused { position_ms, .. } => position_ms,
|
||||||
|
SpircPlayStatus::Playing {
|
||||||
|
nominal_start_time, ..
|
||||||
|
} => {
|
||||||
|
let now = self.now_ms();
|
||||||
|
(now - nominal_start_time) as u32
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let position_ms = ((position_ms as i32) + offset_ms).max(0) as u32;
|
||||||
|
|
||||||
|
self.handle_seek(position_ms);
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_shuffle(&mut self, shuffle: bool) -> Result<(), Error> {
|
fn handle_shuffle(&mut self, shuffle: bool) -> Result<(), Error> {
|
||||||
self.player.emit_shuffle_changed_event(shuffle);
|
self.player.emit_shuffle_changed_event(shuffle);
|
||||||
self.connect_state.handle_shuffle(shuffle)
|
self.connect_state.handle_shuffle(shuffle)
|
||||||
|
|
|
@ -406,7 +406,7 @@ fn launch_libmdns(
|
||||||
}
|
}
|
||||||
.map_err(|e| DiscoveryError::DnsSdError(Box::new(e)))?;
|
.map_err(|e| DiscoveryError::DnsSdError(Box::new(e)))?;
|
||||||
|
|
||||||
let svc = responder.register(&DNS_SD_SERVICE_NAME, &name, port, &TXT_RECORD);
|
let svc = responder.register(DNS_SD_SERVICE_NAME, &name, port, &TXT_RECORD);
|
||||||
|
|
||||||
let _ = shutdown_rx.blocking_recv();
|
let _ = shutdown_rx.blocking_recv();
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,7 @@ pub enum UniqueFields {
|
||||||
Track {
|
Track {
|
||||||
artists: ArtistsWithRole,
|
artists: ArtistsWithRole,
|
||||||
album: String,
|
album: String,
|
||||||
|
album_date: Date,
|
||||||
album_artists: Vec<String>,
|
album_artists: Vec<String>,
|
||||||
popularity: u8,
|
popularity: u8,
|
||||||
number: u32,
|
number: u32,
|
||||||
|
@ -80,6 +81,8 @@ impl AudioItem {
|
||||||
let uri_string = uri.to_uri()?;
|
let uri_string = uri.to_uri()?;
|
||||||
let album = track.album.name;
|
let album = track.album.name;
|
||||||
|
|
||||||
|
let album_date = track.album.date;
|
||||||
|
|
||||||
let album_artists = track
|
let album_artists = track
|
||||||
.album
|
.album
|
||||||
.artists
|
.artists
|
||||||
|
@ -113,6 +116,7 @@ impl AudioItem {
|
||||||
let unique_fields = UniqueFields::Track {
|
let unique_fields = UniqueFields::Track {
|
||||||
artists: track.artists_with_role,
|
artists: track.artists_with_role,
|
||||||
album,
|
album,
|
||||||
|
album_date,
|
||||||
album_artists,
|
album_artists,
|
||||||
popularity,
|
popularity,
|
||||||
number,
|
number,
|
||||||
|
|
|
@ -142,8 +142,8 @@ pub enum PlayerEvent {
|
||||||
},
|
},
|
||||||
// Fired when the player is stopped (e.g. by issuing a "stop" command to the player).
|
// Fired when the player is stopped (e.g. by issuing a "stop" command to the player).
|
||||||
Stopped {
|
Stopped {
|
||||||
play_request_id: u64,
|
play_request_id: Option<u64>,
|
||||||
track_id: SpotifyUri,
|
track_id: Option<SpotifyUri>,
|
||||||
},
|
},
|
||||||
// The player is delayed by loading a track.
|
// The player is delayed by loading a track.
|
||||||
Loading {
|
Loading {
|
||||||
|
@ -267,7 +267,8 @@ impl PlayerEvent {
|
||||||
play_request_id, ..
|
play_request_id, ..
|
||||||
}
|
}
|
||||||
| Stopped {
|
| Stopped {
|
||||||
play_request_id, ..
|
play_request_id: Some(play_request_id),
|
||||||
|
..
|
||||||
}
|
}
|
||||||
| PositionCorrection {
|
| PositionCorrection {
|
||||||
play_request_id, ..
|
play_request_id, ..
|
||||||
|
@ -679,6 +680,7 @@ enum PlayerState {
|
||||||
play_request_id: u64,
|
play_request_id: u64,
|
||||||
start_playback: bool,
|
start_playback: bool,
|
||||||
loader: Pin<Box<dyn FusedFuture<Output = Result<PlayerLoadedTrackData, ()>> + Send>>,
|
loader: Pin<Box<dyn FusedFuture<Output = Result<PlayerLoadedTrackData, ()>> + Send>>,
|
||||||
|
position_ms: u32,
|
||||||
},
|
},
|
||||||
Paused {
|
Paused {
|
||||||
track_id: SpotifyUri,
|
track_id: SpotifyUri,
|
||||||
|
@ -1226,6 +1228,7 @@ impl Future for PlayerInternal {
|
||||||
ref track_id,
|
ref track_id,
|
||||||
start_playback,
|
start_playback,
|
||||||
play_request_id,
|
play_request_id,
|
||||||
|
..
|
||||||
} = self.state
|
} = self.state
|
||||||
{
|
{
|
||||||
// The loader may be terminated if we are trying to load the same track
|
// The loader may be terminated if we are trying to load the same track
|
||||||
|
@ -1541,8 +1544,8 @@ impl PlayerInternal {
|
||||||
|
|
||||||
self.ensure_sink_stopped(false);
|
self.ensure_sink_stopped(false);
|
||||||
self.send_event(PlayerEvent::Stopped {
|
self.send_event(PlayerEvent::Stopped {
|
||||||
track_id,
|
track_id: Some(track_id),
|
||||||
play_request_id,
|
play_request_id: Some(play_request_id),
|
||||||
});
|
});
|
||||||
self.state = PlayerState::Stopped;
|
self.state = PlayerState::Stopped;
|
||||||
}
|
}
|
||||||
|
@ -2020,6 +2023,7 @@ impl PlayerInternal {
|
||||||
play_request_id,
|
play_request_id,
|
||||||
start_playback: play,
|
start_playback: play,
|
||||||
loader,
|
loader,
|
||||||
|
position_ms,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -2163,7 +2167,57 @@ impl PlayerInternal {
|
||||||
|
|
||||||
PlayerCommand::SetSession(session) => self.session = session,
|
PlayerCommand::SetSession(session) => self.session = session,
|
||||||
|
|
||||||
PlayerCommand::AddEventSender(sender) => self.event_senders.push(sender),
|
PlayerCommand::AddEventSender(sender) => {
|
||||||
|
// Send current player state to new event listener
|
||||||
|
match self.state {
|
||||||
|
PlayerState::Loading {
|
||||||
|
ref track_id,
|
||||||
|
play_request_id,
|
||||||
|
position_ms,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let _ = sender.send(PlayerEvent::Loading {
|
||||||
|
play_request_id,
|
||||||
|
track_id: track_id.clone(),
|
||||||
|
position_ms,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
PlayerState::Paused {
|
||||||
|
ref track_id,
|
||||||
|
play_request_id,
|
||||||
|
stream_position_ms,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let _ = sender.send(PlayerEvent::Paused {
|
||||||
|
play_request_id,
|
||||||
|
track_id: track_id.clone(),
|
||||||
|
position_ms: stream_position_ms,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
PlayerState::Playing { ref audio_item, .. } => {
|
||||||
|
let audio_item = Box::new(audio_item.clone());
|
||||||
|
let _ = sender.send(PlayerEvent::TrackChanged { audio_item });
|
||||||
|
}
|
||||||
|
PlayerState::EndOfTrack {
|
||||||
|
play_request_id,
|
||||||
|
ref track_id,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let _ = sender.send(PlayerEvent::EndOfTrack {
|
||||||
|
play_request_id,
|
||||||
|
track_id: track_id.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
PlayerState::Invalid | PlayerState::Stopped => {
|
||||||
|
let _ = sender.send(PlayerEvent::Stopped {
|
||||||
|
play_request_id: None,
|
||||||
|
track_id: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.event_senders.push(sender);
|
||||||
|
}
|
||||||
|
|
||||||
PlayerCommand::SetSinkEventCallback(callback) => self.sink_event_callback = callback,
|
PlayerCommand::SetSinkEventCallback(callback) => self.sink_event_callback = callback,
|
||||||
|
|
||||||
|
|
46
src/main.rs
46
src/main.rs
|
@ -41,6 +41,11 @@ use url::Url;
|
||||||
mod player_event_handler;
|
mod player_event_handler;
|
||||||
use player_event_handler::{EventHandler, run_program_on_sink_events};
|
use player_event_handler::{EventHandler, run_program_on_sink_events};
|
||||||
|
|
||||||
|
#[cfg(feature = "with-mpris")]
|
||||||
|
mod mpris_event_handler;
|
||||||
|
#[cfg(feature = "with-mpris")]
|
||||||
|
use mpris_event_handler::MprisEventHandler;
|
||||||
|
|
||||||
fn device_id(name: &str) -> String {
|
fn device_id(name: &str) -> String {
|
||||||
HEXLOWER.encode(&Sha1::digest(name.as_bytes()))
|
HEXLOWER.encode(&Sha1::digest(name.as_bytes()))
|
||||||
}
|
}
|
||||||
|
@ -270,6 +275,7 @@ async fn get_setup() -> Setup {
|
||||||
#[cfg(feature = "passthrough-decoder")]
|
#[cfg(feature = "passthrough-decoder")]
|
||||||
const PASSTHROUGH: &str = "passthrough";
|
const PASSTHROUGH: &str = "passthrough";
|
||||||
const PASSWORD: &str = "password";
|
const PASSWORD: &str = "password";
|
||||||
|
const POSITION_UPDATE_INTERVAL: &str = "position-update-interval";
|
||||||
const PROXY: &str = "proxy";
|
const PROXY: &str = "proxy";
|
||||||
const QUIET: &str = "quiet";
|
const QUIET: &str = "quiet";
|
||||||
const SYSTEM_CACHE: &str = "system-cache";
|
const SYSTEM_CACHE: &str = "system-cache";
|
||||||
|
@ -315,6 +321,7 @@ async fn get_setup() -> Setup {
|
||||||
#[cfg(feature = "passthrough-decoder")]
|
#[cfg(feature = "passthrough-decoder")]
|
||||||
const PASSTHROUGH_SHORT: &str = "P";
|
const PASSTHROUGH_SHORT: &str = "P";
|
||||||
const PASSWORD_SHORT: &str = "p";
|
const PASSWORD_SHORT: &str = "p";
|
||||||
|
const POSITION_UPDATE_INTERVAL_SHORT: &str = ""; // no short flag
|
||||||
const EMIT_SINK_EVENTS_SHORT: &str = "Q";
|
const EMIT_SINK_EVENTS_SHORT: &str = "Q";
|
||||||
const QUIET_SHORT: &str = "q";
|
const QUIET_SHORT: &str = "q";
|
||||||
const INITIAL_VOLUME_SHORT: &str = "R";
|
const INITIAL_VOLUME_SHORT: &str = "R";
|
||||||
|
@ -625,6 +632,12 @@ async fn get_setup() -> Setup {
|
||||||
"Knee width (dB) of the dynamic limiter from 0.0 to 10.0. Defaults to 5.0.",
|
"Knee width (dB) of the dynamic limiter from 0.0 to 10.0. Defaults to 5.0.",
|
||||||
"KNEE",
|
"KNEE",
|
||||||
)
|
)
|
||||||
|
.optopt(
|
||||||
|
POSITION_UPDATE_INTERVAL_SHORT,
|
||||||
|
POSITION_UPDATE_INTERVAL,
|
||||||
|
"Maximum interval in ms for player to send a position event. Defaults to no forced position update.",
|
||||||
|
"POSITION_UPDATE",
|
||||||
|
)
|
||||||
.optopt(
|
.optopt(
|
||||||
ZEROCONF_PORT_SHORT,
|
ZEROCONF_PORT_SHORT,
|
||||||
ZEROCONF_PORT,
|
ZEROCONF_PORT,
|
||||||
|
@ -1800,6 +1813,22 @@ async fn get_setup() -> Setup {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let position_update_interval = opt_str(POSITION_UPDATE_INTERVAL).as_deref().map(
|
||||||
|
|position_update| match position_update.parse::<u64>() {
|
||||||
|
Ok(value) => Duration::from_millis(value),
|
||||||
|
_ => {
|
||||||
|
invalid_error_msg(
|
||||||
|
POSITION_UPDATE_INTERVAL,
|
||||||
|
POSITION_UPDATE_INTERVAL_SHORT,
|
||||||
|
position_update,
|
||||||
|
"Integer value in ms",
|
||||||
|
"None",
|
||||||
|
);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
#[cfg(feature = "passthrough-decoder")]
|
#[cfg(feature = "passthrough-decoder")]
|
||||||
let passthrough = opt_present(PASSTHROUGH);
|
let passthrough = opt_present(PASSTHROUGH);
|
||||||
#[cfg(not(feature = "passthrough-decoder"))]
|
#[cfg(not(feature = "passthrough-decoder"))]
|
||||||
|
@ -1818,7 +1847,7 @@ async fn get_setup() -> Setup {
|
||||||
normalisation_release_cf,
|
normalisation_release_cf,
|
||||||
normalisation_knee_db,
|
normalisation_knee_db,
|
||||||
ditherer,
|
ditherer,
|
||||||
position_update_interval: None,
|
position_update_interval,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1991,6 +2020,14 @@ async fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "with-mpris")]
|
||||||
|
let mpris = MprisEventHandler::spawn(player.clone(), &setup.connect_config.name, None)
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
error!("could not initialize MPRIS: {e}");
|
||||||
|
exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
credentials = async {
|
credentials = async {
|
||||||
|
@ -2044,6 +2081,10 @@ async fn main() {
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "with-mpris")]
|
||||||
|
mpris.set_spirc(spirc_.clone());
|
||||||
|
|
||||||
spirc = Some(spirc_);
|
spirc = Some(spirc_);
|
||||||
spirc_task = Some(Box::pin(spirc_task_));
|
spirc_task = Some(Box::pin(spirc_task_));
|
||||||
|
|
||||||
|
@ -2089,6 +2130,9 @@ async fn main() {
|
||||||
|
|
||||||
let mut shutdown_tasks = tokio::task::JoinSet::new();
|
let mut shutdown_tasks = tokio::task::JoinSet::new();
|
||||||
|
|
||||||
|
#[cfg(feature = "with-mpris")]
|
||||||
|
shutdown_tasks.spawn(mpris.quit_and_join());
|
||||||
|
|
||||||
// Shutdown spirc if necessary
|
// Shutdown spirc if necessary
|
||||||
if let Some(spirc) = spirc {
|
if let Some(spirc) = spirc {
|
||||||
if let Err(e) = spirc.shutdown() {
|
if let Err(e) = spirc.shutdown() {
|
||||||
|
|
1474
src/mpris_event_handler.rs
Normal file
1474
src/mpris_event_handler.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -61,6 +61,7 @@ impl EventHandler {
|
||||||
UniqueFields::Track {
|
UniqueFields::Track {
|
||||||
artists,
|
artists,
|
||||||
album,
|
album,
|
||||||
|
album_date,
|
||||||
album_artists,
|
album_artists,
|
||||||
popularity,
|
popularity,
|
||||||
number,
|
number,
|
||||||
|
@ -81,6 +82,10 @@ impl EventHandler {
|
||||||
album_artists.join("\n"),
|
album_artists.join("\n"),
|
||||||
);
|
);
|
||||||
env_vars.insert("ALBUM", album);
|
env_vars.insert("ALBUM", album);
|
||||||
|
env_vars.insert(
|
||||||
|
"ALBUM_DATE",
|
||||||
|
album_date.unix_timestamp().to_string(),
|
||||||
|
);
|
||||||
env_vars
|
env_vars
|
||||||
.insert("POPULARITY", popularity.to_string());
|
.insert("POPULARITY", popularity.to_string());
|
||||||
env_vars.insert("NUMBER", number.to_string());
|
env_vars.insert("NUMBER", number.to_string());
|
||||||
|
@ -104,13 +109,18 @@ impl EventHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PlayerEvent::Stopped { track_id, .. } => match track_id.to_id() {
|
PlayerEvent::Stopped { track_id, .. } => {
|
||||||
Err(e) => warn!("PlayerEvent::Stopped: Invalid track id: {e}"),
|
env_vars.insert("PLAYER_EVENT", "stopped".to_string());
|
||||||
Ok(id) => {
|
match track_id.map(|track_id| track_id.to_id()) {
|
||||||
env_vars.insert("PLAYER_EVENT", "stopped".to_string());
|
Some(Err(e)) => {
|
||||||
env_vars.insert("TRACK_ID", id);
|
warn!("PlayerEvent::Stopped: Invalid track id: {e}")
|
||||||
|
}
|
||||||
|
Some(Ok(id)) => {
|
||||||
|
env_vars.insert("TRACK_ID", id);
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
PlayerEvent::Playing {
|
PlayerEvent::Playing {
|
||||||
track_id,
|
track_id,
|
||||||
position_ms,
|
position_ms,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue