-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPatchMix.m
More file actions
140 lines (123 loc) · 5.36 KB
/
PatchMix.m
File metadata and controls
140 lines (123 loc) · 5.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// PatchMix.m
@import Foundation;
@import UIKit;
@import AVFoundation;
#import <objc/runtime.h>
#import <objc/message.h>
static BOOL oka_isForeground(void) {
UIApplication *app = [UIApplication sharedApplication];
return app.applicationState == UIApplicationStateActive;
}
static AVAudioSessionCategoryOptions oka_withMix(AVAudioSessionCategoryOptions opts) {
return (opts | AVAudioSessionCategoryOptionMixWithOthers);
}
static NSString *oka_foregroundCategory(NSString *original) {
// Foreground: Ambient + MixWithOthers
return AVAudioSessionCategoryAmbient;
}
static NSString *oka_backgroundCategory(NSString *original) {
// Background: keep original (e.g., Playback), only ensure MixWithOthers
return original ?: AVAudioSessionCategoryPlayback;
}
// iOS 10+: -setCategory:withOptions:error:
typedef BOOL (*SetCatOptErrIMP)(id, SEL, NSString*, AVAudioSessionCategoryOptions, NSError**);
// iOS older: -setCategory:error:
typedef BOOL (*SetCatErrIMP)(id, SEL, NSString*, NSError**);
static IMP g_orig_setCatOptErr = NULL;
static IMP g_orig_setCatErr = NULL;
static BOOL oka_setCategory_options_error(id self, SEL _cmd,
NSString *category,
AVAudioSessionCategoryOptions options,
NSError **error)
{
NSString *cat = oka_isForeground() ? oka_foregroundCategory(category)
: oka_backgroundCategory(category);
AVAudioSessionCategoryOptions opts = oka_withMix(options);
SetCatOptErrIMP orig = (SetCatOptErrIMP)g_orig_setCatOptErr;
if (!orig) {
orig = (SetCatOptErrIMP)[[AVAudioSession class] instanceMethodForSelector:_cmd];
}
return orig(self, _cmd, cat, opts, error);
}
static BOOL oka_setCategory_error(id self, SEL _cmd,
NSString *category, NSError **error)
{
NSString *cat = oka_isForeground() ? oka_foregroundCategory(category)
: oka_backgroundCategory(category);
SetCatErrIMP orig = (SetCatErrIMP)g_orig_setCatErr;
if (!orig) {
orig = (SetCatErrIMP)[[AVAudioSession class] instanceMethodForSelector:_cmd];
}
BOOL ok = orig(self, _cmd, cat, error);
if (ok) {
AVAudioSession *s = (AVAudioSession *)self;
[s setActive:NO error:nil];
if ([s respondsToSelector:@selector(setCategory:withOptions:error:)]) {
[s setCategory:cat withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil];
}
[s setActive:YES error:nil];
}
return ok;
}
static void oka_applyForCurrentState(void) {
AVAudioSession *s = [AVAudioSession sharedInstance];
NSString *current = s.category;
NSError *err = nil;
if (oka_isForeground()) {
[s setActive:NO error:&err];
[s setCategory:AVAudioSessionCategoryAmbient
withOptions:AVAudioSessionCategoryOptionMixWithOthers
error:&err];
[s setActive:YES error:&err];
} else {
[s setActive:NO error:&err];
if ([s respondsToSelector:@selector(setCategory:withOptions:error:)]) {
[s setCategory:(current ?: AVAudioSessionCategoryPlayback)
withOptions:AVAudioSessionCategoryOptionMixWithOthers
error:&err];
} else {
[s setCategory:(current ?: AVAudioSessionCategoryPlayback) error:&err];
[s setCategory:(current ?: AVAudioSessionCategoryPlayback)
withOptions:AVAudioSessionCategoryOptionMixWithOthers
error:&err];
}
[s setActive:YES error:&err];
}
}
static void oka_swizzle_method(Class cls, SEL sel, IMP repl, IMP *storeOrig) {
Method m = class_getInstanceMethod(cls, sel);
if (m) {
IMP old = method_getImplementation(m);
if (storeOrig) *storeOrig = old;
method_setImplementation(m, repl);
} else {
// If method not found, just add ours (best effort).
class_addMethod(cls, sel, repl, "c@:@@^@");
}
}
__attribute__((constructor))
static void oka_patch_init(void) {
Class cls = [AVAudioSession class];
// swizzle -setCategory:withOptions:error:
SEL sel1 = @selector(setCategory:withOptions:error:);
oka_swizzle_method(cls, sel1, (IMP)oka_setCategory_options_error, &g_orig_setCatOptErr);
// swizzle -setCategory:error:
SEL sel2 = @selector(setCategory:error:);
oka_swizzle_method(cls, sel2, (IMP)oka_setCategory_error, &g_orig_setCatErr);
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:nil usingBlock:^(__unused NSNotification * _Nonnull n) {
oka_applyForCurrentState();
}];
[nc addObserverForName:UIApplicationWillResignActiveNotification object:nil queue:nil usingBlock:^(__unused NSNotification * _Nonnull n) {
oka_applyForCurrentState();
}];
[nc addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil queue:nil usingBlock:^(__unused NSNotification * _Nonnull n) {
oka_applyForCurrentState();
}];
[nc addObserverForName:UIApplicationWillEnterForegroundNotification object:nil queue:nil usingBlock:^(__unused NSNotification * _Nonnull n) {
oka_applyForCurrentState();
}];
dispatch_async(dispatch_get_main_queue(), ^{
oka_applyForCurrentState();
});
}