├── RCTSilentSwitch.h ├── RCTSilentSwitch.m ├── RCTSilentSwitch.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── grant.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── grant.xcuserdatad │ └── xcschemes │ ├── RCTSilentSwitch.xcscheme │ └── xcschememanagement.plist ├── README.md ├── SharkfoodMuteSwitchDetector.h ├── SharkfoodMuteSwitchDetector.m ├── index.android.js ├── index.ios.js ├── mute.caf └── package.json /RCTSilentSwitch.h: -------------------------------------------------------------------------------- 1 | #if __has_include() 2 | #import 3 | #else 4 | #import "RCTBridgeModule.h" 5 | #endif 6 | 7 | #import "SharkfoodMuteSwitchDetector.h" 8 | 9 | @interface RCTSilentSwitch : NSObject 10 | 11 | @property (nonatomic,strong) SharkfoodMuteSwitchDetector* detector; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /RCTSilentSwitch.m: -------------------------------------------------------------------------------- 1 | #import "RCTSilentSwitch.h" 2 | #import "RCTEventDispatcher.h" 3 | 4 | @implementation RCTSilentSwitch 5 | 6 | @synthesize bridge = _bridge; 7 | 8 | -(id)init { 9 | if (self) { 10 | self.detector = [SharkfoodMuteSwitchDetector shared]; 11 | } 12 | return self; 13 | } 14 | 15 | RCT_EXPORT_MODULE() 16 | 17 | RCT_EXPORT_METHOD(subscribe) { 18 | self.detector.silentNotify = ^(BOOL silent) { 19 | [self.bridge.eventDispatcher sendAppEventWithName:@"SilentSwitch" 20 | body:@{@"status": silent?@"ON":@"OFF"}]; 21 | }; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /RCTSilentSwitch.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 13BE3DEE1AC21097009241FE /* RCTSilentSwitch.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BE3DED1AC21097009241FE /* RCTSilentSwitch.m */; }; 11 | 9334BD681CD41E2D00918348 /* SharkfoodMuteSwitchDetector.m in Sources */ = {isa = PBXBuildFile; fileRef = 9334BD671CD41E2D00918348 /* SharkfoodMuteSwitchDetector.m */; }; 12 | /* End PBXBuildFile section */ 13 | 14 | /* Begin PBXCopyFilesBuildPhase section */ 15 | 58B511D91A9E6C8500147676 /* CopyFiles */ = { 16 | isa = PBXCopyFilesBuildPhase; 17 | buildActionMask = 2147483647; 18 | dstPath = "include/$(PRODUCT_NAME)"; 19 | dstSubfolderSpec = 16; 20 | files = ( 21 | ); 22 | runOnlyForDeploymentPostprocessing = 0; 23 | }; 24 | /* End PBXCopyFilesBuildPhase section */ 25 | 26 | /* Begin PBXFileReference section */ 27 | 134814201AA4EA6300B7C361 /* libRCTSilentSwitch.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTSilentSwitch.a; sourceTree = BUILT_PRODUCTS_DIR; }; 28 | 13BE3DEC1AC21097009241FE /* RCTSilentSwitch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSilentSwitch.h; sourceTree = ""; }; 29 | 13BE3DED1AC21097009241FE /* RCTSilentSwitch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSilentSwitch.m; sourceTree = ""; }; 30 | 9334BD661CD41E2D00918348 /* SharkfoodMuteSwitchDetector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SharkfoodMuteSwitchDetector.h; sourceTree = ""; }; 31 | 9334BD671CD41E2D00918348 /* SharkfoodMuteSwitchDetector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SharkfoodMuteSwitchDetector.m; sourceTree = ""; }; 32 | 9334BD6A1CD41F2300918348 /* mute.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = mute.caf; sourceTree = ""; }; 33 | /* End PBXFileReference section */ 34 | 35 | /* Begin PBXFrameworksBuildPhase section */ 36 | 58B511D81A9E6C8500147676 /* Frameworks */ = { 37 | isa = PBXFrameworksBuildPhase; 38 | buildActionMask = 2147483647; 39 | files = ( 40 | ); 41 | runOnlyForDeploymentPostprocessing = 0; 42 | }; 43 | /* End PBXFrameworksBuildPhase section */ 44 | 45 | /* Begin PBXGroup section */ 46 | 134814211AA4EA7D00B7C361 /* Products */ = { 47 | isa = PBXGroup; 48 | children = ( 49 | 134814201AA4EA6300B7C361 /* libRCTSilentSwitch.a */, 50 | ); 51 | name = Products; 52 | sourceTree = ""; 53 | }; 54 | 58B511D21A9E6C8500147676 = { 55 | isa = PBXGroup; 56 | children = ( 57 | 9334BD661CD41E2D00918348 /* SharkfoodMuteSwitchDetector.h */, 58 | 9334BD671CD41E2D00918348 /* SharkfoodMuteSwitchDetector.m */, 59 | 13BE3DEC1AC21097009241FE /* RCTSilentSwitch.h */, 60 | 13BE3DED1AC21097009241FE /* RCTSilentSwitch.m */, 61 | 9334BD6A1CD41F2300918348 /* mute.caf */, 62 | 134814211AA4EA7D00B7C361 /* Products */, 63 | ); 64 | sourceTree = ""; 65 | }; 66 | /* End PBXGroup section */ 67 | 68 | /* Begin PBXNativeTarget section */ 69 | 58B511DA1A9E6C8500147676 /* RCTSilentSwitch */ = { 70 | isa = PBXNativeTarget; 71 | buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RCTSilentSwitch" */; 72 | buildPhases = ( 73 | 58B511D71A9E6C8500147676 /* Sources */, 74 | 58B511D81A9E6C8500147676 /* Frameworks */, 75 | 58B511D91A9E6C8500147676 /* CopyFiles */, 76 | ); 77 | buildRules = ( 78 | ); 79 | dependencies = ( 80 | ); 81 | name = RCTSilentSwitch; 82 | productName = RCTDataManager; 83 | productReference = 134814201AA4EA6300B7C361 /* libRCTSilentSwitch.a */; 84 | productType = "com.apple.product-type.library.static"; 85 | }; 86 | /* End PBXNativeTarget section */ 87 | 88 | /* Begin PBXProject section */ 89 | 58B511D31A9E6C8500147676 /* Project object */ = { 90 | isa = PBXProject; 91 | attributes = { 92 | LastUpgradeCheck = 0730; 93 | ORGANIZATIONNAME = Facebook; 94 | TargetAttributes = { 95 | 58B511DA1A9E6C8500147676 = { 96 | CreatedOnToolsVersion = 6.1.1; 97 | }; 98 | }; 99 | }; 100 | buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RCTSilentSwitch" */; 101 | compatibilityVersion = "Xcode 3.2"; 102 | developmentRegion = English; 103 | hasScannedForEncodings = 0; 104 | knownRegions = ( 105 | en, 106 | ); 107 | mainGroup = 58B511D21A9E6C8500147676; 108 | productRefGroup = 58B511D21A9E6C8500147676; 109 | projectDirPath = ""; 110 | projectRoot = ""; 111 | targets = ( 112 | 58B511DA1A9E6C8500147676 /* RCTSilentSwitch */, 113 | ); 114 | }; 115 | /* End PBXProject section */ 116 | 117 | /* Begin PBXSourcesBuildPhase section */ 118 | 58B511D71A9E6C8500147676 /* Sources */ = { 119 | isa = PBXSourcesBuildPhase; 120 | buildActionMask = 2147483647; 121 | files = ( 122 | 9334BD681CD41E2D00918348 /* SharkfoodMuteSwitchDetector.m in Sources */, 123 | 13BE3DEE1AC21097009241FE /* RCTSilentSwitch.m in Sources */, 124 | ); 125 | runOnlyForDeploymentPostprocessing = 0; 126 | }; 127 | /* End PBXSourcesBuildPhase section */ 128 | 129 | /* Begin XCBuildConfiguration section */ 130 | 58B511ED1A9E6C8500147676 /* Debug */ = { 131 | isa = XCBuildConfiguration; 132 | buildSettings = { 133 | ALWAYS_SEARCH_USER_PATHS = NO; 134 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 135 | CLANG_CXX_LIBRARY = "libc++"; 136 | CLANG_ENABLE_MODULES = YES; 137 | CLANG_ENABLE_OBJC_ARC = YES; 138 | CLANG_WARN_BOOL_CONVERSION = YES; 139 | CLANG_WARN_CONSTANT_CONVERSION = YES; 140 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 141 | CLANG_WARN_EMPTY_BODY = YES; 142 | CLANG_WARN_ENUM_CONVERSION = YES; 143 | CLANG_WARN_INT_CONVERSION = YES; 144 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 145 | CLANG_WARN_UNREACHABLE_CODE = YES; 146 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 147 | COPY_PHASE_STRIP = NO; 148 | ENABLE_STRICT_OBJC_MSGSEND = YES; 149 | ENABLE_TESTABILITY = YES; 150 | GCC_C_LANGUAGE_STANDARD = gnu99; 151 | GCC_DYNAMIC_NO_PIC = NO; 152 | GCC_OPTIMIZATION_LEVEL = 0; 153 | GCC_PREPROCESSOR_DEFINITIONS = ( 154 | "DEBUG=1", 155 | "$(inherited)", 156 | ); 157 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 158 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 159 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 160 | GCC_WARN_UNDECLARED_SELECTOR = YES; 161 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 162 | GCC_WARN_UNUSED_FUNCTION = YES; 163 | GCC_WARN_UNUSED_VARIABLE = YES; 164 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 165 | MTL_ENABLE_DEBUG_INFO = YES; 166 | ONLY_ACTIVE_ARCH = YES; 167 | SDKROOT = iphoneos; 168 | }; 169 | name = Debug; 170 | }; 171 | 58B511EE1A9E6C8500147676 /* Release */ = { 172 | isa = XCBuildConfiguration; 173 | buildSettings = { 174 | ALWAYS_SEARCH_USER_PATHS = NO; 175 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 176 | CLANG_CXX_LIBRARY = "libc++"; 177 | CLANG_ENABLE_MODULES = YES; 178 | CLANG_ENABLE_OBJC_ARC = YES; 179 | CLANG_WARN_BOOL_CONVERSION = YES; 180 | CLANG_WARN_CONSTANT_CONVERSION = YES; 181 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 182 | CLANG_WARN_EMPTY_BODY = YES; 183 | CLANG_WARN_ENUM_CONVERSION = YES; 184 | CLANG_WARN_INT_CONVERSION = YES; 185 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 186 | CLANG_WARN_UNREACHABLE_CODE = YES; 187 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 188 | COPY_PHASE_STRIP = YES; 189 | ENABLE_NS_ASSERTIONS = NO; 190 | ENABLE_STRICT_OBJC_MSGSEND = YES; 191 | GCC_C_LANGUAGE_STANDARD = gnu99; 192 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 193 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 194 | GCC_WARN_UNDECLARED_SELECTOR = YES; 195 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 196 | GCC_WARN_UNUSED_FUNCTION = YES; 197 | GCC_WARN_UNUSED_VARIABLE = YES; 198 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 199 | MTL_ENABLE_DEBUG_INFO = NO; 200 | SDKROOT = iphoneos; 201 | VALIDATE_PRODUCT = YES; 202 | }; 203 | name = Release; 204 | }; 205 | 58B511F01A9E6C8500147676 /* Debug */ = { 206 | isa = XCBuildConfiguration; 207 | buildSettings = { 208 | HEADER_SEARCH_PATHS = ( 209 | "$(inherited)", 210 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 211 | "$(SRCROOT)/../../React/**", 212 | "$(SRCROOT)/../../node_modules/react-native/React/**", 213 | ); 214 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 215 | OTHER_LDFLAGS = "-ObjC"; 216 | PRODUCT_NAME = RCTSilentSwitch; 217 | SKIP_INSTALL = YES; 218 | }; 219 | name = Debug; 220 | }; 221 | 58B511F11A9E6C8500147676 /* Release */ = { 222 | isa = XCBuildConfiguration; 223 | buildSettings = { 224 | HEADER_SEARCH_PATHS = ( 225 | "$(inherited)", 226 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 227 | "$(SRCROOT)/../../React/**", 228 | "$(SRCROOT)/../../node_modules/react-native/React/**", 229 | ); 230 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 231 | OTHER_LDFLAGS = "-ObjC"; 232 | PRODUCT_NAME = RCTSilentSwitch; 233 | SKIP_INSTALL = YES; 234 | }; 235 | name = Release; 236 | }; 237 | /* End XCBuildConfiguration section */ 238 | 239 | /* Begin XCConfigurationList section */ 240 | 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RCTSilentSwitch" */ = { 241 | isa = XCConfigurationList; 242 | buildConfigurations = ( 243 | 58B511ED1A9E6C8500147676 /* Debug */, 244 | 58B511EE1A9E6C8500147676 /* Release */, 245 | ); 246 | defaultConfigurationIsVisible = 0; 247 | defaultConfigurationName = Release; 248 | }; 249 | 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RCTSilentSwitch" */ = { 250 | isa = XCConfigurationList; 251 | buildConfigurations = ( 252 | 58B511F01A9E6C8500147676 /* Debug */, 253 | 58B511F11A9E6C8500147676 /* Release */, 254 | ); 255 | defaultConfigurationIsVisible = 0; 256 | defaultConfigurationName = Release; 257 | }; 258 | /* End XCConfigurationList section */ 259 | }; 260 | rootObject = 58B511D31A9E6C8500147676 /* Project object */; 261 | } 262 | -------------------------------------------------------------------------------- /RCTSilentSwitch.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /RCTSilentSwitch.xcodeproj/project.xcworkspace/xcuserdata/grant.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gnestor/react-native-silent-switch/fa4cebcc2d67083f071683eb11301373a1b24407/RCTSilentSwitch.xcodeproj/project.xcworkspace/xcuserdata/grant.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /RCTSilentSwitch.xcodeproj/xcuserdata/grant.xcuserdatad/xcschemes/RCTSilentSwitch.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /RCTSilentSwitch.xcodeproj/xcuserdata/grant.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | RCTSilentSwitch.xcscheme 8 | 9 | orderHint 10 | 8 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 58B511DA1A9E6C8500147676 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Native Silent Switch 2 | 3 | Detect the iOS silent switch using React Native 4 | 5 | ## Install 6 | 7 | ### Using [rnpm](https://github.com/rnpm/rnpm) 8 | 9 | * Install: `rnpm install react-native-silent-switch` 10 | * Add `mute.caf` from the library to your project bundle 11 | * Project Navigator > [YOUR PROJECT NAME] > Build Phases > Copy Bundle Resources 12 | * Verify that `$(SRCROOT)/../../node_modules/react-native/React` is in the library's header search paths for both Debug and Release schemes 13 | * Project Navigator > RCTSilentSwitch.xcodeproj > Build Settings > Header Search Paths > Debug AND Release 14 | 15 | ### Manually 16 | 17 | * Install: `npm install react-native-silent-switch --save` 18 | * Link library in Xcode: See [React Native guide](https://facebook.github.io/react-native/docs/linking-libraries-ios.html) 19 | * Add `mute.caf` from the library to your project bundle 20 | * Project Navigator > [YOUR PROJECT NAME] > Build Phases > Copy Bundle Resources 21 | * Verify that `$(SRCROOT)/../../node_modules/react-native/React` is in the library's header search paths for both Debug and Release schemes 22 | * Project Navigator > RCTSilentSwitch.xcodeproj > Build Settings > Header Search Paths > Debug AND Release 23 | 24 | ## Usage 25 | 26 | ```js 27 | import SilentSwitch from 'react-native-silent-switch' 28 | ``` 29 | 30 | ```js 31 | componentDidMount() { 32 | // Listen for silent switch toggle events 33 | SilentSwitch.addEventListener(silent => { 34 | // When switched to silent, the callback will return true. When switched from silent, it will return false. 35 | if (silent) this.navigator.push({id: 'SilentAlert'}) 36 | }) 37 | } 38 | 39 | componentWillUnmount() { 40 | SilentSwitch.removeEventListener() 41 | } 42 | ``` 43 | 44 | ## Usage with [react-native-statusbar-alert](https://github.com/gnestor/react-native-statusbar-alert) 45 | 46 | ```js 47 | import SilentSwitch from 'react-native-silent-switch' 48 | import StatusBarAlert from 'react-native-statusbar-alert' 49 | ``` 50 | 51 | ```js 52 | componentDidMount() { 53 | SilentSwitch.addEventListener(silent => { 54 | if (silent) { 55 | this.setState({ 56 | alerts: [{ 57 | message: 'Silent Switch ON', 58 | onPress: () => this.navigator.push({id: 'SilentAlert'}) 59 | }, ...this.state.alerts] 60 | }) 61 | } else { 62 | this.setState({ 63 | alerts: this.state.alerts.filter(alert => alert.message !== 'Silent Switch ON') 64 | }) 65 | } 66 | }) 67 | } 68 | 69 | componentWillUnmount() { 70 | SilentSwitch.removeEventListener() 71 | } 72 | 73 | render() { 74 | return ( 75 | 76 | 0} 78 | {...this.state.alerts[0]} 79 | /> 80 | 88 | } 89 | /> 90 | 91 | ) 92 | } 93 | ``` 94 | -------------------------------------------------------------------------------- /SharkfoodMuteSwitchDetector.h: -------------------------------------------------------------------------------- 1 | // 2 | // SharkfoodMuteSwitchDetector.h 3 | // 4 | // Created by Moshe Gottlieb on 6/2/13. 5 | // Copyright (c) 2013 Sharkfood. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | 11 | typedef void(^SharkfoodMuteSwitchDetectorBlock)(BOOL silent); 12 | 13 | @interface SharkfoodMuteSwitchDetector : NSObject 14 | 15 | +(SharkfoodMuteSwitchDetector*)shared; 16 | 17 | @property (nonatomic,readonly) BOOL isMute; 18 | @property (nonatomic,copy) SharkfoodMuteSwitchDetectorBlock silentNotify; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /SharkfoodMuteSwitchDetector.m: -------------------------------------------------------------------------------- 1 | // 2 | // SharkfoodMuteSwitchDetector.m 3 | // 4 | // Created by Moshe Gottlieb on 6/2/13. 5 | // Copyright (c) 2013 Sharkfood. All rights reserved. 6 | // 7 | 8 | #import "SharkfoodMuteSwitchDetector.h" 9 | #import 10 | 11 | /** 12 | Sound completion proc - this is the real magic, we simply calculate how long it took for the sound to finish playing 13 | In silent mode, playback will end very fast (but not in zero time) 14 | */ 15 | void SharkfoodSoundMuteNotificationCompletionProc(SystemSoundID ssID,void* clientData); 16 | 17 | @interface SharkfoodMuteSwitchDetector() 18 | /** 19 | Find out how fast the completion call is called 20 | */ 21 | @property (nonatomic,assign) NSTimeInterval interval; 22 | /** 23 | Our silent sound (0.5 sec) 24 | */ 25 | @property (nonatomic,assign) SystemSoundID soundId; 26 | /** 27 | Set to true after the block was set or during init. 28 | Otherwise the block is called only when the switch value actually changes 29 | */ 30 | @property (nonatomic,assign) BOOL forceEmit; 31 | /** 32 | Sound completion, objc 33 | */ 34 | -(void)complete; 35 | /** 36 | Our loop, checks sound switch 37 | */ 38 | -(void)loopCheck; 39 | /** 40 | Pause while in the background, if your app supports playing audio in the background, you want this. 41 | Otherwise your app will be rejected. 42 | */ 43 | -(void)didEnterBackground; 44 | /** 45 | Resume when entering foreground 46 | */ 47 | -(void)willReturnToForeground; 48 | /** 49 | Schedule a next check 50 | */ 51 | -(void)scheduleCall; 52 | /** 53 | Is paused? 54 | */ 55 | @property (nonatomic,assign) BOOL isPaused; 56 | /** 57 | Currently playing? used when returning from the background (if went to background and foreground really quickly) 58 | */ 59 | @property (nonatomic,assign) BOOL isPlaying; 60 | 61 | @end 62 | 63 | 64 | 65 | void SharkfoodSoundMuteNotificationCompletionProc(SystemSoundID ssID,void* clientData){ 66 | SharkfoodMuteSwitchDetector* detecotr = (__bridge SharkfoodMuteSwitchDetector*)clientData; 67 | [detecotr complete]; 68 | } 69 | 70 | 71 | @implementation SharkfoodMuteSwitchDetector 72 | 73 | -(id)init{ 74 | self = [super init]; 75 | if (self){ 76 | NSURL* url = [[NSBundle mainBundle] URLForResource:@"mute" withExtension:@"caf"]; 77 | if (AudioServicesCreateSystemSoundID((__bridge CFURLRef)url, &_soundId) == kAudioServicesNoError){ 78 | AudioServicesAddSystemSoundCompletion(self.soundId, CFRunLoopGetMain(), kCFRunLoopDefaultMode, SharkfoodSoundMuteNotificationCompletionProc,(__bridge void *)(self)); 79 | UInt32 yes = 1; 80 | AudioServicesSetProperty(kAudioServicesPropertyIsUISound, sizeof(_soundId),&_soundId,sizeof(yes), &yes); 81 | [self performSelector:@selector(loopCheck) withObject:nil afterDelay:1]; 82 | self.forceEmit = YES; 83 | } else { 84 | self.soundId = -1; 85 | } 86 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground) name:NSExtensionHostDidEnterBackgroundNotification object:nil]; 87 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willReturnToForeground) name:NSExtensionHostWillEnterForegroundNotification object:nil]; 88 | } 89 | return self; 90 | } 91 | 92 | -(void)didEnterBackground{ 93 | self.isPaused = YES; 94 | } 95 | -(void)willReturnToForeground{ 96 | self.isPaused = NO; 97 | if (!self.isPlaying){ 98 | [self scheduleCall]; 99 | } 100 | } 101 | 102 | -(void)setSilentNotify:(SharkfoodMuteSwitchDetectorBlock)silentNotify{ 103 | _silentNotify = [silentNotify copy]; 104 | self.forceEmit = YES; 105 | } 106 | 107 | +(SharkfoodMuteSwitchDetector*)shared{ 108 | static SharkfoodMuteSwitchDetector* sShared = nil; 109 | if (!sShared) 110 | sShared = [SharkfoodMuteSwitchDetector new]; 111 | return sShared; 112 | } 113 | 114 | -(void)scheduleCall{ 115 | [[self class] cancelPreviousPerformRequestsWithTarget:self selector:@selector(loopCheck) object:nil]; 116 | [self performSelector:@selector(loopCheck) withObject:nil afterDelay:1]; 117 | } 118 | 119 | 120 | -(void)complete{ 121 | self.isPlaying = NO; 122 | NSTimeInterval elapsed = [NSDate timeIntervalSinceReferenceDate] - self.interval; 123 | BOOL isMute = elapsed < 0.1; // Should have been 0.5 sec, but it seems to return much faster (0.3something) 124 | if (self.isMute != isMute || self.forceEmit) { 125 | self.forceEmit = NO; 126 | _isMute = isMute; 127 | if (self.silentNotify) 128 | self.silentNotify(isMute); 129 | } 130 | [self scheduleCall]; 131 | } 132 | 133 | -(void)loopCheck{ 134 | if (!self.isPaused){ 135 | self.interval = [NSDate timeIntervalSinceReferenceDate]; 136 | self.isPlaying = YES; 137 | AudioServicesPlaySystemSound(self.soundId); 138 | } 139 | } 140 | 141 | 142 | // For reference only, this DTOR will never be invoked. 143 | 144 | -(void)dealloc{ 145 | if (self.soundId != -1){ 146 | AudioServicesRemoveSystemSoundCompletion(self.soundId); 147 | AudioServicesDisposeSystemSoundID(self.soundId); 148 | } 149 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 150 | } 151 | 152 | @end 153 | -------------------------------------------------------------------------------- /index.android.js: -------------------------------------------------------------------------------- 1 | export default { 2 | addEventListener: callback => {}, 3 | removeEventListener: () => {} 4 | } 5 | -------------------------------------------------------------------------------- /index.ios.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {NativeAppEventEmitter, NativeModules} from 'react-native' 3 | const {SilentSwitch} = NativeModules 4 | 5 | let eventListener = null 6 | 7 | export default { 8 | addEventListener: callback => { 9 | SilentSwitch.subscribe() 10 | eventListener = NativeAppEventEmitter.addListener('SilentSwitch', ({status}) => { 11 | if (status === 'ON') return callback(true) 12 | if (status === 'OFF') return callback(false) 13 | }) 14 | }, 15 | removeEventListener: () => eventListener.remove() 16 | } 17 | -------------------------------------------------------------------------------- /mute.caf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gnestor/react-native-silent-switch/fa4cebcc2d67083f071683eb11301373a1b24407/mute.caf -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-silent-switch", 3 | "version": "0.1.0", 4 | "description": "Detect the iOS silent switch using React Native", 5 | "keywords": [ 6 | "react-native", 7 | "ios", 8 | "silent", 9 | "mute", 10 | "sound", 11 | "switch" 12 | ], 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/gnestor/react-native-silent-switch" 16 | }, 17 | "author": "Grant Nestor (grantnestor@gmail.com)", 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/gnestor/react-native-silent-switch/issues" 21 | }, 22 | "homepage": "https://github.com/gnestor/react-native-silent-switch", 23 | "peerDependencies": { 24 | "react": "^16", 25 | "react-native": ">=0.26" 26 | }, 27 | "rnpm": { 28 | "assets": [ 29 | "mute.caf" 30 | ] 31 | } 32 | } 33 | --------------------------------------------------------------------------------