@@ -18,11 +18,12 @@ pub struct PlayerState {
1818pub struct MpvEngine {
1919 mpv : Option < Mpv > ,
2020 current_url : Option < String > ,
21+ audio_logged_playing : bool ,
2122}
2223
2324impl MpvEngine {
2425 pub fn new ( ) -> Self {
25- Self { mpv : None , current_url : None }
26+ Self { mpv : None , current_url : None , audio_logged_playing : false }
2627 }
2728
2829 /// Create a new Mpv instance with the provided options.
@@ -31,6 +32,7 @@ impl MpvEngine {
3132 /// before calling `loadfile`.
3233 pub fn create ( & mut self , options : & [ ( & str , & str ) ] ) -> Result < & mut Mpv , String > {
3334 self . stop ( ) ;
35+ tracing:: info!( "[MPV] create with options: {:?}" , options) ;
3436 let opts: Vec < ( String , String ) > = options
3537 . iter ( )
3638 . map ( |( k, v) | ( k. to_string ( ) , v. to_string ( ) ) )
@@ -46,11 +48,105 @@ impl MpvEngine {
4648 Ok ( self . mpv . as_mut ( ) . unwrap ( ) )
4749 }
4850
51+ /// Set audio properties after mpv_initialize. Options like `aid` and `mute`
52+ /// must be set as properties (not init options) because `mpv_set_option_string`
53+ /// rejects them with MPV_ERROR_OPTION_ERROR (-7) on many libmpv builds.
54+ pub fn configure_audio ( & self ) -> Result < ( ) , String > {
55+ let mpv = self . mpv . as_ref ( ) . ok_or ( "no mpv instance" ) ?;
56+ if let Err ( e) = mpv. set_property ( "aid" , "auto" ) {
57+ tracing:: warn!( "[MPV] set aid=auto failed: {e}" ) ;
58+ }
59+ if let Err ( e) = mpv. set_property ( "mute" , false ) {
60+ tracing:: warn!( "[MPV] set mute=false failed: {e}" ) ;
61+ }
62+ if let Err ( e) = mpv. set_property ( "volume" , 100.0_f64 ) {
63+ tracing:: warn!( "[MPV] set volume=100 failed: {e}" ) ;
64+ }
65+ self . log_audio_state ( "after configure_audio" ) ;
66+ Ok ( ( ) )
67+ }
68+
4969 /// Issue the loadfile command. Must be called AFTER render context is attached.
5070 pub fn loadfile ( & self , url : & str ) -> Result < ( ) , String > {
5171 let mpv = self . mpv . as_ref ( ) . ok_or ( "no mpv instance" ) ?;
5272 mpv. command ( "loadfile" , & [ url, "replace" ] )
53- . map_err ( |e| format ! ( "loadfile: {}" , e) )
73+ . map_err ( |e| format ! ( "loadfile: {}" , e) ) ?;
74+ self . log_audio_state ( "after loadfile" ) ;
75+ Ok ( ( ) )
76+ }
77+
78+ /// Log audio-related mpv properties to help diagnose "no sound" reports.
79+ /// Called both right after loadfile (before mpv has picked an output) and
80+ /// from a delayed check once the decoder has started.
81+ pub fn log_audio_state ( & self , stage : & str ) {
82+ let Some ( ref mpv) = self . mpv else { return } ;
83+ let get_str = |k : & str | {
84+ mpv. get_property :: < String > ( k)
85+ . unwrap_or_else ( |_| "<unset>" . to_string ( ) )
86+ } ;
87+ let get_bool = |k : & str | {
88+ mpv. get_property :: < bool > ( k)
89+ . map ( |v| v. to_string ( ) )
90+ . unwrap_or_else ( |_| "<unset>" . to_string ( ) )
91+ } ;
92+ let get_f64 = |k : & str | {
93+ mpv. get_property :: < f64 > ( k)
94+ . map ( |v| v. to_string ( ) )
95+ . unwrap_or_else ( |_| "<unset>" . to_string ( ) )
96+ } ;
97+ tracing:: info!(
98+ "[MPV audio {stage}] current-ao={} audio-device={} aid={} mute={} volume={} ao-volume={} audio-codec={} audio-params={} track-count={} track-list={}" ,
99+ get_str( "current-ao" ) ,
100+ get_str( "audio-device" ) ,
101+ get_str( "aid" ) ,
102+ get_bool( "mute" ) ,
103+ get_f64( "volume" ) ,
104+ get_f64( "ao-volume" ) ,
105+ get_str( "audio-codec" ) ,
106+ get_str( "audio-params" ) ,
107+ get_str( "track-list/count" ) ,
108+ get_str( "track-list" ) ,
109+ ) ;
110+ }
111+
112+ /// If mpv ended up with `aid=no` despite available audio tracks, force
113+ /// `aid=auto` so the audio track gets selected. This can happen when a
114+ /// system-wide config or internal mpv logic disables audio after our
115+ /// initial `configure_audio()` call.
116+ fn ensure_audio_selected ( & self ) {
117+ let Some ( ref mpv) = self . mpv else { return } ;
118+ let aid = mpv. get_property :: < String > ( "aid" ) . unwrap_or_default ( ) ;
119+ if aid != "no" {
120+ return ;
121+ }
122+ let track_count = mpv
123+ . get_property :: < String > ( "track-list/count" )
124+ . and_then ( |s| s. parse :: < i64 > ( ) . map_err ( |_| libmpv2:: Error :: Null ) )
125+ . unwrap_or ( 0 ) ;
126+ if track_count == 0 {
127+ return ;
128+ }
129+
130+ let current_ao = mpv
131+ . get_property :: < String > ( "current-ao" )
132+ . unwrap_or_default ( ) ;
133+ if current_ao. is_empty ( ) || current_ao == "<unset>" {
134+ tracing:: error!(
135+ "[MPV] NO AUDIO OUTPUT DRIVER — libmpv has no AO backends compiled in. \
136+ Rebuild libmpv with audio support: \
137+ sudo apt-get install libpulse-dev libasound2-dev libpipewire-0.3-dev && \
138+ ./scripts/build-libmpv.sh linux"
139+ ) ;
140+ return ;
141+ }
142+
143+ tracing:: warn!(
144+ "[MPV] aid=no despite {track_count} tracks — forcing aid=auto"
145+ ) ;
146+ if let Err ( e) = mpv. set_property ( "aid" , "auto" ) {
147+ tracing:: error!( "[MPV] failed to force aid=auto: {e}" ) ;
148+ }
149+ self . log_audio_state ( "after force-aid" ) ;
54150 }
55151
56152 /// Record the current URL (called by MpvState after loadfile succeeds).
@@ -65,6 +161,7 @@ impl MpvEngine {
65161 }
66162 self . mpv = None ;
67163 self . current_url = None ;
164+ self . audio_logged_playing = false ;
68165 }
69166
70167 pub fn play ( & self ) -> Result < ( ) , String > {
@@ -151,7 +248,7 @@ impl MpvEngine {
151248 . map_err ( |e| e. to_string ( ) )
152249 }
153250
154- pub fn get_state ( & self ) -> PlayerState {
251+ pub fn get_state ( & mut self ) -> PlayerState {
155252 let mut state = PlayerState {
156253 current_url : self . current_url . clone ( ) ,
157254 volume : 100.0 ,
@@ -164,6 +261,11 @@ impl MpvEngine {
164261 state. volume = mpv. get_property :: < f64 > ( "volume" ) . unwrap_or ( 100.0 ) ;
165262 state. is_playing = !state. is_paused && state. current_url . is_some ( ) ;
166263 }
264+ if !self . audio_logged_playing && state. position > 0.0 {
265+ self . audio_logged_playing = true ;
266+ self . log_audio_state ( "playing" ) ;
267+ self . ensure_audio_selected ( ) ;
268+ }
167269 state
168270 }
169271}
0 commit comments