blob: 9313a460872e502207b1dbb3cb87bf7bed4bea08 [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"
Alexandre Lision74dd47f2015-04-14 13:47:42 -040051
Alexandre Lisionc5148052015-03-04 15:10:35 -050052@interface RendererConnectionsHolder : NSObject
53
54@property QMetaObject::Connection frameUpdated;
55@property QMetaObject::Connection started;
56@property QMetaObject::Connection stopped;
57
58@end
59
60@implementation RendererConnectionsHolder
61
62@end
63
Kateryna Kostiuk1ba5a012018-12-12 17:07:38 -050064@interface CurrentCallVC () <NSPopoverDelegate> {
Olivier Soldano994971f2017-12-05 16:30:12 -050065 std::string convUid_;
66 std::string callUid_;
67 const lrc::api::account::Info *accountInfo_;
Anthony Léonard66a050c2018-01-03 16:32:34 -050068 NSTimer* refreshDurationTimer;
Olivier Soldano994971f2017-12-05 16:30:12 -050069}
Alexandre Lisionc5148052015-03-04 15:10:35 -050070
Alexandre Lision210fe212016-01-27 11:15:13 -050071// Main container
72@property (unsafe_unretained) IBOutlet NSSplitView* splitView;
Kateryna Kostiuk89398b62018-11-20 16:45:04 -050073@property (unsafe_unretained) IBOutlet NSView* backgroundImage;
Kateryna Kostiuk81afce72018-10-30 16:40:50 -040074@property (unsafe_unretained) IBOutlet NSBox* bluerBackgroundEffect;
Alexandre Lision210fe212016-01-27 11:15:13 -050075
Alexandre Lision89edc6a2015-11-09 11:30:47 -050076// Header info
77@property (unsafe_unretained) IBOutlet NSView* headerContainer;
Alexandre Lision883719f2015-10-22 17:37:45 -040078@property (unsafe_unretained) IBOutlet NSTextField* personLabel;
79@property (unsafe_unretained) IBOutlet NSTextField* stateLabel;
Alexandre Lision89edc6a2015-11-09 11:30:47 -050080@property (unsafe_unretained) IBOutlet NSTextField* timeSpentLabel;
Alexandre Lision210fe212016-01-27 11:15:13 -050081@property (unsafe_unretained) IBOutlet NSImageView* personPhoto;
Alexandre Lision89edc6a2015-11-09 11:30:47 -050082
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
89@property (unsafe_unretained) IBOutlet NSView* 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;
129
130@property (unsafe_unretained) IBOutlet CallMTKView *videoMTKView;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500131
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400132@property RendererConnectionsHolder* renderConnections;
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400133@property QMetaObject::Connection videoStarted;
Alexandre Lision4d9905e2016-01-22 12:01:36 -0500134@property QMetaObject::Connection selectedCallChanged;
Alexandre Lisionb65c0272015-07-22 15:51:29 -0400135@property QMetaObject::Connection messageConnection;
136@property QMetaObject::Connection mediaAddedConnection;
Kateryna Kostiuk133364b2018-10-31 11:36:08 -0400137@property QMetaObject::Connection profileUpdatedConnection;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400138@property NSImageView *testView;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500139
140@end
141
142@implementation CurrentCallVC
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400143lrc::api::AVModel* mediaModel;
Olivier Soldano994971f2017-12-05 16:30:12 -0500144@synthesize personLabel, personPhoto, stateLabel, holdOnOffButton, hangUpButton,
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500145 recordOnOffButton, pickUpButton, chatButton, transferButton, addParticipantButton, timeSpentLabel,
Olivier Soldano994971f2017-12-05 16:30:12 -0500146 muteVideoButton, muteAudioButton, controlsPanel, advancedPanel, advancedButton, headerContainer, videoView,
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400147 incomingDisplayName, incomingPersonPhoto, previewView, splitView, loadingIndicator, ringingPanel,
148 outgoingPanel, outgoingPersonLabel, outgoingStateLabel, outgoingPhoto, outgoingId,
149 callRecordButtonMarginLeft, audioCallView, audioCallPhoto, audioCallTime, audioCallPersonLabel, audioCallPersonId, backgroundImage, bluerBackgroundEffect;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500150
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400151@synthesize renderConnections;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400152CVPixelBufferPoolRef pixelBufferPoolDistantView;
153CVPixelBufferRef pixelBufferDistantView;
154CVPixelBufferPoolRef pixelBufferPoolPreview;
155CVPixelBufferRef pixelBufferPreview;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500156
Olivier Soldano994971f2017-12-05 16:30:12 -0500157-(void) setCurrentCall:(const std::string&)callUid
158 conversation:(const std::string&)convUid
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400159 account:(const lrc::api::account::Info*)account
160 avModel:(lrc::api::AVModel *)avModel;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500161{
Olivier Soldano994971f2017-12-05 16:30:12 -0500162 if(account == nil)
163 return;
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400164
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400165 mediaModel = avModel;
Olivier Soldano994971f2017-12-05 16:30:12 -0500166 auto* callModel = account->callModel.get();
Alexandre Lisionc5148052015-03-04 15:10:35 -0500167
Olivier Soldano994971f2017-12-05 16:30:12 -0500168 if (not callModel->hasCall(callUid)){
169 callUid_.clear();
Alexandre Lision2db8f472015-07-22 15:05:46 -0400170 return;
171 }
Olivier Soldano994971f2017-12-05 16:30:12 -0500172 callUid_ = callUid;
173 convUid_ = convUid;
174 accountInfo_ = account;
Anthony Léonard4b0935f2017-12-26 12:05:22 -0500175 [self.chatVC setConversationUid:convUid model:account->conversationModel.get()];
Kateryna Kostiuk54d76972018-06-07 15:45:07 -0400176 auto currentCall = callModel->getCall(callUid_);
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400177 [muteVideoButton setHidden: currentCall.isAudioOnly ? YES: NO];
178 callRecordButtonMarginLeft.constant = currentCall.isAudioOnly ? -40.0f: 10.0f;
179 [previewView setHidden: YES];
Kateryna Kostiukd76f6202018-09-11 17:15:28 -0400180 videoView.callId = callUid;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500181}
182
183- (void)awakeFromNib
184{
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400185 [self.view setWantsLayer:YES];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500186 [controlsPanel setWantsLayer:YES];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500187 [controlsPanel.layer setBackgroundColor:[NSColor clearColor].CGColor];
188 [controlsPanel.layer setFrame:controlsPanel.frame];
189
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400190 renderConnections = [[RendererConnectionsHolder alloc] init];
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400191
Alexandre Lisionf47a2562015-06-15 15:48:29 -0400192 [loadingIndicator setColor:[NSColor whiteColor]];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400193 [loadingIndicator setNumberOfLines:200];
194 [loadingIndicator setWidthOfLine:4];
195 [loadingIndicator setLengthOfLine:4];
196 [loadingIndicator setInnerMargin:59];
Alexandre Lisionf47a2562015-06-15 15:48:29 -0400197
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -0400198 [self.videoView setCallDelegate:self];
Kateryna Kostiuk1004caa2018-03-29 13:48:07 -0400199 CGColor* color = [[[NSColor blackColor] colorWithAlphaComponent:0.2] CGColor];
200 [headerContainer.layer setBackgroundColor:color];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400201 [bluerBackgroundEffect setWantsLayer:YES];
202 bluerBackgroundEffect.alphaValue = 0.6;
203 [audioCallView setWantsLayer:YES];
204 [audioCallView.layer setBackgroundColor: [[NSColor clearColor] CGColor]];
Kateryna Kostiuk89398b62018-11-20 16:45:04 -0500205 [backgroundImage setWantsLayer: YES];
206 backgroundImage.layer.contentsGravity = kCAGravityResizeAspectFill;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500207}
208
Anthony Léonard66a050c2018-01-03 16:32:34 -0500209-(void) updateDurationLabel
210{
211 if (accountInfo_ != nil) {
212 auto* callModel = accountInfo_->callModel.get();
213 if (callModel->hasCall(callUid_)) {
214 auto& callStatus = callModel->getCall(callUid_).status;
215 if (callStatus != lrc::api::call::Status::ENDED &&
216 callStatus != lrc::api::call::Status::TERMINATING &&
217 callStatus != lrc::api::call::Status::INVALID) {
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400218 if(callModel->getCall(callUid_).isAudioOnly) {
219 [audioCallTime setStringValue:@(callModel->getFormattedCallDuration(callUid_).c_str())];
220 } else {
221 [timeSpentLabel setStringValue:@(callModel->getFormattedCallDuration(callUid_).c_str())];
222 }
Anthony Léonard66a050c2018-01-03 16:32:34 -0500223 return;
224 }
225 }
226 }
227
228 // If call is not running anymore or accountInfo_ is not set for any reason
229 // we stop the refresh loop
230 [refreshDurationTimer invalidate];
231 refreshDurationTimer = nil;
232 [timeSpentLabel setHidden:YES];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400233 [audioCallView setHidden:YES];
Anthony Léonard66a050c2018-01-03 16:32:34 -0500234}
235
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400236-(void) updateCall
Alexandre Lisionc5148052015-03-04 15:10:35 -0500237{
Olivier Soldano994971f2017-12-05 16:30:12 -0500238 if (accountInfo_ == nil)
239 return;
Alexandre Lisionbb5dbcd2015-07-09 16:36:47 -0400240
Olivier Soldano994971f2017-12-05 16:30:12 -0500241 auto* callModel = accountInfo_->callModel.get();
242 if (not callModel->hasCall(callUid_)) {
243 return;
244 }
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500245
Olivier Soldano994971f2017-12-05 16:30:12 -0500246 auto currentCall = callModel->getCall(callUid_);
247 NSLog(@"\n status %@ \n",@(lrc::api::call::to_string(currentCall.status).c_str()));
Anthony Léonard8ee26782018-01-05 10:23:04 -0500248 auto convIt = getConversationFromUid(convUid_, *accountInfo_->conversationModel);
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400249 if (convIt != accountInfo_->conversationModel->allFilteredConversations().end()) {
250 NSString* bestName = bestNameForConversation(*convIt, *accountInfo_->conversationModel);
251 [personLabel setStringValue:bestName];
252 [outgoingPersonLabel setStringValue:bestName];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400253 [audioCallPersonLabel setStringValue:bestName];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400254 NSString* ringID = bestIDForConversation(*convIt, *accountInfo_->conversationModel);
255 if([bestName isEqualToString:ringID]) {
256 ringID = @"";
257 }
258 [outgoingId setStringValue:ringID];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400259 [audioCallPersonId setStringValue:ringID];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400260 }
261
Anthony Léonard66a050c2018-01-03 16:32:34 -0500262 [timeSpentLabel setStringValue:@(callModel->getFormattedCallDuration(callUid_).c_str())];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400263 [audioCallTime setStringValue:@(callModel->getFormattedCallDuration(callUid_).c_str())];
Anthony Léonard66a050c2018-01-03 16:32:34 -0500264 if (refreshDurationTimer == nil)
265 refreshDurationTimer = [NSTimer scheduledTimerWithTimeInterval:1.0
266 target:self
267 selector:@selector(updateDurationLabel)
268 userInfo:nil
269 repeats:YES];
Olivier Soldano994971f2017-12-05 16:30:12 -0500270 [stateLabel setStringValue:@(to_string(currentCall.status).c_str())];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400271 [outgoingStateLabel setStringValue:@(to_string(currentCall.status).c_str())];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500272
Olivier Soldano994971f2017-12-05 16:30:12 -0500273 // Default values for this views
274 [loadingIndicator setHidden:YES];
Olivier Soldano994971f2017-12-05 16:30:12 -0500275 [ringingPanel setHidden:YES];
276 [outgoingPanel setHidden:YES];
277 [controlsPanel setHidden:NO];
278 [headerContainer setHidden:NO];
Anthony Léonard4b0935f2017-12-26 12:05:22 -0500279 [chatButton setHidden:NO];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400280 [self setBackground];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500281
Olivier Soldano994971f2017-12-05 16:30:12 -0500282 using Status = lrc::api::call::Status;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400283 holdOnOffButton.image = currentCall.status == lrc::api::call::Status::PAUSED ?
284 [NSImage imageNamed:@"ic_action_holdoff.png"] : [NSImage imageNamed:@"ic_action_hold.png"];
Olivier Soldano994971f2017-12-05 16:30:12 -0500285 switch (currentCall.status) {
286 case Status::SEARCHING:
Olivier Soldano994971f2017-12-05 16:30:12 -0500287 case Status::CONNECTING:
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400288 [headerContainer setHidden:YES];
289 [outgoingPanel setHidden:NO];
290 [outgoingPhoto setHidden:NO];
291 [self setupContactInfo:outgoingPhoto];
Olivier Soldano994971f2017-12-05 16:30:12 -0500292 [loadingIndicator setHidden:NO];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400293 [controlsPanel setHidden:YES];
Olivier Soldano994971f2017-12-05 16:30:12 -0500294 break;
295 case Status::INCOMING_RINGING:
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400296 [outgoingPhoto setHidden:YES];
Olivier Soldano994971f2017-12-05 16:30:12 -0500297 [controlsPanel setHidden:YES];
298 [outgoingPanel setHidden:YES];
299 [self setupIncoming:callUid_];
300 break;
301 case Status::OUTGOING_RINGING:
302 [controlsPanel setHidden:YES];
303 [outgoingPanel setHidden:NO];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400304 [loadingIndicator setHidden:NO];
305 [headerContainer setHidden:YES];
Olivier Soldano994971f2017-12-05 16:30:12 -0500306 break;
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400307 /*case Status::CONFERENCE:
308 [self setupConference:currentCall];
309 break;*/
Olivier Soldano994971f2017-12-05 16:30:12 -0500310 case Status::PAUSED:
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400311 [self.videoMTKView fillWithBlack];
312 [self.previewView fillWithBlack];
313 [bluerBackgroundEffect setHidden:NO];
314 [backgroundImage setHidden:NO];
315 [self.previewView setHidden: YES];
316 [self.videoMTKView setHidden: YES];
317 self.previewView.stopRendering = true;
318 self.videoMTKView.stopRendering = true;
319 break;
Olivier Soldano994971f2017-12-05 16:30:12 -0500320 case Status::INACTIVE:
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400321 if(currentCall.isAudioOnly) {
322 [self setUpAudioOnlyView];
323 } else {
324 [self setUpVideoCallView];
325 }
326 break;
Olivier Soldano994971f2017-12-05 16:30:12 -0500327 case Status::IN_PROGRESS:
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400328 self.previewView.stopRendering = false;
329 self.videoMTKView.stopRendering = false;
330 [previewView fillWithBlack];
331 [self.videoMTKView fillWithBlack];
332 [self.previewView setHidden: NO];
333 [self.videoMTKView setHidden: NO];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400334 if(currentCall.isAudioOnly) {
335 [self setUpAudioOnlyView];
336 } else {
337 [self setUpVideoCallView];
338 }
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400339 break;
Olivier Soldano994971f2017-12-05 16:30:12 -0500340 case Status::CONNECTED:
Olivier Soldano994971f2017-12-05 16:30:12 -0500341 break;
342 case Status::ENDED:
343 case Status::TERMINATING:
344 case Status::INVALID:
Olivier Soldano994971f2017-12-05 16:30:12 -0500345 break;
346 }
Alexandre Lision4d9905e2016-01-22 12:01:36 -0500347}
348
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400349-(void) setUpVideoCallView {
350 [self setupContactInfo:personPhoto];
351 [timeSpentLabel setHidden:NO];
352 [outgoingPhoto setHidden:YES];
353 [headerContainer setHidden:NO];
354 [previewView setHidden: NO];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400355 [self.videoMTKView setHidden:NO];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400356 [bluerBackgroundEffect setHidden:YES];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400357 [backgroundImage setHidden:YES];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400358 [audioCallView setHidden:YES];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400359}
360
361-(void) setUpAudioOnlyView {
362 [audioCallView setHidden:NO];
363 [headerContainer setHidden:YES];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400364 [self.previewView setHidden: YES];
365 [self.videoMTKView setHidden: YES];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400366 [audioCallPhoto setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
367}
368
369-(void) setBackground {
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400370 auto* convModel = accountInfo_->conversationModel.get();
371 auto it = getConversationFromUid(convUid_, *convModel);
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400372 NSImage *image= [self getContactImageOfSize:120.0 withDefaultAvatar:NO];
373 if(image) {
Kateryna Kostiuk89398b62018-11-20 16:45:04 -0500374 CIImage * ciImage = [[CIImage alloc] initWithData:[image TIFFRepresentation]];
375 CIContext *context = [[CIContext alloc] init];
376 CIFilter *clamp = [CIFilter filterWithName:@"CIAffineClamp"];
377 [clamp setValue:[NSAffineTransform transform] forKey:@"inputTransform"];
378 [clamp setValue:ciImage forKey: kCIInputImageKey];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400379 CIFilter* bluerFilter = [CIFilter filterWithName:@"CIGaussianBlur"];
380 [bluerFilter setDefaults];
Kateryna Kostiuk89398b62018-11-20 16:45:04 -0500381 [bluerFilter setValue:[NSNumber numberWithFloat: 9] forKey:@"inputRadius"];
382 [bluerFilter setValue:[clamp valueForKey:kCIOutputImageKey] forKey: kCIInputImageKey];
383 CIImage *result = [bluerFilter valueForKey:kCIOutputImageKey];
384 CGRect extent = [result extent];
385 CGImageRef cgImage = [context createCGImage:result fromRect: [ciImage extent]];
386 NSImage *bluredImage = [[NSImage alloc] initWithCGImage:cgImage size:NSSizeFromCGSize(CGSizeMake(image.size.width, image.size.height))];
387 backgroundImage.layer.contents = bluredImage;
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400388 [backgroundImage setHidden:NO];
389 } else {
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400390 [bluerBackgroundEffect setFillColor:[NSColor ringDarkGrey]];
391 [backgroundImage setHidden:YES];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400392 }
393}
394
Kateryna Kostiuk133364b2018-10-31 11:36:08 -0400395-(NSImage *) getContactImageOfSize: (double) size withDefaultAvatar:(BOOL) shouldDrawDefault {
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400396 auto* convModel = accountInfo_->conversationModel.get();
397 auto convIt = getConversationFromUid(convUid_, *convModel);
398 if (convIt == convModel->allFilteredConversations().end()) {
399 return nil;
400 }
401 if(shouldDrawDefault) {
402 auto& imgManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
403 QVariant photo = imgManip.conversationPhoto(*convIt, *accountInfo_, QSize(size, size), NO);
404 return QtMac::toNSImage(qvariant_cast<QPixmap>(photo));
405 }
406 auto contact = accountInfo_->contactModel->getContact(convIt->participants[0]);
407 NSData *imageData = [[NSData alloc] initWithBase64EncodedString:@(contact.profileInfo.avatar.c_str()) options:NSDataBase64DecodingIgnoreUnknownCharacters];
408 return [[NSImage alloc] initWithData:imageData];
409}
410
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400411-(void) setupContactInfo:(NSImageView*)imageView
412{
413 [imageView setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
414}
415
Olivier Soldano994971f2017-12-05 16:30:12 -0500416-(void) setupIncoming:(const std::string&) callId
Alexandre Lision4d9905e2016-01-22 12:01:36 -0500417{
Olivier Soldano994971f2017-12-05 16:30:12 -0500418 if (accountInfo_ == nil)
419 return;
420
421 auto* callModel = accountInfo_->callModel.get();
422 if (not callModel->hasCall(callUid_)) {
423 return;
424 }
425
426 auto call = callModel->getCall(callUid_);
427 auto* convModel = accountInfo_->conversationModel.get();
428
429 [ringingPanel setHidden:NO];
430 [controlsPanel setHidden:YES];
431 [headerContainer setHidden:YES];
Anthony Léonard6f819752018-01-05 09:53:40 -0500432 auto it = getConversationFromUid(convUid_, *convModel);
Olivier Soldano994971f2017-12-05 16:30:12 -0500433 if (it != convModel->allFilteredConversations().end()) {
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400434 [incomingPersonPhoto setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
Anthony Léonard8ee26782018-01-05 10:23:04 -0500435 [incomingDisplayName setStringValue:bestNameForConversation(*it, *convModel)];
Olivier Soldano994971f2017-12-05 16:30:12 -0500436 }
437}
438
Olivier Soldano994971f2017-12-05 16:30:12 -0500439-(void) setupConference:(Call*) c
440{
441 [videoView setShouldAcceptInteractions:YES];
442 [self.chatButton setHidden:NO];
Olivier Soldano994971f2017-12-05 16:30:12 -0500443 [self.addParticipantButton setHidden:NO];
444 [self.transferButton setHidden:YES];
445}
446
447-(void)collapseRightView
448{
449 NSView *right = [[splitView subviews] objectAtIndex:1];
450 NSView *left = [[splitView subviews] objectAtIndex:0];
451 NSRect leftFrame = [left frame];
452 [right setHidden:YES];
453 [splitView display];
454}
455
456- (void) changeCallSelection:(std::string&)callUid
457{
458 if (accountInfo_ == nil)
459 return;
460
461 auto* callModel = accountInfo_->callModel.get();
462 if (not callModel->hasCall(callUid)) {
463 return;
464 }
465
Alexandre Lision4d9905e2016-01-22 12:01:36 -0500466 QObject::disconnect(self.selectedCallChanged);
Olivier Soldano994971f2017-12-05 16:30:12 -0500467 self.selectedCallChanged = QObject::connect(callModel,
468 &lrc::api::NewCallModel::callStatusChanged,
469 [self](const std::string callId) {
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400470 [self updateCall];
Alexandre Lision4d9905e2016-01-22 12:01:36 -0500471 });
Alexandre Lisionc5148052015-03-04 15:10:35 -0500472}
473
474-(void) connectVideoSignals
475{
Olivier Soldano994971f2017-12-05 16:30:12 -0500476 if (accountInfo_ == nil)
477 return;
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400478 [self connectRenderer];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500479}
480
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400481-(void) connectRenderer
Alexandre Lisionc5148052015-03-04 15:10:35 -0500482{
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400483 QObject::disconnect(renderConnections.frameUpdated);
484 QObject::disconnect(renderConnections.stopped);
485 QObject::disconnect(renderConnections.started);
486 renderConnections.started =
487 QObject::connect(mediaModel,
488 &lrc::api::AVModel::rendererStarted,
489 [=](const std::string& id) {
490 if (id == lrc::api::video::PREVIEW_RENDERER_ID) {
491 [self.previewView setHidden:NO];
492 self.previewView.stopRendering = false;
493 } else {
494 [self mouseIsMoving: NO];
495 self.videoMTKView.stopRendering = false;
496 [self.videoMTKView setHidden:NO];
497 [bluerBackgroundEffect setHidden:YES];
498 [backgroundImage setHidden:YES];
499 [videoView setShouldAcceptInteractions:YES];
500 }
501 QObject::disconnect(renderConnections.frameUpdated);
502 renderConnections.frameUpdated =
503 QObject::connect(mediaModel,
504 &lrc::api::AVModel::frameUpdated,
505 [=](const std::string& id) {
506 if (id == lrc::api::video::PREVIEW_RENDERER_ID) {
507 auto renderer = &mediaModel->getRenderer(lrc::api::video::PREVIEW_RENDERER_ID);
508 if(!renderer->isRendering()) {
509 return;
510 }
511 [self renderer: renderer renderFrameForPreviewView:previewView];
512
513 } else {
514 auto renderer = &mediaModel->getRenderer(id);
515 if(!renderer->isRendering()) {
516 return;
517 }
518 [self renderer:renderer renderFrameForDistantView: self.videoMTKView];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400519 }
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400520 });
Alexandre Lisionc5148052015-03-04 15:10:35 -0500521 });
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400522 renderConnections.stopped =
523 QObject::connect(mediaModel,
524 &lrc::api::AVModel::rendererStopped,
525 [=](const std::string& id) {
526 QObject::disconnect(renderConnections.frameUpdated);
527 if (id == lrc::api::video::PREVIEW_RENDERER_ID) {
528 [self.previewView setHidden:YES];
529 self.previewView.stopRendering = true;
530 } else {
531 [self mouseIsMoving: YES];
532 self.videoMTKView.stopRendering = true;
533 [self.videoMTKView setHidden:YES];
534 [bluerBackgroundEffect setHidden:NO];
535 [backgroundImage setHidden:NO];
536 [videoView setShouldAcceptInteractions:NO];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400537 }
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400538 });
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 }
548 [self renderer: renderer renderFrameForPreviewView:previewView];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500549
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400550 } else {
551 auto renderer = &mediaModel->getRenderer(id);
552 if(!renderer->isRendering()) {
553 return;
554 }
555 [self renderer:renderer renderFrameForDistantView: self.videoMTKView];
Kateryna Kostiuk732b4062018-11-28 09:59:28 -0500556 }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500557 });
558}
559
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400560-(void) renderer: (const lrc::api::video::Renderer*)renderer renderFrameForPreviewView:(CallMTKView*) view
Alexandre Lisionc5148052015-03-04 15:10:35 -0500561{
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400562 @autoreleasepool {
563 auto framePtr = renderer->currentAVFrame();
564 auto frame = framePtr.get();
565 if(!frame || !frame->width || !frame->height) {
566 return;
567 }
568 auto frameSize = CGSizeMake(frame->width, frame->height);
569 auto rotation = 0;
570 if (frame->data[3] != NULL && (CVPixelBufferRef)frame->data[3]) {
571 [view renderWithPixelBuffer:(CVPixelBufferRef)frame->data[3]
572 size: frameSize
573 rotation: rotation
574 fillFrame: true];
575 return;
576 }
577 else if (CVPixelBufferRef pixelBuffer = [self getBufferForPreviewFromFrame:frame]) {
578 [view renderWithPixelBuffer: pixelBuffer
579 size: frameSize
580 rotation: rotation
581 fillFrame: true];
582 }
583 }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500584}
585
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400586-(void) renderer: (const lrc::api::video::Renderer*)renderer renderFrameForDistantView:(CallMTKView*) view
Anthony Léonard14e7bf32017-06-08 08:13:16 -0400587{
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400588 @autoreleasepool {
589 auto framePtr = renderer->currentAVFrame();
590 auto frame = framePtr.get();
591 if(!frame || !frame->width || !frame->height) {
592 return;
593 }
594 auto frameSize = CGSizeMake(frame->width, frame->height);
595 auto rotation = 0;
596 if (auto matrix = av_frame_get_side_data(frame, AV_FRAME_DATA_DISPLAYMATRIX)) {
597 const int32_t* data = reinterpret_cast<int32_t*>(matrix->data);
598 rotation = av_display_rotation_get(data);
599 }
600 if (frame->data[3] != NULL && (CVPixelBufferRef)frame->data[3]) {
601 [view renderWithPixelBuffer: (CVPixelBufferRef)frame->data[3]
602 size: frameSize
603 rotation: rotation
604 fillFrame: false];
605 }
606 if (CVPixelBufferRef pixelBuffer = [self getBufferForDistantViewFromFrame:frame]) {
607 [view renderWithPixelBuffer: pixelBuffer
608 size: frameSize
609 rotation: rotation
610 fillFrame: false];
611 }
Kateryna Kostiukbcf24e92018-11-20 10:09:06 -0500612 }
Anthony Léonard14e7bf32017-06-08 08:13:16 -0400613}
614
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400615-(CVPixelBufferRef) getBufferForPreviewFromFrame:(const AVFrame*)frame {
616 if(!frame || !frame->data[0] || !frame->data[1]) {
617 return nil;
618 }
619 CVReturn theError;
620 bool createPool = false;
621 if (!pixelBufferPoolPreview) {
622 createPool = true;
623 } else {
624 NSDictionary* atributes = (__bridge NSDictionary*)CVPixelBufferPoolGetAttributes(pixelBufferPoolPreview);
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400625 if(!atributes)
626 atributes = (__bridge NSDictionary*)CVPixelBufferPoolGetPixelBufferAttributes(pixelBufferPoolPreview);
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400627 int width = [[atributes objectForKey:(NSString*)kCVPixelBufferWidthKey] intValue];
628 int height = [[atributes objectForKey:(NSString*)kCVPixelBufferHeightKey] intValue];
629 if (width != frame->width || height != frame->height) {
630 createPool = true;
631 }
632 }
633 if (createPool) {
634 CVPixelBufferPoolRelease(pixelBufferPoolPreview);
635 CVPixelBufferRelease(pixelBufferPreview);
636 pixelBufferPreview = nil;
637 pixelBufferPoolPreview = nil;
638 NSMutableDictionary* attributes = [NSMutableDictionary dictionary];
639 [attributes setObject:[NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange] forKey:(NSString*)kCVPixelBufferPixelFormatTypeKey];
640 [attributes setObject:[NSNumber numberWithInt:frame->width] forKey: (NSString*)kCVPixelBufferWidthKey];
641 [attributes setObject:[NSNumber numberWithInt:frame->height] forKey: (NSString*)kCVPixelBufferHeightKey];
642 [attributes setObject:@(frame->linesize[0]) forKey:(NSString*)kCVPixelBufferBytesPerRowAlignmentKey];
643 [attributes setObject:[NSDictionary dictionary] forKey:(NSString*)kCVPixelBufferIOSurfacePropertiesKey];
644 theError = CVPixelBufferPoolCreate(kCFAllocatorDefault, NULL, (__bridge CFDictionaryRef) attributes, &pixelBufferPoolPreview);
645 if (theError != kCVReturnSuccess) {
646 NSLog(@"CVPixelBufferPoolCreate Failed");
647 return nil;
648 }
649 }
650 if(!pixelBufferPreview) {
651 theError = CVPixelBufferPoolCreatePixelBuffer(NULL, pixelBufferPoolPreview, &pixelBufferPreview);
652 if(theError != kCVReturnSuccess) {
653 NSLog(@"CVPixelBufferPoolCreatePixelBuffer Failed");
654 return nil;
655 }
656 }
657 theError = CVPixelBufferLockBaseAddress(pixelBufferPreview, 0);
658 if (theError != kCVReturnSuccess) {
659 NSLog(@"lock error");
660 return nil;
661 }
662 size_t bytePerRowY = CVPixelBufferGetBytesPerRowOfPlane(pixelBufferPreview, 0);
663 size_t bytesPerRowUV = CVPixelBufferGetBytesPerRowOfPlane(pixelBufferPreview, 1);
664 void* base = CVPixelBufferGetBaseAddressOfPlane(pixelBufferPreview, 0);
665 memcpy(base, frame->data[0], bytePerRowY * frame->height);
666 base = CVPixelBufferGetBaseAddressOfPlane(pixelBufferPreview, 1);
667 memcpy(base, frame->data[1], bytesPerRowUV * frame->height/2);
668 CVPixelBufferUnlockBaseAddress(pixelBufferPreview, 0);
669 return pixelBufferPreview;
670}
671
672-(CVPixelBufferRef) getBufferForDistantViewFromFrame:(const AVFrame*)frame {
673 if(!frame || !frame->data[0] || !frame->data[1]) {
674 return nil;
675 }
676 CVReturn theError;
677 bool createPool = false;
678 if (!pixelBufferPoolDistantView){
679 createPool = true;
680 }
681 NSDictionary* atributes = (__bridge NSDictionary*)CVPixelBufferPoolGetPixelBufferAttributes(pixelBufferPoolDistantView);
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400682 if(!atributes)
683 atributes = (__bridge NSDictionary*)CVPixelBufferPoolGetPixelBufferAttributes(pixelBufferPoolDistantView);
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400684 int width = [[atributes objectForKey:(NSString*)kCVPixelBufferWidthKey] intValue];
685 int height = [[atributes objectForKey:(NSString*)kCVPixelBufferHeightKey] intValue];
686 if (width != frame->width || height != frame->height) {
687 createPool = true;
688 }
689 if (createPool) {
690 CVPixelBufferPoolRelease(pixelBufferPoolDistantView);
691 CVPixelBufferRelease(pixelBufferDistantView);
692 pixelBufferDistantView = nil;
693 pixelBufferPoolDistantView = nil;
694 NSMutableDictionary* attributes = [NSMutableDictionary dictionary];
695 [attributes setObject:[NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange] forKey:(NSString*)kCVPixelBufferPixelFormatTypeKey];
696 [attributes setObject:[NSNumber numberWithInt:frame->width] forKey: (NSString*)kCVPixelBufferWidthKey];
697 [attributes setObject:[NSNumber numberWithInt:frame->height] forKey: (NSString*)kCVPixelBufferHeightKey];
698 [attributes setObject:@(frame->linesize[0]) forKey:(NSString*)kCVPixelBufferBytesPerRowAlignmentKey];
699 [attributes setObject:[NSDictionary dictionary] forKey:(NSString*)kCVPixelBufferIOSurfacePropertiesKey];
700 theError = CVPixelBufferPoolCreate(kCFAllocatorDefault, NULL, (__bridge CFDictionaryRef) attributes, &pixelBufferPoolDistantView);
701 if (theError != kCVReturnSuccess) {
702 return nil;
703 NSLog(@"CVPixelBufferPoolCreate Failed");
704 }
705 }
706 if(!pixelBufferDistantView) {
707 theError = CVPixelBufferPoolCreatePixelBuffer(NULL, pixelBufferPoolDistantView, &pixelBufferDistantView);
708 if(theError != kCVReturnSuccess) {
709 return nil;
710 NSLog(@"CVPixelBufferPoolCreatePixelBuffer Failed");
711 }
712 }
713 theError = CVPixelBufferLockBaseAddress(pixelBufferDistantView, 0);
714 if (theError != kCVReturnSuccess) {
715 return nil;
716 NSLog(@"lock error");
717 }
718 size_t bytePerRowY = CVPixelBufferGetBytesPerRowOfPlane(pixelBufferDistantView, 0);
719 size_t bytesPerRowUV = CVPixelBufferGetBytesPerRowOfPlane(pixelBufferDistantView, 1);
720 void* base = CVPixelBufferGetBaseAddressOfPlane(pixelBufferDistantView, 0);
721 memcpy(base, frame->data[0], bytePerRowY * frame->height);
722 base = CVPixelBufferGetBaseAddressOfPlane(pixelBufferDistantView, 1);
723 uint32_t size = frame->linesize[1] * frame->height / 2;
724 uint8_t* dstData = new uint8_t[2 * size];
725 uint8_t * firstData = new uint8_t[size];
726 memcpy(firstData, frame->data[1], size);
727 uint8_t * secondData = new uint8_t[size];
728 memcpy(secondData, frame->data[2], size);
729 for (int i = 0; i < 2 * size; i++){
730 if (i % 2 == 0){
731 dstData[i] = firstData[i/2];
732 }else {
733 dstData[i] = secondData[i/2];
734 }
735 }
736 memcpy(base, dstData, bytesPerRowUV * frame->height/2);
737 CVPixelBufferUnlockBaseAddress(pixelBufferDistantView, 0);
738 free(dstData);
739 free(firstData);
740 free(secondData);
741 return pixelBufferDistantView;
742}
743
Alexandre Lisionc5148052015-03-04 15:10:35 -0500744- (void) initFrame
745{
746 [self.view setFrame:self.view.superview.bounds];
747 [self.view setHidden:YES];
748 self.view.layer.position = self.view.frame.origin;
Alexandre Lisionbb5dbcd2015-07-09 16:36:47 -0400749 [self collapseRightView];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400750 self.testView = [[NSImageView alloc] initWithFrame:self.view.frame];
751 [self.view addSubview:self.testView];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500752}
753
754# pragma private IN/OUT animations
755
Olivier Soldano994971f2017-12-05 16:30:12 -0500756-(void)uncollapseRightView
757{
758 NSView *left = [[splitView subviews] objectAtIndex:0];
759 NSView *right = [[splitView subviews] objectAtIndex:1];
760 [right setHidden:NO];
761
762 CGFloat dividerThickness = [splitView dividerThickness];
763
764 // get the different frames
765 NSRect leftFrame = [left frame];
766 NSRect rightFrame = [right frame];
767
768 leftFrame.size.width = (leftFrame.size.width - rightFrame.size.width - dividerThickness);
769 rightFrame.origin.x = leftFrame.size.width + dividerThickness;
770 [left setFrameSize:leftFrame.size];
771 [right setFrame:rightFrame];
772 [splitView display];
773
774 [self.chatVC takeFocus];
775}
776
Alexandre Lisionc5148052015-03-04 15:10:35 -0500777-(void) cleanUp
778{
Anthony Léonard5c287642018-02-21 15:54:19 -0500779 if(self.splitView.isInFullScreenMode)
780 [self.splitView exitFullScreenModeWithOptions:nil];
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400781 QObject::disconnect(renderConnections.frameUpdated);
782 QObject::disconnect(renderConnections.started);
783 QObject::disconnect(renderConnections.stopped);
Anthony Léonardc0013bb2018-02-20 16:23:02 -0500784 QObject::disconnect(self.messageConnection);
Alexandre Lision883719f2015-10-22 17:37:45 -0400785
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500786 [self.chatButton setHidden:YES];
Alexandre Lision4baba4c2016-02-11 13:00:57 -0500787 [self.chatButton setPressed:NO];
Alexandre Lision883719f2015-10-22 17:37:45 -0400788 [self collapseRightView];
Alexandre Lision57227772016-01-15 17:19:36 -0500789
790 [personLabel setStringValue:@""];
791 [timeSpentLabel setStringValue:@""];
792 [stateLabel setStringValue:@""];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400793 //audio view
794 [audioCallTime setStringValue:@""];
795 [audioCallPersonId setStringValue:@""];
796 [audioCallPersonLabel setStringValue:@""];
797 [audioCallView setHidden:YES];
798 [audioCallPhoto setImage:nil];
799 //background view
800 [bluerBackgroundEffect setHidden:NO];
801 [backgroundImage setHidden:NO];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400802 //outgoing view
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400803 [outgoingPersonLabel setStringValue:@""];
804 [outgoingStateLabel setStringValue:@""];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400805 [self.previewView setHidden:YES];
806 [self.videoMTKView setHidden:YES];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500807}
808
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400809-(void) setupCallView
810{
811 if (accountInfo_ == nil)
812 return;
813
814 auto* callModel = accountInfo_->callModel.get();
815 auto* convModel = accountInfo_->conversationModel.get();
816
817 // when call comes in we want to show the controls/header
Kateryna Kostiuk1004caa2018-03-29 13:48:07 -0400818 [self mouseIsMoving: YES];
819 [videoView setShouldAcceptInteractions: NO];
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400820
821 [self connectVideoSignals];
822 /* check if text media is already present */
823 if(not callModel->hasCall(callUid_))
824 return;
825
826 [loadingIndicator setAnimates:YES];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400827 [self updateCall];
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400828
829 /* monitor media for messaging text messaging */
830 QObject::disconnect(self.messageConnection);
831 self.messageConnection = QObject::connect(convModel,
832 &lrc::api::ConversationModel::interactionStatusUpdated,
833 [self] (std::string convUid,
834 uint64_t msgId,
835 lrc::api::interaction::Info msg) {
836 if (msg.type == lrc::api::interaction::Type::TEXT) {
837 if(not [[self splitView] isSubviewCollapsed:[[[self splitView] subviews] objectAtIndex: 1]]){
838 return;
839 }
840 [self uncollapseRightView];
841 }
842 });
Kateryna Kostiuk133364b2018-10-31 11:36:08 -0400843 //monitor for updated profile
844 QObject::disconnect(self.profileUpdatedConnection);
845 self.profileUpdatedConnection =
846 QObject::connect(accountInfo_->contactModel.get(),
847 &lrc::api::ContactModel::contactAdded,
848 [self](const std::string &contactUri) {
849 auto convIt = getConversationFromUid(convUid_, *accountInfo_->conversationModel.get());
850 if (convIt == accountInfo_->conversationModel->allFilteredConversations().end()) {
851 return;
852 }
853 if (convIt->participants.empty()) {
854 return;
855
856 }
857 auto& contact = accountInfo_->contactModel->getContact(convIt->participants[0]);
858 if (contact.profileInfo.type == lrc::api::profile::Type::RING && contact.profileInfo.uri == contactUri)
859 accountInfo_->conversationModel->makePermanent(convUid_);
860 [incomingPersonPhoto setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
861 [outgoingPhoto setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
Kateryna Kostiuk732b4062018-11-28 09:59:28 -0500862 [self.delegate conversationInfoUpdatedFor:convUid_];
Kateryna Kostiuk133364b2018-10-31 11:36:08 -0400863 if(accountInfo_->callModel.get()->getCall(callUid_).isAudioOnly) {
864 [audioCallPhoto setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
865 [self setBackground];
866 return;
867 }
868 [personPhoto setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
869 });
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400870}
871
872-(void) showWithAnimation:(BOOL)animate
873{
874 if (!animate) {
875 [self.view setHidden:NO];
876 [self setupCallView];
877 return;
878 }
879
880 CGRect frame = CGRectOffset(self.view.superview.bounds, -self.view.superview.bounds.size.width, 0);
881 [self.view setHidden:NO];
882
883 [CATransaction begin];
884 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
885 [animation setFromValue:[NSValue valueWithPoint:frame.origin]];
886 [animation setToValue:[NSValue valueWithPoint:self.view.superview.bounds.origin]];
887 [animation setDuration:0.2f];
888 [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]];
889 [CATransaction setCompletionBlock:^{
890 [self setupCallView];
891 }];
892
893 [self.view.layer addAnimation:animation forKey:animation.keyPath];
894 [CATransaction commit];
895}
896
897-(void) hideWithAnimation:(BOOL)animate
Alexandre Lisionc5148052015-03-04 15:10:35 -0500898{
Alexandre Lisionc5148052015-03-04 15:10:35 -0500899 if(self.view.frame.origin.x < 0) {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500900 return;
901 }
902
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400903 if (!animate) {
904 [self.view setHidden:YES];
905 return;
906 }
907
Alexandre Lisionc5148052015-03-04 15:10:35 -0500908 CGRect frame = CGRectOffset(self.view.frame, -self.view.frame.size.width, 0);
909 [CATransaction begin];
910 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
911 [animation setFromValue:[NSValue valueWithPoint:self.view.frame.origin]];
912 [animation setToValue:[NSValue valueWithPoint:frame.origin]];
913 [animation setDuration:0.2f];
Alexandre Lision22d615b2016-01-25 12:54:34 -0500914 [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500915
916 [CATransaction setCompletionBlock:^{
917 [self.view setHidden:YES];
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400918 // first make sure everything is disconnected
Alexandre Lisionc5148052015-03-04 15:10:35 -0500919 [self cleanUp];
Olivier Soldano994971f2017-12-05 16:30:12 -0500920// if (currentCall_) {
921// [self animateIn];
922// }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500923 }];
924 [self.view.layer addAnimation:animation forKey:animation.keyPath];
Alexandre Lisiona1c6d752015-06-23 12:27:38 -0400925
926 [self.view.layer setPosition:frame.origin];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500927 [CATransaction commit];
928}
929
930/**
931 * Debug purpose
932 */
933-(void) dumpFrame:(CGRect) frame WithName:(NSString*) name
934{
935 NSLog(@"frame %@ : %f %f %f %f \n\n",name ,frame.origin.x, frame.origin.y, frame.size.width, frame.size.height);
936}
937
Alexandre Lision58cab672015-06-09 15:25:40 -0400938#pragma mark - Button methods
939
Alexandre Lisionc5148052015-03-04 15:10:35 -0500940- (IBAction)hangUp:(id)sender {
Olivier Soldano994971f2017-12-05 16:30:12 -0500941 if (accountInfo_ == nil)
942 return;
943
944 auto* callModel = accountInfo_->callModel.get();
Olivier Soldano994971f2017-12-05 16:30:12 -0500945 callModel->hangUp(callUid_);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500946}
947
948- (IBAction)accept:(id)sender {
Olivier Soldano994971f2017-12-05 16:30:12 -0500949 if (accountInfo_ == nil)
950 return;
951
Anthony Léonardf584cc52018-01-19 12:00:19 -0500952 // If we accept a conversation with a non trusted contact, we first accept it
953 auto convIt = getConversationFromUid(convUid_, *accountInfo_->conversationModel.get());
954 if (convIt != accountInfo_->conversationModel->allFilteredConversations().end()) {
955 auto& contact = accountInfo_->contactModel->getContact(convIt->participants[0]);
956 if (contact.profileInfo.type == lrc::api::profile::Type::PENDING)
957 accountInfo_->conversationModel->makePermanent(convUid_);
958 }
959
Olivier Soldano994971f2017-12-05 16:30:12 -0500960 auto* callModel = accountInfo_->callModel.get();
961
962 callModel->accept(callUid_);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500963}
964
965- (IBAction)toggleRecording:(id)sender {
Olivier Soldano994971f2017-12-05 16:30:12 -0500966 if (accountInfo_ == nil)
967 return;
968
969 auto* callModel = accountInfo_->callModel.get();
970
971 callModel->toggleAudioRecord(callUid_);
Kateryna Kostiuk54d76972018-06-07 15:45:07 -0400972 [recordOnOffButton setPressed:!recordOnOffButton.isPressed];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500973}
974
975- (IBAction)toggleHold:(id)sender {
Olivier Soldano994971f2017-12-05 16:30:12 -0500976 if (accountInfo_ == nil)
977 return;
978
979 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiuk54d76972018-06-07 15:45:07 -0400980 auto currentCall = callModel->getCall(callUid_);
981 if (currentCall.status != lrc::api::call::Status::PAUSED) {
982 holdOnOffButton.image = [NSImage imageNamed:@"ic_action_holdoff.png"];
983 } else {
984 holdOnOffButton.image = [NSImage imageNamed:@"ic_action_hold.png"];
985 }
Olivier Soldano994971f2017-12-05 16:30:12 -0500986
987 callModel->togglePause(callUid_);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500988}
989
Alexandre Lision62005312016-01-28 15:55:16 -0500990- (IBAction)toggleAdvancedControls:(id)sender {
991 [advancedButton setPressed:!advancedButton.isPressed];
992 [advancedPanel setHidden:![advancedButton isPressed]];
993}
994
995- (IBAction)showDialpad:(id)sender {
996 AppDelegate* appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
997 [appDelegate showDialpad];
998}
999
Alexandre Lision58cab672015-06-09 15:25:40 -04001000-(IBAction)toggleChat:(id)sender;
1001{
1002 BOOL rightViewCollapsed = [[self splitView] isSubviewCollapsed:[[[self splitView] subviews] objectAtIndex: 1]];
1003 if (rightViewCollapsed) {
1004 [self uncollapseRightView];
Alexandre Lision58cab672015-06-09 15:25:40 -04001005 } else {
1006 [self collapseRightView];
1007 }
Alexandre Lision62005312016-01-28 15:55:16 -05001008 [chatButton setPressed:rightViewCollapsed];
Alexandre Lision58cab672015-06-09 15:25:40 -04001009}
1010
Alexandre Lisiond18fa272015-06-15 11:18:03 -04001011- (IBAction)muteAudio:(id)sender
1012{
Olivier Soldano994971f2017-12-05 16:30:12 -05001013 if (accountInfo_ == nil)
1014 return;
1015
1016 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiuk54d76972018-06-07 15:45:07 -04001017 auto currentCall = callModel->getCall(callUid_);
1018 if (currentCall.audioMuted) {
1019 muteAudioButton.image = [NSImage imageNamed:@"ic_action_audio.png"];
1020 } else {
1021 muteAudioButton.image = [NSImage imageNamed:@"ic_action_mute_audio.png"];
1022 }
Olivier Soldano994971f2017-12-05 16:30:12 -05001023 callModel->toggleMedia(callUid_, lrc::api::NewCallModel::Media::AUDIO);
Alexandre Lisiond18fa272015-06-15 11:18:03 -04001024}
1025
1026- (IBAction)muteVideo:(id)sender
1027{
Olivier Soldano994971f2017-12-05 16:30:12 -05001028 if (accountInfo_ == nil)
1029 return;
Olivier Soldano994971f2017-12-05 16:30:12 -05001030 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiuk54d76972018-06-07 15:45:07 -04001031 auto currentCall = callModel->getCall(callUid_);
1032 if(!currentCall.isAudioOnly) {
1033 if (currentCall.videoMuted) {
1034 muteVideoButton.image = [NSImage imageNamed:@"ic_action_video.png"];
1035 } else {
1036 muteVideoButton.image = [NSImage imageNamed:@"ic_action_mute_video.png"];
1037 }
1038 }
Olivier Soldano994971f2017-12-05 16:30:12 -05001039 callModel->toggleMedia(callUid_, lrc::api::NewCallModel::Media::VIDEO);
Alexandre Lisiond18fa272015-06-15 11:18:03 -04001040}
Alexandre Lision883719f2015-10-22 17:37:45 -04001041
1042- (IBAction)toggleTransferView:(id)sender {
Kateryna Kostiuk1ba5a012018-12-12 17:07:38 -05001043
Alexandre Lisionf23ec5a2015-07-16 11:24:06 -04001044}
Alexandre Lisiond18fa272015-06-15 11:18:03 -04001045
Alexandre Lision89edc6a2015-11-09 11:30:47 -05001046- (IBAction)toggleAddParticipantView:(id)sender {
Kateryna Kostiuk1ba5a012018-12-12 17:07:38 -05001047
Alexandre Lision89edc6a2015-11-09 11:30:47 -05001048}
1049
Alexandre Lision2db8f472015-07-22 15:05:46 -04001050#pragma mark - NSPopOverDelegate
1051
Alexandre Lision266fca02015-09-28 14:47:05 -04001052- (void)popoverWillClose:(NSNotification *)notification
1053{
Alexandre Lision89edc6a2015-11-09 11:30:47 -05001054 if (_brokerPopoverVC != nullptr) {
1055 [_brokerPopoverVC performClose:self];
1056 _brokerPopoverVC = NULL;
Alexandre Lision883719f2015-10-22 17:37:45 -04001057 }
Alexandre Lision266fca02015-09-28 14:47:05 -04001058
Alexandre Lision2db8f472015-07-22 15:05:46 -04001059 if (self.addToContactPopover != nullptr) {
1060 [self.addToContactPopover performClose:self];
1061 self.addToContactPopover = NULL;
1062 }
Alexandre Lision883719f2015-10-22 17:37:45 -04001063
Alexandre Lision62005312016-01-28 15:55:16 -05001064 [self.transferButton setPressed:NO];
Alexandre Lision89edc6a2015-11-09 11:30:47 -05001065 [self.addParticipantButton setState:NSOffState];
Alexandre Lision883719f2015-10-22 17:37:45 -04001066}
1067
1068- (void)popoverDidClose:(NSNotification *)notification
1069{
1070 [videoView setCallDelegate:self];
Alexandre Lision2db8f472015-07-22 15:05:46 -04001071}
1072
1073#pragma mark - ContactLinkedDelegate
1074
1075- (void)contactLinked
1076{
1077 if (self.addToContactPopover != nullptr) {
1078 [self.addToContactPopover performClose:self];
1079 self.addToContactPopover = NULL;
1080 }
1081}
1082
Alexandre Lision58cab672015-06-09 15:25:40 -04001083#pragma mark - NSSplitViewDelegate
1084
1085/* 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.
1086 */
1087- (BOOL)splitView:(NSSplitView *)splitView shouldCollapseSubview:(NSView *)subview forDoubleClickOnDividerAtIndex:(NSInteger)dividerIndex;
1088{
1089 NSView* rightView = [[splitView subviews] objectAtIndex:1];
1090 return ([subview isEqual:rightView]);
1091}
1092
1093
1094- (BOOL)splitView:(NSSplitView *)splitView canCollapseSubview:(NSView *)subview;
1095{
1096 NSView* rightView = [[splitView subviews] objectAtIndex:1];
1097 return ([subview isEqual:rightView]);
1098}
1099
1100
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -04001101# pragma mark - CallnDelegate
Alexandre Lision58cab672015-06-09 15:25:40 -04001102
1103- (void) callShouldToggleFullScreen
1104{
1105 if(self.splitView.isInFullScreenMode)
1106 [self.splitView exitFullScreenModeWithOptions:nil];
1107 else {
1108 NSApplicationPresentationOptions options = NSApplicationPresentationDefault +NSApplicationPresentationAutoHideDock +
1109 NSApplicationPresentationAutoHideMenuBar + NSApplicationPresentationAutoHideToolbar;
1110 NSDictionary *opts = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInt:options],
1111 NSFullScreenModeApplicationPresentationOptions, nil];
1112
1113 [self.splitView enterFullScreenMode:[NSScreen mainScreen] withOptions:opts];
1114 }
1115}
1116
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -04001117-(void) mouseIsMoving:(BOOL) move
1118{
1119 [[controlsPanel animator] setAlphaValue:move]; // fade out
1120 [[headerContainer animator] setAlphaValue:move];
1121}
1122
Kateryna Kostiuk89398b62018-11-20 16:45:04 -05001123- (BOOL)splitView:(NSSplitView *)splitView shouldHideDividerAtIndex:(NSInteger)dividerIndex
1124{
1125 return YES;
1126}
1127
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -04001128-(void) screenShare {
1129 NSScreen *mainScreen = [NSScreen mainScreen];
1130 NSRect screenFrame = mainScreen.frame;
1131 QRect captureRect = QRect(screenFrame.origin.x, screenFrame.origin.y, screenFrame.size.width, screenFrame.size.height);
1132 mediaModel->setDisplay(0, screenFrame.origin.x, screenFrame.origin.y, screenFrame.size.width, screenFrame.size.height);
1133}
1134-(void) switchToDevice:(int)deviceID {
1135 auto devices = mediaModel->getDevices();
1136 auto device = devices[deviceID];
1137 mediaModel->switchInputTo(device);
1138}
1139
1140-(std::vector<std::string>) getDeviceList {
1141 return mediaModel->getDevices();
1142}
1143
1144-(void) switchToFile:(std::string)uri {
1145 mediaModel->setInputFile(QUrl::fromLocalFile(uri.c_str()).toLocalFile().toStdString());
1146}
1147
Alexandre Lisionc5148052015-03-04 15:10:35 -05001148@end