From e0c556da10509fe47e0e26dad8f4f62634040d9a Mon Sep 17 00:00:00 2001 From: TrueZhuanjia Date: Mon, 3 Nov 2025 16:18:21 +0800 Subject: [PATCH] feat: mute in background switch --- VoidLink/Base.lproj/iPad.storyboard | 44 ++++++++++++++----- VoidLink/Base.lproj/iPhone.storyboard | 28 ++++++++++-- VoidLink/Database/DataManager.h | 1 + VoidLink/Database/DataManager.m | 2 + VoidLink/Database/TemporarySettings.h | 1 + VoidLink/Database/TemporarySettings.m | 1 + .../VoidLink v1.0.xcdatamodel/contents | 1 + VoidLink/Stream/Connection.h | 4 +- VoidLink/Stream/Connection.m | 8 ++++ VoidLink/Stream/VideoDecoderRenderer.h | 1 - VoidLink/Stream/VideoDecoderRenderer.m | 6 +-- .../ViewControllers/SettingsViewController.h | 3 ++ .../ViewControllers/SettingsViewController.m | 10 +++++ .../StreamFrameViewController.m | 11 ++--- VoidLink/mul.lproj/iPad.xcstrings | 30 +++++++++++++ VoidLink/mul.lproj/iPhone.xcstrings | 30 +++++++++++++ moonlight-common/moonlight-common-c | 2 +- 17 files changed, 158 insertions(+), 25 deletions(-) diff --git a/VoidLink/Base.lproj/iPad.storyboard b/VoidLink/Base.lproj/iPad.storyboard index edc5735f..831f22a9 100644 --- a/VoidLink/Base.lproj/iPad.storyboard +++ b/VoidLink/Base.lproj/iPad.storyboard @@ -1186,6 +1186,26 @@ + + + + + + + + + + + + + + + @@ -1360,16 +1380,16 @@ - + - + @@ -1494,24 +1514,24 @@ - - + + - - + @@ -1586,6 +1606,8 @@ + + diff --git a/VoidLink/Base.lproj/iPhone.storyboard b/VoidLink/Base.lproj/iPhone.storyboard index faacc571..7c427a78 100644 --- a/VoidLink/Base.lproj/iPhone.storyboard +++ b/VoidLink/Base.lproj/iPhone.storyboard @@ -2214,16 +2214,16 @@ - + - + @@ -2297,6 +2297,26 @@ + + + + + + + + + + + + + + + @@ -2370,6 +2390,8 @@ + + diff --git a/VoidLink/Database/DataManager.h b/VoidLink/Database/DataManager.h index c06734b8..df00dc02 100644 --- a/VoidLink/Database/DataManager.h +++ b/VoidLink/Database/DataManager.h @@ -128,6 +128,7 @@ typedef NS_ENUM(NSInteger, WidgetSizeTransition) { audioEngine:(NSInteger)audioEngine appendLeftClick:(BOOL)appendLeftClick duckOtherApps:(BOOL)duckOtherApps + muteInBackground:(BOOL)muteInBackground backgroundSessionTimer:(NSInteger)backgroundSessionTimer; - (NSArray*) getHosts; diff --git a/VoidLink/Database/DataManager.m b/VoidLink/Database/DataManager.m index 0fe30730..02ee6214 100644 --- a/VoidLink/Database/DataManager.m +++ b/VoidLink/Database/DataManager.m @@ -111,6 +111,7 @@ audioEngine:(NSInteger)audioEngine appendLeftClick:(BOOL)appendLeftClick duckOtherApps:(BOOL)duckOtherApps + muteInBackground:(BOOL)muteInBackground backgroundSessionTimer:(NSInteger)backgroundSessionTimer{ [_managedObjectContext performBlockAndWait:^{ @@ -172,6 +173,7 @@ settingsToSave.audioEngine = [NSNumber numberWithInteger:audioEngine]; settingsToSave.appendLeftClick = appendLeftClick; settingsToSave.duckOtherApps = duckOtherApps; + settingsToSave.muteInBackground = muteInBackground; settingsToSave.rememberFoldState = rememberFoldState; [self saveData]; }]; diff --git a/VoidLink/Database/TemporarySettings.h b/VoidLink/Database/TemporarySettings.h index 1ca791c2..c4883e98 100644 --- a/VoidLink/Database/TemporarySettings.h +++ b/VoidLink/Database/TemporarySettings.h @@ -85,6 +85,7 @@ @property (nonatomic, retain) NSNumber * audioEngine; @property (nonatomic) BOOL appendLeftClick; @property (nonatomic) BOOL duckOtherApps; +@property (nonatomic) BOOL muteInBackground; @property (nonatomic, retain) NSNumber * framePacingMode; - (id) initFromSettings:(Settings*)settings; diff --git a/VoidLink/Database/TemporarySettings.m b/VoidLink/Database/TemporarySettings.m index 353bc642..29380707 100644 --- a/VoidLink/Database/TemporarySettings.m +++ b/VoidLink/Database/TemporarySettings.m @@ -142,6 +142,7 @@ self.audioEngine = settings.audioEngine; self.appendLeftClick = settings.appendLeftClick; self.duckOtherApps = settings.duckOtherApps; + self.muteInBackground = settings.muteInBackground; #endif return self; diff --git a/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents b/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents index 0ae9a686..ad6cca75 100644 --- a/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents +++ b/VoidLink/Limelight.xcdatamodeld/VoidLink v1.0.xcdatamodel/contents @@ -57,6 +57,7 @@ + diff --git a/VoidLink/Stream/Connection.h b/VoidLink/Stream/Connection.h index 136fdefc..562d037a 100644 --- a/VoidLink/Stream/Connection.h +++ b/VoidLink/Stream/Connection.h @@ -19,6 +19,8 @@ #define CONN_TEST_SERVER "www.baidu.com" @interface Connection : NSOperation +@property (class, nonatomic, assign) bool muteInBackground; +@property (class, nonatomic, assign) bool useSystemAudioEngine; -(id) initWithConfig:(StreamConfiguration*)config renderer:(VideoDecoderRenderer*)myRenderer connectionCallbacks:(id)callbacks; -(void) terminate; @@ -26,7 +28,7 @@ -(BandwidthTracker *) getBwTracker; -(BOOL) getVideoStats:(video_stats_t*)stats; -(NSString*) getActiveCodecName; + + (void)setVolume:(float)newVolume; -+ (void)setUseSystemAudioEngine:(bool)useSysAudioEngine; @end diff --git a/VoidLink/Stream/Connection.m b/VoidLink/Stream/Connection.m index a18dbb82..d8fea102 100644 --- a/VoidLink/Stream/Connection.m +++ b/VoidLink/Stream/Connection.m @@ -56,6 +56,8 @@ static AVAudioPlayerNode *audioPlayerNode; static AVAudioPCMBuffer *pcmBuffer; static AVAudioFormat *audioFormat; +static bool muteInBackground; + static VideoDecoderRenderer* renderer; static BandwidthTracker *bwTracker; @@ -334,6 +336,10 @@ void ArCleanup(void) volume = powf(linearVolume, exponent); } ++ (void)setMuteInBackground:(bool)mute { + muteInBackground = mute; +} + + (void)setUseSystemAudioEngine:(bool)useSysAudioEngine{ useSystemAudioEngine = useSysAudioEngine; } @@ -393,6 +399,8 @@ void AudioEngineInit(int sampleRate, int channelCount) { void ArDecodeAndPlaySample(char* sampleData, int sampleLength) { + if(appDidEnterBackgroundWithoutPip && muteInBackground) return; + int decodeLen; // Don't queue if there's already more than 30 ms of audio data waiting diff --git a/VoidLink/Stream/VideoDecoderRenderer.h b/VoidLink/Stream/VideoDecoderRenderer.h index be311fd1..3bc61d06 100644 --- a/VoidLink/Stream/VideoDecoderRenderer.h +++ b/VoidLink/Stream/VideoDecoderRenderer.h @@ -18,7 +18,6 @@ @property (atomic, readonly) PlotMetrics decodeMetrics; @property (atomic, readonly) PlotMetrics frameQueueMetrics; -@property (atomic, assign) bool appDidEnterBackgroundWithoutPiP; @property (nonatomic, strong, readonly) AVSampleBufferDisplayLayer *displayLayer; diff --git a/VoidLink/Stream/VideoDecoderRenderer.m b/VoidLink/Stream/VideoDecoderRenderer.m index fa655275..47ea99e8 100644 --- a/VoidLink/Stream/VideoDecoderRenderer.m +++ b/VoidLink/Stream/VideoDecoderRenderer.m @@ -115,7 +115,7 @@ extern int ff_isom_write_av1c(AVIOContext *pb, const uint8_t *buf, int size, NSLog(@"initializing video decoder %f", CACurrentMediaTime()); self = [super init]; - _appDidEnterBackgroundWithoutPiP = false; + appDidEnterBackgroundWithoutPip = false; _sq = dispatch_queue_create("com.moonlight.VideoDecoderRenderer", dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0)); @@ -329,7 +329,7 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit); // Legacy frame pacing callback - matches upstream/Integration behavior exactly - (void)displayLinkCallback:(CADisplayLink *)sender { - if(_appDidEnterBackgroundWithoutPiP) return; + if(appDidEnterBackgroundWithoutPip) return; VIDEO_FRAME_HANDLE handle; PDECODE_UNIT du; @@ -379,7 +379,7 @@ int DrSubmitDecodeUnit(PDECODE_UNIT decodeUnit); Log(LOG_I, @"Setting timebase for stream to %d / %d", pts.value, pts.timescale); } - if(!_appDidEnterBackgroundWithoutPiP) [self->_displayLayer enqueueSampleBuffer:frame.sampleBuffer]; + if(!appDidEnterBackgroundWithoutPip) [self->_displayLayer enqueueSampleBuffer:frame.sampleBuffer]; #ifdef DISPLAYLINK_VERBOSE // Some OS-level metrics I'm not sure what to do with diff --git a/VoidLink/ViewControllers/SettingsViewController.h b/VoidLink/ViewControllers/SettingsViewController.h index 4ec296b1..82f0d841 100644 --- a/VoidLink/ViewControllers/SettingsViewController.h +++ b/VoidLink/ViewControllers/SettingsViewController.h @@ -187,6 +187,9 @@ @property (strong, nonatomic) IBOutlet UIStackView *duckOtherAppStack; @property (strong, nonatomic) IBOutlet UISwitch *duckOtherAppSwitch; +@property (strong, nonatomic) IBOutlet UIStackView *muteInBackgroundStack; +@property (strong, nonatomic) IBOutlet UISwitch *muteInBackgroundSwitch; + @property (strong, nonatomic) IBOutlet UIStackView *testStack; @property (nonatomic, strong) MicHandler *micHandler; diff --git a/VoidLink/ViewControllers/SettingsViewController.m b/VoidLink/ViewControllers/SettingsViewController.m index b45a2e9b..70824094 100644 --- a/VoidLink/ViewControllers/SettingsViewController.m +++ b/VoidLink/ViewControllers/SettingsViewController.m @@ -792,6 +792,7 @@ BOOL isCustomResolution(int resolutionSelected) { [self addSetting:self.useBuiltinMicStack ofId:@"useBuiltinMicStack" withInfoTag:YES withDynamicLabel:NO to:audioSection]; [self addSetting:self.micVolumeStack ofId:@"micVolumeStack" withInfoTag:NO withDynamicLabel:YES to:audioSection]; [self addSetting:self.duckOtherAppStack ofId:@"duckOtherAppStack" withInfoTag:NO withDynamicLabel:NO to:audioSection]; + [self addSetting:self.muteInBackgroundStack ofId:@"muteInBackgroundStack" withInfoTag:NO withDynamicLabel:NO to:audioSection]; // [self addSetting:self.audioEngineStack ofId:@"audioEngineStack" withInfoTag:YES withDynamicLabel:NO to:audioSection]; // cancel audio engine selector due to system engine is unable to playback multi-channel audio [self addSetting:self.audioConfigStack ofId:@"audioConfigStack" withInfoTag:NO withDynamicLabel:NO to:audioSection]; @@ -1718,6 +1719,9 @@ BOOL isCustomResolution(int resolutionSelected) { [self.duckOtherAppSwitch setOn:self->tempSettings.duckOtherApps]; + [self.muteInBackgroundSwitch setOn:tempSettings.muteInBackground]; + [self.muteInBackgroundSwitch addTarget:self action:@selector(muteInBackgroundSwitchFlipped:) forControlEvents:UIControlEventValueChanged]; + [self.micVolumeSlider setValue:self->tempSettings.micVolume.floatValue*100]; [self.micVolumeSlider addTarget:self action:@selector(micVolumeSliderMoved:) forControlEvents:UIControlEventValueChanged]; [self micVolumeSliderMoved:self.self.micVolumeSlider]; @@ -2387,6 +2391,10 @@ BOOL isCustomResolution(int resolutionSelected) { }); } +- (void)muteInBackgroundSwitchFlipped:(UISwitch* )sender{ + Connection.muteInBackground = sender.isOn; +} + - (void)redirectMicSwitchFlipped:(UISwitch* )sender{ if(sender.isOn) [self checkAndRequestMicPermission]; [self setHidden:!sender.isOn forStack:_useBuiltinMicStack]; @@ -2875,6 +2883,7 @@ BOOL isCustomResolution(int resolutionSelected) { NSInteger audioEngine = self.audioEngineSelector.selectedSegmentIndex; BOOL appendLeftClick = self.appendLeftClickSwitch.isOn; BOOL duckOtherApps = self.duckOtherAppSwitch.isOn; + BOOL muteInBackground = self.muteInBackgroundSwitch.isOn; NSInteger backgroundSessionTimer = self.backgroundSessionTimerSlider.value == self.backgroundSessionTimerSlider.maximumValue ? (uint32_t) INT16_MAX : (uint32_t)self.backgroundSessionTimerSlider.value; [dataMan saveSettingsWithBitrate:_bitrate @@ -2932,6 +2941,7 @@ BOOL isCustomResolution(int resolutionSelected) { audioEngine:audioEngine appendLeftClick:appendLeftClick duckOtherApps:duckOtherApps + muteInBackground:muteInBackground backgroundSessionTimer:backgroundSessionTimer]; } diff --git a/VoidLink/ViewControllers/StreamFrameViewController.m b/VoidLink/ViewControllers/StreamFrameViewController.m index 0f722a2b..d5b72a18 100644 --- a/VoidLink/ViewControllers/StreamFrameViewController.m +++ b/VoidLink/ViewControllers/StreamFrameViewController.m @@ -374,7 +374,8 @@ } else [micHandler stopTappingWithStopEngine:false]; - [Connection setUseSystemAudioEngine:_settings.audioEngine.intValue == SystemAudioEngine]; + Connection.useSystemAudioEngine = _settings.audioEngine.intValue == SystemAudioEngine; + Connection.muteInBackground = _settings.muteInBackground; if(!viewJustLoaded) [_controllerSupport updateControllerSupport:self.streamConfig delegate:self]; // reload controllerSupport obj, this is mandatory for OSC reload,especially when the stream view is launched without OSC @@ -1033,7 +1034,7 @@ - (void)applicationWillResignActive:(NSNotification *)notification { //[self.pipController startPictureInPicture]; //sleep(1); - _streamMan.videoRenderer.appDidEnterBackgroundWithoutPiP = true; + appDidEnterBackgroundWithoutPip = true; [_streamView saveRelocatedWidgetViews]; @@ -1050,7 +1051,7 @@ } - (void)applicationDidBecomeActive:(NSNotification *)notification { - _streamMan.videoRenderer.appDidEnterBackgroundWithoutPiP = false; + appDidEnterBackgroundWithoutPip = false; // Stop the background timer, since we're foregrounded again if (_inactivityTimer != nil) { Log(LOG_I, @"Stopping inactivity timer after becoming active again"); @@ -1083,9 +1084,9 @@ NSLog(@"did enter background, %d, %@, %d", _settings.enablePIP, self.pipController, self.pipController.isPictureInPictureActive); if (_settings.enablePIP && self.pipController && self.pipController.isPictureInPictureActive) { //Log(LOG_I, @"PIP is active, not terminating stream"); - _streamMan.videoRenderer.appDidEnterBackgroundWithoutPiP = false; + appDidEnterBackgroundWithoutPip = false; } else { - _streamMan.videoRenderer.appDidEnterBackgroundWithoutPiP = true; + appDidEnterBackgroundWithoutPip = true; if ([_settings.renderingBackend intValue] == RENDER_METAL && self.metalViewController) { Log(LOG_I, @"Pausing Metal renderer on background"); diff --git a/VoidLink/mul.lproj/iPad.xcstrings b/VoidLink/mul.lproj/iPad.xcstrings index 1e58bfa1..72a90a02 100644 --- a/VoidLink/mul.lproj/iPad.xcstrings +++ b/VoidLink/mul.lproj/iPad.xcstrings @@ -2419,6 +2419,36 @@ } } }, + "hPX-cJ-dgH.text" : { + "comment" : "Class = \"UILabel\"; text = \"Mute in Background\"; ObjectID = \"hPX-cJ-dgH\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Mute in Background" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "后台静音" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "背景執行静音" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "背景執行静音" + } + } + } + }, "hq8-dy-8BF.text" : { "comment" : "Class = \"UILabel\"; text = \"Custom Resolution\"; ObjectID = \"hq8-dy-8BF\";", "extractionState" : "extracted_with_value", diff --git a/VoidLink/mul.lproj/iPhone.xcstrings b/VoidLink/mul.lproj/iPhone.xcstrings index 4ab8ec20..4b3cc398 100644 --- a/VoidLink/mul.lproj/iPhone.xcstrings +++ b/VoidLink/mul.lproj/iPhone.xcstrings @@ -3115,6 +3115,36 @@ } } }, + "PaG-9w-YxB.text" : { + "comment" : "Class = \"UILabel\"; text = \"Mute in Background\"; ObjectID = \"PaG-9w-YxB\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Mute in Background" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "后台静音" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "背景執行静音" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "背景執行静音" + } + } + } + }, "Pdb-Bs-kMJ.text" : { "comment" : "Class = \"UILabel\"; text = \"Statistics Overlay\"; ObjectID = \"Pdb-Bs-kMJ\";", "extractionState" : "extracted_with_value", diff --git a/moonlight-common/moonlight-common-c b/moonlight-common/moonlight-common-c index 857026df..623adaae 160000 --- a/moonlight-common/moonlight-common-c +++ b/moonlight-common/moonlight-common-c @@ -1 +1 @@ -Subproject commit 857026df71792e97e3156948e1c440050bc579d0 +Subproject commit 623adaae5e138a7ed435e4a45ccc0f2c81befab0