blob: 6393044ffaaa627492f9c771478a0b3a6bd28956 [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>
37#import <api/call.h>
38#import <api/conversationmodel.h>
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -040039#import <api/avmodel.h>
Alexandre Lision210fe212016-01-27 11:15:13 -050040#import <globalinstances.h>
Alexandre Lisionc5148052015-03-04 15:10:35 -050041
Alexandre Lision62005312016-01-28 15:55:16 -050042#import "AppDelegate.h"
Alexandre Lisionf47a2562015-06-15 15:48:29 -040043#import "views/ITProgressIndicator.h"
Alexandre Lision74dd47f2015-04-14 13:47:42 -040044#import "views/CallView.h"
Kateryna Kostiuk81afce72018-10-30 16:40:50 -040045#import "views/NSColor+RingTheme.h"
Alexandre Lision210fe212016-01-27 11:15:13 -050046#import "delegates/ImageManipulationDelegate.h"
Alexandre Lision16d9c0a2015-08-10 12:05:15 -040047#import "ChatVC.h"
Alexandre Lision4baba4c2016-02-11 13:00:57 -050048#import "views/IconButton.h"
Anthony Léonard6f819752018-01-05 09:53:40 -050049#import "utils.h"
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -040050#import "views/CallMTKView.h"
Kateryna Kostiukc5fd3b92019-07-24 10:12:44 -040051#import "VideoCommon.h"
Kateryna Kostiuk88a6ee82019-09-10 17:55:08 -040052#import "views/GradientView.h"
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -040053#import "views/MovableView.h"
Alexandre Lision74dd47f2015-04-14 13:47:42 -040054
Alexandre Lisionc5148052015-03-04 15:10:35 -050055@interface RendererConnectionsHolder : NSObject
56
57@property QMetaObject::Connection frameUpdated;
58@property QMetaObject::Connection started;
59@property QMetaObject::Connection stopped;
60
61@end
62
63@implementation RendererConnectionsHolder
64
65@end
66
Kateryna Kostiuk1ba5a012018-12-12 17:07:38 -050067@interface CurrentCallVC () <NSPopoverDelegate> {
Olivier Soldano994971f2017-12-05 16:30:12 -050068 std::string convUid_;
69 std::string callUid_;
70 const lrc::api::account::Info *accountInfo_;
Anthony Léonard66a050c2018-01-03 16:32:34 -050071 NSTimer* refreshDurationTimer;
Olivier Soldano994971f2017-12-05 16:30:12 -050072}
Alexandre Lisionc5148052015-03-04 15:10:35 -050073
Alexandre Lision210fe212016-01-27 11:15:13 -050074// Main container
75@property (unsafe_unretained) IBOutlet NSSplitView* splitView;
Kateryna Kostiuk89398b62018-11-20 16:45:04 -050076@property (unsafe_unretained) IBOutlet NSView* backgroundImage;
Kateryna Kostiuk81afce72018-10-30 16:40:50 -040077@property (unsafe_unretained) IBOutlet NSBox* bluerBackgroundEffect;
Alexandre Lision210fe212016-01-27 11:15:13 -050078
Alexandre Lision89edc6a2015-11-09 11:30:47 -050079// Header info
80@property (unsafe_unretained) IBOutlet NSView* headerContainer;
Alexandre Lision89edc6a2015-11-09 11:30:47 -050081@property (unsafe_unretained) IBOutlet NSTextField* timeSpentLabel;
82
Andreas Traczyk77a50d52018-05-08 17:47:31 -040083@property (unsafe_unretained) IBOutlet NSImageView* outgoingPhoto;
84@property (unsafe_unretained) IBOutlet NSTextField* outgoingPersonLabel;
85@property (unsafe_unretained) IBOutlet NSTextField* outgoingStateLabel;
86@property (unsafe_unretained) IBOutlet NSTextField* outgoingId;
87
Alexandre Lision89edc6a2015-11-09 11:30:47 -050088// Call Controls
Kateryna Kostiuk88a6ee82019-09-10 17:55:08 -040089@property (unsafe_unretained) IBOutlet GradientView* controlsPanel;
Alexandre Lision62005312016-01-28 15:55:16 -050090
Alexandre Lision4baba4c2016-02-11 13:00:57 -050091@property (unsafe_unretained) IBOutlet IconButton* holdOnOffButton;
92@property (unsafe_unretained) IBOutlet IconButton* hangUpButton;
93@property (unsafe_unretained) IBOutlet IconButton* recordOnOffButton;
94@property (unsafe_unretained) IBOutlet IconButton* pickUpButton;
95@property (unsafe_unretained) IBOutlet IconButton* muteAudioButton;
96@property (unsafe_unretained) IBOutlet IconButton* muteVideoButton;
Alexandre Lision4baba4c2016-02-11 13:00:57 -050097@property (unsafe_unretained) IBOutlet IconButton* transferButton;
98@property (unsafe_unretained) IBOutlet IconButton* addParticipantButton;
99@property (unsafe_unretained) IBOutlet IconButton* chatButton;
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400100@property (unsafe_unretained) IBOutlet IconButton* qualityButton;
Alexandre Lisiond18fa272015-06-15 11:18:03 -0400101
Alexandre Lision62005312016-01-28 15:55:16 -0500102@property (unsafe_unretained) IBOutlet NSView* advancedPanel;
103@property (unsafe_unretained) IBOutlet IconButton* advancedButton;
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400104@property (assign) IBOutlet NSLayoutConstraint* callRecordButtonMarginLeft;
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500105
Alexandre Lision2db8f472015-07-22 15:05:46 -0400106@property (strong) NSPopover* addToContactPopover;
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500107@property (strong) NSPopover* brokerPopoverVC;
Alexandre Lision883719f2015-10-22 17:37:45 -0400108@property (strong) IBOutlet ChatVC* chatVC;
Alexandre Lisionf23ec5a2015-07-16 11:24:06 -0400109
Alexandre Lision210fe212016-01-27 11:15:13 -0500110// Ringing call panel
111@property (unsafe_unretained) IBOutlet NSView* ringingPanel;
112@property (unsafe_unretained) IBOutlet NSImageView* incomingPersonPhoto;
113@property (unsafe_unretained) IBOutlet NSTextField* incomingDisplayName;
114
115// Outgoing call panel
116@property (unsafe_unretained) IBOutlet NSView* outgoingPanel;
117@property (unsafe_unretained) IBOutlet ITProgressIndicator *loadingIndicator;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500118
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400119// audio only view
120@property (unsafe_unretained) IBOutlet NSView* audioCallView;
121@property (unsafe_unretained) IBOutlet NSImageView* audioCallPhoto;
122@property (unsafe_unretained) IBOutlet NSTextField* audioCallTime;
123@property (unsafe_unretained) IBOutlet NSTextField* audioCallPersonLabel;
124@property (unsafe_unretained) IBOutlet NSTextField* audioCallPersonId;
125
Alexandre Lisionc5148052015-03-04 15:10:35 -0500126// Video
Alexandre Lision58cab672015-06-09 15:25:40 -0400127@property (unsafe_unretained) IBOutlet CallView *videoView;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400128@property (unsafe_unretained) IBOutlet CallMTKView *previewView;
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -0400129@property (unsafe_unretained) IBOutlet MovableView *movableBaseForView;
130@property (unsafe_unretained) IBOutlet NSView* hidePreviewBackground;
131@property (unsafe_unretained) IBOutlet NSButton* hidePreviewButton;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400132
133@property (unsafe_unretained) IBOutlet CallMTKView *videoMTKView;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500134
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400135@property RendererConnectionsHolder* renderConnections;
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400136@property QMetaObject::Connection videoStarted;
Alexandre Lision4d9905e2016-01-22 12:01:36 -0500137@property QMetaObject::Connection selectedCallChanged;
Alexandre Lisionb65c0272015-07-22 15:51:29 -0400138@property QMetaObject::Connection messageConnection;
139@property QMetaObject::Connection mediaAddedConnection;
Kateryna Kostiuk133364b2018-10-31 11:36:08 -0400140@property QMetaObject::Connection profileUpdatedConnection;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500141
142@end
143
144@implementation CurrentCallVC
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400145lrc::api::AVModel* mediaModel;
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -0400146
147NSInteger const PREVIEW_WIDTH = 185;
148NSInteger const PREVIEW_HEIGHT = 130;
149NSInteger const HIDE_PREVIEW_BUTTON_MIN_SIZE = 25;
150NSInteger const HIDE_PREVIEW_BUTTON_MAX_SIZE = 35;
151NSInteger const PREVIEW_MARGIN = 20;
152
Kateryna Kostiuk14194c02019-09-19 16:45:55 -0400153@synthesize holdOnOffButton, hangUpButton,
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500154 recordOnOffButton, pickUpButton, chatButton, transferButton, addParticipantButton, timeSpentLabel,
Olivier Soldano994971f2017-12-05 16:30:12 -0500155 muteVideoButton, muteAudioButton, controlsPanel, advancedPanel, advancedButton, headerContainer, videoView,
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400156 incomingDisplayName, incomingPersonPhoto, previewView, splitView, loadingIndicator, ringingPanel,
157 outgoingPanel, outgoingPersonLabel, outgoingStateLabel, outgoingPhoto, outgoingId,
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -0400158 callRecordButtonMarginLeft, audioCallView, audioCallPhoto, audioCallTime, audioCallPersonLabel, audioCallPersonId, backgroundImage, bluerBackgroundEffect, hidePreviewButton, hidePreviewBackground, movableBaseForView;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500159
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400160@synthesize renderConnections;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400161CVPixelBufferPoolRef pixelBufferPoolDistantView;
162CVPixelBufferRef pixelBufferDistantView;
163CVPixelBufferPoolRef pixelBufferPoolPreview;
164CVPixelBufferRef pixelBufferPreview;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500165
Olivier Soldano994971f2017-12-05 16:30:12 -0500166-(void) setCurrentCall:(const std::string&)callUid
167 conversation:(const std::string&)convUid
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400168 account:(const lrc::api::account::Info*)account
169 avModel:(lrc::api::AVModel *)avModel;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500170{
Olivier Soldano994971f2017-12-05 16:30:12 -0500171 if(account == nil)
172 return;
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400173
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400174 mediaModel = avModel;
Olivier Soldano994971f2017-12-05 16:30:12 -0500175 auto* callModel = account->callModel.get();
Alexandre Lisionc5148052015-03-04 15:10:35 -0500176
Olivier Soldano994971f2017-12-05 16:30:12 -0500177 if (not callModel->hasCall(callUid)){
178 callUid_.clear();
Alexandre Lision2db8f472015-07-22 15:05:46 -0400179 return;
180 }
Olivier Soldano994971f2017-12-05 16:30:12 -0500181 callUid_ = callUid;
182 convUid_ = convUid;
183 accountInfo_ = account;
Anthony Léonard4b0935f2017-12-26 12:05:22 -0500184 [self.chatVC setConversationUid:convUid model:account->conversationModel.get()];
Kateryna Kostiuk54d76972018-06-07 15:45:07 -0400185 auto currentCall = callModel->getCall(callUid_);
Kateryna Kostiuk88a6ee82019-09-10 17:55:08 -0400186 [self setUpButtons: currentCall isRecording: (callModel->isRecording(callUid_) || avModel->getAlwaysRecord())];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400187 callRecordButtonMarginLeft.constant = currentCall.isAudioOnly ? -40.0f: 10.0f;
188 [previewView setHidden: YES];
Kateryna Kostiukd76f6202018-09-11 17:15:28 -0400189 videoView.callId = callUid;
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -0400190 [self setUpPrewviewFrame];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500191}
192
Kateryna Kostiuk88a6ee82019-09-10 17:55:08 -0400193-(void) setUpButtons:(lrc::api::call::Info&)callInfo isRecording:(BOOL) isRecording {
194 muteAudioButton.image = callInfo.audioMuted ? [NSImage imageNamed:@"ic_action_mute_audio.png"] :
195 [NSImage imageNamed:@"ic_action_audio.png"];
196 muteVideoButton.image = callInfo.videoMuted ? [NSImage imageNamed:@"ic_action_mute_video.png"] :
197 [NSImage imageNamed:@"ic_action_video.png"];
198 [muteVideoButton setHidden: callInfo.isAudioOnly ? YES: NO];
199 if (isRecording) {
200 [recordOnOffButton startBlinkAnimationfrom:[NSColor buttonBlinkColorColor] to:[NSColor whiteColor] scaleFactor: 1 duration: 1.5];
201 } else {
202 [recordOnOffButton stopBlinkAnimation];
203 }
204}
205
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -0400206- (void) setUpPrewviewFrame {
207 CGPoint previewOrigin = CGPointMake(self.videoView.frame.size.width - PREVIEW_WIDTH - PREVIEW_MARGIN, PREVIEW_MARGIN);
208 movableBaseForView.frame = CGRectMake(previewOrigin.x, previewOrigin.y, PREVIEW_WIDTH, PREVIEW_HEIGHT);
209 movableBaseForView.wantsLayer = YES;
210 movableBaseForView.layer.cornerRadius = 4;
211 movableBaseForView.layer.masksToBounds = true;
212 [movableBaseForView setAutoresizingMask: NSViewNotSizable | NSViewMaxXMargin | NSViewMaxYMargin | NSViewMinXMargin | NSViewMinYMargin];
213 movableBaseForView.hostingView = self.videoView;
214 self.movableBaseForView.movable = true;
215 previewView.frame = movableBaseForView.bounds;
216 [previewView setAutoresizingMask: NSViewNotSizable | NSViewMaxXMargin | NSViewMaxYMargin | NSViewMinXMargin | NSViewMinYMargin];
217 hidePreviewBackground.frame = [self frameForExpendPreviewButton: false];;
218 [hidePreviewBackground setAutoresizingMask: NSViewNotSizable | NSViewMinXMargin | NSViewMinYMargin| NSViewMaxXMargin | NSViewMaxYMargin];
219}
220
Alexandre Lisionc5148052015-03-04 15:10:35 -0500221- (void)awakeFromNib
222{
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400223 [self.view setWantsLayer:YES];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500224
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400225 renderConnections = [[RendererConnectionsHolder alloc] init];
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400226
Alexandre Lisionf47a2562015-06-15 15:48:29 -0400227 [loadingIndicator setColor:[NSColor whiteColor]];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400228 [loadingIndicator setNumberOfLines:200];
229 [loadingIndicator setWidthOfLine:4];
230 [loadingIndicator setLengthOfLine:4];
231 [loadingIndicator setInnerMargin:59];
Alexandre Lisionf47a2562015-06-15 15:48:29 -0400232
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -0400233 [self.videoView setCallDelegate:self];
Kateryna Kostiuk1004caa2018-03-29 13:48:07 -0400234 CGColor* color = [[[NSColor blackColor] colorWithAlphaComponent:0.2] CGColor];
235 [headerContainer.layer setBackgroundColor:color];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400236 [bluerBackgroundEffect setWantsLayer:YES];
237 bluerBackgroundEffect.alphaValue = 0.6;
238 [audioCallView setWantsLayer:YES];
239 [audioCallView.layer setBackgroundColor: [[NSColor clearColor] CGColor]];
Kateryna Kostiuk89398b62018-11-20 16:45:04 -0500240 [backgroundImage setWantsLayer: YES];
241 backgroundImage.layer.contentsGravity = kCAGravityResizeAspectFill;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500242}
243
Anthony Léonard66a050c2018-01-03 16:32:34 -0500244-(void) updateDurationLabel
245{
246 if (accountInfo_ != nil) {
247 auto* callModel = accountInfo_->callModel.get();
248 if (callModel->hasCall(callUid_)) {
249 auto& callStatus = callModel->getCall(callUid_).status;
250 if (callStatus != lrc::api::call::Status::ENDED &&
251 callStatus != lrc::api::call::Status::TERMINATING &&
252 callStatus != lrc::api::call::Status::INVALID) {
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400253 if(callModel->getCall(callUid_).isAudioOnly) {
254 [audioCallTime setStringValue:@(callModel->getFormattedCallDuration(callUid_).c_str())];
255 } else {
256 [timeSpentLabel setStringValue:@(callModel->getFormattedCallDuration(callUid_).c_str())];
257 }
Anthony Léonard66a050c2018-01-03 16:32:34 -0500258 return;
259 }
260 }
261 }
262
263 // If call is not running anymore or accountInfo_ is not set for any reason
264 // we stop the refresh loop
265 [refreshDurationTimer invalidate];
266 refreshDurationTimer = nil;
267 [timeSpentLabel setHidden:YES];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400268 [audioCallView setHidden:YES];
Anthony Léonard66a050c2018-01-03 16:32:34 -0500269}
270
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400271-(void) updateCall
Alexandre Lisionc5148052015-03-04 15:10:35 -0500272{
Olivier Soldano994971f2017-12-05 16:30:12 -0500273 if (accountInfo_ == nil)
274 return;
Alexandre Lisionbb5dbcd2015-07-09 16:36:47 -0400275
Olivier Soldano994971f2017-12-05 16:30:12 -0500276 auto* callModel = accountInfo_->callModel.get();
277 if (not callModel->hasCall(callUid_)) {
278 return;
279 }
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500280
Olivier Soldano994971f2017-12-05 16:30:12 -0500281 auto currentCall = callModel->getCall(callUid_);
282 NSLog(@"\n status %@ \n",@(lrc::api::call::to_string(currentCall.status).c_str()));
Anthony Léonard8ee26782018-01-05 10:23:04 -0500283 auto convIt = getConversationFromUid(convUid_, *accountInfo_->conversationModel);
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400284 if (convIt != accountInfo_->conversationModel->allFilteredConversations().end()) {
285 NSString* bestName = bestNameForConversation(*convIt, *accountInfo_->conversationModel);
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400286 [outgoingPersonLabel setStringValue:bestName];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400287 [audioCallPersonLabel setStringValue:bestName];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400288 NSString* ringID = bestIDForConversation(*convIt, *accountInfo_->conversationModel);
289 if([bestName isEqualToString:ringID]) {
290 ringID = @"";
291 }
292 [outgoingId setStringValue:ringID];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400293 [audioCallPersonId setStringValue:ringID];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400294 }
295
Anthony Léonard66a050c2018-01-03 16:32:34 -0500296 [timeSpentLabel setStringValue:@(callModel->getFormattedCallDuration(callUid_).c_str())];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400297 [audioCallTime setStringValue:@(callModel->getFormattedCallDuration(callUid_).c_str())];
Anthony Léonard66a050c2018-01-03 16:32:34 -0500298 if (refreshDurationTimer == nil)
299 refreshDurationTimer = [NSTimer scheduledTimerWithTimeInterval:1.0
300 target:self
301 selector:@selector(updateDurationLabel)
302 userInfo:nil
303 repeats:YES];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400304 [outgoingStateLabel setStringValue:@(to_string(currentCall.status).c_str())];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500305
Olivier Soldano994971f2017-12-05 16:30:12 -0500306 // Default values for this views
307 [loadingIndicator setHidden:YES];
Olivier Soldano994971f2017-12-05 16:30:12 -0500308 [ringingPanel setHidden:YES];
309 [outgoingPanel setHidden:YES];
310 [controlsPanel setHidden:NO];
311 [headerContainer setHidden:NO];
Anthony Léonard4b0935f2017-12-26 12:05:22 -0500312 [chatButton setHidden:NO];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400313 [self setBackground];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500314
Olivier Soldano994971f2017-12-05 16:30:12 -0500315 using Status = lrc::api::call::Status;
Kateryna Kostiuk88a6ee82019-09-10 17:55:08 -0400316 if (currentCall.status == Status::PAUSED) {
317 [holdOnOffButton startBlinkAnimationfrom:[NSColor buttonBlinkColorColor] to:[NSColor whiteColor] scaleFactor: 1.0 duration: 1.5];
318 } else {
319 [holdOnOffButton stopBlinkAnimation];
320 }
Olivier Soldano994971f2017-12-05 16:30:12 -0500321 switch (currentCall.status) {
322 case Status::SEARCHING:
Olivier Soldano994971f2017-12-05 16:30:12 -0500323 case Status::CONNECTING:
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400324 [headerContainer setHidden:YES];
325 [outgoingPanel setHidden:NO];
326 [outgoingPhoto setHidden:NO];
327 [self setupContactInfo:outgoingPhoto];
Olivier Soldano994971f2017-12-05 16:30:12 -0500328 [loadingIndicator setHidden:NO];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400329 [controlsPanel setHidden:YES];
Olivier Soldano994971f2017-12-05 16:30:12 -0500330 break;
331 case Status::INCOMING_RINGING:
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400332 [outgoingPhoto setHidden:YES];
Olivier Soldano994971f2017-12-05 16:30:12 -0500333 [controlsPanel setHidden:YES];
334 [outgoingPanel setHidden:YES];
335 [self setupIncoming:callUid_];
336 break;
337 case Status::OUTGOING_RINGING:
338 [controlsPanel setHidden:YES];
339 [outgoingPanel setHidden:NO];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400340 [loadingIndicator setHidden:NO];
341 [headerContainer setHidden:YES];
Olivier Soldano994971f2017-12-05 16:30:12 -0500342 break;
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400343 /*case Status::CONFERENCE:
344 [self setupConference:currentCall];
345 break;*/
Olivier Soldano994971f2017-12-05 16:30:12 -0500346 case Status::PAUSED:
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400347 [self.videoMTKView fillWithBlack];
348 [self.previewView fillWithBlack];
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -0400349 [hidePreviewBackground setHidden:YES];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400350 [bluerBackgroundEffect setHidden:NO];
351 [backgroundImage setHidden:NO];
352 [self.previewView setHidden: YES];
353 [self.videoMTKView setHidden: YES];
354 self.previewView.stopRendering = true;
355 self.videoMTKView.stopRendering = true;
356 break;
Olivier Soldano994971f2017-12-05 16:30:12 -0500357 case Status::INACTIVE:
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400358 if(currentCall.isAudioOnly) {
359 [self setUpAudioOnlyView];
360 } else {
361 [self setUpVideoCallView];
362 }
363 break;
Olivier Soldano994971f2017-12-05 16:30:12 -0500364 case Status::IN_PROGRESS:
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400365 self.previewView.stopRendering = false;
366 self.videoMTKView.stopRendering = false;
367 [previewView fillWithBlack];
368 [self.videoMTKView fillWithBlack];
369 [self.previewView setHidden: NO];
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -0400370 [hidePreviewBackground setHidden: YES];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400371 [self.videoMTKView setHidden: NO];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400372 if(currentCall.isAudioOnly) {
373 [self setUpAudioOnlyView];
374 } else {
375 [self setUpVideoCallView];
376 }
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400377 break;
Olivier Soldano994971f2017-12-05 16:30:12 -0500378 case Status::CONNECTED:
Olivier Soldano994971f2017-12-05 16:30:12 -0500379 break;
380 case Status::ENDED:
381 case Status::TERMINATING:
382 case Status::INVALID:
Olivier Soldano994971f2017-12-05 16:30:12 -0500383 break;
384 }
Alexandre Lision4d9905e2016-01-22 12:01:36 -0500385}
386
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400387-(void) setUpVideoCallView {
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400388 [timeSpentLabel setHidden:NO];
389 [outgoingPhoto setHidden:YES];
390 [headerContainer setHidden:NO];
391 [previewView setHidden: NO];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400392 [self.videoMTKView setHidden:NO];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400393 [bluerBackgroundEffect setHidden:YES];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400394 [backgroundImage setHidden:YES];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400395 [audioCallView setHidden:YES];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400396}
397
398-(void) setUpAudioOnlyView {
399 [audioCallView setHidden:NO];
400 [headerContainer setHidden:YES];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400401 [self.previewView setHidden: YES];
402 [self.videoMTKView setHidden: YES];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400403 [audioCallPhoto setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
404}
405
406-(void) setBackground {
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400407 auto* convModel = accountInfo_->conversationModel.get();
408 auto it = getConversationFromUid(convUid_, *convModel);
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400409 NSImage *image= [self getContactImageOfSize:120.0 withDefaultAvatar:NO];
410 if(image) {
Kateryna Kostiuk89398b62018-11-20 16:45:04 -0500411 CIImage * ciImage = [[CIImage alloc] initWithData:[image TIFFRepresentation]];
412 CIContext *context = [[CIContext alloc] init];
413 CIFilter *clamp = [CIFilter filterWithName:@"CIAffineClamp"];
414 [clamp setValue:[NSAffineTransform transform] forKey:@"inputTransform"];
415 [clamp setValue:ciImage forKey: kCIInputImageKey];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400416 CIFilter* bluerFilter = [CIFilter filterWithName:@"CIGaussianBlur"];
417 [bluerFilter setDefaults];
Kateryna Kostiuk89398b62018-11-20 16:45:04 -0500418 [bluerFilter setValue:[NSNumber numberWithFloat: 9] forKey:@"inputRadius"];
419 [bluerFilter setValue:[clamp valueForKey:kCIOutputImageKey] forKey: kCIInputImageKey];
420 CIImage *result = [bluerFilter valueForKey:kCIOutputImageKey];
421 CGRect extent = [result extent];
422 CGImageRef cgImage = [context createCGImage:result fromRect: [ciImage extent]];
423 NSImage *bluredImage = [[NSImage alloc] initWithCGImage:cgImage size:NSSizeFromCGSize(CGSizeMake(image.size.width, image.size.height))];
424 backgroundImage.layer.contents = bluredImage;
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400425 [backgroundImage setHidden:NO];
426 } else {
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400427 [bluerBackgroundEffect setFillColor:[NSColor ringDarkGrey]];
428 [backgroundImage setHidden:YES];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400429 }
430}
431
Kateryna Kostiuk133364b2018-10-31 11:36:08 -0400432-(NSImage *) getContactImageOfSize: (double) size withDefaultAvatar:(BOOL) shouldDrawDefault {
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400433 auto* convModel = accountInfo_->conversationModel.get();
434 auto convIt = getConversationFromUid(convUid_, *convModel);
435 if (convIt == convModel->allFilteredConversations().end()) {
436 return nil;
437 }
438 if(shouldDrawDefault) {
439 auto& imgManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
440 QVariant photo = imgManip.conversationPhoto(*convIt, *accountInfo_, QSize(size, size), NO);
441 return QtMac::toNSImage(qvariant_cast<QPixmap>(photo));
442 }
443 auto contact = accountInfo_->contactModel->getContact(convIt->participants[0]);
444 NSData *imageData = [[NSData alloc] initWithBase64EncodedString:@(contact.profileInfo.avatar.c_str()) options:NSDataBase64DecodingIgnoreUnknownCharacters];
445 return [[NSImage alloc] initWithData:imageData];
446}
447
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400448-(void) setupContactInfo:(NSImageView*)imageView
449{
450 [imageView setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
451}
452
Olivier Soldano994971f2017-12-05 16:30:12 -0500453-(void) setupIncoming:(const std::string&) callId
Alexandre Lision4d9905e2016-01-22 12:01:36 -0500454{
Olivier Soldano994971f2017-12-05 16:30:12 -0500455 if (accountInfo_ == nil)
456 return;
457
458 auto* callModel = accountInfo_->callModel.get();
459 if (not callModel->hasCall(callUid_)) {
460 return;
461 }
462
463 auto call = callModel->getCall(callUid_);
464 auto* convModel = accountInfo_->conversationModel.get();
465
466 [ringingPanel setHidden:NO];
467 [controlsPanel setHidden:YES];
468 [headerContainer setHidden:YES];
Anthony Léonard6f819752018-01-05 09:53:40 -0500469 auto it = getConversationFromUid(convUid_, *convModel);
Olivier Soldano994971f2017-12-05 16:30:12 -0500470 if (it != convModel->allFilteredConversations().end()) {
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400471 [incomingPersonPhoto setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
Anthony Léonard8ee26782018-01-05 10:23:04 -0500472 [incomingDisplayName setStringValue:bestNameForConversation(*it, *convModel)];
Olivier Soldano994971f2017-12-05 16:30:12 -0500473 }
474}
475
Kateryna Kostiukbe7e2e42019-07-26 17:44:12 -0400476-(void) setupConference
Olivier Soldano994971f2017-12-05 16:30:12 -0500477{
478 [videoView setShouldAcceptInteractions:YES];
479 [self.chatButton setHidden:NO];
Olivier Soldano994971f2017-12-05 16:30:12 -0500480 [self.addParticipantButton setHidden:NO];
481 [self.transferButton setHidden:YES];
482}
483
484-(void)collapseRightView
485{
486 NSView *right = [[splitView subviews] objectAtIndex:1];
487 NSView *left = [[splitView subviews] objectAtIndex:0];
488 NSRect leftFrame = [left frame];
489 [right setHidden:YES];
490 [splitView display];
491}
492
493- (void) changeCallSelection:(std::string&)callUid
494{
495 if (accountInfo_ == nil)
496 return;
497
498 auto* callModel = accountInfo_->callModel.get();
499 if (not callModel->hasCall(callUid)) {
500 return;
501 }
502
Alexandre Lision4d9905e2016-01-22 12:01:36 -0500503 QObject::disconnect(self.selectedCallChanged);
Olivier Soldano994971f2017-12-05 16:30:12 -0500504 self.selectedCallChanged = QObject::connect(callModel,
505 &lrc::api::NewCallModel::callStatusChanged,
506 [self](const std::string callId) {
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400507 [self updateCall];
Alexandre Lision4d9905e2016-01-22 12:01:36 -0500508 });
Alexandre Lisionc5148052015-03-04 15:10:35 -0500509}
510
511-(void) connectVideoSignals
512{
Olivier Soldano994971f2017-12-05 16:30:12 -0500513 if (accountInfo_ == nil)
514 return;
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400515 [self connectRenderer];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500516}
517
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400518-(void) connectRenderer
Alexandre Lisionc5148052015-03-04 15:10:35 -0500519{
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400520 QObject::disconnect(renderConnections.frameUpdated);
521 QObject::disconnect(renderConnections.stopped);
522 QObject::disconnect(renderConnections.started);
523 renderConnections.started =
524 QObject::connect(mediaModel,
525 &lrc::api::AVModel::rendererStarted,
526 [=](const std::string& id) {
527 if (id == lrc::api::video::PREVIEW_RENDERER_ID) {
528 [self.previewView setHidden:NO];
529 self.previewView.stopRendering = false;
530 } else {
531 [self mouseIsMoving: NO];
532 self.videoMTKView.stopRendering = false;
533 [self.videoMTKView setHidden:NO];
534 [bluerBackgroundEffect setHidden:YES];
535 [backgroundImage setHidden:YES];
536 [videoView setShouldAcceptInteractions:YES];
537 }
538 QObject::disconnect(renderConnections.frameUpdated);
539 renderConnections.frameUpdated =
540 QObject::connect(mediaModel,
541 &lrc::api::AVModel::frameUpdated,
542 [=](const std::string& id) {
543 if (id == lrc::api::video::PREVIEW_RENDERER_ID) {
544 auto renderer = &mediaModel->getRenderer(lrc::api::video::PREVIEW_RENDERER_ID);
545 if(!renderer->isRendering()) {
546 return;
547 }
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -0400548 [hidePreviewBackground setHidden: NO];
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400549 [self renderer: renderer renderFrameForPreviewView:previewView];
550
551 } else {
552 auto renderer = &mediaModel->getRenderer(id);
553 if(!renderer->isRendering()) {
554 return;
555 }
556 [self renderer:renderer renderFrameForDistantView: self.videoMTKView];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400557 }
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400558 });
Alexandre Lisionc5148052015-03-04 15:10:35 -0500559 });
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400560 renderConnections.stopped =
561 QObject::connect(mediaModel,
562 &lrc::api::AVModel::rendererStopped,
563 [=](const std::string& id) {
564 QObject::disconnect(renderConnections.frameUpdated);
565 if (id == lrc::api::video::PREVIEW_RENDERER_ID) {
566 [self.previewView setHidden:YES];
567 self.previewView.stopRendering = true;
568 } else {
569 [self mouseIsMoving: YES];
570 self.videoMTKView.stopRendering = true;
571 [self.videoMTKView setHidden:YES];
572 [bluerBackgroundEffect setHidden:NO];
573 [backgroundImage setHidden:NO];
574 [videoView setShouldAcceptInteractions:NO];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400575 }
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400576 });
577 renderConnections.frameUpdated =
578 QObject::connect(mediaModel,
579 &lrc::api::AVModel::frameUpdated,
580 [=](const std::string& id) {
581 if (id == lrc::api::video::PREVIEW_RENDERER_ID) {
582 auto renderer = &mediaModel->getRenderer(lrc::api::video::PREVIEW_RENDERER_ID);
583 if(!renderer->isRendering()) {
584 return;
585 }
586 [self renderer: renderer renderFrameForPreviewView:previewView];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500587
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400588 } else {
589 auto renderer = &mediaModel->getRenderer(id);
590 if(!renderer->isRendering()) {
591 return;
592 }
593 [self renderer:renderer renderFrameForDistantView: self.videoMTKView];
Kateryna Kostiuk732b4062018-11-28 09:59:28 -0500594 }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500595 });
596}
597
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400598-(void) renderer: (const lrc::api::video::Renderer*)renderer renderFrameForPreviewView:(CallMTKView*) view
Alexandre Lisionc5148052015-03-04 15:10:35 -0500599{
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400600 @autoreleasepool {
601 auto framePtr = renderer->currentAVFrame();
602 auto frame = framePtr.get();
603 if(!frame || !frame->width || !frame->height) {
604 return;
605 }
606 auto frameSize = CGSizeMake(frame->width, frame->height);
607 auto rotation = 0;
608 if (frame->data[3] != NULL && (CVPixelBufferRef)frame->data[3]) {
609 [view renderWithPixelBuffer:(CVPixelBufferRef)frame->data[3]
610 size: frameSize
611 rotation: rotation
612 fillFrame: true];
613 return;
614 }
615 else if (CVPixelBufferRef pixelBuffer = [self getBufferForPreviewFromFrame:frame]) {
616 [view renderWithPixelBuffer: pixelBuffer
617 size: frameSize
618 rotation: rotation
619 fillFrame: true];
620 }
621 }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500622}
623
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400624-(void) renderer: (const lrc::api::video::Renderer*)renderer renderFrameForDistantView:(CallMTKView*) view
Anthony Léonard14e7bf32017-06-08 08:13:16 -0400625{
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400626 @autoreleasepool {
627 auto framePtr = renderer->currentAVFrame();
628 auto frame = framePtr.get();
629 if(!frame || !frame->width || !frame->height) {
630 return;
631 }
632 auto frameSize = CGSizeMake(frame->width, frame->height);
633 auto rotation = 0;
634 if (auto matrix = av_frame_get_side_data(frame, AV_FRAME_DATA_DISPLAYMATRIX)) {
635 const int32_t* data = reinterpret_cast<int32_t*>(matrix->data);
636 rotation = av_display_rotation_get(data);
637 }
638 if (frame->data[3] != NULL && (CVPixelBufferRef)frame->data[3]) {
639 [view renderWithPixelBuffer: (CVPixelBufferRef)frame->data[3]
640 size: frameSize
641 rotation: rotation
642 fillFrame: false];
643 }
644 if (CVPixelBufferRef pixelBuffer = [self getBufferForDistantViewFromFrame:frame]) {
645 [view renderWithPixelBuffer: pixelBuffer
646 size: frameSize
647 rotation: rotation
648 fillFrame: false];
649 }
Kateryna Kostiukbcf24e92018-11-20 10:09:06 -0500650 }
Anthony Léonard14e7bf32017-06-08 08:13:16 -0400651}
652
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400653-(CVPixelBufferRef) getBufferForPreviewFromFrame:(const AVFrame*)frame {
Kateryna Kostiukc5fd3b92019-07-24 10:12:44 -0400654 [VideoCommon fillPixelBuffr:&pixelBufferPreview fromFrame:frame bufferPool:&pixelBufferPoolPreview];
655 CVPixelBufferRef buffer = pixelBufferPreview;
656 return buffer;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400657}
658
659-(CVPixelBufferRef) getBufferForDistantViewFromFrame:(const AVFrame*)frame {
Kateryna Kostiukc5fd3b92019-07-24 10:12:44 -0400660 [VideoCommon fillPixelBuffr:&pixelBufferDistantView fromFrame:frame bufferPool:&pixelBufferPoolDistantView];
661 CVPixelBufferRef buffer = pixelBufferDistantView;
662 return buffer;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400663}
664
Alexandre Lisionc5148052015-03-04 15:10:35 -0500665- (void) initFrame
666{
667 [self.view setFrame:self.view.superview.bounds];
668 [self.view setHidden:YES];
669 self.view.layer.position = self.view.frame.origin;
Alexandre Lisionbb5dbcd2015-07-09 16:36:47 -0400670 [self collapseRightView];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500671}
672
673# pragma private IN/OUT animations
674
Olivier Soldano994971f2017-12-05 16:30:12 -0500675-(void)uncollapseRightView
676{
677 NSView *left = [[splitView subviews] objectAtIndex:0];
678 NSView *right = [[splitView subviews] objectAtIndex:1];
679 [right setHidden:NO];
680
681 CGFloat dividerThickness = [splitView dividerThickness];
682
683 // get the different frames
684 NSRect leftFrame = [left frame];
685 NSRect rightFrame = [right frame];
686
687 leftFrame.size.width = (leftFrame.size.width - rightFrame.size.width - dividerThickness);
688 rightFrame.origin.x = leftFrame.size.width + dividerThickness;
689 [left setFrameSize:leftFrame.size];
690 [right setFrame:rightFrame];
691 [splitView display];
692
693 [self.chatVC takeFocus];
694}
695
Alexandre Lisionc5148052015-03-04 15:10:35 -0500696-(void) cleanUp
697{
Anthony Léonard5c287642018-02-21 15:54:19 -0500698 if(self.splitView.isInFullScreenMode)
699 [self.splitView exitFullScreenModeWithOptions:nil];
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400700 QObject::disconnect(renderConnections.frameUpdated);
701 QObject::disconnect(renderConnections.started);
702 QObject::disconnect(renderConnections.stopped);
Anthony Léonardc0013bb2018-02-20 16:23:02 -0500703 QObject::disconnect(self.messageConnection);
Alexandre Lision883719f2015-10-22 17:37:45 -0400704
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500705 [self.chatButton setHidden:YES];
Alexandre Lision4baba4c2016-02-11 13:00:57 -0500706 [self.chatButton setPressed:NO];
Alexandre Lision883719f2015-10-22 17:37:45 -0400707 [self collapseRightView];
Alexandre Lision57227772016-01-15 17:19:36 -0500708
Alexandre Lision57227772016-01-15 17:19:36 -0500709 [timeSpentLabel setStringValue:@""];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400710 //audio view
711 [audioCallTime setStringValue:@""];
712 [audioCallPersonId setStringValue:@""];
713 [audioCallPersonLabel setStringValue:@""];
714 [audioCallView setHidden:YES];
715 [audioCallPhoto setImage:nil];
716 //background view
717 [bluerBackgroundEffect setHidden:NO];
718 [backgroundImage setHidden:NO];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400719 //outgoing view
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400720 [outgoingPersonLabel setStringValue:@""];
721 [outgoingStateLabel setStringValue:@""];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400722 [self.previewView setHidden:YES];
723 [self.videoMTKView setHidden:YES];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500724}
725
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400726-(void) setupCallView
727{
728 if (accountInfo_ == nil)
729 return;
730
731 auto* callModel = accountInfo_->callModel.get();
732 auto* convModel = accountInfo_->conversationModel.get();
733
734 // when call comes in we want to show the controls/header
Kateryna Kostiuk1004caa2018-03-29 13:48:07 -0400735 [self mouseIsMoving: YES];
736 [videoView setShouldAcceptInteractions: NO];
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400737
738 [self connectVideoSignals];
739 /* check if text media is already present */
740 if(not callModel->hasCall(callUid_))
741 return;
742
743 [loadingIndicator setAnimates:YES];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400744 [self updateCall];
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400745
746 /* monitor media for messaging text messaging */
747 QObject::disconnect(self.messageConnection);
748 self.messageConnection = QObject::connect(convModel,
749 &lrc::api::ConversationModel::interactionStatusUpdated,
750 [self] (std::string convUid,
751 uint64_t msgId,
752 lrc::api::interaction::Info msg) {
753 if (msg.type == lrc::api::interaction::Type::TEXT) {
754 if(not [[self splitView] isSubviewCollapsed:[[[self splitView] subviews] objectAtIndex: 1]]){
755 return;
756 }
757 [self uncollapseRightView];
758 }
759 });
Kateryna Kostiuk133364b2018-10-31 11:36:08 -0400760 //monitor for updated profile
761 QObject::disconnect(self.profileUpdatedConnection);
762 self.profileUpdatedConnection =
763 QObject::connect(accountInfo_->contactModel.get(),
764 &lrc::api::ContactModel::contactAdded,
765 [self](const std::string &contactUri) {
766 auto convIt = getConversationFromUid(convUid_, *accountInfo_->conversationModel.get());
767 if (convIt == accountInfo_->conversationModel->allFilteredConversations().end()) {
768 return;
769 }
770 if (convIt->participants.empty()) {
771 return;
772
773 }
774 auto& contact = accountInfo_->contactModel->getContact(convIt->participants[0]);
775 if (contact.profileInfo.type == lrc::api::profile::Type::RING && contact.profileInfo.uri == contactUri)
776 accountInfo_->conversationModel->makePermanent(convUid_);
777 [incomingPersonPhoto setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
778 [outgoingPhoto setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
Kateryna Kostiuk732b4062018-11-28 09:59:28 -0500779 [self.delegate conversationInfoUpdatedFor:convUid_];
Kateryna Kostiuk133364b2018-10-31 11:36:08 -0400780 if(accountInfo_->callModel.get()->getCall(callUid_).isAudioOnly) {
781 [audioCallPhoto setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
782 [self setBackground];
783 return;
784 }
Kateryna Kostiuk133364b2018-10-31 11:36:08 -0400785 });
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400786}
787
788-(void) showWithAnimation:(BOOL)animate
789{
790 if (!animate) {
791 [self.view setHidden:NO];
792 [self setupCallView];
793 return;
794 }
795
796 CGRect frame = CGRectOffset(self.view.superview.bounds, -self.view.superview.bounds.size.width, 0);
797 [self.view setHidden:NO];
798
799 [CATransaction begin];
800 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
801 [animation setFromValue:[NSValue valueWithPoint:frame.origin]];
802 [animation setToValue:[NSValue valueWithPoint:self.view.superview.bounds.origin]];
803 [animation setDuration:0.2f];
804 [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]];
805 [CATransaction setCompletionBlock:^{
806 [self setupCallView];
807 }];
808
809 [self.view.layer addAnimation:animation forKey:animation.keyPath];
810 [CATransaction commit];
811}
812
813-(void) hideWithAnimation:(BOOL)animate
Alexandre Lisionc5148052015-03-04 15:10:35 -0500814{
Alexandre Lisionc5148052015-03-04 15:10:35 -0500815 if(self.view.frame.origin.x < 0) {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500816 return;
817 }
818
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400819 if (!animate) {
820 [self.view setHidden:YES];
821 return;
822 }
823
Alexandre Lisionc5148052015-03-04 15:10:35 -0500824 CGRect frame = CGRectOffset(self.view.frame, -self.view.frame.size.width, 0);
825 [CATransaction begin];
826 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
827 [animation setFromValue:[NSValue valueWithPoint:self.view.frame.origin]];
828 [animation setToValue:[NSValue valueWithPoint:frame.origin]];
829 [animation setDuration:0.2f];
Alexandre Lision22d615b2016-01-25 12:54:34 -0500830 [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500831
832 [CATransaction setCompletionBlock:^{
833 [self.view setHidden:YES];
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400834 // first make sure everything is disconnected
Alexandre Lisionc5148052015-03-04 15:10:35 -0500835 [self cleanUp];
Olivier Soldano994971f2017-12-05 16:30:12 -0500836// if (currentCall_) {
837// [self animateIn];
838// }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500839 }];
840 [self.view.layer addAnimation:animation forKey:animation.keyPath];
Alexandre Lisiona1c6d752015-06-23 12:27:38 -0400841
842 [self.view.layer setPosition:frame.origin];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500843 [CATransaction commit];
844}
845
846/**
847 * Debug purpose
848 */
849-(void) dumpFrame:(CGRect) frame WithName:(NSString*) name
850{
851 NSLog(@"frame %@ : %f %f %f %f \n\n",name ,frame.origin.x, frame.origin.y, frame.size.width, frame.size.height);
852}
853
Alexandre Lision58cab672015-06-09 15:25:40 -0400854#pragma mark - Button methods
855
Alexandre Lisionc5148052015-03-04 15:10:35 -0500856- (IBAction)hangUp:(id)sender {
Olivier Soldano994971f2017-12-05 16:30:12 -0500857 if (accountInfo_ == nil)
858 return;
859
860 auto* callModel = accountInfo_->callModel.get();
Olivier Soldano994971f2017-12-05 16:30:12 -0500861 callModel->hangUp(callUid_);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500862}
863
864- (IBAction)accept:(id)sender {
Olivier Soldano994971f2017-12-05 16:30:12 -0500865 if (accountInfo_ == nil)
866 return;
867
Anthony Léonardf584cc52018-01-19 12:00:19 -0500868 // If we accept a conversation with a non trusted contact, we first accept it
869 auto convIt = getConversationFromUid(convUid_, *accountInfo_->conversationModel.get());
870 if (convIt != accountInfo_->conversationModel->allFilteredConversations().end()) {
871 auto& contact = accountInfo_->contactModel->getContact(convIt->participants[0]);
872 if (contact.profileInfo.type == lrc::api::profile::Type::PENDING)
873 accountInfo_->conversationModel->makePermanent(convUid_);
874 }
875
Olivier Soldano994971f2017-12-05 16:30:12 -0500876 auto* callModel = accountInfo_->callModel.get();
877
878 callModel->accept(callUid_);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500879}
880
881- (IBAction)toggleRecording:(id)sender {
Olivier Soldano994971f2017-12-05 16:30:12 -0500882 if (accountInfo_ == nil)
883 return;
884
885 auto* callModel = accountInfo_->callModel.get();
886
887 callModel->toggleAudioRecord(callUid_);
Kateryna Kostiuk88a6ee82019-09-10 17:55:08 -0400888 if (callModel->isRecording(callUid_)) {
889 [recordOnOffButton startBlinkAnimationfrom:[NSColor buttonBlinkColorColor] to:[NSColor whiteColor] scaleFactor: 1 duration: 1.5];
890 } else {
891 [recordOnOffButton stopBlinkAnimation];
892 }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500893}
894
895- (IBAction)toggleHold:(id)sender {
Olivier Soldano994971f2017-12-05 16:30:12 -0500896 if (accountInfo_ == nil)
897 return;
898
899 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiuk54d76972018-06-07 15:45:07 -0400900 auto currentCall = callModel->getCall(callUid_);
Olivier Soldano994971f2017-12-05 16:30:12 -0500901
902 callModel->togglePause(callUid_);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500903}
904
Alexandre Lision62005312016-01-28 15:55:16 -0500905- (IBAction)toggleAdvancedControls:(id)sender {
906 [advancedButton setPressed:!advancedButton.isPressed];
907 [advancedPanel setHidden:![advancedButton isPressed]];
908}
909
910- (IBAction)showDialpad:(id)sender {
911 AppDelegate* appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
912 [appDelegate showDialpad];
913}
914
Alexandre Lision58cab672015-06-09 15:25:40 -0400915-(IBAction)toggleChat:(id)sender;
916{
917 BOOL rightViewCollapsed = [[self splitView] isSubviewCollapsed:[[[self splitView] subviews] objectAtIndex: 1]];
918 if (rightViewCollapsed) {
919 [self uncollapseRightView];
Alexandre Lision58cab672015-06-09 15:25:40 -0400920 } else {
921 [self collapseRightView];
922 }
Alexandre Lision62005312016-01-28 15:55:16 -0500923 [chatButton setPressed:rightViewCollapsed];
Alexandre Lision58cab672015-06-09 15:25:40 -0400924}
925
Alexandre Lisiond18fa272015-06-15 11:18:03 -0400926- (IBAction)muteAudio:(id)sender
927{
Olivier Soldano994971f2017-12-05 16:30:12 -0500928 if (accountInfo_ == nil)
929 return;
930
931 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiuk54d76972018-06-07 15:45:07 -0400932 auto currentCall = callModel->getCall(callUid_);
933 if (currentCall.audioMuted) {
934 muteAudioButton.image = [NSImage imageNamed:@"ic_action_audio.png"];
935 } else {
936 muteAudioButton.image = [NSImage imageNamed:@"ic_action_mute_audio.png"];
937 }
Olivier Soldano994971f2017-12-05 16:30:12 -0500938 callModel->toggleMedia(callUid_, lrc::api::NewCallModel::Media::AUDIO);
Alexandre Lisiond18fa272015-06-15 11:18:03 -0400939}
940
941- (IBAction)muteVideo:(id)sender
942{
Olivier Soldano994971f2017-12-05 16:30:12 -0500943 if (accountInfo_ == nil)
944 return;
Olivier Soldano994971f2017-12-05 16:30:12 -0500945 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiuk54d76972018-06-07 15:45:07 -0400946 auto currentCall = callModel->getCall(callUid_);
947 if(!currentCall.isAudioOnly) {
948 if (currentCall.videoMuted) {
949 muteVideoButton.image = [NSImage imageNamed:@"ic_action_video.png"];
950 } else {
951 muteVideoButton.image = [NSImage imageNamed:@"ic_action_mute_video.png"];
952 }
953 }
Olivier Soldano994971f2017-12-05 16:30:12 -0500954 callModel->toggleMedia(callUid_, lrc::api::NewCallModel::Media::VIDEO);
Alexandre Lisiond18fa272015-06-15 11:18:03 -0400955}
Alexandre Lision883719f2015-10-22 17:37:45 -0400956
957- (IBAction)toggleTransferView:(id)sender {
Kateryna Kostiuk1ba5a012018-12-12 17:07:38 -0500958
Alexandre Lisionf23ec5a2015-07-16 11:24:06 -0400959}
Alexandre Lisiond18fa272015-06-15 11:18:03 -0400960
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500961- (IBAction)toggleAddParticipantView:(id)sender {
Kateryna Kostiuk1ba5a012018-12-12 17:07:38 -0500962
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500963}
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -0400964- (IBAction)hidePreview:(id)sender {
965 CGRect previewFrame = previewView.frame;
966 CGRect newPreviewFrame, bcHidePreviewFrame;
967 if (previewFrame.size.width > HIDE_PREVIEW_BUTTON_MAX_SIZE) {
968 self.movableBaseForView.movable = false;
969 newPreviewFrame = self.getVideoPreviewCollapsedSize;
970 bcHidePreviewFrame = [self frameForExpendPreviewButton: true];
971 hidePreviewButton.image = [NSImage imageNamed: NSImageNameTouchBarEnterFullScreenTemplate];
972 } else {
973 self.movableBaseForView.movable = true;
974 newPreviewFrame = CGRectMake(0, 0, PREVIEW_WIDTH, PREVIEW_HEIGHT);
975 bcHidePreviewFrame = [self frameForExpendPreviewButton: false];
976 hidePreviewButton.image = [NSImage imageNamed: NSImageNameTouchBarExitFullScreenTemplate];
977 }
978 [NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull context) {
979 context.duration = 0.2f;
980 context.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut];
981 previewView.animator.frame = newPreviewFrame;
982 } completionHandler: nil];
983 hidePreviewBackground.frame = bcHidePreviewFrame;
984}
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500985
Alexandre Lision2db8f472015-07-22 15:05:46 -0400986#pragma mark - NSPopOverDelegate
987
Alexandre Lision266fca02015-09-28 14:47:05 -0400988- (void)popoverWillClose:(NSNotification *)notification
989{
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500990 if (_brokerPopoverVC != nullptr) {
991 [_brokerPopoverVC performClose:self];
992 _brokerPopoverVC = NULL;
Alexandre Lision883719f2015-10-22 17:37:45 -0400993 }
Alexandre Lision266fca02015-09-28 14:47:05 -0400994
Alexandre Lision2db8f472015-07-22 15:05:46 -0400995 if (self.addToContactPopover != nullptr) {
996 [self.addToContactPopover performClose:self];
997 self.addToContactPopover = NULL;
998 }
Alexandre Lision883719f2015-10-22 17:37:45 -0400999
Alexandre Lision62005312016-01-28 15:55:16 -05001000 [self.transferButton setPressed:NO];
Alexandre Lision89edc6a2015-11-09 11:30:47 -05001001 [self.addParticipantButton setState:NSOffState];
Alexandre Lision883719f2015-10-22 17:37:45 -04001002}
1003
1004- (void)popoverDidClose:(NSNotification *)notification
1005{
1006 [videoView setCallDelegate:self];
Alexandre Lision2db8f472015-07-22 15:05:46 -04001007}
1008
1009#pragma mark - ContactLinkedDelegate
1010
1011- (void)contactLinked
1012{
1013 if (self.addToContactPopover != nullptr) {
1014 [self.addToContactPopover performClose:self];
1015 self.addToContactPopover = NULL;
1016 }
1017}
1018
Alexandre Lision58cab672015-06-09 15:25:40 -04001019#pragma mark - NSSplitViewDelegate
1020
1021/* 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.
1022 */
1023- (BOOL)splitView:(NSSplitView *)splitView shouldCollapseSubview:(NSView *)subview forDoubleClickOnDividerAtIndex:(NSInteger)dividerIndex;
1024{
1025 NSView* rightView = [[splitView subviews] objectAtIndex:1];
1026 return ([subview isEqual:rightView]);
1027}
1028
1029
1030- (BOOL)splitView:(NSSplitView *)splitView canCollapseSubview:(NSView *)subview;
1031{
1032 NSView* rightView = [[splitView subviews] objectAtIndex:1];
1033 return ([subview isEqual:rightView]);
1034}
1035
1036
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -04001037# pragma mark - CallnDelegate
Alexandre Lision58cab672015-06-09 15:25:40 -04001038
1039- (void) callShouldToggleFullScreen
1040{
1041 if(self.splitView.isInFullScreenMode)
1042 [self.splitView exitFullScreenModeWithOptions:nil];
1043 else {
1044 NSApplicationPresentationOptions options = NSApplicationPresentationDefault +NSApplicationPresentationAutoHideDock +
1045 NSApplicationPresentationAutoHideMenuBar + NSApplicationPresentationAutoHideToolbar;
1046 NSDictionary *opts = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInt:options],
1047 NSFullScreenModeApplicationPresentationOptions, nil];
1048
1049 [self.splitView enterFullScreenMode:[NSScreen mainScreen] withOptions:opts];
1050 }
1051}
1052
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -04001053-(void) mouseIsMoving:(BOOL) move
1054{
1055 [[controlsPanel animator] setAlphaValue:move]; // fade out
1056 [[headerContainer animator] setAlphaValue:move];
1057}
1058
Kateryna Kostiuk89398b62018-11-20 16:45:04 -05001059- (BOOL)splitView:(NSSplitView *)splitView shouldHideDividerAtIndex:(NSInteger)dividerIndex
1060{
1061 return YES;
1062}
1063
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -04001064-(void) screenShare {
1065 NSScreen *mainScreen = [NSScreen mainScreen];
1066 NSRect screenFrame = mainScreen.frame;
1067 QRect captureRect = QRect(screenFrame.origin.x, screenFrame.origin.y, screenFrame.size.width, screenFrame.size.height);
1068 mediaModel->setDisplay(0, screenFrame.origin.x, screenFrame.origin.y, screenFrame.size.width, screenFrame.size.height);
1069}
1070-(void) switchToDevice:(int)deviceID {
1071 auto devices = mediaModel->getDevices();
1072 auto device = devices[deviceID];
1073 mediaModel->switchInputTo(device);
1074}
1075
1076-(std::vector<std::string>) getDeviceList {
1077 return mediaModel->getDevices();
1078}
1079
1080-(void) switchToFile:(std::string)uri {
1081 mediaModel->setInputFile(QUrl::fromLocalFile(uri.c_str()).toLocalFile().toStdString());
1082}
1083
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -04001084-(CGRect) getVideoPreviewCollapsedSize {
1085 CGPoint origin;
1086 switch (movableBaseForView.closestCorner) {
1087 case TOP_LEFT:
1088 origin = CGPointMake(0, movableBaseForView.frame.size.height - HIDE_PREVIEW_BUTTON_MAX_SIZE);
1089 break;
1090 case BOTTOM_LEFT:
1091 origin = CGPointMake(0, 0);
1092 break;
1093 case TOP_RIGHT:
1094 origin = CGPointMake(movableBaseForView.frame.size.width - HIDE_PREVIEW_BUTTON_MAX_SIZE, movableBaseForView.frame.size.height - HIDE_PREVIEW_BUTTON_MAX_SIZE);
1095 break;
1096 case BOTTOM_RIGHT:
1097 origin = CGPointMake(movableBaseForView.frame.size.width - HIDE_PREVIEW_BUTTON_MAX_SIZE, 0);
1098 break;
1099 }
1100 return CGRectMake(origin.x, origin.y, HIDE_PREVIEW_BUTTON_MAX_SIZE, HIDE_PREVIEW_BUTTON_MAX_SIZE);
1101}
1102
1103-(CGRect) frameForExpendPreviewButton:(BOOL)collapsed {
1104 CGFloat size = collapsed ? HIDE_PREVIEW_BUTTON_MAX_SIZE : HIDE_PREVIEW_BUTTON_MIN_SIZE;
1105 CGPoint origin = CGPointMake(self.previewView.frame.size.width - size,
1106 self.previewView.frame.size.height - size);
1107 return CGRectMake(origin.x, origin.y, size, size);
1108}
1109
Alexandre Lisionc5148052015-03-04 15:10:35 -05001110@end