CallParticipantViewCell.m 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. /**
  2. * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
  3. * SPDX-License-Identifier: GPL-3.0-or-later
  4. */
  5. #import "CallParticipantViewCell.h"
  6. #import "CallViewController.h"
  7. #import "NCAPIController.h"
  8. #import "NCDatabaseManager.h"
  9. #import "NextcloudTalk-Swift.h"
  10. NSString *const kCallParticipantCellIdentifier = @"CallParticipantCellIdentifier";
  11. NSString *const kCallParticipantCellNibName = @"CallParticipantViewCell";
  12. CGFloat const kCallParticipantCellMinHeight = 128;
  13. @interface CallParticipantViewCell()
  14. {
  15. UIView<RTCVideoRenderer> *_videoView;
  16. CGSize _remoteVideoSize;
  17. NSTimer *_disconnectedTimer;
  18. }
  19. @end
  20. @implementation CallParticipantViewCell
  21. - (void)awakeFromNib
  22. {
  23. [super awakeFromNib];
  24. self.audioOffIndicator.hidden = YES;
  25. self.screensharingIndicator.hidden = YES;
  26. self.raisedHandIndicator.hidden = YES;
  27. self.audioOffIndicator.layer.cornerRadius = 4;
  28. self.audioOffIndicator.clipsToBounds = YES;
  29. self.screensharingIndicator.layer.cornerRadius = 4;
  30. self.screensharingIndicator.clipsToBounds = YES;
  31. self.activityIndicator.radius = 50.0f;
  32. self.activityIndicator.cycleColors = @[UIColor.lightGrayColor];
  33. self.peerAvatarImageView.hidden = YES;
  34. self.peerAvatarImageView.layer.cornerRadius = self.peerAvatarImageView.bounds.size.width / 2;
  35. self.peerAvatarImageView.layer.masksToBounds = YES;
  36. self.layer.cornerRadius = 22.0f;
  37. [self.layer setMasksToBounds:YES];
  38. _showOriginalSize = NO;
  39. UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleZoom)];
  40. [tapGestureRecognizer setNumberOfTapsRequired:2];
  41. [self.contentView addGestureRecognizer:tapGestureRecognizer];
  42. }
  43. - (void)prepareForReuse
  44. {
  45. [super prepareForReuse];
  46. [_peerAvatarImageView cancelCurrentRequest];
  47. _peerAvatarImageView.image = nil;
  48. _peerAvatarImageView.alpha = 1;
  49. _displayName = nil;
  50. _peerNameLabel.text = nil;
  51. [_videoView removeFromSuperview];
  52. _videoView = nil;
  53. _showOriginalSize = NO;
  54. self.layer.borderWidth = 0.0f;
  55. [self hideLoadingSpinner];
  56. [self invalidateDisconnectedTimer];
  57. }
  58. - (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
  59. {
  60. [super applyLayoutAttributes:layoutAttributes];
  61. [self resizeRemoteVideoView];
  62. }
  63. - (void)layoutSubviews
  64. {
  65. [super layoutSubviews];
  66. CGRect bounds = self.bounds;
  67. // Usually we have a padding to the side of the cell of 22 (= cornerRadius)
  68. // But when the cell is really small adjust the padding to be 11 (= cornerRadius / 2)
  69. if (bounds.size.width <= 200 || bounds.size.height <= 200) {
  70. self.stackViewLeftConstraint.constant = 11;
  71. self.stackViewRightConstraint.constant = 11;
  72. self.stackViewBottomConstraint.constant = 11;
  73. self.screensharingIndiciatorTopConstraint.constant = 11;
  74. self.screensharingIndiciatorRightConstraint.constant = 11;
  75. } else {
  76. self.stackViewLeftConstraint.constant = 22;
  77. self.stackViewRightConstraint.constant = 22;
  78. self.stackViewBottomConstraint.constant = 22;
  79. self.screensharingIndiciatorTopConstraint.constant = 22;
  80. self.screensharingIndiciatorRightConstraint.constant = 22;
  81. }
  82. [self.contentView layoutSubviews];
  83. self.peerAvatarImageView.layer.cornerRadius = self.peerAvatarImageView.bounds.size.width / 2;
  84. self.activityIndicator.radius = self.peerAvatarImageView.bounds.size.width / 2;
  85. }
  86. - (void)toggleZoom
  87. {
  88. _showOriginalSize = !_showOriginalSize;
  89. [self.actionsDelegate cellWantsToChangeZoom:self showOriginalSize:_showOriginalSize];
  90. [self resizeRemoteVideoView];
  91. }
  92. - (void)setAvatarForActor:(TalkActor * _Nullable)actor
  93. {
  94. if (actor.id == nil || actor.id.length == 0) {
  95. [self setBackgroundColor:[UIColor colorWithWhite:0.5 alpha:1]];
  96. } else if (actor.displayName && actor.displayName.length > 0) {
  97. [self setBackgroundColor:[[ColorGenerator shared] usernameToColor:actor.displayName]];
  98. } else {
  99. [self setBackgroundColor:[[ColorGenerator shared] usernameToColor:actor.id]];
  100. }
  101. [self.peerAvatarImageView setActorAvatarForId:actor.id withType:actor.type withDisplayName:actor.displayName withRoomToken:nil];
  102. }
  103. - (void)setDisplayName:(NSString *)displayName
  104. {
  105. _displayName = displayName;
  106. if (!displayName || [displayName isKindOfClass:[NSNull class]] || [displayName isEqualToString:@""]) {
  107. _displayName = NSLocalizedString(@"Guest", nil);
  108. }
  109. if ([self.peerNameLabel.text isEqualToString:_displayName]) {
  110. return;
  111. }
  112. dispatch_async(dispatch_get_main_queue(), ^{
  113. self.peerNameLabel.text = self->_displayName;
  114. [self setBackgroundColor:[[ColorGenerator shared] usernameToColor:self->_displayName]];
  115. });
  116. }
  117. - (void)setAudioDisabled:(BOOL)audioDisabled
  118. {
  119. _audioDisabled = audioDisabled;
  120. dispatch_async(dispatch_get_main_queue(), ^{
  121. [self configureParticipantButtons];
  122. });
  123. }
  124. - (void)setScreenShared:(BOOL)screenShared
  125. {
  126. _screenShared = screenShared;
  127. dispatch_async(dispatch_get_main_queue(), ^{
  128. [self configureParticipantButtons];
  129. });
  130. }
  131. - (void)setConnectionState:(RTCIceConnectionState)connectionState
  132. {
  133. _connectionState = connectionState;
  134. [self invalidateDisconnectedTimer];
  135. if (connectionState == RTCIceConnectionStateDisconnected) {
  136. [self setDisconnectedTimer];
  137. } else if (connectionState == RTCIceConnectionStateFailed) {
  138. [self setFailedConnectionUI];
  139. } else if (connectionState != RTCIceConnectionStateCompleted && connectionState != RTCIceConnectionStateConnected) {
  140. [self setConnectingUI];
  141. } else {
  142. [self setConnectedUI];
  143. }
  144. }
  145. - (void)setDisconnectedTimer
  146. {
  147. _disconnectedTimer = [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(setDisconnectedUI) userInfo:nil repeats:NO];
  148. }
  149. - (void)invalidateDisconnectedTimer
  150. {
  151. [_disconnectedTimer invalidate];
  152. _disconnectedTimer = nil;
  153. }
  154. - (void)setDisconnectedUI
  155. {
  156. if (_connectionState == RTCIceConnectionStateDisconnected) {
  157. dispatch_async(dispatch_get_main_queue(), ^{
  158. self.peerNameLabel.text = [NSString stringWithFormat:NSLocalizedString(@"Connecting to %@ …", nil), self->_displayName];
  159. self.peerAvatarImageView.alpha = 0.3;
  160. [self hideLoadingSpinner];
  161. });
  162. }
  163. }
  164. - (void)setConnectingUI
  165. {
  166. dispatch_async(dispatch_get_main_queue(), ^{
  167. self.peerAvatarImageView.alpha = 0.3;
  168. [self showLoadingSpinner];
  169. });
  170. }
  171. - (void)setFailedConnectionUI
  172. {
  173. dispatch_async(dispatch_get_main_queue(), ^{
  174. self.peerNameLabel.text = [NSString stringWithFormat:NSLocalizedString(@"Failed to connect to %@", nil), self->_displayName];
  175. self.peerAvatarImageView.alpha = 0.3;
  176. });
  177. }
  178. - (void)setConnectedUI
  179. {
  180. dispatch_async(dispatch_get_main_queue(), ^{
  181. self.peerNameLabel.text = self->_displayName;
  182. self.peerAvatarImageView.alpha = 1;
  183. [self hideLoadingSpinner];
  184. });
  185. }
  186. - (void)showLoadingSpinner
  187. {
  188. [self.activityIndicator startAnimating];
  189. [self.activityIndicator setHidden:NO];
  190. }
  191. - (void)hideLoadingSpinner
  192. {
  193. [self.activityIndicator stopAnimating];
  194. [self.activityIndicator setHidden:YES];
  195. }
  196. - (IBAction)screenSharingButtonPressed:(id)sender
  197. {
  198. [self.actionsDelegate cellWantsToPresentScreenSharing:self];
  199. }
  200. - (void)configureParticipantButtons
  201. {
  202. self.audioOffIndicator.hidden = !_audioDisabled;
  203. self.screensharingIndicator.hidden = !_screenShared;
  204. }
  205. - (void)setVideoDisabled:(BOOL)videoDisabled
  206. {
  207. _videoDisabled = videoDisabled;
  208. if (videoDisabled) {
  209. [_videoView setHidden:YES];
  210. [_peerAvatarImageView setHidden:NO];
  211. } else {
  212. [_peerAvatarImageView setHidden:YES];
  213. [_videoView setHidden:NO];
  214. }
  215. }
  216. - (void)setSpeaking:(BOOL)speaking
  217. {
  218. if (speaking) {
  219. self.layer.borderColor = [UIColor whiteColor].CGColor;
  220. self.layer.borderWidth = 2.0f;
  221. } else {
  222. self.layer.borderWidth = 0.0f;
  223. }
  224. }
  225. - (void)setRaiseHand:(BOOL)raised
  226. {
  227. dispatch_async(dispatch_get_main_queue(), ^{
  228. self.raisedHandIndicator.hidden = !raised;
  229. });
  230. }
  231. - (void)setVideoView:(RTCMTLVideoView *)videoView
  232. {
  233. dispatch_async(dispatch_get_main_queue(), ^{
  234. if (videoView == self->_videoView) {
  235. return;
  236. }
  237. [self->_videoView removeFromSuperview];
  238. self->_videoView = nil;
  239. self->_videoView = videoView;
  240. [self->_peerVideoView addSubview:self->_videoView];
  241. [self->_videoView setHidden:self->_videoDisabled];
  242. [self resizeRemoteVideoView];
  243. });
  244. }
  245. - (CGSize)getRemoteVideoSize
  246. {
  247. return self->_remoteVideoSize;
  248. }
  249. - (void)setRemoteVideoSize:(CGSize)size
  250. {
  251. self->_remoteVideoSize = size;
  252. [self resizeRemoteVideoView];
  253. }
  254. - (void)resizeRemoteVideoView
  255. {
  256. CGRect bounds = self.bounds;
  257. CGSize videoSize = _remoteVideoSize;
  258. if (videoSize.width > 0 && videoSize.height > 0) {
  259. // Aspect fill remote video into bounds.
  260. CGRect remoteVideoFrame = AVMakeRectWithAspectRatioInsideRect(videoSize, bounds);
  261. CGFloat scale = 1;
  262. if (!_showOriginalSize) {
  263. CGFloat scaleHeight = bounds.size.height / remoteVideoFrame.size.height;
  264. CGFloat scaleWidth = bounds.size.width / remoteVideoFrame.size.width;
  265. // Always grab the bigger scale to make video cover the whole cell
  266. scale = (scaleHeight > scaleWidth) ? scaleHeight : scaleWidth;
  267. }
  268. remoteVideoFrame.size.height *= scale;
  269. remoteVideoFrame.size.width *= scale;
  270. _videoView.frame = remoteVideoFrame;
  271. _videoView.center = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
  272. } else {
  273. _videoView.frame = bounds;
  274. }
  275. }
  276. @end