@@ -372,12 +372,52 @@ func TestSetupMirrorNoAudioStillNegotiatesAudioSession(t *testing.T) {
372372 {name : "skip record" , skipRecord : true },
373373 } {
374374 t .Run (test .name , func (t * testing.T ) {
375- testSetupMirrorNoAudioStillNegotiatesAudioSession (t , test .skipRecord )
375+ testSetupMirrorAudioSessionNegotiation (t , audioSessionCase { skipRecord : test .skipRecord , noAudio : true } )
376376 })
377377 }
378378}
379379
380- func testSetupMirrorNoAudioStillNegotiatesAudioSession (t * testing.T , skipRecord bool ) {
380+ // A session that DOES carry audio must still set the receiver's volume, so the
381+ // -no-audio guard narrows that behaviour rather than removing it.
382+ func TestSetupMirrorWithAudioSetsReceiverVolume (t * testing.T ) {
383+ for _ , test := range []struct {
384+ name string
385+ skipRecord bool
386+ }{
387+ {name : "record" , skipRecord : false },
388+ {name : "skip record" , skipRecord : true },
389+ } {
390+ t .Run (test .name , func (t * testing.T ) {
391+ testSetupMirrorAudioSessionNegotiation (t , audioSessionCase {skipRecord : test .skipRecord , noAudio : false })
392+ })
393+ }
394+ }
395+
396+ // A no-audio session must not be able to write the receiver's volume through
397+ // the daemon's mute control either: unmuting sends 0 dB, which is full scale.
398+ func TestSetAudioMutedRefusesWhenSessionHasNoAudio (t * testing.T ) {
399+ for _ , muted := range []bool {true , false } {
400+ session := & MirrorSession {client : & AirPlayClient {}, sessionURI : "rtsp://example/session" , noAudio : true }
401+ err := session .SetAudioMuted (muted )
402+ if err == nil {
403+ t .Fatalf ("SetAudioMuted(%v) = nil, want a refusal for a no-audio session" , muted )
404+ }
405+ // Assert the REASON, not merely that something failed: without the
406+ // guard this call still errors (no connection), so an error alone
407+ // would pass on the unfixed code.
408+ if ! strings .Contains (err .Error (), "audio disabled" ) {
409+ t .Fatalf ("SetAudioMuted(%v) error = %q, want a refusal naming the disabled audio" , muted , err )
410+ }
411+ }
412+ }
413+
414+ type audioSessionCase struct {
415+ skipRecord bool
416+ noAudio bool
417+ }
418+
419+ func testSetupMirrorAudioSessionNegotiation (t * testing.T , test audioSessionCase ) {
420+ skipRecord , noAudio := test .skipRecord , test .noAudio
381421 eventListener , err := net .Listen ("tcp" , "127.0.0.1:0" )
382422 if err != nil {
383423 t .Fatalf ("listen event channel: %v" , err )
@@ -652,14 +692,18 @@ func testSetupMirrorNoAudioStillNegotiatesAudioSession(t *testing.T, skipRecord
652692 defer client .Close ()
653693 // -no-audio is also the escape hatch for receivers that advertise no screen
654694 // audio codec we can encode. Timing and video setup must remain usable.
655- client .info = & ReceiverInfo {SupportedFormats : StreamFormats {ScreenStream : 0x800000 }}
695+ screenStreamFormats := FormatMask (0x40000 ) // ALAC
696+ if noAudio {
697+ screenStreamFormats = 0x800000 // deliberately unsupported
698+ }
699+ client .info = & ReceiverInfo {SupportedFormats : StreamFormats {ScreenStream : screenStreamFormats }}
656700
657- session , err := client .SetupMirror (ctx , StreamConfig {FPS : 30 , NoAudio : true })
701+ session , err := client .SetupMirror (ctx , StreamConfig {FPS : 30 , NoAudio : noAudio })
658702 if err != nil {
659- t .Fatalf ("SetupMirror(no audio ): %v" , err )
703+ t .Fatalf ("SetupMirror(noAudio=%v ): %v" , noAudio , err )
660704 }
661705 if ! session .HasAudio () {
662- t .Fatal ( " expected no-audio session setup to keep the negotiated audio stream state" )
706+ t .Fatalf ( "noAudio=%v: expected the negotiated audio stream state to survive setup" , noAudio )
663707 }
664708 if session .timestampBias != defaultVideoLatencyNormal {
665709 t .Fatalf ("session timestamp bias = %v, want %v" , session .timestampBias , defaultVideoLatencyNormal )
@@ -697,7 +741,13 @@ func testSetupMirrorNoAudioStillNegotiatesAudioSession(t *testing.T, skipRecord
697741 recordIndex = len (wantMethods )
698742 wantMethods = append (wantMethods , "RECORD" )
699743 }
700- wantMethods = append (wantMethods , "SET_PARAMETER" , "SET_PARAMETER" , "POST" , "TEARDOWN" )
744+ // The two volume SET_PARAMETERs are sent only when the session carries
745+ // audio: `volume: 0.000000` is 0 dB — full scale — so a video-only
746+ // session would force the receiver to maximum.
747+ if ! noAudio {
748+ wantMethods = append (wantMethods , "SET_PARAMETER" , "SET_PARAMETER" )
749+ }
750+ wantMethods = append (wantMethods , "POST" , "TEARDOWN" )
701751 got := make ([]rtspTestRequest , 0 , len (wantMethods ))
702752 for range wantMethods {
703753 select {
0 commit comments