2018-02-20 22:09:48 +00:00
use futures ::sync ::mpsc ::UnboundedReceiver ;
2018-03-23 05:13:01 +00:00
use futures ::{ Async , Future , Poll , Stream } ;
2020-01-17 14:35:46 +00:00
use log ::{ error , info , trace , warn } ;
2019-11-05 19:34:43 +00:00
use sha1 ::{ Digest , Sha1 } ;
2021-03-12 22:05:38 +00:00
use std ::convert ::TryFrom ;
2017-01-10 16:31:12 +00:00
use std ::env ;
2021-01-18 13:30:24 +00:00
use std ::io ::{ stderr , Write } ;
2018-02-26 01:50:41 +00:00
use std ::mem ;
2021-01-25 01:22:25 +00:00
use std ::path ::Path ;
2017-02-21 23:25:04 +00:00
use std ::process ::exit ;
2017-01-10 16:31:12 +00:00
use std ::str ::FromStr ;
2020-01-24 01:26:16 +00:00
use std ::time ::Instant ;
2018-02-26 01:50:41 +00:00
use tokio_core ::reactor ::{ Core , Handle } ;
2018-01-21 20:52:31 +00:00
use tokio_io ::IoStream ;
2018-03-24 08:00:38 +00:00
use url ::Url ;
2015-07-19 20:36:14 +00:00
2019-10-09 16:05:08 +00:00
use librespot ::core ::authentication ::{ get_credentials , Credentials } ;
use librespot ::core ::cache ::Cache ;
2020-07-25 07:38:08 +00:00
use librespot ::core ::config ::{ ConnectConfig , DeviceType , SessionConfig , VolumeCtrl } ;
2021-01-18 13:30:24 +00:00
use librespot ::core ::session ::{ AuthenticationError , Session } ;
2019-10-09 16:05:08 +00:00
use librespot ::core ::version ;
2017-08-03 18:58:44 +00:00
2018-02-26 01:50:41 +00:00
use librespot ::connect ::discovery ::{ discovery , DiscoveryStream } ;
use librespot ::connect ::spirc ::{ Spirc , SpircTask } ;
2018-02-09 01:05:50 +00:00
use librespot ::playback ::audio_backend ::{ self , Sink , BACKENDS } ;
2021-03-12 22:05:38 +00:00
use librespot ::playback ::config ::{
AudioFormat , Bitrate , NormalisationMethod , NormalisationType , PlayerConfig ,
} ;
2018-09-11 16:53:18 +00:00
use librespot ::playback ::mixer ::{ self , Mixer , MixerConfig } ;
2021-02-24 20:39:42 +00:00
use librespot ::playback ::player ::{ NormalisationData , Player , PlayerEvent } ;
2016-03-16 00:05:05 +00:00
2018-02-15 23:16:38 +00:00
mod player_event_handler ;
2020-03-10 12:26:01 +00:00
use crate ::player_event_handler ::{ emit_sink_event , run_program_on_events } ;
2018-02-15 23:16:38 +00:00
2021-02-24 20:39:42 +00:00
const MILLIS : f32 = 1000.0 ;
2018-02-24 15:34:04 +00:00
fn device_id ( name : & str ) -> String {
2018-07-23 13:41:39 +00:00
hex ::encode ( Sha1 ::digest ( name . as_bytes ( ) ) )
2018-02-24 15:34:04 +00:00
}
2016-03-14 23:41:51 +00:00
fn usage ( program : & str , opts : & getopts ::Options ) -> String {
2015-07-19 20:36:14 +00:00
let brief = format! ( " Usage: {} [options] " , program ) ;
2017-01-29 16:25:09 +00:00
opts . usage ( & brief )
2015-07-19 20:36:14 +00:00
}
2017-01-10 16:31:12 +00:00
fn setup_logging ( verbose : bool ) {
2019-05-03 05:40:13 +00:00
let mut builder = env_logger ::Builder ::new ( ) ;
2017-01-10 16:31:12 +00:00
match env ::var ( " RUST_LOG " ) {
Ok ( config ) = > {
2019-05-03 05:40:13 +00:00
builder . parse_filters ( & config ) ;
builder . init ( ) ;
2017-01-10 16:31:12 +00:00
if verbose {
warn! ( " `--verbose` flag overidden by `RUST_LOG` environment variable " ) ;
}
}
Err ( _ ) = > {
if verbose {
2020-05-10 11:22:19 +00:00
builder . parse_filters ( " libmdns=info,librespot=trace " ) ;
2017-01-10 16:31:12 +00:00
} else {
2020-05-10 11:22:19 +00:00
builder . parse_filters ( " libmdns=info,librespot=info " ) ;
2017-01-10 16:31:12 +00:00
}
2019-05-03 05:40:13 +00:00
builder . init ( ) ;
2017-01-10 16:31:12 +00:00
}
}
}
fn list_backends ( ) {
println! ( " Available Backends : " ) ;
for ( & ( name , _ ) , idx ) in BACKENDS . iter ( ) . zip ( 0 .. ) {
if idx = = 0 {
println! ( " - {} (default) " , name ) ;
} else {
println! ( " - {} " , name ) ;
}
}
}
2021-02-23 18:35:57 +00:00
fn print_version ( ) {
println! (
" librespot {semver} {sha} (Built on {build_date}, Build ID: {build_id}) " ,
semver = version ::SEMVER ,
sha = version ::SHA_SHORT ,
build_date = version ::BUILD_DATE ,
build_id = version ::BUILD_ID
) ;
}
2017-02-22 04:17:04 +00:00
#[ derive(Clone) ]
2017-01-18 18:41:22 +00:00
struct Setup {
2021-03-12 22:05:38 +00:00
format : AudioFormat ,
backend : fn ( Option < String > , AudioFormat ) -> Box < dyn Sink > ,
2017-02-22 04:17:04 +00:00
device : Option < String > ,
2020-01-17 14:35:46 +00:00
mixer : fn ( Option < MixerConfig > ) -> Box < dyn Mixer > ,
2017-02-22 04:17:04 +00:00
2017-01-29 15:36:39 +00:00
cache : Option < Cache > ,
2017-08-03 18:31:15 +00:00
player_config : PlayerConfig ,
session_config : SessionConfig ,
connect_config : ConnectConfig ,
2018-09-11 16:53:18 +00:00
mixer_config : MixerConfig ,
2017-02-22 04:17:04 +00:00
credentials : Option < Credentials > ,
enable_discovery : bool ,
2018-01-30 20:38:54 +00:00
zeroconf_port : u16 ,
2018-02-20 22:09:48 +00:00
player_event_program : Option < String > ,
2020-03-10 12:26:01 +00:00
emit_sink_events : bool ,
2017-01-18 18:41:22 +00:00
}
fn setup ( args : & [ String ] ) -> Setup {
2017-01-10 16:31:12 +00:00
let mut opts = getopts ::Options ::new ( ) ;
2018-02-26 01:50:41 +00:00
opts . optopt (
" c " ,
" cache " ,
" Path to a directory where files will be cached. " ,
" CACHE " ,
2020-07-24 21:18:29 +00:00
) . optopt (
" " ,
" system-cache " ,
" Path to a directory where system files (credentials, volume) will be cached. Can be different from cache option value " ,
" SYTEMCACHE " ,
2018-03-23 05:13:01 +00:00
) . optflag ( " " , " disable-audio-cache " , " Disable caching of the audio data. " )
2021-02-23 18:35:57 +00:00
. optopt ( " n " , " name " , " Device name " , " NAME " )
2017-08-03 18:31:15 +00:00
. optopt ( " " , " device-type " , " Displayed device type " , " DEVICE_TYPE " )
2018-02-26 01:50:41 +00:00
. optopt (
" b " ,
" bitrate " ,
" Bitrate (96, 160 or 320). Defaults to 160 " ,
" BITRATE " ,
)
. optopt (
" " ,
" onevent " ,
" Run PROGRAM when playback is about to begin. " ,
" PROGRAM " ,
)
2020-03-10 12:26:01 +00:00
. optflag ( " " , " emit-sink-events " , " Run program set by --onevent before sink is opened and after it is closed. " )
2017-01-10 16:31:12 +00:00
. optflag ( " v " , " verbose " , " Enable verbose output " )
2021-02-23 18:35:57 +00:00
. optflag ( " V " , " version " , " Display librespot version string " )
2017-01-10 16:31:12 +00:00
. optopt ( " u " , " username " , " Username to sign in with " , " USERNAME " )
. optopt ( " p " , " password " , " Password " , " PASSWORD " )
2018-03-23 05:15:15 +00:00
. optopt ( " " , " proxy " , " HTTP proxy to use when connecting " , " PROXY " )
2018-07-03 11:09:22 +00:00
. optopt ( " " , " ap-port " , " Connect to AP with specified port. If no AP with that port are present fallback AP will be used. Available ports are usually 80, 443 and 4070 " , " AP_PORT " )
2017-02-22 04:17:04 +00:00
. optflag ( " " , " disable-discovery " , " Disable discovery mode " )
2018-02-26 01:50:41 +00:00
. optopt (
" " ,
" backend " ,
" Audio backend to use. Use '?' to list options " ,
" BACKEND " ,
)
. optopt (
" " ,
" device " ,
2018-11-01 16:40:42 +00:00
" Audio device to use. Use '?' to list options if using portaudio or alsa " ,
2018-02-26 01:50:41 +00:00
" DEVICE " ,
)
2021-03-12 22:05:38 +00:00
. optopt (
" " ,
" format " ,
2021-03-16 19:22:00 +00:00
" Output format (F32, S32 or S16). Defaults to S16 " ,
2021-03-12 22:05:38 +00:00
" FORMAT " ,
)
2020-05-02 10:14:53 +00:00
. optopt ( " " , " mixer " , " Mixer to use (alsa or softvol) " , " MIXER " )
2018-09-11 16:53:18 +00:00
. optopt (
" m " ,
" mixer-name " ,
" Alsa mixer name, e.g \" PCM \" or \" Master \" . Defaults to 'PCM' " ,
" MIXER_NAME " ,
)
. optopt (
" " ,
" mixer-card " ,
" Alsa mixer card, e.g \" hw:0 \" or similar from `aplay -l`. Defaults to 'default' " ,
" MIXER_CARD " ,
)
. optopt (
" " ,
" mixer-index " ,
" Alsa mixer index, Index of the cards mixer. Defaults to 0 " ,
" MIXER_INDEX " ,
)
2018-09-17 15:28:54 +00:00
. optflag (
" " ,
" mixer-linear-volume " ,
" Disable alsa's mapped volume scale (cubic). Default false " ,
)
2018-02-26 01:50:41 +00:00
. optopt (
" " ,
" initial-volume " ,
" Initial volume in %, once connected (must be from 0 to 100) " ,
" VOLUME " ,
)
. optopt (
" " ,
" zeroconf-port " ,
" The port the internal server advertised over zeroconf uses. " ,
" ZEROCONF_PORT " ,
)
. optflag (
" " ,
" enable-volume-normalisation " ,
" Play all tracks at the same volume " ,
)
2021-02-24 20:39:42 +00:00
. optopt (
" " ,
" normalisation-method " ,
" Specify the normalisation method to use - [basic, dynamic]. Default is dynamic. " ,
" NORMALISATION_METHOD " ,
)
2021-01-21 19:16:05 +00:00
. optopt (
" " ,
" normalisation-gain-type " ,
2021-01-30 02:31:16 +00:00
" Specify the normalisation gain type to use - [track, album]. Default is album. " ,
2021-01-21 19:16:05 +00:00
" GAIN_TYPE " ,
)
2018-02-26 01:50:41 +00:00
. optopt (
" " ,
" normalisation-pregain " ,
" Pregain (dB) applied by volume normalisation " ,
" PREGAIN " ,
2018-03-11 10:27:28 +00:00
)
2021-02-24 20:39:42 +00:00
. optopt (
" " ,
" normalisation-threshold " ,
" Threshold (dBFS) to prevent clipping. Default is -1.0. " ,
" THRESHOLD " ,
)
. optopt (
" " ,
" normalisation-attack " ,
" Attack time (ms) in which the dynamic limiter is reducing gain. Default is 5. " ,
" ATTACK " ,
)
. optopt (
" " ,
" normalisation-release " ,
" Release or decay time (ms) in which the dynamic limiter is restoring gain. Default is 100. " ,
" RELEASE " ,
)
. optopt (
" " ,
2021-03-14 13:28:16 +00:00
" normalisation-knee " ,
" Knee steepness of the dynamic limiter. Default is 1.0. " ,
" KNEE " ,
2021-02-24 20:39:42 +00:00
)
2020-07-25 07:38:08 +00:00
. optopt (
2018-03-11 10:27:28 +00:00
" " ,
2020-07-25 07:38:08 +00:00
" volume-ctrl " ,
" Volume control type - [linear, log, fixed]. Default is logarithmic " ,
" VOLUME_CTRL "
2019-11-05 19:34:43 +00:00
)
. optflag (
" " ,
" autoplay " ,
" autoplay similar songs when your music ends. " ,
2020-03-10 12:00:57 +00:00
)
. optflag (
" " ,
" disable-gapless " ,
" disable gapless playback. " ,
2021-01-07 06:42:38 +00:00
)
. optflag (
" " ,
" passthrough " ,
" Pass raw stream to output, only works for \" pipe \" . "
2018-02-26 01:50:41 +00:00
) ;
2016-03-14 23:41:51 +00:00
2015-07-19 20:36:14 +00:00
let matches = match opts . parse ( & args [ 1 .. ] ) {
2016-01-02 15:19:39 +00:00
Ok ( m ) = > m ,
Err ( f ) = > {
2019-10-08 09:31:18 +00:00
writeln! (
stderr ( ) ,
" error: {} \n {} " ,
f . to_string ( ) ,
usage ( & args [ 0 ] , & opts )
)
. unwrap ( ) ;
2017-01-05 13:25:14 +00:00
exit ( 1 ) ;
2016-04-24 11:15:53 +00:00
}
} ;
2016-03-13 20:45:31 +00:00
2021-02-23 18:35:57 +00:00
if matches . opt_present ( " version " ) {
print_version ( ) ;
exit ( 0 ) ;
}
2017-01-10 16:31:12 +00:00
let verbose = matches . opt_present ( " verbose " ) ;
setup_logging ( verbose ) ;
2018-02-26 01:50:41 +00:00
info! (
2021-02-17 14:13:57 +00:00
" librespot {semver} {sha} (Built on {build_date}, Build ID: {build_id}) " ,
semver = version ::SEMVER ,
sha = version ::SHA_SHORT ,
build_date = version ::BUILD_DATE ,
build_id = version ::BUILD_ID
2018-02-26 01:50:41 +00:00
) ;
2017-01-10 16:31:12 +00:00
let backend_name = matches . opt_str ( " backend " ) ;
if backend_name = = Some ( " ? " . into ( ) ) {
list_backends ( ) ;
exit ( 0 ) ;
}
2018-02-26 01:50:41 +00:00
let backend = audio_backend ::find ( backend_name ) . expect ( " Invalid backend " ) ;
2017-01-10 16:31:12 +00:00
2021-03-12 22:05:38 +00:00
let format = matches
. opt_str ( " format " )
. as_ref ( )
. map ( | format | AudioFormat ::try_from ( format ) . expect ( " Invalid output format " ) )
. unwrap_or ( AudioFormat ::default ( ) ) ;
2017-08-03 18:31:15 +00:00
let device = matches . opt_str ( " device " ) ;
2018-11-15 21:46:26 +00:00
if device = = Some ( " ? " . into ( ) ) {
2021-03-12 22:05:38 +00:00
backend ( device , format ) ;
2018-11-15 21:46:26 +00:00
exit ( 0 ) ;
}
2017-08-03 18:31:15 +00:00
2017-02-21 22:46:19 +00:00
let mixer_name = matches . opt_str ( " mixer " ) ;
2018-02-26 01:50:41 +00:00
let mixer = mixer ::find ( mixer_name . as_ref ( ) ) . expect ( " Invalid mixer " ) ;
2018-01-30 23:05:54 +00:00
2018-09-11 16:53:18 +00:00
let mixer_config = MixerConfig {
2019-10-08 09:31:18 +00:00
card : matches
. opt_str ( " mixer-card " )
. unwrap_or ( String ::from ( " default " ) ) ,
2018-11-01 16:40:42 +00:00
mixer : matches . opt_str ( " mixer-name " ) . unwrap_or ( String ::from ( " PCM " ) ) ,
index : matches
. opt_str ( " mixer-index " )
. map ( | index | index . parse ::< u32 > ( ) . unwrap ( ) )
. unwrap_or ( 0 ) ,
2018-09-17 15:28:54 +00:00
mapped_volume : ! matches . opt_present ( " mixer-linear-volume " ) ,
2018-09-11 16:53:18 +00:00
} ;
2021-01-25 01:22:25 +00:00
let cache = {
let audio_dir ;
let system_dir ;
if matches . opt_present ( " disable-audio-cache " ) {
audio_dir = None ;
system_dir = matches
. opt_str ( " system-cache " )
. or_else ( | | matches . opt_str ( " c " ) )
. map ( | p | p . into ( ) ) ;
} else {
let cache_dir = matches . opt_str ( " c " ) ;
audio_dir = cache_dir
. as_ref ( )
. map ( | p | AsRef ::< Path > ::as_ref ( p ) . join ( " files " ) ) ;
system_dir = matches
. opt_str ( " system-cache " )
. or_else ( | | cache_dir )
. map ( | p | p . into ( ) ) ;
}
2021-01-23 21:37:41 +00:00
2021-01-25 01:22:25 +00:00
match Cache ::new ( system_dir , audio_dir ) {
Ok ( cache ) = > Some ( cache ) ,
Err ( e ) = > {
warn! ( " Cannot create cache: {} " , e ) ;
None
}
2021-01-23 21:37:41 +00:00
}
} ;
2018-05-17 01:15:17 +00:00
2018-01-31 05:45:48 +00:00
let initial_volume = matches
. opt_str ( " initial-volume " )
. map ( | volume | {
2018-05-17 01:15:17 +00:00
let volume = volume . parse ::< u16 > ( ) . unwrap ( ) ;
if volume > 100 {
2018-01-31 05:45:48 +00:00
panic! ( " Initial volume must be in the range 0-100 " ) ;
2017-12-06 14:22:28 +00:00
}
2018-05-17 01:15:17 +00:00
( volume as i32 * 0xFFFF / 100 ) as u16
2019-11-05 19:34:43 +00:00
} )
. or_else ( | | cache . as_ref ( ) . and_then ( Cache ::volume ) )
2021-01-25 09:52:06 +00:00
. unwrap_or ( 0x8000 ) ;
2018-01-31 05:45:48 +00:00
2018-02-26 01:50:41 +00:00
let zeroconf_port = matches
. opt_str ( " zeroconf-port " )
. map ( | port | port . parse ::< u16 > ( ) . unwrap ( ) )
. unwrap_or ( 0 ) ;
2018-01-30 20:38:54 +00:00
2021-02-23 18:35:57 +00:00
let name = matches . opt_str ( " name " ) . unwrap_or ( " Librespot " . to_string ( ) ) ;
2017-01-18 17:07:20 +00:00
2017-08-03 18:31:15 +00:00
let credentials = {
let cached_credentials = cache . as_ref ( ) . and_then ( Cache ::credentials ) ;
2017-01-18 17:07:20 +00:00
2018-02-25 04:40:00 +00:00
let password = | username : & String | -> String {
write! ( stderr ( ) , " Password for {}: " , username ) . unwrap ( ) ;
stderr ( ) . flush ( ) . unwrap ( ) ;
rpassword ::read_password ( ) . unwrap ( )
} ;
2017-08-03 18:31:15 +00:00
get_credentials (
matches . opt_str ( " username " ) ,
matches . opt_str ( " password " ) ,
2018-02-25 04:40:00 +00:00
cached_credentials ,
2018-02-26 01:50:41 +00:00
password ,
2017-08-03 18:31:15 +00:00
)
} ;
2017-01-18 17:07:20 +00:00
2017-08-03 18:31:15 +00:00
let session_config = {
2018-02-24 15:34:04 +00:00
let device_id = device_id ( & name ) ;
2017-02-22 04:17:04 +00:00
2017-08-03 18:31:15 +00:00
SessionConfig {
2021-02-09 18:42:56 +00:00
user_agent : version ::VERSION_STRING . to_string ( ) ,
2017-08-03 18:31:15 +00:00
device_id : device_id ,
2018-03-24 08:00:38 +00:00
proxy : matches . opt_str ( " proxy " ) . or ( std ::env ::var ( " http_proxy " ) . ok ( ) ) . map (
| s | {
match Url ::parse ( & s ) {
2018-07-03 11:09:22 +00:00
Ok ( url ) = > {
2020-02-16 08:52:09 +00:00
if url . host ( ) . is_none ( ) | | url . port_or_known_default ( ) . is_none ( ) {
2018-07-03 11:09:22 +00:00
panic! ( " Invalid proxy url, only urls on the format \" http://host:port \" are allowed " ) ;
}
if url . scheme ( ) ! = " http " {
panic! ( " Only unsecure http:// proxies are supported " ) ;
}
url
} ,
Err ( err ) = > panic! ( " Invalid proxy url: {} , only urls on the format \" http://host:port \" are allowed " , err )
2018-03-24 08:00:38 +00:00
}
} ,
) ,
2018-07-03 11:09:22 +00:00
ap_port : matches
. opt_str ( " ap-port " )
. map ( | port | port . parse ::< u16 > ( ) . expect ( " Invalid port " ) ) ,
2017-08-03 18:31:15 +00:00
}
2017-01-10 16:31:12 +00:00
} ;
2021-01-07 06:42:38 +00:00
let passthrough = matches . opt_present ( " passthrough " ) ;
2017-08-03 18:31:15 +00:00
let player_config = {
2018-02-26 01:50:41 +00:00
let bitrate = matches
. opt_str ( " b " )
. as_ref ( )
2017-08-03 18:31:15 +00:00
. map ( | bitrate | Bitrate ::from_str ( bitrate ) . expect ( " Invalid bitrate " ) )
. unwrap_or ( Bitrate ::default ( ) ) ;
2021-01-21 19:16:05 +00:00
let gain_type = matches
. opt_str ( " normalisation-gain-type " )
. as_ref ( )
. map ( | gain_type | {
NormalisationType ::from_str ( gain_type ) . expect ( " Invalid normalisation type " )
} )
. unwrap_or ( NormalisationType ::default ( ) ) ;
2021-02-24 20:39:42 +00:00
let normalisation_method = matches
. opt_str ( " normalisation-method " )
. as_ref ( )
. map ( | gain_type | {
NormalisationMethod ::from_str ( gain_type ) . expect ( " Invalid normalisation method " )
} )
. unwrap_or ( NormalisationMethod ::default ( ) ) ;
2017-08-03 18:31:15 +00:00
PlayerConfig {
bitrate : bitrate ,
2020-03-10 12:00:57 +00:00
gapless : ! matches . opt_present ( " disable-gapless " ) ,
2018-02-23 19:08:20 +00:00
normalisation : matches . opt_present ( " enable-volume-normalisation " ) ,
2021-02-24 20:39:42 +00:00
normalisation_method : normalisation_method ,
2021-01-21 19:16:05 +00:00
normalisation_type : gain_type ,
2018-02-26 01:50:41 +00:00
normalisation_pregain : matches
. opt_str ( " normalisation-pregain " )
2018-02-23 19:08:20 +00:00
. map ( | pregain | pregain . parse ::< f32 > ( ) . expect ( " Invalid pregain float value " ) )
. unwrap_or ( PlayerConfig ::default ( ) . normalisation_pregain ) ,
2021-02-24 20:39:42 +00:00
normalisation_threshold : NormalisationData ::db_to_ratio (
matches
. opt_str ( " normalisation-threshold " )
. map ( | threshold | {
threshold
. parse ::< f32 > ( )
. expect ( " Invalid threshold float value " )
} )
. unwrap_or ( PlayerConfig ::default ( ) . normalisation_threshold ) ,
) ,
normalisation_attack : matches
. opt_str ( " normalisation-attack " )
. map ( | attack | attack . parse ::< f32 > ( ) . expect ( " Invalid attack float value " ) )
. unwrap_or ( PlayerConfig ::default ( ) . normalisation_attack * MILLIS )
/ MILLIS ,
normalisation_release : matches
. opt_str ( " normalisation-release " )
. map ( | release | release . parse ::< f32 > ( ) . expect ( " Invalid release float value " ) )
. unwrap_or ( PlayerConfig ::default ( ) . normalisation_release * MILLIS )
/ MILLIS ,
2021-03-14 13:28:16 +00:00
normalisation_knee : matches
. opt_str ( " normalisation-knee " )
. map ( | knee | knee . parse ::< f32 > ( ) . expect ( " Invalid knee float value " ) )
. unwrap_or ( PlayerConfig ::default ( ) . normalisation_knee ) ,
2021-01-07 06:42:38 +00:00
passthrough ,
2017-08-03 18:31:15 +00:00
}
} ;
let connect_config = {
2018-02-26 01:50:41 +00:00
let device_type = matches
. opt_str ( " device-type " )
. as_ref ( )
2017-08-03 18:31:15 +00:00
. map ( | device_type | DeviceType ::from_str ( device_type ) . expect ( " Invalid device type " ) )
. unwrap_or ( DeviceType ::default ( ) ) ;
2020-07-25 07:38:08 +00:00
let volume_ctrl = matches
. opt_str ( " volume-ctrl " )
. as_ref ( )
. map ( | volume_ctrl | VolumeCtrl ::from_str ( volume_ctrl ) . expect ( " Invalid volume ctrl type " ) )
. unwrap_or ( VolumeCtrl ::default ( ) ) ;
2017-08-03 18:31:15 +00:00
ConnectConfig {
name : name ,
device_type : device_type ,
2017-12-06 13:37:34 +00:00
volume : initial_volume ,
2020-07-25 07:38:08 +00:00
volume_ctrl : volume_ctrl ,
2019-11-05 19:34:43 +00:00
autoplay : matches . opt_present ( " autoplay " ) ,
2017-08-03 18:31:15 +00:00
}
} ;
let enable_discovery = ! matches . opt_present ( " disable-discovery " ) ;
2017-01-05 13:25:14 +00:00
2017-01-18 18:41:22 +00:00
Setup {
2021-03-12 22:05:38 +00:00
format : format ,
2017-01-18 18:41:22 +00:00
backend : backend ,
cache : cache ,
2017-08-03 18:31:15 +00:00
session_config : session_config ,
player_config : player_config ,
connect_config : connect_config ,
2017-01-18 18:41:22 +00:00
credentials : credentials ,
device : device ,
2017-02-22 04:17:04 +00:00
enable_discovery : enable_discovery ,
2018-01-30 20:38:54 +00:00
zeroconf_port : zeroconf_port ,
2017-02-22 04:17:04 +00:00
mixer : mixer ,
2018-09-11 16:53:18 +00:00
mixer_config : mixer_config ,
2018-02-20 22:09:48 +00:00
player_event_program : matches . opt_str ( " onevent " ) ,
2020-03-10 12:26:01 +00:00
emit_sink_events : matches . opt_present ( " emit-sink-events " ) ,
2017-01-18 18:41:22 +00:00
}
2017-01-10 16:31:12 +00:00
}
2017-02-22 04:17:04 +00:00
struct Main {
cache : Option < Cache > ,
2017-08-03 18:31:15 +00:00
player_config : PlayerConfig ,
session_config : SessionConfig ,
connect_config : ConnectConfig ,
2021-03-12 22:05:38 +00:00
format : AudioFormat ,
backend : fn ( Option < String > , AudioFormat ) -> Box < dyn Sink > ,
2017-02-22 04:17:04 +00:00
device : Option < String > ,
2020-01-17 14:35:46 +00:00
mixer : fn ( Option < MixerConfig > ) -> Box < dyn Mixer > ,
2018-09-11 16:53:18 +00:00
mixer_config : MixerConfig ,
2017-02-22 04:17:04 +00:00
handle : Handle ,
2017-01-18 18:41:22 +00:00
2017-02-22 04:17:04 +00:00
discovery : Option < DiscoveryStream > ,
signal : IoStream < ( ) > ,
2016-03-20 16:16:11 +00:00
2017-02-22 04:17:04 +00:00
spirc : Option < Spirc > ,
spirc_task : Option < SpircTask > ,
2021-01-18 13:30:24 +00:00
connect : Box < dyn Future < Item = Session , Error = AuthenticationError > > ,
2016-12-31 11:51:44 +00:00
2017-02-22 04:17:04 +00:00
shutdown : bool ,
2020-01-22 14:24:59 +00:00
last_credentials : Option < Credentials > ,
2020-01-23 08:09:26 +00:00
auto_connect_times : Vec < Instant > ,
2018-02-20 22:09:48 +00:00
player_event_channel : Option < UnboundedReceiver < PlayerEvent > > ,
player_event_program : Option < String > ,
2020-03-10 12:26:01 +00:00
emit_sink_events : bool ,
2017-02-22 04:17:04 +00:00
}
2017-01-18 18:41:22 +00:00
2017-02-22 04:17:04 +00:00
impl Main {
2017-08-03 18:31:15 +00:00
fn new ( handle : Handle , setup : Setup ) -> Main {
let mut task = Main {
2017-02-22 04:17:04 +00:00
handle : handle . clone ( ) ,
2017-08-03 18:31:15 +00:00
cache : setup . cache ,
session_config : setup . session_config ,
player_config : setup . player_config ,
connect_config : setup . connect_config ,
2021-03-12 22:05:38 +00:00
format : setup . format ,
2017-08-03 18:31:15 +00:00
backend : setup . backend ,
device : setup . device ,
mixer : setup . mixer ,
2018-09-11 16:53:18 +00:00
mixer_config : setup . mixer_config ,
2017-02-22 04:17:04 +00:00
connect : Box ::new ( futures ::future ::empty ( ) ) ,
discovery : None ,
spirc : None ,
spirc_task : None ,
shutdown : false ,
2020-01-22 14:24:59 +00:00
last_credentials : None ,
2020-01-23 08:09:26 +00:00
auto_connect_times : Vec ::new ( ) ,
2019-07-08 08:08:32 +00:00
signal : Box ::new ( tokio_signal ::ctrl_c ( ) . flatten_stream ( ) ) ,
2018-02-20 22:09:48 +00:00
player_event_channel : None ,
player_event_program : setup . player_event_program ,
2020-03-10 12:26:01 +00:00
emit_sink_events : setup . emit_sink_events ,
2017-08-03 18:31:15 +00:00
} ;
if setup . enable_discovery {
let config = task . connect_config . clone ( ) ;
let device_id = task . session_config . device_id . clone ( ) ;
2019-10-08 09:31:18 +00:00
task . discovery =
Some ( discovery ( & handle , config , device_id , setup . zeroconf_port ) . unwrap ( ) ) ;
2017-02-22 04:17:04 +00:00
}
2017-01-18 18:41:22 +00:00
2017-08-03 18:31:15 +00:00
if let Some ( credentials ) = setup . credentials {
task . credentials ( credentials ) ;
}
2017-04-28 22:24:55 +00:00
2017-08-03 18:31:15 +00:00
task
2017-02-22 04:17:04 +00:00
}
2017-01-18 18:41:22 +00:00
2017-02-22 04:17:04 +00:00
fn credentials ( & mut self , credentials : Credentials ) {
2020-01-22 14:24:59 +00:00
self . last_credentials = Some ( credentials . clone ( ) ) ;
2017-08-03 18:31:15 +00:00
let config = self . session_config . clone ( ) ;
2017-02-22 04:17:04 +00:00
let handle = self . handle . clone ( ) ;
2017-02-22 14:28:09 +00:00
2017-02-22 04:17:04 +00:00
let connection = Session ::connect ( config , credentials , self . cache . clone ( ) , handle ) ;
self . connect = connection ;
self . spirc = None ;
let task = mem ::replace ( & mut self . spirc_task , None ) ;
if let Some ( task ) = task {
self . handle . spawn ( task ) ;
}
}
}
impl Future for Main {
type Item = ( ) ;
type Error = ( ) ;
fn poll ( & mut self ) -> Poll < ( ) , ( ) > {
loop {
let mut progress = false ;
2019-10-08 09:31:18 +00:00
if let Some ( Async ::Ready ( Some ( creds ) ) ) =
self . discovery . as_mut ( ) . map ( | d | d . poll ( ) . unwrap ( ) )
{
2017-02-22 04:17:04 +00:00
if let Some ( ref spirc ) = self . spirc {
spirc . shutdown ( ) ;
}
2020-01-23 08:09:26 +00:00
self . auto_connect_times . clear ( ) ;
2017-02-22 04:17:04 +00:00
self . credentials ( creds ) ;
progress = true ;
2017-02-21 23:25:04 +00:00
}
2017-01-18 18:41:22 +00:00
2020-01-28 22:45:06 +00:00
match self . connect . poll ( ) {
Ok ( Async ::Ready ( session ) ) = > {
self . connect = Box ::new ( futures ::future ::empty ( ) ) ;
let mixer_config = self . mixer_config . clone ( ) ;
let mixer = ( self . mixer ) ( Some ( mixer_config ) ) ;
let player_config = self . player_config . clone ( ) ;
let connect_config = self . connect_config . clone ( ) ;
let audio_filter = mixer . get_audio_filter ( ) ;
2021-03-12 22:05:38 +00:00
let format = self . format ;
2020-01-28 22:45:06 +00:00
let backend = self . backend ;
let device = self . device . clone ( ) ;
let ( player , event_channel ) =
Player ::new ( player_config , session . clone ( ) , audio_filter , move | | {
2021-03-12 22:05:38 +00:00
( backend ) ( device , format )
2020-01-28 22:45:06 +00:00
} ) ;
2020-03-10 12:26:01 +00:00
if self . emit_sink_events {
if let Some ( player_event_program ) = & self . player_event_program {
let player_event_program = player_event_program . clone ( ) ;
player . set_sink_event_callback ( Some ( Box ::new ( move | sink_status | {
emit_sink_event ( sink_status , & player_event_program )
} ) ) ) ;
}
}
2020-01-28 22:45:06 +00:00
let ( spirc , spirc_task ) = Spirc ::new ( connect_config , session , player , mixer ) ;
self . spirc = Some ( spirc ) ;
self . spirc_task = Some ( spirc_task ) ;
self . player_event_channel = Some ( event_channel ) ;
progress = true ;
}
Ok ( Async ::NotReady ) = > ( ) ,
2020-01-28 22:51:26 +00:00
Err ( error ) = > {
error! ( " Could not connect to server: {} " , error ) ;
2020-01-28 22:45:06 +00:00
self . connect = Box ::new ( futures ::future ::empty ( ) ) ;
}
2017-02-22 04:17:04 +00:00
}
if let Async ::Ready ( Some ( ( ) ) ) = self . signal . poll ( ) . unwrap ( ) {
2018-04-30 12:21:49 +00:00
trace! ( " Ctrl-C received " ) ;
2017-02-22 04:17:04 +00:00
if ! self . shutdown {
if let Some ( ref spirc ) = self . spirc {
spirc . shutdown ( ) ;
2019-03-13 18:47:56 +00:00
} else {
return Ok ( Async ::Ready ( ( ) ) ) ;
2017-02-22 04:17:04 +00:00
}
self . shutdown = true ;
} else {
return Ok ( Async ::Ready ( ( ) ) ) ;
}
progress = true ;
}
2020-01-23 23:12:16 +00:00
let mut drop_spirc_and_try_to_reconnect = false ;
2017-02-22 04:17:04 +00:00
if let Some ( ref mut spirc_task ) = self . spirc_task {
if let Async ::Ready ( ( ) ) = spirc_task . poll ( ) . unwrap ( ) {
if self . shutdown {
return Ok ( Async ::Ready ( ( ) ) ) ;
} else {
2020-01-22 10:55:45 +00:00
warn! ( " Spirc shut down unexpectedly " ) ;
2020-01-23 23:12:16 +00:00
drop_spirc_and_try_to_reconnect = true ;
2017-02-22 04:17:04 +00:00
}
2020-01-23 08:51:09 +00:00
progress = true ;
2017-02-22 04:17:04 +00:00
}
}
2020-01-23 23:12:16 +00:00
if drop_spirc_and_try_to_reconnect {
self . spirc_task = None ;
2020-01-24 01:26:16 +00:00
while ( ! self . auto_connect_times . is_empty ( ) )
& & ( ( Instant ::now ( ) - self . auto_connect_times [ 0 ] ) . as_secs ( ) > 600 )
{
2020-01-23 10:10:55 +00:00
let _ = self . auto_connect_times . remove ( 0 ) ;
}
if let Some ( credentials ) = self . last_credentials . clone ( ) {
if self . auto_connect_times . len ( ) > = 5 {
warn! ( " Spirc shut down too often. Not reconnecting automatically. " ) ;
} else {
self . auto_connect_times . push ( Instant ::now ( ) ) ;
self . credentials ( credentials ) ;
}
}
}
2018-02-20 22:09:48 +00:00
if let Some ( ref mut player_event_channel ) = self . player_event_channel {
if let Async ::Ready ( Some ( event ) ) = player_event_channel . poll ( ) . unwrap ( ) {
2020-03-12 12:01:45 +00:00
progress = true ;
2018-02-20 22:09:48 +00:00
if let Some ( ref program ) = self . player_event_program {
2020-02-03 07:58:44 +00:00
if let Some ( child ) = run_program_on_events ( event , program ) {
let child = child
. expect ( " program failed to start " )
. map ( | status | {
if ! status . success ( ) {
error! ( " child exited with status {:?} " , status . code ( ) ) ;
}
} )
. map_err ( | e | error! ( " failed to wait on child process: {} " , e ) ) ;
self . handle . spawn ( child ) ;
}
2018-02-20 22:09:48 +00:00
}
}
}
2017-02-22 04:17:04 +00:00
if ! progress {
return Ok ( Async ::NotReady ) ;
}
}
}
}
fn main ( ) {
2018-02-13 15:46:10 +00:00
if env ::var ( " RUST_BACKTRACE " ) . is_err ( ) {
env ::set_var ( " RUST_BACKTRACE " , " full " )
}
2017-02-22 04:17:04 +00:00
let mut core = Core ::new ( ) . unwrap ( ) ;
let handle = core . handle ( ) ;
let args : Vec < String > = std ::env ::args ( ) . collect ( ) ;
2015-07-02 17:24:25 +00:00
2017-08-03 18:31:15 +00:00
core . run ( Main ::new ( handle , setup ( & args ) ) ) . unwrap ( )
2015-07-01 17:49:03 +00:00
}