WFCUFloatingWindow.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. //
  2. // WFCUFloatingWindow.m
  3. // WFDemo
  4. //
  5. // Created by heavyrain on 17/9/27.
  6. // Copyright © 2017年 WildFireChat. All rights reserved.
  7. //
  8. #if WFCU_SUPPORT_VOIP
  9. #import "WFCUFloatingWindow.h"
  10. #import <CoreTelephony/CTCall.h>
  11. #import <CoreTelephony/CTCallCenter.h>
  12. #import <UIKit/UIKit.h>
  13. @interface WFCUFloatingWindow () <WFAVCallSessionDelegate>
  14. @property(nonatomic, strong) NSTimer *activeTimer;
  15. @property(nonatomic, copy) void (^touchedBlock)(WFAVCallSession *callSession);
  16. @property(nonatomic, strong) CTCallCenter *callCenter;
  17. @property(nonatomic, strong) NSString *focusUserId;
  18. @end
  19. static WFCUFloatingWindow *staticWindow = nil;
  20. static NSString *kFloatingWindowPosX = @"kFloatingWindowPosX";
  21. static NSString *kFloatingWindowPosY = @"kFloatingWindowPosY";
  22. @implementation WFCUFloatingWindow
  23. + (void)startCallFloatingWindow:(WFAVCallSession *)callSession focusUser:(NSString *)focusUserId
  24. withTouchedBlock:(void (^)(WFAVCallSession *callSession))touchedBlock {
  25. staticWindow = [[WFCUFloatingWindow alloc] init];
  26. staticWindow.callSession = callSession;
  27. [staticWindow.callSession setDelegate:staticWindow];
  28. staticWindow.touchedBlock = touchedBlock;
  29. staticWindow.focusUserId = focusUserId;
  30. [staticWindow initWindow];
  31. }
  32. + (void)stopCallFloatingWindow {
  33. [staticWindow hideCallFloatingWindow];
  34. [staticWindow clearCallFloatingWindow];
  35. staticWindow = nil;
  36. }
  37. - (void)initWindow {
  38. if (self.callSession.state == kWFAVEngineStateIdle) {
  39. [self performSelector:@selector(clearCallFloatingWindow) withObject:nil afterDelay:2];
  40. }
  41. [self updateActiveTimer];
  42. [self startActiveTimer];
  43. [self updateWindow];
  44. [self registerTelephonyEvent];
  45. [[NSNotificationCenter defaultCenter] addObserver:self
  46. selector:@selector(onOrientationChanged:)
  47. name:UIApplicationDidChangeStatusBarOrientationNotification
  48. object:nil];
  49. [self addProximityMonitoringObserver];
  50. }
  51. - (void)registerTelephonyEvent {
  52. self.callCenter = [[CTCallCenter alloc] init];
  53. __weak __typeof(self) weakSelf = self;
  54. self.callCenter.callEventHandler = ^(CTCall *call) {
  55. if ([call.callState isEqualToString:CTCallStateConnected]) {
  56. [weakSelf.callSession endCall];
  57. }
  58. };
  59. }
  60. - (void)onOrientationChanged:(NSNotification *)notification {
  61. [self updateWindow];
  62. }
  63. - (void)startActiveTimer {
  64. self.activeTimer = [NSTimer scheduledTimerWithTimeInterval:1
  65. target:self
  66. selector:@selector(updateActiveTimer)
  67. userInfo:nil
  68. repeats:YES];
  69. [self.activeTimer fire];
  70. }
  71. - (void)stopActiveTimer {
  72. if (self.activeTimer) {
  73. [self.activeTimer invalidate];
  74. self.activeTimer = nil;
  75. }
  76. }
  77. - (void)updateActiveTimer {
  78. long sec = [[NSDate date] timeIntervalSince1970] - self.callSession.connectedTime / 1000;
  79. if (self.callSession.state == kWFAVEngineStateConnected && ![self isVideoViewEnabledSession]) {
  80. NSString *timeStr;
  81. if (sec < 60 * 60) {
  82. timeStr = [NSString stringWithFormat:@"%02ld:%02ld", sec / 60, sec % 60];
  83. } else {
  84. timeStr = [NSString stringWithFormat:@"%02ld:%02ld:%02ld", sec / 60 / 60, (sec / 60) % 60, sec % 60];
  85. }
  86. [self.floatingButton setTitle:timeStr forState:UIControlStateNormal];
  87. [self layoutTextUnderImageButton:self.floatingButton];
  88. }
  89. }
  90. - (void)updateWindow {
  91. CGFloat posX = [[[NSUserDefaults standardUserDefaults] objectForKey:kFloatingWindowPosX] floatValue];
  92. CGFloat posY = [[[NSUserDefaults standardUserDefaults] objectForKey:kFloatingWindowPosY] floatValue];
  93. posX = posX ? posX : 30;
  94. posY = posY ? posY : 30;
  95. CGRect screenBounds = [UIScreen mainScreen].bounds;
  96. posX = (posX + 30) > screenBounds.size.width ? (screenBounds.size.width - 30) : posX;
  97. posY = (posY + 48) > screenBounds.size.height ? (screenBounds.size.height - 48) : posY;
  98. if ([UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeLeft &&
  99. [self isSupportOrientation:UIInterfaceOrientationLandscapeLeft]) {
  100. self.window.transform = CGAffineTransformMakeRotation(M_PI / 2);
  101. self.window.frame = CGRectMake(posX, posY, 64, 96);
  102. self.floatingButton.frame = CGRectMake(0, 0, 96, 64);
  103. if ([self isVideoViewEnabledSession]) {
  104. self.videoView.frame = CGRectMake(0, 0, 96, 64);
  105. }
  106. } else if ([UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeRight &&
  107. [self isSupportOrientation:UIInterfaceOrientationLandscapeRight]) {
  108. self.window.transform = CGAffineTransformMakeRotation(-M_PI / 2);
  109. self.window.frame = CGRectMake(posX, posY, 64, 96);
  110. self.floatingButton.frame = CGRectMake(0, 0, 96, 64);
  111. if ([self isVideoViewEnabledSession]) {
  112. self.videoView.frame = CGRectMake(0, 0, 96, 64);
  113. }
  114. } else {
  115. if ([UIDevice currentDevice].orientation == UIDeviceOrientationPortraitUpsideDown &&
  116. [self isSupportOrientation:UIInterfaceOrientationPortraitUpsideDown]) {
  117. self.window.transform = CGAffineTransformMakeRotation(M_PI);
  118. } else {
  119. self.window.transform = CGAffineTransformMakeRotation(0);
  120. }
  121. self.window.frame = CGRectMake(posX, posY, 64, 96);
  122. self.floatingButton.frame = CGRectMake(0, 0, 64, 96);
  123. if ([self isVideoViewEnabledSession]) {
  124. self.videoView.frame = CGRectMake(0, 0, 64, 96);
  125. }
  126. }
  127. if ([self isVideoViewEnabledSession]) {
  128. if (self.callSession.state == kWFAVEngineStateOutgoing) {
  129. [self.callSession setupLocalVideoView:self.videoView scalingType:kWFAVVideoScalingTypeAspectBalanced];
  130. } else if (self.callSession.state == kWFAVEngineStateConnected) {
  131. if ([self.focusUserId isEqualToString:[WFCCNetworkService sharedInstance].userId]) {
  132. [self.callSession setupLocalVideoView:self.videoView scalingType:kWFAVVideoScalingTypeAspectBalanced];
  133. } else {
  134. [self.callSession setupRemoteVideoView:self.videoView scalingType:kWFAVVideoScalingTypeAspectBalanced forUser:self.focusUserId];
  135. }
  136. } else if (self.callSession.state == kWFAVEngineStateIdle) {
  137. UILabel *videoStopTips =
  138. [[UILabel alloc] initWithFrame:CGRectMake(0, self.videoView.frame.size.height / 2 - 10,
  139. self.videoView.frame.size.width, 20)];
  140. videoStopTips.textAlignment = NSTextAlignmentCenter;
  141. videoStopTips.text = WFCString(@"Ended");
  142. videoStopTips.textColor = HEXCOLOR(0x0195ff);
  143. [self.videoView addSubview:videoStopTips];
  144. }
  145. } else {
  146. if (self.callSession.state == kWFAVEngineStateIdle) {
  147. [self.floatingButton setBackgroundColor:[UIColor clearColor]];
  148. [self.floatingButton setTitle:WFCString(@"Ended")
  149. forState:UIControlStateNormal];
  150. } else {
  151. [self.floatingButton setImage:[UIImage imageNamed:@"floatingaudio"]
  152. forState:UIControlStateNormal];
  153. }
  154. }
  155. }
  156. - (UIWindow *)window {
  157. if (!_window) {
  158. CGFloat posX = [[[NSUserDefaults standardUserDefaults] objectForKey:kFloatingWindowPosX] floatValue];
  159. CGFloat posY = [[[NSUserDefaults standardUserDefaults] objectForKey:kFloatingWindowPosY] floatValue];
  160. posX = (posX - 30) ? posX : 30;
  161. posY = (posY - 48) ? posY : 48;
  162. CGRect screenBounds = [UIScreen mainScreen].bounds;
  163. posX = (posX + 30) > screenBounds.size.width ? (screenBounds.size.width - 30) : posX;
  164. posY = (posY + 48) > screenBounds.size.height ? (screenBounds.size.height - 48) : posY;
  165. _window = [[UIWindow alloc] initWithFrame:CGRectMake(posX, posY, 64, 96)];
  166. _window.backgroundColor = [UIColor whiteColor];
  167. _window.windowLevel = UIWindowLevelAlert + 1;
  168. _window.layer.cornerRadius = 4;
  169. _window.layer.masksToBounds = YES;
  170. _window.layer.borderWidth = 1;
  171. _window.layer.borderColor = [HEXCOLOR(0x0A88E1) CGColor];
  172. [_window makeKeyAndVisible]; //关键语句,显示window
  173. UIPanGestureRecognizer *panGestureRecognizer =
  174. [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGestures:)];
  175. panGestureRecognizer.minimumNumberOfTouches = 1;
  176. panGestureRecognizer.maximumNumberOfTouches = 1;
  177. [_window addGestureRecognizer:panGestureRecognizer];
  178. }
  179. return _window;
  180. }
  181. - (UIView *)videoView {
  182. if (!_videoView) {
  183. _videoView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 64, 96)];
  184. _videoView.backgroundColor = [UIColor blackColor];
  185. CGRect windowFrame = self.window.frame;
  186. windowFrame.size.width = _videoView.frame.size.width;
  187. windowFrame.size.height = _videoView.frame.size.height;
  188. self.window.frame = windowFrame;
  189. [self.window addSubview:_videoView];
  190. UITapGestureRecognizer *tap =
  191. [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(touchedWindow:)];
  192. [_videoView addGestureRecognizer:tap];
  193. }
  194. return _videoView;
  195. }
  196. - (UIButton *)floatingButton {
  197. if (!_floatingButton) {
  198. _floatingButton = [UIButton buttonWithType:UIButtonTypeCustom];
  199. if (self.callSession.isAudioOnly) {
  200. [_floatingButton setImage:[UIImage imageNamed:@"floatingaudio"]
  201. forState:UIControlStateNormal];
  202. } else {
  203. [_floatingButton setImage:[UIImage imageNamed:@"floatingvideo"]
  204. forState:UIControlStateNormal];
  205. }
  206. [_floatingButton setTitle:@"" forState:UIControlStateNormal];
  207. _floatingButton.backgroundColor = [UIColor clearColor];
  208. _floatingButton.frame = CGRectMake(0, 0, 64, 96);
  209. CGRect windowFrame = self.window.frame;
  210. windowFrame.size.width = _floatingButton.frame.size.width;
  211. windowFrame.size.height = _floatingButton.frame.size.height;
  212. self.window.frame = windowFrame;
  213. [_floatingButton addTarget:self action:@selector(touchedWindow:) forControlEvents:UIControlEventTouchUpInside];
  214. [self.window addSubview:_floatingButton];
  215. }
  216. return _floatingButton;
  217. }
  218. - (BOOL)isSupportOrientation:(UIInterfaceOrientation)orientation {
  219. UIInterfaceOrientationMask mask =
  220. [[UIApplication sharedApplication] supportedInterfaceOrientationsForWindow:self.window];
  221. return mask & (1 << orientation);
  222. }
  223. - (void)handlePanGestures:(UIPanGestureRecognizer *)paramSender {
  224. if (paramSender.state != UIGestureRecognizerStateEnded && paramSender.state != UIGestureRecognizerStateFailed) {
  225. CGPoint location = [paramSender locationInView:[UIApplication sharedApplication].windows[0]];
  226. if ([UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeLeft &&
  227. [self isSupportOrientation:UIInterfaceOrientationLandscapeLeft]) {
  228. CGFloat tmp = location.x;
  229. location.x = [UIScreen mainScreen].bounds.size.height - location.y;
  230. location.y = tmp;
  231. } else if ([UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeRight &&
  232. [self isSupportOrientation:UIInterfaceOrientationLandscapeRight]) {
  233. CGFloat tmp = location.x;
  234. location.x = location.y;
  235. location.y = [UIScreen mainScreen].bounds.size.width - tmp;
  236. } else if ([UIDevice currentDevice].orientation == UIDeviceOrientationPortraitUpsideDown &&
  237. [self isSupportOrientation:UIInterfaceOrientationPortraitUpsideDown]) {
  238. CGFloat tmp = location.x;
  239. location.x = [UIScreen mainScreen].bounds.size.height - location.y;
  240. location.y = [UIScreen mainScreen].bounds.size.width - tmp;
  241. }
  242. CGRect frame = self.window.frame;
  243. frame.origin.x = location.x - frame.size.width / 2;
  244. frame.origin.y = location.y - frame.size.height / 2;
  245. if (frame.origin.x < 0) {
  246. frame.origin.x = 2;
  247. }
  248. if (frame.origin.y < 0) {
  249. frame.origin.y = 2;
  250. }
  251. CGRect screenBounds = [UIScreen mainScreen].bounds;
  252. BOOL isLandscape = screenBounds.size.width > screenBounds.size.height;
  253. if (isLandscape && [self isSupportOrientation:(UIInterfaceOrientation)[UIDevice currentDevice].orientation]) {
  254. if (frame.origin.y + frame.size.height > [UIScreen mainScreen].bounds.size.width) {
  255. frame.origin.y = [UIScreen mainScreen].bounds.size.width - 2 - frame.size.height;
  256. }
  257. if (frame.origin.x + frame.size.width > [UIScreen mainScreen].bounds.size.height) {
  258. frame.origin.x = [UIScreen mainScreen].bounds.size.height - 2 - frame.size.width;
  259. }
  260. } else {
  261. if (frame.origin.x + frame.size.width > [UIScreen mainScreen].bounds.size.width) {
  262. frame.origin.x = [UIScreen mainScreen].bounds.size.width - 2 - frame.size.width;
  263. }
  264. if (frame.origin.y + frame.size.height > [UIScreen mainScreen].bounds.size.height) {
  265. frame.origin.y = [UIScreen mainScreen].bounds.size.height - 2 - frame.size.height;
  266. }
  267. }
  268. self.window.frame = frame;
  269. } else if (paramSender.state == UIGestureRecognizerStateEnded) {
  270. CGRect frame = self.window.frame;
  271. [[NSUserDefaults standardUserDefaults] setObject:@(frame.origin.x) forKey:kFloatingWindowPosX];
  272. [[NSUserDefaults standardUserDefaults] setObject:@(frame.origin.y) forKey:kFloatingWindowPosY];
  273. [[NSUserDefaults standardUserDefaults] synchronize];
  274. }
  275. }
  276. - (void)touchedWindow:(id)sender {
  277. [self hideCallFloatingWindow];
  278. if (self.touchedBlock) {
  279. self.touchedBlock(self.callSession);
  280. }
  281. [self clearCallFloatingWindow];
  282. }
  283. - (void)hideCallFloatingWindow {
  284. [self stopActiveTimer];
  285. if (_videoView) {
  286. [_videoView removeFromSuperview];
  287. _videoView = nil;
  288. }
  289. if (_floatingButton) {
  290. [_floatingButton removeFromSuperview];
  291. _floatingButton = nil;
  292. }
  293. [_window setHidden:YES];
  294. }
  295. - (void)clearCallFloatingWindow {
  296. [[NSNotificationCenter defaultCenter] removeObserver:self];
  297. _activeTimer = nil;
  298. _callSession = nil;
  299. _touchedBlock = nil;
  300. _floatingButton = nil;
  301. _videoView = nil;
  302. _window = nil;
  303. staticWindow = nil;
  304. }
  305. - (void)layoutTextUnderImageButton:(UIButton *)button {
  306. [button.titleLabel setFont:[UIFont systemFontOfSize:16]];
  307. [button setTitleColor:HEXCOLOR(0x0195ff) forState:UIControlStateNormal];
  308. button.titleEdgeInsets = UIEdgeInsetsMake(0, -button.imageView.frame.size.width,
  309. -button.imageView.frame.size.height - 6 / 2, 0);
  310. // button.imageEdgeInsets =
  311. // UIEdgeInsetsMake(-button.titleLabel.frame.size.height-offset/2, 0, 0,
  312. // -button.titleLabel.frame.size.width);
  313. // 由于iOS8中titleLabel的size为0,用上面这样设置有问题,修改一下即可
  314. button.imageEdgeInsets = UIEdgeInsetsMake(-button.titleLabel.intrinsicContentSize.height - 6 / 2,
  315. 0, 0, -button.titleLabel.intrinsicContentSize.width);
  316. }
  317. - (BOOL)isVideoViewEnabledSession {
  318. return !self.callSession.isAudioOnly;
  319. }
  320. #pragma mark - WFAVCallSessionDelegate
  321. - (void)didChangeState:(WFAVEngineState)state {
  322. switch (state) {
  323. case kWFAVEngineStateIdle:
  324. [self updateWindow];
  325. [self performSelector:@selector(clearCallFloatingWindow) withObject:nil afterDelay:2];
  326. [self removeProximityMonitoringObserver];
  327. break;
  328. case kWFAVEngineStateConnected:
  329. [self updateWindow];
  330. break;
  331. default:
  332. break;
  333. }
  334. }
  335. - (void)didCallEndWithReason:(WFAVCallEndReason)reason {
  336. }
  337. - (void)didParticipantJoined:(NSString *)userId {
  338. }
  339. - (void)didParticipantConnected:(NSString *)userId {
  340. }
  341. - (void)didParticipantLeft:(NSString *)userId withReason:(WFAVCallEndReason)reason {
  342. }
  343. - (void)didError:(NSError *)error {
  344. }
  345. - (void)didGetStats:(NSArray *)stats {
  346. }
  347. - (void)didCreateLocalVideoTrack:(RTCVideoTrack *)localVideoTrack {
  348. }
  349. - (void)didReceiveRemoteVideoTrack:(RTCVideoTrack *)remoteVideoTrack fromUser:(NSString *)userId {
  350. }
  351. - (void)didVideoMuted:(BOOL)videoMuted fromUser:(NSString *)userId {
  352. }
  353. - (void)didReportAudioVolume:(NSInteger)volume ofUser:(NSString *)userId {
  354. }
  355. - (void)didChangeMode:(BOOL)isAudioOnly {
  356. [self.videoView removeFromSuperview];
  357. [self initWindow];
  358. }
  359. - (void)addProximityMonitoringObserver {
  360. [UIDevice currentDevice].proximityMonitoringEnabled = YES;
  361. [[NSNotificationCenter defaultCenter] addObserver:self
  362. selector:@selector(proximityStatueChanged:)
  363. name:UIDeviceProximityStateDidChangeNotification
  364. object:nil];
  365. }
  366. - (void)removeProximityMonitoringObserver {
  367. [UIDevice currentDevice].proximityMonitoringEnabled = NO;
  368. [[NSNotificationCenter defaultCenter] removeObserver:self
  369. name:UIDeviceProximityStateDidChangeNotification
  370. object:nil];
  371. }
  372. - (void)proximityStatueChanged:(NSNotificationCenter *)notification {
  373. }
  374. @end
  375. #endif