blob: 3d1619dab64bc23e6771d5ec1e30690d44b2342a [file] [log] [blame]
Alexandre Lisionc5148052015-03-04 15:10:35 -05001/*
Alexandre Lision9fe374b2016-01-06 10:17:31 -05002 * Copyright (C) 2015-2016 Savoir-faire Linux Inc.
Alexandre Lisionc5148052015-03-04 15:10:35 -05003 * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Alexandre Lisionc5148052015-03-04 15:10:35 -050018 */
19#import "CurrentCallVC.h"
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -040020extern "C" {
21#import "libavutil/frame.h"
22#import "libavutil/display.h"
23}
Alexandre Lisionc5148052015-03-04 15:10:35 -050024
25#import <QuartzCore/QuartzCore.h>
26
Alexandre Lision210fe212016-01-27 11:15:13 -050027///Qt
28#import <QMimeData>
29#import <QtMacExtras/qmacfunctions.h>
30#import <QtCore/qabstractitemmodel.h>
Alexandre Lision210fe212016-01-27 11:15:13 -050031#import <QPixmap>
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -040032#import <QUrl>
Alexandre Lision210fe212016-01-27 11:15:13 -050033
34///LRC
Alexandre Lisionc5148052015-03-04 15:10:35 -050035#import <video/renderer.h>
Olivier Soldano994971f2017-12-05 16:30:12 -050036#import <api/newcallmodel.h>
Kateryna Kostiukf362e2b2020-09-10 19:00:52 -040037#import <api/newaccountmodel.h>
Olivier Soldano994971f2017-12-05 16:30:12 -050038#import <api/call.h>
39#import <api/conversationmodel.h>
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -040040#import <api/avmodel.h>
Alexandre Lision210fe212016-01-27 11:15:13 -050041#import <globalinstances.h>
Alexandre Lisionc5148052015-03-04 15:10:35 -050042
Alexandre Lision62005312016-01-28 15:55:16 -050043#import "AppDelegate.h"
Alexandre Lisionf47a2562015-06-15 15:48:29 -040044#import "views/ITProgressIndicator.h"
Alexandre Lision74dd47f2015-04-14 13:47:42 -040045#import "views/CallView.h"
Kateryna Kostiuk81afce72018-10-30 16:40:50 -040046#import "views/NSColor+RingTheme.h"
Alexandre Lision210fe212016-01-27 11:15:13 -050047#import "delegates/ImageManipulationDelegate.h"
Alexandre Lision16d9c0a2015-08-10 12:05:15 -040048#import "ChatVC.h"
Alexandre Lision4baba4c2016-02-11 13:00:57 -050049#import "views/IconButton.h"
Anthony Léonard6f819752018-01-05 09:53:40 -050050#import "utils.h"
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -040051#import "views/CallMTKView.h"
Kateryna Kostiukc5fd3b92019-07-24 10:12:44 -040052#import "VideoCommon.h"
Kateryna Kostiuk88a6ee82019-09-10 17:55:08 -040053#import "views/GradientView.h"
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -040054#import "views/MovableView.h"
Kateryna Kostiuk06681682020-05-07 20:50:56 -040055#import "views/RenderingView.h"
Alexandre Lision74dd47f2015-04-14 13:47:42 -040056
Kateryna Kostiuk1ba5a012018-12-12 17:07:38 -050057@interface CurrentCallVC () <NSPopoverDelegate> {
Kateryna Kostiukc867eb92020-03-08 13:15:17 -040058 QString convUid_;
59 QString callUid_;
60 QString confUid_;
Olivier Soldano994971f2017-12-05 16:30:12 -050061 const lrc::api::account::Info *accountInfo_;
Anthony Léonard66a050c2018-01-03 16:32:34 -050062 NSTimer* refreshDurationTimer;
Olivier Soldano994971f2017-12-05 16:30:12 -050063}
Alexandre Lisionc5148052015-03-04 15:10:35 -050064
Alexandre Lision210fe212016-01-27 11:15:13 -050065// Main container
66@property (unsafe_unretained) IBOutlet NSSplitView* splitView;
Kateryna Kostiuk89398b62018-11-20 16:45:04 -050067@property (unsafe_unretained) IBOutlet NSView* backgroundImage;
Kateryna Kostiuk81afce72018-10-30 16:40:50 -040068@property (unsafe_unretained) IBOutlet NSBox* bluerBackgroundEffect;
Alexandre Lision210fe212016-01-27 11:15:13 -050069
Alexandre Lision89edc6a2015-11-09 11:30:47 -050070// Header info
71@property (unsafe_unretained) IBOutlet NSView* headerContainer;
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -040072@property (unsafe_unretained) IBOutlet NSView* headerGradientView;
Alexandre Lision89edc6a2015-11-09 11:30:47 -050073@property (unsafe_unretained) IBOutlet NSTextField* timeSpentLabel;
74
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -040075// info
76@property (unsafe_unretained) IBOutlet NSStackView* infoContainer;
77@property (unsafe_unretained) IBOutlet NSImageView* contactPhoto;
78@property (unsafe_unretained) IBOutlet NSTextField* contactNameLabel;
79@property (unsafe_unretained) IBOutlet NSTextField* callStateLabel;
80@property (unsafe_unretained) IBOutlet NSTextField* contactIdLabel;
81@property (unsafe_unretained) IBOutlet IconButton* cancelCallButton;
82@property (unsafe_unretained) IBOutlet IconButton* pickUpButton;
83@property (unsafe_unretained) IBOutlet ITProgressIndicator *loadingIndicator;
Andreas Traczyk77a50d52018-05-08 17:47:31 -040084
Alexandre Lision89edc6a2015-11-09 11:30:47 -050085// Call Controls
Kateryna Kostiuk88a6ee82019-09-10 17:55:08 -040086@property (unsafe_unretained) IBOutlet GradientView* controlsPanel;
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -040087@property (unsafe_unretained) IBOutlet NSStackView* controlsStackView;
Alexandre Lision62005312016-01-28 15:55:16 -050088
Alexandre Lision4baba4c2016-02-11 13:00:57 -050089@property (unsafe_unretained) IBOutlet IconButton* holdOnOffButton;
90@property (unsafe_unretained) IBOutlet IconButton* hangUpButton;
91@property (unsafe_unretained) IBOutlet IconButton* recordOnOffButton;
Alexandre Lision4baba4c2016-02-11 13:00:57 -050092@property (unsafe_unretained) IBOutlet IconButton* muteAudioButton;
93@property (unsafe_unretained) IBOutlet IconButton* muteVideoButton;
Alexandre Lision4baba4c2016-02-11 13:00:57 -050094@property (unsafe_unretained) IBOutlet IconButton* transferButton;
95@property (unsafe_unretained) IBOutlet IconButton* addParticipantButton;
96@property (unsafe_unretained) IBOutlet IconButton* chatButton;
Andreas Traczyk77a50d52018-05-08 17:47:31 -040097@property (unsafe_unretained) IBOutlet IconButton* qualityButton;
Alexandre Lisiond18fa272015-06-15 11:18:03 -040098
Alexandre Lisionc5148052015-03-04 15:10:35 -050099// Video
Alexandre Lision58cab672015-06-09 15:25:40 -0400100@property (unsafe_unretained) IBOutlet CallView *videoView;
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400101@property (unsafe_unretained) IBOutlet RenderingView *previewView;
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -0400102@property (unsafe_unretained) IBOutlet MovableView *movableBaseForView;
103@property (unsafe_unretained) IBOutlet NSView* hidePreviewBackground;
104@property (unsafe_unretained) IBOutlet NSButton* hidePreviewButton;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400105
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400106@property (unsafe_unretained) IBOutlet RenderingView *distantView;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500107
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400108@property RendererConnectionsHolder* renderConnections;
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400109@property QMetaObject::Connection videoStarted;
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400110@property QMetaObject::Connection callStateChanged;
Alexandre Lisionb65c0272015-07-22 15:51:29 -0400111@property QMetaObject::Connection messageConnection;
Kateryna Kostiuk133364b2018-10-31 11:36:08 -0400112@property QMetaObject::Connection profileUpdatedConnection;
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -0400113@property QMetaObject::Connection participantsChangedConnection;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500114
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400115//conference
116@property (unsafe_unretained) IBOutlet NSStackView *callingWidgetsContainer;
117
118@property (strong) NSPopover* brokerPopoverVC;
119
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400120@property (strong) IBOutlet ChatVC* chatVC;
121
Alexandre Lisionc5148052015-03-04 15:10:35 -0500122@end
123
124@implementation CurrentCallVC
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400125lrc::api::AVModel* mediaModel;
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400126NSMutableDictionary *connectingCalls;
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -0400127NSMutableDictionary *participantsOverlays;
128NSSize framesize;
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -0400129
130NSInteger const PREVIEW_WIDTH = 185;
131NSInteger const PREVIEW_HEIGHT = 130;
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400132NSInteger const HIDE_PREVIEW_BUTTON_SIZE = 25;
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -0400133NSInteger const PREVIEW_MARGIN = 20;
134
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400135@synthesize holdOnOffButton, hangUpButton, recordOnOffButton, pickUpButton, chatButton, transferButton, addParticipantButton, timeSpentLabel, muteVideoButton, muteAudioButton, controlsPanel, headerContainer, videoView, previewView, splitView, loadingIndicator, backgroundImage, bluerBackgroundEffect, hidePreviewButton, hidePreviewBackground, movableBaseForView, infoContainer, contactPhoto, contactNameLabel, callStateLabel, contactIdLabel, cancelCallButton, headerGradientView, controlsStackView, callingWidgetsContainer, brokerPopoverVC;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500136
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400137@synthesize renderConnections;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400138CVPixelBufferPoolRef pixelBufferPoolDistantView;
139CVPixelBufferRef pixelBufferDistantView;
140CVPixelBufferPoolRef pixelBufferPoolPreview;
141CVPixelBufferRef pixelBufferPreview;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500142
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400143/* update call and conversation info
144 * set info for chat view
145 * connect signals
146 */
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400147-(void) setCurrentCall:(const QString&)callUid
148 conversation:(const QString&)convUid
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400149 account:(const lrc::api::account::Info*)account
150 avModel:(lrc::api::AVModel *)avModel;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500151{
Olivier Soldano994971f2017-12-05 16:30:12 -0500152 if(account == nil)
153 return;
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400154
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400155 mediaModel = avModel;
Olivier Soldano994971f2017-12-05 16:30:12 -0500156 auto* callModel = account->callModel.get();
Alexandre Lisionc5148052015-03-04 15:10:35 -0500157
Olivier Soldano994971f2017-12-05 16:30:12 -0500158 if (not callModel->hasCall(callUid)){
159 callUid_.clear();
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400160 confUid_.clear();
Alexandre Lision2db8f472015-07-22 15:05:46 -0400161 return;
162 }
Olivier Soldano994971f2017-12-05 16:30:12 -0500163 callUid_ = callUid;
164 convUid_ = convUid;
165 accountInfo_ = account;
Kateryna Kostiuk84101912020-08-31 16:06:33 -0400166 auto conv = accountInfo_->conversationModel->getConversationForUID(convUid_);
167 if (conv.uid.isEmpty()) {
168 return;
169 }
170 confUid_ = conv.confId;
Kateryna Kostiukf362e2b2020-09-10 19:00:52 -0400171 [self.chatVC setConversationUid:convUid model: account->conversationModel.get()];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400172 [self connectSignals];
173}
174
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -0400175-(void)updateConferenceOverlays:(QString)callID {
176 auto* callModel = accountInfo_->callModel.get();
177 auto call = callModel->getCall(callID);
178 using Status = lrc::api::call::Status;
179 switch (call.status) {
180 case Status::ENDED:
181 case Status::TERMINATING:
182 case Status::INVALID:
183 case Status::PEER_BUSY:
184 case Status::TIMEOUT:
185 if (participantsOverlays[call.peerUri.toNSString()] != nil) {
186 [participantsOverlays[call.peerUri.toNSString()] removeFromSuperview];
187 participantsOverlays[call.peerUri.toNSString()] = nil;
188 if (![self isCurrentCall: callID]) {
189 [self updateCall];
190 [self setCurrentCall: callUid_ conversation:convUid_ account:accountInfo_ avModel:mediaModel];
191 }
192 }
193 break;
194 }
195}
196
Kateryna Kostiuk84101912020-08-31 16:06:33 -0400197-(void)switchToNextConferenceCall:(QString)confId {
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -0400198 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -0400199 AppDelegate* appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
200 auto activeCalls = [appDelegate getActiveCalls];
201 if (activeCalls.isEmpty()) {
202 return;
203 }
Kateryna Kostiuk84101912020-08-31 16:06:33 -0400204 auto subcalls = [appDelegate getConferenceSubcalls: confId];
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -0400205 QString callId;
206 if (subcalls.isEmpty()) {
207 for(auto subcall: activeCalls) {
208 if(subcall != callUid_) {
209 callId = subcall;
210 }
211 }
212 } else {
213 for(auto subcall: subcalls) {
214 if(subcall != callUid_) {
215 callId = subcall;
216 }
217 }
218 }
219 if (!callModel->hasCall(callId)) {
220 return;
221 }
222 auto callToSwitch = callModel->getCall(callId);
223 auto convIt = getConversationFromURI(callToSwitch.peerUri, *accountInfo_->conversationModel);
224 if (convIt == accountInfo_->conversationModel->allFilteredConversations().end()) {
225 return;
226 }
227 [self.delegate chooseConversation: *convIt model:accountInfo_->conversationModel.get()];
228}
229
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400230-(void) connectSignals {
231 auto* callModel = accountInfo_->callModel.get();
232 auto* convModel = accountInfo_->conversationModel.get();
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -0400233 QObject::disconnect(self.participantsChangedConnection);
234 self.participantsChangedConnection = QObject::connect(callModel,
235 &lrc::api::NewCallModel::onParticipantsChanged,
236 [self](const QString& confId) {
237 auto* callModel = accountInfo_->callModel.get();
238 if (!callModel->hasCall(confId)) {
239 return;
240 }
241 if ([self isCurrentCall: confId]) {
242 [self updateConference: confId];
243 }
244 });
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400245 //monitor for updated call state
246 QObject::disconnect(self.callStateChanged);
247 self.callStateChanged = QObject::connect(callModel,
248 &lrc::api::NewCallModel::callStatusChanged,
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400249 [self](const QString& callId) {
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -0400250 [self updateConferenceOverlays: callId];
251 if ([self isCurrentCall: callId]) {
252 [self updateCall];
253 }
254 });
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400255 /* monitor media for messaging text messaging */
256 QObject::disconnect(self.messageConnection);
257 self.messageConnection = QObject::connect(convModel,
258 &lrc::api::ConversationModel::interactionStatusUpdated,
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400259 [self] (const QString& convUid,
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400260 uint64_t msgId,
261 lrc::api::interaction::Info msg) {
262 if (msg.type == lrc::api::interaction::Type::TEXT) {
263 if(not [[self splitView] isSubviewCollapsed:[[[self splitView] subviews] objectAtIndex: 1]]){
264 return;
265 }
266 [self uncollapseRightView];
267 }
268 });
269 //monitor for updated profile
270 QObject::disconnect(self.profileUpdatedConnection);
271 self.profileUpdatedConnection =
272 QObject::connect(accountInfo_->contactModel.get(),
273 &lrc::api::ContactModel::contactAdded,
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400274 [self](const QString &contactUri) {
Kateryna Kostiuk84101912020-08-31 16:06:33 -0400275 auto conv = accountInfo_->conversationModel->getConversationForUID(convUid_);
276 if (conv.uid.isEmpty() || conv.participants.empty()) {
277 return;
278 }
279 auto& contact = accountInfo_->contactModel->getContact(conv.participants[0]);
280 if (contact.profileInfo.type == lrc::api::profile::Type::RING && contact.profileInfo.uri == contactUri)
281 accountInfo_->conversationModel->makePermanent(convUid_);
282 [contactPhoto setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
283 [self.delegate conversationInfoUpdatedFor:convUid_];
284 [self setBackground];
285 });
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400286 [self connectVideoSignals];
287}
288
289-(void) updatePendingCalls {
290 for (NSView *view in callingWidgetsContainer.subviews) {
291 view.removeFromSuperview;
292 }
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400293 NSDictionary * calls = connectingCalls[callUid_.toNSString()];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400294 for (NSViewController * callView in calls.allValues) {
295 [self.callingWidgetsContainer addView: callView.view inGravity:NSStackViewGravityBottom];
296 }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500297}
298
Kateryna Kostiuk88a6ee82019-09-10 17:55:08 -0400299-(void) setUpButtons:(lrc::api::call::Info&)callInfo isRecording:(BOOL) isRecording {
300 muteAudioButton.image = callInfo.audioMuted ? [NSImage imageNamed:@"ic_action_mute_audio.png"] :
301 [NSImage imageNamed:@"ic_action_audio.png"];
302 muteVideoButton.image = callInfo.videoMuted ? [NSImage imageNamed:@"ic_action_mute_video.png"] :
303 [NSImage imageNamed:@"ic_action_video.png"];
304 [muteVideoButton setHidden: callInfo.isAudioOnly ? YES: NO];
305 if (isRecording) {
306 [recordOnOffButton startBlinkAnimationfrom:[NSColor buttonBlinkColorColor] to:[NSColor whiteColor] scaleFactor: 1 duration: 1.5];
307 } else {
308 [recordOnOffButton stopBlinkAnimation];
309 }
310}
311
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400312- (void) setUpPreviewFrame {
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -0400313 CGPoint previewOrigin = CGPointMake(self.videoView.frame.size.width - PREVIEW_WIDTH - PREVIEW_MARGIN, PREVIEW_MARGIN);
314 movableBaseForView.frame = CGRectMake(previewOrigin.x, previewOrigin.y, PREVIEW_WIDTH, PREVIEW_HEIGHT);
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -0400315 self.movableBaseForView.movable = true;
316 previewView.frame = movableBaseForView.bounds;
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400317 hidePreviewBackground.frame = [self frameForExpendPreviewButton];
Kateryna Kostiuke9eb38b2020-03-23 14:33:08 -0400318 if ([hidePreviewButton respondsToSelector:@selector(contentTintColor)]) {
319 hidePreviewButton.contentTintColor = [NSColor blackColor];
320 }
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -0400321}
322
Alexandre Lisionc5148052015-03-04 15:10:35 -0500323- (void)awakeFromNib
324{
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400325 [self.view setWantsLayer:YES];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500326
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400327 renderConnections = [[RendererConnectionsHolder alloc] init];
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400328
Alexandre Lisionf47a2562015-06-15 15:48:29 -0400329 [loadingIndicator setColor:[NSColor whiteColor]];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400330 [loadingIndicator setNumberOfLines:200];
331 [loadingIndicator setWidthOfLine:4];
332 [loadingIndicator setLengthOfLine:4];
333 [loadingIndicator setInnerMargin:59];
Alexandre Lisionf47a2562015-06-15 15:48:29 -0400334
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -0400335 [self.videoView setCallDelegate:self];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400336 [bluerBackgroundEffect setWantsLayer:YES];
Kateryna Kostiuk89398b62018-11-20 16:45:04 -0500337 [backgroundImage setWantsLayer: YES];
338 backgroundImage.layer.contentsGravity = kCAGravityResizeAspectFill;
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400339 movableBaseForView.wantsLayer = YES;
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400340 movableBaseForView.shadow = [[NSShadow alloc] init];
341 movableBaseForView.layer.shadowOpacity = 0.6;
342 movableBaseForView.layer.shadowColor = [[NSColor blackColor] CGColor];
343 movableBaseForView.layer.shadowOffset = NSMakeSize(0, -3);
344 movableBaseForView.layer.shadowRadius = 10;
345 previewView.wantsLayer = YES;
346 previewView.layer.cornerRadius = 5;
347 previewView.layer.masksToBounds = true;
348 hidePreviewBackground.wantsLayer = YES;
349 hidePreviewBackground.layer.cornerRadius = 5;
350 hidePreviewBackground.layer.maskedCorners = kCALayerMinXMinYCorner;
351 hidePreviewBackground.layer.masksToBounds = true;
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400352 movableBaseForView.hostingView = self.videoView;
353 [movableBaseForView setAutoresizingMask: NSViewNotSizable | NSViewMaxXMargin | NSViewMaxYMargin | NSViewMinXMargin | NSViewMinYMargin];
354 [previewView setAutoresizingMask: NSViewNotSizable | NSViewMaxXMargin | NSViewMaxYMargin | NSViewMinXMargin | NSViewMinYMargin];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400355 connectingCalls = [[NSMutableDictionary alloc] init];
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -0400356 participantsOverlays = [[NSMutableDictionary alloc] init];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500357}
358
Anthony Léonard66a050c2018-01-03 16:32:34 -0500359-(void) updateDurationLabel
360{
361 if (accountInfo_ != nil) {
362 auto* callModel = accountInfo_->callModel.get();
363 if (callModel->hasCall(callUid_)) {
364 auto& callStatus = callModel->getCall(callUid_).status;
365 if (callStatus != lrc::api::call::Status::ENDED &&
366 callStatus != lrc::api::call::Status::TERMINATING &&
367 callStatus != lrc::api::call::Status::INVALID) {
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400368 [timeSpentLabel setStringValue:callModel->getFormattedCallDuration(callUid_).toNSString()];
Anthony Léonard66a050c2018-01-03 16:32:34 -0500369 return;
370 }
371 }
372 }
373
374 // If call is not running anymore or accountInfo_ is not set for any reason
375 // we stop the refresh loop
376 [refreshDurationTimer invalidate];
377 refreshDurationTimer = nil;
Anthony Léonard66a050c2018-01-03 16:32:34 -0500378}
379
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -0400380-(void)removeConferenceLayout {
381 for (ConferenceOverlayView* overlay: [participantsOverlays allValues]) {
382 [overlay removeFromSuperview];
383 }
384 participantsOverlays = [[NSMutableDictionary alloc] init];
385}
386
387-(void)updateConference:(QString)confId
388{
389 if (confId.isEmpty()) {
390 [self removeConferenceLayout];
391 return;
392 }
393 auto* callModel = accountInfo_->callModel.get();
394 auto& call = callModel->getCall(confId);
395 using Status = lrc::api::call::Status;
396 auto participants = call.participantsInfos;
397 if (participants.size() == 0 || call.status == Status::ENDED ||
398 call.status == Status::TERMINATING || call.status == Status::INVALID) {
399 [self removeConferenceLayout];
400 return;
401 }
402 for (auto participant: participants) {
403 ConferenceParticipant conferenceParticipant;
404 conferenceParticipant.x = participant["x"].toFloat();
405 conferenceParticipant.y = participant["y"].toFloat();
406 conferenceParticipant.width = participant["w"].toFloat();
407 conferenceParticipant.hight = participant["h"].toFloat();
408 conferenceParticipant.uri = participant["uri"].toNSString();
409 conferenceParticipant.active = participant["active"] == "true";
410 conferenceParticipant.isLocal = false;
411 conferenceParticipant.bestName = participant["uri"].toNSString();
412 if (accountInfo_->profileInfo.uri == participant["uri"]) {
413 conferenceParticipant.isLocal = true;
414 conferenceParticipant.bestName = NSLocalizedString(@"Me", @"Conference name");
415 } else {
416 try {
417 auto contact = accountInfo_->contactModel->getContact(participant["uri"]);
418 conferenceParticipant.bestName = bestNameForContact(contact);
419 } catch (...) {}
420 }
421 if (participantsOverlays[conferenceParticipant.uri] != nil) {
422 ConferenceOverlayView* overlay = participantsOverlays[conferenceParticipant.uri];
423 overlay.framesize = framesize;
424 [overlay updateViewWithParticipant: conferenceParticipant];
425 } else {
426 ConferenceOverlayView* overlay = [[ConferenceOverlayView alloc] init];
427 overlay.framesize = framesize;
428 overlay.delegate = self;
429 [overlay configureView];
430 [self.distantView addSubview: overlay];
431 participantsOverlays[conferenceParticipant.uri] = overlay;
432 [overlay updateViewWithParticipant: conferenceParticipant];
433 }
434 }
435}
436
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400437-(void) updateCall
Alexandre Lisionc5148052015-03-04 15:10:35 -0500438{
Olivier Soldano994971f2017-12-05 16:30:12 -0500439 if (accountInfo_ == nil)
440 return;
Alexandre Lisionbb5dbcd2015-07-09 16:36:47 -0400441
Olivier Soldano994971f2017-12-05 16:30:12 -0500442 auto* callModel = accountInfo_->callModel.get();
443 if (not callModel->hasCall(callUid_)) {
444 return;
445 }
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500446
Olivier Soldano994971f2017-12-05 16:30:12 -0500447 auto currentCall = callModel->getCall(callUid_);
Kateryna Kostiuk84101912020-08-31 16:06:33 -0400448 auto convIt = accountInfo_->conversationModel->getConversationForUID(convUid_);
449 if (!convIt.uid.isEmpty()) {
450 NSString* bestName = bestNameForConversation(convIt, *accountInfo_->conversationModel);
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400451 [contactNameLabel setStringValue:bestName];
Kateryna Kostiuk84101912020-08-31 16:06:33 -0400452 NSString* ringID = bestIDForConversation(convIt, *accountInfo_->conversationModel);
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400453 if([bestName isEqualToString:ringID]) {
454 ringID = @"";
455 }
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400456 [contactIdLabel setStringValue:ringID];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400457 }
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400458 [self setupContactInfo:contactPhoto];
Kateryna Kostiuk84101912020-08-31 16:06:33 -0400459 confUid_ = convIt.confId;
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400460 [muteAudioButton setHidden:!confUid_.isEmpty()];
461 [muteVideoButton setHidden:!confUid_.isEmpty()];
462 [recordOnOffButton setHidden:!confUid_.isEmpty()];
463 [holdOnOffButton setHidden:!confUid_.isEmpty()];
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -0400464 [movableBaseForView setHidden:!confUid_.isEmpty()];
465 [self updateConference: confUid_];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400466
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400467 [timeSpentLabel setStringValue:callModel->getFormattedCallDuration(callUid_).toNSString()];
Anthony Léonard66a050c2018-01-03 16:32:34 -0500468 if (refreshDurationTimer == nil)
469 refreshDurationTimer = [NSTimer scheduledTimerWithTimeInterval:1.0
470 target:self
471 selector:@selector(updateDurationLabel)
472 userInfo:nil
473 repeats:YES];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400474 [self setBackground];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500475
Olivier Soldano994971f2017-12-05 16:30:12 -0500476 using Status = lrc::api::call::Status;
Kateryna Kostiuk88a6ee82019-09-10 17:55:08 -0400477 if (currentCall.status == Status::PAUSED) {
478 [holdOnOffButton startBlinkAnimationfrom:[NSColor buttonBlinkColorColor] to:[NSColor whiteColor] scaleFactor: 1.0 duration: 1.5];
479 } else {
480 [holdOnOffButton stopBlinkAnimation];
481 }
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400482
Kateryna Kostiuka7404812019-10-28 12:24:46 -0400483 [self setUpButtons: currentCall isRecording: (callModel->isRecording([self getcallID]) || mediaModel->getAlwaysRecord())];
484
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400485 [videoView setShouldAcceptInteractions: currentCall.status == Status::IN_PROGRESS];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400486 callStateLabel.stringValue = currentCall.status == Status::INCOMING_RINGING ? @"wants to talk to you" : to_string(currentCall.status).toNSString();
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400487 loadingIndicator.hidden = (currentCall.status == Status::SEARCHING ||
488 currentCall.status == Status::CONNECTING ||
489 currentCall.status == Status::OUTGOING_RINGING) ? NO : YES;
490 pickUpButton.hidden = (currentCall.status == Status::INCOMING_RINGING) ? NO : YES;
491 callStateLabel.hidden = (currentCall.status == Status::IN_PROGRESS) ? YES : NO;
492 cancelCallButton.hidden = (currentCall.status == Status::IN_PROGRESS ||
493 currentCall.status == Status::PAUSED) ? YES : NO;
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400494 callingWidgetsContainer.hidden = (currentCall.status == Status::IN_PROGRESS) ? NO : YES;
Kateryna Kostiukf362e2b2020-09-10 19:00:52 -0400495 lrc::api::account::ConfProperties_t accountProperties = accountInfo_->accountModel->getAccountConfig(accountInfo_->id);
496
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400497
Olivier Soldano994971f2017-12-05 16:30:12 -0500498 switch (currentCall.status) {
499 case Status::SEARCHING:
Olivier Soldano994971f2017-12-05 16:30:12 -0500500 case Status::CONNECTING:
Olivier Soldano994971f2017-12-05 16:30:12 -0500501 case Status::OUTGOING_RINGING:
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400502 case Status::INCOMING_RINGING:
503 [infoContainer setHidden: NO];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400504 [headerContainer setHidden:YES];
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400505 [headerGradientView setHidden:YES];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400506 [controlsPanel setHidden:YES];
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400507 [controlsStackView setHidden:YES];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400508 [self.distantView fillWithBlack];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400509 [self.previewView fillWithBlack];
510 [hidePreviewBackground setHidden:YES];
511 [self.previewView setHidden: YES];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400512 [self.distantView setHidden: YES];
513 self.previewView.videoRunning = NO;
514 self.distantView.videoRunning = NO;
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400515 [backgroundImage setHidden:NO];
516 [bluerBackgroundEffect setHidden:NO];
Olivier Soldano994971f2017-12-05 16:30:12 -0500517 break;
Olivier Soldano994971f2017-12-05 16:30:12 -0500518 case Status::PAUSED:
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400519 [infoContainer setHidden: NO];
520 [headerContainer setHidden:NO];
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400521 [headerGradientView setHidden:NO];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400522 [controlsPanel setHidden:NO];
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400523 [controlsStackView setHidden:NO];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400524 [backgroundImage setHidden:NO];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400525 [bluerBackgroundEffect setHidden:NO];
526 if(!currentCall.isAudioOnly) {
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400527 [self.distantView fillWithBlack];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400528 [self.previewView fillWithBlack];
529 [hidePreviewBackground setHidden:YES];
530 [self.previewView setHidden: YES];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400531 [self.distantView setHidden: YES];
532 self.previewView.videoRunning = NO;
533 self.distantView.videoRunning = NO;
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400534 }
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400535 break;
Olivier Soldano994971f2017-12-05 16:30:12 -0500536 case Status::INACTIVE:
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400537 if(currentCall.isAudioOnly) {
538 [self setUpAudioOnlyView];
539 } else {
540 [self setUpVideoCallView];
541 }
542 break;
Olivier Soldano994971f2017-12-05 16:30:12 -0500543 case Status::IN_PROGRESS:
“Kateryna”414738e2020-04-07 11:44:35 -0400544 callModel->setCurrentCall([self getcallID]);
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400545 [headerContainer setHidden:NO];
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400546 [headerGradientView setHidden:NO];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400547 [controlsPanel setHidden:NO];
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400548 [controlsStackView setHidden:NO];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400549 if(currentCall.isAudioOnly) {
550 [self setUpAudioOnlyView];
551 } else {
552 [self setUpVideoCallView];
553 }
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400554 break;
Olivier Soldano994971f2017-12-05 16:30:12 -0500555 case Status::CONNECTED:
Olivier Soldano994971f2017-12-05 16:30:12 -0500556 break;
557 case Status::ENDED:
558 case Status::TERMINATING:
559 case Status::INVALID:
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400560 case Status::PEER_BUSY:
561 case Status::TIMEOUT:
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400562 connectingCalls[callUid_.toNSString()] = nil;
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400563 [self.delegate callFinished];
Kateryna Kostiukf362e2b2020-09-10 19:00:52 -0400564 if (!accountProperties.isRendezVous) {
565 [self removeConferenceLayout];
566 [self switchToNextConferenceCall: confUid_];
567 }
Olivier Soldano994971f2017-12-05 16:30:12 -0500568 break;
569 }
Kateryna Kostiukf362e2b2020-09-10 19:00:52 -0400570 if (accountProperties.isRendezVous) {
571 [controlsStackView setHidden:YES];
572 }
Alexandre Lision4d9905e2016-01-22 12:01:36 -0500573}
574
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400575-(void) setUpVideoCallView {
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400576 [previewView fillWithBlack];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400577 [self.distantView fillWithBlack];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400578 [previewView setHidden: NO];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400579 [self.distantView setHidden:NO];
580 [hidePreviewBackground setHidden: !self.previewView.videoRunning];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400581 [bluerBackgroundEffect setHidden:YES];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400582 [backgroundImage setHidden:YES];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400583 self.previewView.videoRunning = true;
584 self.distantView.videoRunning = true;
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400585}
586
587-(void) setUpAudioOnlyView {
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400588 [self.previewView setHidden: YES];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400589 [self.distantView setHidden: YES];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400590 [hidePreviewBackground setHidden: YES];
591 [bluerBackgroundEffect setHidden:NO];
592 [backgroundImage setHidden:NO];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400593}
594
595-(void) setBackground {
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400596 NSImage *image= [self getContactImageOfSize:120.0 withDefaultAvatar:NO];
597 if(image) {
Kateryna Kostiuk89398b62018-11-20 16:45:04 -0500598 CIImage * ciImage = [[CIImage alloc] initWithData:[image TIFFRepresentation]];
599 CIContext *context = [[CIContext alloc] init];
600 CIFilter *clamp = [CIFilter filterWithName:@"CIAffineClamp"];
601 [clamp setValue:[NSAffineTransform transform] forKey:@"inputTransform"];
602 [clamp setValue:ciImage forKey: kCIInputImageKey];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400603 CIFilter* bluerFilter = [CIFilter filterWithName:@"CIGaussianBlur"];
604 [bluerFilter setDefaults];
Kateryna Kostiuk89398b62018-11-20 16:45:04 -0500605 [bluerFilter setValue:[NSNumber numberWithFloat: 9] forKey:@"inputRadius"];
606 [bluerFilter setValue:[clamp valueForKey:kCIOutputImageKey] forKey: kCIInputImageKey];
607 CIImage *result = [bluerFilter valueForKey:kCIOutputImageKey];
608 CGRect extent = [result extent];
609 CGImageRef cgImage = [context createCGImage:result fromRect: [ciImage extent]];
610 NSImage *bluredImage = [[NSImage alloc] initWithCGImage:cgImage size:NSSizeFromCGSize(CGSizeMake(image.size.width, image.size.height))];
611 backgroundImage.layer.contents = bluredImage;
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400612 [backgroundImage setHidden:NO];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400613 [bluerBackgroundEffect setFillColor:[NSColor darkGrayColor]];
614 [bluerBackgroundEffect setAlphaValue:0.6];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400615 } else {
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400616 contactNameLabel.textColor = [NSColor textColor];
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400617 contactIdLabel.textColor = [NSColor textColor];
618 callStateLabel.textColor = [NSColor textColor];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400619 backgroundImage.layer.contents = nil;
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -0400620 [bluerBackgroundEffect setFillColor:[NSColor windowBackgroundColor]];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400621 [bluerBackgroundEffect setAlphaValue:1];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400622 [backgroundImage setHidden:YES];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400623 }
624}
625
Kateryna Kostiuk133364b2018-10-31 11:36:08 -0400626-(NSImage *) getContactImageOfSize: (double) size withDefaultAvatar:(BOOL) shouldDrawDefault {
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400627 auto* convModel = accountInfo_->conversationModel.get();
Kateryna Kostiuk84101912020-08-31 16:06:33 -0400628 auto conv = accountInfo_->conversationModel->getConversationForUID(convUid_);
629 if (conv.uid.isEmpty() || conv.participants.empty()) {
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400630 return nil;
631 }
632 if(shouldDrawDefault) {
633 auto& imgManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
Kateryna Kostiuk84101912020-08-31 16:06:33 -0400634 QVariant photo = imgManip.conversationPhoto(conv, *accountInfo_, QSize(size, size), NO);
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400635 return QtMac::toNSImage(qvariant_cast<QPixmap>(photo));
636 }
Kateryna Kostiuk84101912020-08-31 16:06:33 -0400637 auto contact = accountInfo_->contactModel->getContact(conv.participants[0]);
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400638 NSData *imageData = [[NSData alloc] initWithBase64EncodedString:contact.profileInfo.avatar.toNSString() options:NSDataBase64DecodingIgnoreUnknownCharacters];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400639 return [[NSImage alloc] initWithData:imageData];
640}
641
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400642-(void) setupContactInfo:(NSImageView*)imageView
643{
644 [imageView setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
645}
646
Olivier Soldano994971f2017-12-05 16:30:12 -0500647-(void)collapseRightView
648{
649 NSView *right = [[splitView subviews] objectAtIndex:1];
650 NSView *left = [[splitView subviews] objectAtIndex:0];
651 NSRect leftFrame = [left frame];
652 [right setHidden:YES];
653 [splitView display];
654}
655
Alexandre Lisionc5148052015-03-04 15:10:35 -0500656-(void) connectVideoSignals
657{
Olivier Soldano994971f2017-12-05 16:30:12 -0500658 if (accountInfo_ == nil)
659 return;
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400660 [self connectRenderer];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500661}
662
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400663-(void) connectRenderer
Alexandre Lisionc5148052015-03-04 15:10:35 -0500664{
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400665 QObject::disconnect(renderConnections.frameUpdated);
666 QObject::disconnect(renderConnections.stopped);
667 QObject::disconnect(renderConnections.started);
668 renderConnections.started =
669 QObject::connect(mediaModel,
670 &lrc::api::AVModel::rendererStarted,
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400671 [=](const QString& id) {
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400672 if (id == lrc::api::video::PREVIEW_RENDERER_ID) {
673 [self.previewView setHidden:NO];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400674 [hidePreviewBackground setHidden: NO];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400675 self.previewView.videoRunning = true;
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400676 } else if ([self isCurrentCall: id]) {
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400677 [self mouseIsMoving: NO];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400678 self.distantView.videoRunning = true;
679 [self.distantView setHidden:NO];
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400680 [bluerBackgroundEffect setHidden:YES];
681 [backgroundImage setHidden:YES];
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400682 }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500683 });
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400684 renderConnections.stopped =
685 QObject::connect(mediaModel,
686 &lrc::api::AVModel::rendererStopped,
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400687 [=](const QString& id) {
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400688 if (id == lrc::api::video::PREVIEW_RENDERER_ID) {
689 [self.previewView setHidden:YES];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400690 self.previewView.videoRunning = false;
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400691 } else if ([self isCurrentCall: id]) {
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400692 [self mouseIsMoving: YES];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400693 self.distantView.videoRunning = false;
694 [self.distantView setHidden:YES];
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400695 [bluerBackgroundEffect setHidden:NO];
696 [backgroundImage setHidden:NO];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400697 }
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400698 });
699 renderConnections.frameUpdated =
700 QObject::connect(mediaModel,
701 &lrc::api::AVModel::frameUpdated,
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400702 [=](const QString& id) {
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400703 if (id == lrc::api::video::PREVIEW_RENDERER_ID) {
704 auto renderer = &mediaModel->getRenderer(lrc::api::video::PREVIEW_RENDERER_ID);
705 if(!renderer->isRendering()) {
706 return;
707 }
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400708 [hidePreviewBackground setHidden: !self.previewView.videoRunning];
709 [self rendererPreview: renderer];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400710 } else if ([self isCurrentCall: id]) {
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400711 auto renderer = &mediaModel->getRenderer(id);
712 if(!renderer->isRendering()) {
713 return;
714 }
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400715 [self rendererDistantView: renderer];
Kateryna Kostiuk732b4062018-11-28 09:59:28 -0500716 }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500717 });
718}
719
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400720-(void) rendererDistantView: (const lrc::api::video::Renderer*)renderer {
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400721 @autoreleasepool {
722 auto framePtr = renderer->currentAVFrame();
723 auto frame = framePtr.get();
724 if(!frame || !frame->width || !frame->height) {
725 return;
726 }
727 auto frameSize = CGSizeMake(frame->width, frame->height);
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -0400728 framesize = frameSize;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400729 auto rotation = 0;
730 if (auto matrix = av_frame_get_side_data(frame, AV_FRAME_DATA_DISPLAYMATRIX)) {
731 const int32_t* data = reinterpret_cast<int32_t*>(matrix->data);
732 rotation = av_display_rotation_get(data);
733 }
734 if (frame->data[3] != NULL && (CVPixelBufferRef)frame->data[3]) {
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400735 [self.distantView renderWithPixelBuffer: (CVPixelBufferRef)frame->data[3]
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400736 size: frameSize
737 rotation: rotation
738 fillFrame: false];
739 }
740 if (CVPixelBufferRef pixelBuffer = [self getBufferForDistantViewFromFrame:frame]) {
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400741 [self.distantView renderWithPixelBuffer: pixelBuffer
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400742 size: frameSize
743 rotation: rotation
744 fillFrame: false];
745 }
Kateryna Kostiukbcf24e92018-11-20 10:09:06 -0500746 }
Anthony Léonard14e7bf32017-06-08 08:13:16 -0400747}
748
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400749-(void) rendererPreview: (const lrc::api::video::Renderer*)renderer {
750 @autoreleasepool {
751 auto framePtr = renderer->currentAVFrame();
752 auto frame = framePtr.get();
753 if(!frame || !frame->width || !frame->height) {
754 return;
755 }
756 auto frameSize = CGSizeMake(frame->width, frame->height);
757 auto rotation = 0;
758 if (auto matrix = av_frame_get_side_data(frame, AV_FRAME_DATA_DISPLAYMATRIX)) {
759 const int32_t* data = reinterpret_cast<int32_t*>(matrix->data);
760 rotation = av_display_rotation_get(data);
761 }
762 if (frame->data[3] != NULL && (CVPixelBufferRef)frame->data[3]) {
763 [self.previewView renderWithPixelBuffer: (CVPixelBufferRef)frame->data[3]
764 size: frameSize
765 rotation: rotation
766 fillFrame: false];
767 }
768 if (CVPixelBufferRef pixelBuffer = [self getBufferForPreviewFromFrame:frame]) {
769 [self.previewView renderWithPixelBuffer: pixelBuffer
770 size: frameSize
771 rotation: rotation
772 fillFrame: true];
773 }
774 }
775}
776
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400777-(CVPixelBufferRef) getBufferForPreviewFromFrame:(const AVFrame*)frame {
Kateryna Kostiukc5fd3b92019-07-24 10:12:44 -0400778 [VideoCommon fillPixelBuffr:&pixelBufferPreview fromFrame:frame bufferPool:&pixelBufferPoolPreview];
779 CVPixelBufferRef buffer = pixelBufferPreview;
780 return buffer;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400781}
782
783-(CVPixelBufferRef) getBufferForDistantViewFromFrame:(const AVFrame*)frame {
Kateryna Kostiukc5fd3b92019-07-24 10:12:44 -0400784 [VideoCommon fillPixelBuffr:&pixelBufferDistantView fromFrame:frame bufferPool:&pixelBufferPoolDistantView];
785 CVPixelBufferRef buffer = pixelBufferDistantView;
786 return buffer;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400787}
788
Alexandre Lisionc5148052015-03-04 15:10:35 -0500789- (void) initFrame
790{
791 [self.view setFrame:self.view.superview.bounds];
792 [self.view setHidden:YES];
793 self.view.layer.position = self.view.frame.origin;
Alexandre Lisionbb5dbcd2015-07-09 16:36:47 -0400794 [self collapseRightView];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500795}
796
797# pragma private IN/OUT animations
798
Olivier Soldano994971f2017-12-05 16:30:12 -0500799-(void)uncollapseRightView
800{
801 NSView *left = [[splitView subviews] objectAtIndex:0];
802 NSView *right = [[splitView subviews] objectAtIndex:1];
803 [right setHidden:NO];
804
805 CGFloat dividerThickness = [splitView dividerThickness];
806
807 // get the different frames
808 NSRect leftFrame = [left frame];
809 NSRect rightFrame = [right frame];
810
811 leftFrame.size.width = (leftFrame.size.width - rightFrame.size.width - dividerThickness);
812 rightFrame.origin.x = leftFrame.size.width + dividerThickness;
813 [left setFrameSize:leftFrame.size];
814 [right setFrame:rightFrame];
815 [splitView display];
816
817 [self.chatVC takeFocus];
818}
819
Alexandre Lisionc5148052015-03-04 15:10:35 -0500820-(void) cleanUp
821{
Anthony Léonard5c287642018-02-21 15:54:19 -0500822 if(self.splitView.isInFullScreenMode)
823 [self.splitView exitFullScreenModeWithOptions:nil];
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400824 QObject::disconnect(renderConnections.frameUpdated);
825 QObject::disconnect(renderConnections.started);
826 QObject::disconnect(renderConnections.stopped);
Anthony Léonardc0013bb2018-02-20 16:23:02 -0500827 QObject::disconnect(self.messageConnection);
Alexandre Lision883719f2015-10-22 17:37:45 -0400828
Alexandre Lision4baba4c2016-02-11 13:00:57 -0500829 [self.chatButton setPressed:NO];
Alexandre Lision883719f2015-10-22 17:37:45 -0400830 [self collapseRightView];
Alexandre Lision57227772016-01-15 17:19:36 -0500831
Alexandre Lision57227772016-01-15 17:19:36 -0500832 [timeSpentLabel setStringValue:@""];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400833 [contactIdLabel setStringValue:@""];
834 [contactNameLabel setStringValue:@""];
835 [contactPhoto setImage:nil];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400836 //background view
837 [bluerBackgroundEffect setHidden:NO];
838 [backgroundImage setHidden:NO];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400839 backgroundImage.layer.contents = nil;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400840 [self.previewView setHidden:YES];
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400841 [self.distantView setHidden:YES];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400842
843 contactNameLabel.textColor = [NSColor highlightColor];
844 contactNameLabel.textColor = [NSColor highlightColor];
845 contactIdLabel.textColor = [NSColor highlightColor];
846 callStateLabel.textColor = [NSColor highlightColor];
Kateryna Kostiuk5acaefd2020-03-25 11:14:25 -0400847 [self.chatVC clearData];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500848}
849
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400850/*
851 * update ui
852*/
853
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400854-(void) setupCallView
855{
856 if (accountInfo_ == nil)
857 return;
858
859 auto* callModel = accountInfo_->callModel.get();
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400860
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400861 /* check if text media is already present */
862 if(not callModel->hasCall(callUid_))
863 return;
864
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400865 // when call comes in we want to show the controls/header
866 [self mouseIsMoving: YES];
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400867 [loadingIndicator setAnimates:YES];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400868 auto currentCall = callModel->getCall([self getcallID]);
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400869 [previewView setHidden: YES];
870 [self setUpPreviewFrame];
871 [self updatePendingCalls];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400872 [self updateCall];
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400873}
874
875-(void) showWithAnimation:(BOOL)animate
876{
877 if (!animate) {
878 [self.view setHidden:NO];
879 [self setupCallView];
880 return;
881 }
882
883 CGRect frame = CGRectOffset(self.view.superview.bounds, -self.view.superview.bounds.size.width, 0);
884 [self.view setHidden:NO];
885
886 [CATransaction begin];
887 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
888 [animation setFromValue:[NSValue valueWithPoint:frame.origin]];
889 [animation setToValue:[NSValue valueWithPoint:self.view.superview.bounds.origin]];
890 [animation setDuration:0.2f];
891 [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]];
892 [CATransaction setCompletionBlock:^{
893 [self setupCallView];
894 }];
895
896 [self.view.layer addAnimation:animation forKey:animation.keyPath];
897 [CATransaction commit];
898}
899
900-(void) hideWithAnimation:(BOOL)animate
Alexandre Lisionc5148052015-03-04 15:10:35 -0500901{
Alexandre Lisionc5148052015-03-04 15:10:35 -0500902 if(self.view.frame.origin.x < 0) {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500903 return;
904 }
905
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400906 if (!animate) {
907 [self.view setHidden:YES];
908 return;
909 }
910
Alexandre Lisionc5148052015-03-04 15:10:35 -0500911 CGRect frame = CGRectOffset(self.view.frame, -self.view.frame.size.width, 0);
912 [CATransaction begin];
913 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
914 [animation setFromValue:[NSValue valueWithPoint:self.view.frame.origin]];
915 [animation setToValue:[NSValue valueWithPoint:frame.origin]];
916 [animation setDuration:0.2f];
Alexandre Lision22d615b2016-01-25 12:54:34 -0500917 [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500918
919 [CATransaction setCompletionBlock:^{
920 [self.view setHidden:YES];
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400921 // first make sure everything is disconnected
Alexandre Lisionc5148052015-03-04 15:10:35 -0500922 [self cleanUp];
Olivier Soldano994971f2017-12-05 16:30:12 -0500923// if (currentCall_) {
924// [self animateIn];
925// }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500926 }];
927 [self.view.layer addAnimation:animation forKey:animation.keyPath];
Alexandre Lisiona1c6d752015-06-23 12:27:38 -0400928
929 [self.view.layer setPosition:frame.origin];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500930 [CATransaction commit];
931}
932
933/**
934 * Debug purpose
935 */
936-(void) dumpFrame:(CGRect) frame WithName:(NSString*) name
937{
938 NSLog(@"frame %@ : %f %f %f %f \n\n",name ,frame.origin.x, frame.origin.y, frame.size.width, frame.size.height);
939}
940
Alexandre Lision58cab672015-06-09 15:25:40 -0400941#pragma mark - Button methods
942
Alexandre Lisionc5148052015-03-04 15:10:35 -0500943- (IBAction)hangUp:(id)sender {
Olivier Soldano994971f2017-12-05 16:30:12 -0500944 if (accountInfo_ == nil)
945 return;
946
947 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiuk28b99be2019-11-18 14:54:08 -0500948 callModel->hangUp(callUid_);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500949}
950
951- (IBAction)accept:(id)sender {
Olivier Soldano994971f2017-12-05 16:30:12 -0500952 if (accountInfo_ == nil)
953 return;
954
Anthony Léonardf584cc52018-01-19 12:00:19 -0500955 // If we accept a conversation with a non trusted contact, we first accept it
Kateryna Kostiuk84101912020-08-31 16:06:33 -0400956 auto conv = accountInfo_->conversationModel->getConversationForUID(convUid_);
957 if (conv.uid.isEmpty() || conv.participants.empty()) {
958 return;
959 }
960 auto& contact = accountInfo_->contactModel->getContact(conv.participants[0]);
961 if (contact.profileInfo.type == lrc::api::profile::Type::PENDING) {
962 accountInfo_->conversationModel->makePermanent(convUid_);
Anthony Léonardf584cc52018-01-19 12:00:19 -0500963 }
964
Olivier Soldano994971f2017-12-05 16:30:12 -0500965 auto* callModel = accountInfo_->callModel.get();
Olivier Soldano994971f2017-12-05 16:30:12 -0500966 callModel->accept(callUid_);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500967}
968
969- (IBAction)toggleRecording:(id)sender {
Olivier Soldano994971f2017-12-05 16:30:12 -0500970 if (accountInfo_ == nil)
971 return;
972
973 auto* callModel = accountInfo_->callModel.get();
Olivier Soldano994971f2017-12-05 16:30:12 -0500974 callModel->toggleAudioRecord(callUid_);
Kateryna Kostiuk88a6ee82019-09-10 17:55:08 -0400975 if (callModel->isRecording(callUid_)) {
976 [recordOnOffButton startBlinkAnimationfrom:[NSColor buttonBlinkColorColor] to:[NSColor whiteColor] scaleFactor: 1 duration: 1.5];
977 } else {
978 [recordOnOffButton stopBlinkAnimation];
979 }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500980}
981
982- (IBAction)toggleHold:(id)sender {
Olivier Soldano994971f2017-12-05 16:30:12 -0500983 if (accountInfo_ == nil)
984 return;
985
986 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiuk4db61092019-10-17 16:57:40 -0400987 callModel->togglePause([self getcallID]);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500988}
989
Alexandre Lision62005312016-01-28 15:55:16 -0500990- (IBAction)showDialpad:(id)sender {
991 AppDelegate* appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
992 [appDelegate showDialpad];
993}
994
Alexandre Lision58cab672015-06-09 15:25:40 -0400995-(IBAction)toggleChat:(id)sender;
996{
997 BOOL rightViewCollapsed = [[self splitView] isSubviewCollapsed:[[[self splitView] subviews] objectAtIndex: 1]];
998 if (rightViewCollapsed) {
999 [self uncollapseRightView];
Alexandre Lision58cab672015-06-09 15:25:40 -04001000 } else {
1001 [self collapseRightView];
1002 }
Alexandre Lision62005312016-01-28 15:55:16 -05001003 [chatButton setPressed:rightViewCollapsed];
Alexandre Lision58cab672015-06-09 15:25:40 -04001004}
1005
Alexandre Lisiond18fa272015-06-15 11:18:03 -04001006- (IBAction)muteAudio:(id)sender
1007{
Olivier Soldano994971f2017-12-05 16:30:12 -05001008 if (accountInfo_ == nil)
1009 return;
1010
1011 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiuk54d76972018-06-07 15:45:07 -04001012 auto currentCall = callModel->getCall(callUid_);
1013 if (currentCall.audioMuted) {
1014 muteAudioButton.image = [NSImage imageNamed:@"ic_action_audio.png"];
1015 } else {
1016 muteAudioButton.image = [NSImage imageNamed:@"ic_action_mute_audio.png"];
1017 }
Olivier Soldano994971f2017-12-05 16:30:12 -05001018 callModel->toggleMedia(callUid_, lrc::api::NewCallModel::Media::AUDIO);
Alexandre Lisiond18fa272015-06-15 11:18:03 -04001019}
1020
1021- (IBAction)muteVideo:(id)sender
1022{
Olivier Soldano994971f2017-12-05 16:30:12 -05001023 if (accountInfo_ == nil)
1024 return;
Olivier Soldano994971f2017-12-05 16:30:12 -05001025 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiuk54d76972018-06-07 15:45:07 -04001026 auto currentCall = callModel->getCall(callUid_);
1027 if(!currentCall.isAudioOnly) {
1028 if (currentCall.videoMuted) {
1029 muteVideoButton.image = [NSImage imageNamed:@"ic_action_video.png"];
1030 } else {
1031 muteVideoButton.image = [NSImage imageNamed:@"ic_action_mute_video.png"];
1032 }
1033 }
Olivier Soldano994971f2017-12-05 16:30:12 -05001034 callModel->toggleMedia(callUid_, lrc::api::NewCallModel::Media::VIDEO);
Alexandre Lisiond18fa272015-06-15 11:18:03 -04001035}
Alexandre Lision883719f2015-10-22 17:37:45 -04001036
1037- (IBAction)toggleTransferView:(id)sender {
Kateryna Kostiuk1ba5a012018-12-12 17:07:38 -05001038
Alexandre Lisionf23ec5a2015-07-16 11:24:06 -04001039}
Alexandre Lisiond18fa272015-06-15 11:18:03 -04001040
Alexandre Lision89edc6a2015-11-09 11:30:47 -05001041- (IBAction)toggleAddParticipantView:(id)sender {
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001042 if (brokerPopoverVC != nullptr) {
1043 [brokerPopoverVC performClose:self];
1044 brokerPopoverVC = NULL;
1045 } else {
1046 auto* contactSelectorVC = [[ChooseContactVC alloc] initWithNibName:@"ChooseContactVC" bundle:nil];
1047 auto* convModel = accountInfo_->conversationModel.get();
1048 [contactSelectorVC setConversationModel:convModel andCurrentConversation: convUid_];
1049 contactSelectorVC.delegate = self;
1050 brokerPopoverVC = [[NSPopover alloc] init];
1051 [brokerPopoverVC setContentSize:contactSelectorVC.view.frame.size];
1052 [brokerPopoverVC setContentViewController:contactSelectorVC];
1053 [brokerPopoverVC setAnimates:YES];
1054 [brokerPopoverVC setBehavior:NSPopoverBehaviorTransient];
1055 [brokerPopoverVC setDelegate:self];
1056 [brokerPopoverVC showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMinYEdge];
1057 }
Alexandre Lision89edc6a2015-11-09 11:30:47 -05001058}
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001059
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001060- (IBAction)hidePreview:(id)sender {
1061 CGRect previewFrame = previewView.frame;
Kateryna Kostiuk28b99be2019-11-18 14:54:08 -05001062 CGRect newPreviewFrame;
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -04001063 if (previewFrame.size.width > HIDE_PREVIEW_BUTTON_SIZE) {
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001064 self.movableBaseForView.movable = false;
1065 newPreviewFrame = self.getVideoPreviewCollapsedSize;
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001066 hidePreviewButton.image = [NSImage imageNamed: NSImageNameTouchBarEnterFullScreenTemplate];
1067 } else {
1068 self.movableBaseForView.movable = true;
1069 newPreviewFrame = CGRectMake(0, 0, PREVIEW_WIDTH, PREVIEW_HEIGHT);
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001070 hidePreviewButton.image = [NSImage imageNamed: NSImageNameTouchBarExitFullScreenTemplate];
1071 }
1072 [NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull context) {
1073 context.duration = 0.2f;
1074 context.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut];
1075 previewView.animator.frame = newPreviewFrame;
1076 } completionHandler: nil];
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001077}
Alexandre Lision89edc6a2015-11-09 11:30:47 -05001078
Alexandre Lision58cab672015-06-09 15:25:40 -04001079#pragma mark - NSSplitViewDelegate
1080
1081/* Return YES if the subview should be collapsed because the user has double-clicked on an adjacent divider. If a split view has a delegate, and the delegate responds to this message, it will be sent once for the subview before a divider when the user double-clicks on that divider, and again for the subview after the divider, but only if the delegate returned YES when sent -splitView:canCollapseSubview: for the subview in question. When the delegate indicates that both subviews should be collapsed NSSplitView's behavior is undefined.
1082 */
1083- (BOOL)splitView:(NSSplitView *)splitView shouldCollapseSubview:(NSView *)subview forDoubleClickOnDividerAtIndex:(NSInteger)dividerIndex;
1084{
1085 NSView* rightView = [[splitView subviews] objectAtIndex:1];
1086 return ([subview isEqual:rightView]);
1087}
1088
1089
1090- (BOOL)splitView:(NSSplitView *)splitView canCollapseSubview:(NSView *)subview;
1091{
1092 NSView* rightView = [[splitView subviews] objectAtIndex:1];
1093 return ([subview isEqual:rightView]);
1094}
1095
1096
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -04001097# pragma mark - CallnDelegate
Alexandre Lision58cab672015-06-09 15:25:40 -04001098
1099- (void) callShouldToggleFullScreen
1100{
1101 if(self.splitView.isInFullScreenMode)
1102 [self.splitView exitFullScreenModeWithOptions:nil];
1103 else {
1104 NSApplicationPresentationOptions options = NSApplicationPresentationDefault +NSApplicationPresentationAutoHideDock +
1105 NSApplicationPresentationAutoHideMenuBar + NSApplicationPresentationAutoHideToolbar;
1106 NSDictionary *opts = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInt:options],
1107 NSFullScreenModeApplicationPresentationOptions, nil];
1108
1109 [self.splitView enterFullScreenMode:[NSScreen mainScreen] withOptions:opts];
1110 }
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -04001111 for (ConferenceOverlayView* participant: [participantsOverlays allValues]) {
1112 [participant sizeChanged];
1113 }
Alexandre Lision58cab672015-06-09 15:25:40 -04001114}
1115
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -04001116-(void) mouseIsMoving:(BOOL) move
1117{
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -04001118 [[controlsPanel animator] setAlphaValue:move];// fade out
1119 [[controlsStackView animator] setAlphaValue:move];
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -04001120 [[headerContainer animator] setAlphaValue:move];
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -04001121 [[headerGradientView animator] setAlphaValue:move];
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -04001122}
1123
Kateryna Kostiuk89398b62018-11-20 16:45:04 -05001124- (BOOL)splitView:(NSSplitView *)splitView shouldHideDividerAtIndex:(NSInteger)dividerIndex
1125{
1126 return YES;
1127}
1128
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -04001129-(void) screenShare {
1130 NSScreen *mainScreen = [NSScreen mainScreen];
1131 NSRect screenFrame = mainScreen.frame;
1132 QRect captureRect = QRect(screenFrame.origin.x, screenFrame.origin.y, screenFrame.size.width, screenFrame.size.height);
1133 mediaModel->setDisplay(0, screenFrame.origin.x, screenFrame.origin.y, screenFrame.size.width, screenFrame.size.height);
1134}
1135-(void) switchToDevice:(int)deviceID {
1136 auto devices = mediaModel->getDevices();
1137 auto device = devices[deviceID];
1138 mediaModel->switchInputTo(device);
1139}
1140
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001141-(QVector<QString>) getDeviceList {
1142 QVector<QString> devicesVector;
Kateryna Kostiuk64533102019-10-29 15:34:29 -04001143 for (auto device : mediaModel->getDevices()) {
1144 try {
1145 auto settings = mediaModel->getDeviceSettings(device);
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001146 devicesVector.append(settings.name);
Kateryna Kostiuk64533102019-10-29 15:34:29 -04001147 } catch (...) {}
1148 }
1149 return devicesVector;
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -04001150}
1151
Kateryna Kostiuk32cf6be2019-10-28 12:22:45 -04001152-(NSString*) getDefaultDeviceName {
1153 auto type = mediaModel->getCurrentRenderedDevice(callUid_).type;
1154 switch (type) {
1155 case lrc::api::video::DeviceType::CAMERA:
Kateryna Kostiuk64533102019-10-29 15:34:29 -04001156 try {
1157 auto device = mediaModel->getCurrentRenderedDevice(callUid_).name;
1158 auto settings = mediaModel->getDeviceSettings(device);
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001159 return settings.name.toNSString();
Kateryna Kostiuk64533102019-10-29 15:34:29 -04001160 } catch (...) {}
Kateryna Kostiuk32cf6be2019-10-28 12:22:45 -04001161 case lrc::api::video::DeviceType::DISPLAY:
1162 return NSLocalizedString(@"Share screen", @"Contextual menu entry");
1163 default:
1164 return @"";
1165 }
1166}
1167
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001168-(void) switchToFile:(QString)uri {
1169 mediaModel->setInputFile(uri);
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -04001170}
1171
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001172-(CGRect) getVideoPreviewCollapsedSize {
1173 CGPoint origin;
1174 switch (movableBaseForView.closestCorner) {
1175 case TOP_LEFT:
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -04001176 origin = CGPointMake(0, movableBaseForView.frame.size.height - HIDE_PREVIEW_BUTTON_SIZE);
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001177 break;
1178 case BOTTOM_LEFT:
1179 origin = CGPointMake(0, 0);
1180 break;
1181 case TOP_RIGHT:
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -04001182 origin = CGPointMake(movableBaseForView.frame.size.width - HIDE_PREVIEW_BUTTON_SIZE, movableBaseForView.frame.size.height - HIDE_PREVIEW_BUTTON_SIZE);
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001183 break;
1184 case BOTTOM_RIGHT:
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -04001185 origin = CGPointMake(movableBaseForView.frame.size.width - HIDE_PREVIEW_BUTTON_SIZE, 0);
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001186 break;
1187 }
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -04001188 return CGRectMake(origin.x, origin.y, HIDE_PREVIEW_BUTTON_SIZE, HIDE_PREVIEW_BUTTON_SIZE);
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001189}
1190
Kateryna Kostiuk2aa39232019-10-10 11:55:11 -04001191-(CGRect) frameForExpendPreviewButton {
1192 CGPoint origin = CGPointMake(self.previewView.frame.size.width - HIDE_PREVIEW_BUTTON_SIZE,
1193 self.previewView.frame.size.height - HIDE_PREVIEW_BUTTON_SIZE);
1194 return CGRectMake(origin.x, origin.y, HIDE_PREVIEW_BUTTON_SIZE, HIDE_PREVIEW_BUTTON_SIZE);
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001195}
1196
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001197# pragma mark -ChooseContactVCDelegate
1198
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001199-(void)callToContact:(const QString&)contactUri convUID:(const QString&)convID {
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001200 if (brokerPopoverVC != nullptr) {
1201 [brokerPopoverVC performClose:self];
1202 brokerPopoverVC = NULL;
1203 }
1204 auto* callModel = accountInfo_->callModel.get();
1205 auto currentCall = callModel->getCall([self getcallID]);
1206 auto* convModel = accountInfo_->conversationModel.get();
1207 auto newCall = callModel->callAndAddParticipant(contactUri,
1208 [self getcallID],
1209 currentCall.isAudioOnly);
1210 [self addPreviewForContactUri:contactUri call: newCall];
1211}
1212
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001213-(void)joinCall:(const QString&)callId {
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001214 if (brokerPopoverVC != nullptr) {
1215 [brokerPopoverVC performClose:self];
1216 brokerPopoverVC = NULL;
1217 }
1218 auto* callModel = accountInfo_->callModel.get();
1219 callModel->joinCalls([self getcallID], callId);
1220}
1221
1222# pragma mark -CallInConferenceVCDelegate
1223
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001224-(void)removePreviewForContactUri:(const QString&)uri forCall:(const QString&) callId {
1225 NSMutableDictionary * calls = connectingCalls[callId.toNSString()];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001226 if (!calls) {
1227 return;
1228 }
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001229 NSViewController *callView = calls[uri.toNSString()];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001230 if (!callView) {
1231 return;
1232 }
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001233 calls[uri.toNSString()] = nil;
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001234 [self.callingWidgetsContainer removeView:callView.view];
1235}
1236
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001237-(void)addPreviewForContactUri:(const QString&)uri call:(const QString&)callId {
1238 NSMutableDictionary *calls = connectingCalls[callUid_.toNSString()];
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001239 if (!calls) {
1240 calls = [[NSMutableDictionary alloc] init];
1241 }
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001242 if (calls[uri.toNSString()]) {
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001243 return;
1244 }
1245 CallInConferenceVC *callingView = [self callingViewForCallId: callId];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001246 calls[uri.toNSString()] = callingView;
1247 connectingCalls[callUid_.toNSString()] = calls;
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001248 [self.callingWidgetsContainer addView:callingView.view inGravity:NSStackViewGravityBottom];
1249 [self.callingWidgetsContainer updateConstraints];
1250 [self.callingWidgetsContainer updateLayer];
1251 [self.callingWidgetsContainer setNeedsDisplay:YES];
1252}
1253
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001254-(CallInConferenceVC*) callingViewForCallId:(const QString&)callId {
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001255 CallInConferenceVC *callView = [[CallInConferenceVC alloc]
1256 initWithNibName:@"CallInConferenceVC"
1257 bundle:nil
1258 callId:callId
1259 accountInfo:accountInfo_];
1260 callView.delegate = self;
1261 callView.initiatorCallId = callUid_;
1262 return callView;
1263}
1264
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001265-(const QString&)getcallID {
1266 return confUid_.isEmpty() ? callUid_ : confUid_;
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001267}
1268
Kateryna Kostiukc867eb92020-03-08 13:15:17 -04001269-(bool)isCurrentCall:(const QString&)callId {
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001270 return (callId == confUid_ || callId == callUid_);
1271}
1272
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -04001273#pragma mark ConferenceLayoutDelegate
1274
1275-(void)hangUpParticipant:(NSString*)uri {
1276 if (accountInfo_ == nil)
1277 return;
1278 auto* callModel = accountInfo_->callModel.get();
1279
1280 auto convIt = getConversationFromURI(QString::fromNSString(uri), *accountInfo_->conversationModel);
1281 auto callId = convIt->callId;
1282 if (not callModel->hasCall(callId)){
1283 return;
1284 }
1285 callModel->hangUp(callId);
1286}
1287
1288-(void)minimizeParticipant {
1289 if (accountInfo_ == nil)
1290 return;
1291 auto* callModel = accountInfo_->callModel.get();
1292 if (not callModel->hasCall(confUid_)) {
1293 return;
1294 }
1295 try {
1296 auto call = callModel->getCall(confUid_);
1297 switch (call.layout) {
1298 case lrc::api::call::Layout::GRID:
1299 break;
1300 case lrc::api::call::Layout::ONE_WITH_SMALL:
1301 callModel->setConferenceLayout(confUid_, lrc::api::call::Layout::GRID);
1302 break;
1303 case lrc::api::call::Layout::ONE:
1304 callModel->setConferenceLayout(confUid_, lrc::api::call::Layout::ONE_WITH_SMALL);
1305 break;
1306 };
1307 } catch (...) {}
1308}
1309
1310-(int)getCurrentLayout {
1311 auto* callModel = accountInfo_->callModel.get();
1312 if (not callModel->hasCall(confUid_)){
1313 return -1;
1314 }
1315 return static_cast<int>(callModel->getCall(confUid_).layout);
1316}
1317
1318-(BOOL)isMasterCall {
Kateryna Kostiuk84101912020-08-31 16:06:33 -04001319 auto conv = accountInfo_->conversationModel->getConversationForUID(convUid_);
1320 if (conv.uid.isEmpty()) {
1321 return false;
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -04001322 }
Kateryna Kostiuk84101912020-08-31 16:06:33 -04001323
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -04001324 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiuk84101912020-08-31 16:06:33 -04001325 if (!conv.confId.isEmpty() && callModel->hasCall(conv.confId)) {
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -04001326 return true;
1327 } else {
Kateryna Kostiuk84101912020-08-31 16:06:33 -04001328 auto call = callModel->getCall(conv.callId);
Kateryna Kostiuk1705a5a2020-08-26 09:48:00 -04001329 return call.participantsInfos.size() == 0;
1330 }
1331}
1332
1333-(void)maximizeParticipant:(NSString*)uri active:(BOOL)isActive {
1334 if (accountInfo_ == nil)
1335 return;
1336 auto convIt = getConversationFromURI(QString::fromNSString(uri), *accountInfo_->conversationModel);
1337 QString callId;
1338 BOOL localVideo = accountInfo_->profileInfo.uri == QString::fromNSString(uri);
1339 if (!localVideo) {
1340 auto convIt = getConversationFromURI(QString::fromNSString(uri), *accountInfo_->conversationModel);
1341 callId = convIt->callId;
1342 }
1343 auto* callModel = accountInfo_->callModel.get();
1344 if ((not callModel->hasCall(callId) || not callModel->hasCall(confUid_)) && !localVideo){
1345 return;
1346 }
1347 try {
1348 auto call = callModel->getCall(confUid_);
1349 switch (call.layout) {
1350 case lrc::api::call::Layout::GRID:
1351 callModel->setActiveParticipant(confUid_, callId);
1352 callModel->setConferenceLayout(confUid_, lrc::api::call::Layout::ONE_WITH_SMALL);
1353 break;
1354 case lrc::api::call::Layout::ONE_WITH_SMALL:
1355 callModel->setActiveParticipant(confUid_, callId);
1356 callModel->setConferenceLayout(confUid_,
1357 isActive ? lrc::api::call::Layout::ONE : lrc::api::call::Layout::ONE_WITH_SMALL);
1358 break;
1359 case lrc::api::call::Layout::ONE:
1360 callModel->setActiveParticipant(confUid_, callId);
1361 callModel->setConferenceLayout(confUid_, lrc::api::call::Layout::GRID);
1362 break;
1363 };
1364 } catch (...) {}
1365}
1366
Kateryna Kostiuk4db61092019-10-17 16:57:40 -04001367#pragma mark Popover delegate
1368
1369- (void)popoverWillClose:(NSNotification *)notification
1370{
1371 if (brokerPopoverVC != nullptr) {
1372 [brokerPopoverVC performClose:self];
1373 brokerPopoverVC = NULL;
1374 }
1375}
Alexandre Lisionc5148052015-03-04 15:10:35 -05001376@end