blob: b78a76eeea226bf129972684577dd23402396119 [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/previewmanager.h>
36#import <video/renderer.h>
Olivier Soldano994971f2017-12-05 16:30:12 -050037#import <api/newcallmodel.h>
38#import <api/call.h>
39#import <api/conversationmodel.h>
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -040040#import <api/avmodel.h>
Alexandre Lision210fe212016-01-27 11:15:13 -050041#import <globalinstances.h>
Alexandre Lisionc5148052015-03-04 15:10:35 -050042
Alexandre Lision62005312016-01-28 15:55:16 -050043#import "AppDelegate.h"
Alexandre Lisionf47a2562015-06-15 15:48:29 -040044#import "views/ITProgressIndicator.h"
Alexandre Lision74dd47f2015-04-14 13:47:42 -040045#import "views/CallView.h"
Kateryna Kostiuk81afce72018-10-30 16:40:50 -040046#import "views/NSColor+RingTheme.h"
Alexandre Lision210fe212016-01-27 11:15:13 -050047#import "delegates/ImageManipulationDelegate.h"
Alexandre Lision16d9c0a2015-08-10 12:05:15 -040048#import "ChatVC.h"
Alexandre Lision4baba4c2016-02-11 13:00:57 -050049#import "views/IconButton.h"
Anthony Léonard6f819752018-01-05 09:53:40 -050050#import "utils.h"
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -040051#import "views/CallMTKView.h"
Alexandre Lision74dd47f2015-04-14 13:47:42 -040052
Alexandre Lisionc5148052015-03-04 15:10:35 -050053@interface RendererConnectionsHolder : NSObject
54
55@property QMetaObject::Connection frameUpdated;
56@property QMetaObject::Connection started;
57@property QMetaObject::Connection stopped;
58
59@end
60
61@implementation RendererConnectionsHolder
62
63@end
64
Kateryna Kostiuk1ba5a012018-12-12 17:07:38 -050065@interface CurrentCallVC () <NSPopoverDelegate> {
Olivier Soldano994971f2017-12-05 16:30:12 -050066 std::string convUid_;
67 std::string callUid_;
68 const lrc::api::account::Info *accountInfo_;
Anthony Léonard66a050c2018-01-03 16:32:34 -050069 NSTimer* refreshDurationTimer;
Olivier Soldano994971f2017-12-05 16:30:12 -050070}
Alexandre Lisionc5148052015-03-04 15:10:35 -050071
Alexandre Lision210fe212016-01-27 11:15:13 -050072// Main container
73@property (unsafe_unretained) IBOutlet NSSplitView* splitView;
Kateryna Kostiuk89398b62018-11-20 16:45:04 -050074@property (unsafe_unretained) IBOutlet NSView* backgroundImage;
Kateryna Kostiuk81afce72018-10-30 16:40:50 -040075@property (unsafe_unretained) IBOutlet NSBox* bluerBackgroundEffect;
Alexandre Lision210fe212016-01-27 11:15:13 -050076
Alexandre Lision89edc6a2015-11-09 11:30:47 -050077// Header info
78@property (unsafe_unretained) IBOutlet NSView* headerContainer;
Alexandre Lision883719f2015-10-22 17:37:45 -040079@property (unsafe_unretained) IBOutlet NSTextField* personLabel;
80@property (unsafe_unretained) IBOutlet NSTextField* stateLabel;
Alexandre Lision89edc6a2015-11-09 11:30:47 -050081@property (unsafe_unretained) IBOutlet NSTextField* timeSpentLabel;
Alexandre Lision210fe212016-01-27 11:15:13 -050082@property (unsafe_unretained) IBOutlet NSImageView* personPhoto;
Alexandre Lision89edc6a2015-11-09 11:30:47 -050083
Andreas Traczyk77a50d52018-05-08 17:47:31 -040084@property (unsafe_unretained) IBOutlet NSImageView* outgoingPhoto;
85@property (unsafe_unretained) IBOutlet NSTextField* outgoingPersonLabel;
86@property (unsafe_unretained) IBOutlet NSTextField* outgoingStateLabel;
87@property (unsafe_unretained) IBOutlet NSTextField* outgoingId;
88
Alexandre Lision89edc6a2015-11-09 11:30:47 -050089// Call Controls
90@property (unsafe_unretained) IBOutlet NSView* controlsPanel;
Alexandre Lision62005312016-01-28 15:55:16 -050091
Alexandre Lision4baba4c2016-02-11 13:00:57 -050092@property (unsafe_unretained) IBOutlet IconButton* holdOnOffButton;
93@property (unsafe_unretained) IBOutlet IconButton* hangUpButton;
94@property (unsafe_unretained) IBOutlet IconButton* recordOnOffButton;
95@property (unsafe_unretained) IBOutlet IconButton* pickUpButton;
96@property (unsafe_unretained) IBOutlet IconButton* muteAudioButton;
97@property (unsafe_unretained) IBOutlet IconButton* muteVideoButton;
Alexandre Lision4baba4c2016-02-11 13:00:57 -050098@property (unsafe_unretained) IBOutlet IconButton* transferButton;
99@property (unsafe_unretained) IBOutlet IconButton* addParticipantButton;
100@property (unsafe_unretained) IBOutlet IconButton* chatButton;
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400101@property (unsafe_unretained) IBOutlet IconButton* qualityButton;
Alexandre Lisiond18fa272015-06-15 11:18:03 -0400102
Alexandre Lision62005312016-01-28 15:55:16 -0500103@property (unsafe_unretained) IBOutlet NSView* advancedPanel;
104@property (unsafe_unretained) IBOutlet IconButton* advancedButton;
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400105@property (assign) IBOutlet NSLayoutConstraint* callRecordButtonMarginLeft;
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500106
Alexandre Lision2db8f472015-07-22 15:05:46 -0400107@property (strong) NSPopover* addToContactPopover;
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500108@property (strong) NSPopover* brokerPopoverVC;
Alexandre Lision883719f2015-10-22 17:37:45 -0400109@property (strong) IBOutlet ChatVC* chatVC;
Alexandre Lisionf23ec5a2015-07-16 11:24:06 -0400110
Alexandre Lision210fe212016-01-27 11:15:13 -0500111// Ringing call panel
112@property (unsafe_unretained) IBOutlet NSView* ringingPanel;
113@property (unsafe_unretained) IBOutlet NSImageView* incomingPersonPhoto;
114@property (unsafe_unretained) IBOutlet NSTextField* incomingDisplayName;
115
116// Outgoing call panel
117@property (unsafe_unretained) IBOutlet NSView* outgoingPanel;
118@property (unsafe_unretained) IBOutlet ITProgressIndicator *loadingIndicator;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500119
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400120// audio only view
121@property (unsafe_unretained) IBOutlet NSView* audioCallView;
122@property (unsafe_unretained) IBOutlet NSImageView* audioCallPhoto;
123@property (unsafe_unretained) IBOutlet NSTextField* audioCallTime;
124@property (unsafe_unretained) IBOutlet NSTextField* audioCallPersonLabel;
125@property (unsafe_unretained) IBOutlet NSTextField* audioCallPersonId;
126
Alexandre Lisionc5148052015-03-04 15:10:35 -0500127// Video
Alexandre Lision58cab672015-06-09 15:25:40 -0400128@property (unsafe_unretained) IBOutlet CallView *videoView;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400129@property (unsafe_unretained) IBOutlet CallMTKView *previewView;
130
131@property (unsafe_unretained) IBOutlet CallMTKView *videoMTKView;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500132
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400133@property RendererConnectionsHolder* renderConnections;
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400134@property QMetaObject::Connection videoStarted;
Alexandre Lision4d9905e2016-01-22 12:01:36 -0500135@property QMetaObject::Connection selectedCallChanged;
Alexandre Lisionb65c0272015-07-22 15:51:29 -0400136@property QMetaObject::Connection messageConnection;
137@property QMetaObject::Connection mediaAddedConnection;
Kateryna Kostiuk133364b2018-10-31 11:36:08 -0400138@property QMetaObject::Connection profileUpdatedConnection;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400139@property NSImageView *testView;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500140
141@end
142
143@implementation CurrentCallVC
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400144lrc::api::AVModel* mediaModel;
Olivier Soldano994971f2017-12-05 16:30:12 -0500145@synthesize personLabel, personPhoto, stateLabel, holdOnOffButton, hangUpButton,
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500146 recordOnOffButton, pickUpButton, chatButton, transferButton, addParticipantButton, timeSpentLabel,
Olivier Soldano994971f2017-12-05 16:30:12 -0500147 muteVideoButton, muteAudioButton, controlsPanel, advancedPanel, advancedButton, headerContainer, videoView,
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400148 incomingDisplayName, incomingPersonPhoto, previewView, splitView, loadingIndicator, ringingPanel,
149 outgoingPanel, outgoingPersonLabel, outgoingStateLabel, outgoingPhoto, outgoingId,
150 callRecordButtonMarginLeft, audioCallView, audioCallPhoto, audioCallTime, audioCallPersonLabel, audioCallPersonId, backgroundImage, bluerBackgroundEffect;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500151
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400152@synthesize renderConnections;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400153CVPixelBufferPoolRef pixelBufferPoolDistantView;
154CVPixelBufferRef pixelBufferDistantView;
155CVPixelBufferPoolRef pixelBufferPoolPreview;
156CVPixelBufferRef pixelBufferPreview;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500157
Olivier Soldano994971f2017-12-05 16:30:12 -0500158-(void) setCurrentCall:(const std::string&)callUid
159 conversation:(const std::string&)convUid
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400160 account:(const lrc::api::account::Info*)account
161 avModel:(lrc::api::AVModel *)avModel;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500162{
Olivier Soldano994971f2017-12-05 16:30:12 -0500163 if(account == nil)
164 return;
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400165
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400166 mediaModel = avModel;
Olivier Soldano994971f2017-12-05 16:30:12 -0500167 auto* callModel = account->callModel.get();
Alexandre Lisionc5148052015-03-04 15:10:35 -0500168
Olivier Soldano994971f2017-12-05 16:30:12 -0500169 if (not callModel->hasCall(callUid)){
170 callUid_.clear();
Alexandre Lision2db8f472015-07-22 15:05:46 -0400171 return;
172 }
Olivier Soldano994971f2017-12-05 16:30:12 -0500173 callUid_ = callUid;
174 convUid_ = convUid;
175 accountInfo_ = account;
Anthony Léonard4b0935f2017-12-26 12:05:22 -0500176 [self.chatVC setConversationUid:convUid model:account->conversationModel.get()];
Kateryna Kostiuk54d76972018-06-07 15:45:07 -0400177 auto currentCall = callModel->getCall(callUid_);
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400178 [muteVideoButton setHidden: currentCall.isAudioOnly ? YES: NO];
179 callRecordButtonMarginLeft.constant = currentCall.isAudioOnly ? -40.0f: 10.0f;
180 [previewView setHidden: YES];
Kateryna Kostiukd76f6202018-09-11 17:15:28 -0400181 videoView.callId = callUid;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500182}
183
184- (void)awakeFromNib
185{
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400186 [self.view setWantsLayer:YES];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500187 [controlsPanel setWantsLayer:YES];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500188 [controlsPanel.layer setBackgroundColor:[NSColor clearColor].CGColor];
189 [controlsPanel.layer setFrame:controlsPanel.frame];
190
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400191 renderConnections = [[RendererConnectionsHolder alloc] init];
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400192
Alexandre Lisionf47a2562015-06-15 15:48:29 -0400193 [loadingIndicator setColor:[NSColor whiteColor]];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400194 [loadingIndicator setNumberOfLines:200];
195 [loadingIndicator setWidthOfLine:4];
196 [loadingIndicator setLengthOfLine:4];
197 [loadingIndicator setInnerMargin:59];
Alexandre Lisionf47a2562015-06-15 15:48:29 -0400198
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -0400199 [self.videoView setCallDelegate:self];
Kateryna Kostiuk1004caa2018-03-29 13:48:07 -0400200 CGColor* color = [[[NSColor blackColor] colorWithAlphaComponent:0.2] CGColor];
201 [headerContainer.layer setBackgroundColor:color];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400202 [bluerBackgroundEffect setWantsLayer:YES];
203 bluerBackgroundEffect.alphaValue = 0.6;
204 [audioCallView setWantsLayer:YES];
205 [audioCallView.layer setBackgroundColor: [[NSColor clearColor] CGColor]];
Kateryna Kostiuk89398b62018-11-20 16:45:04 -0500206 [backgroundImage setWantsLayer: YES];
207 backgroundImage.layer.contentsGravity = kCAGravityResizeAspectFill;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500208}
209
Anthony Léonard66a050c2018-01-03 16:32:34 -0500210-(void) updateDurationLabel
211{
212 if (accountInfo_ != nil) {
213 auto* callModel = accountInfo_->callModel.get();
214 if (callModel->hasCall(callUid_)) {
215 auto& callStatus = callModel->getCall(callUid_).status;
216 if (callStatus != lrc::api::call::Status::ENDED &&
217 callStatus != lrc::api::call::Status::TERMINATING &&
218 callStatus != lrc::api::call::Status::INVALID) {
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400219 if(callModel->getCall(callUid_).isAudioOnly) {
220 [audioCallTime setStringValue:@(callModel->getFormattedCallDuration(callUid_).c_str())];
221 } else {
222 [timeSpentLabel setStringValue:@(callModel->getFormattedCallDuration(callUid_).c_str())];
223 }
Anthony Léonard66a050c2018-01-03 16:32:34 -0500224 return;
225 }
226 }
227 }
228
229 // If call is not running anymore or accountInfo_ is not set for any reason
230 // we stop the refresh loop
231 [refreshDurationTimer invalidate];
232 refreshDurationTimer = nil;
233 [timeSpentLabel setHidden:YES];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400234 [audioCallView setHidden:YES];
Anthony Léonard66a050c2018-01-03 16:32:34 -0500235}
236
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400237-(void) updateCall
Alexandre Lisionc5148052015-03-04 15:10:35 -0500238{
Olivier Soldano994971f2017-12-05 16:30:12 -0500239 if (accountInfo_ == nil)
240 return;
Alexandre Lisionbb5dbcd2015-07-09 16:36:47 -0400241
Olivier Soldano994971f2017-12-05 16:30:12 -0500242 auto* callModel = accountInfo_->callModel.get();
243 if (not callModel->hasCall(callUid_)) {
244 return;
245 }
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500246
Olivier Soldano994971f2017-12-05 16:30:12 -0500247 auto currentCall = callModel->getCall(callUid_);
248 NSLog(@"\n status %@ \n",@(lrc::api::call::to_string(currentCall.status).c_str()));
Anthony Léonard8ee26782018-01-05 10:23:04 -0500249 auto convIt = getConversationFromUid(convUid_, *accountInfo_->conversationModel);
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400250 if (convIt != accountInfo_->conversationModel->allFilteredConversations().end()) {
251 NSString* bestName = bestNameForConversation(*convIt, *accountInfo_->conversationModel);
252 [personLabel setStringValue:bestName];
253 [outgoingPersonLabel setStringValue:bestName];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400254 [audioCallPersonLabel setStringValue:bestName];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400255 NSString* ringID = bestIDForConversation(*convIt, *accountInfo_->conversationModel);
256 if([bestName isEqualToString:ringID]) {
257 ringID = @"";
258 }
259 [outgoingId setStringValue:ringID];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400260 [audioCallPersonId setStringValue:ringID];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400261 }
262
Anthony Léonard66a050c2018-01-03 16:32:34 -0500263 [timeSpentLabel setStringValue:@(callModel->getFormattedCallDuration(callUid_).c_str())];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400264 [audioCallTime setStringValue:@(callModel->getFormattedCallDuration(callUid_).c_str())];
Anthony Léonard66a050c2018-01-03 16:32:34 -0500265 if (refreshDurationTimer == nil)
266 refreshDurationTimer = [NSTimer scheduledTimerWithTimeInterval:1.0
267 target:self
268 selector:@selector(updateDurationLabel)
269 userInfo:nil
270 repeats:YES];
Olivier Soldano994971f2017-12-05 16:30:12 -0500271 [stateLabel setStringValue:@(to_string(currentCall.status).c_str())];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400272 [outgoingStateLabel setStringValue:@(to_string(currentCall.status).c_str())];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500273
Olivier Soldano994971f2017-12-05 16:30:12 -0500274 // Default values for this views
275 [loadingIndicator setHidden:YES];
Olivier Soldano994971f2017-12-05 16:30:12 -0500276 [ringingPanel setHidden:YES];
277 [outgoingPanel setHidden:YES];
278 [controlsPanel setHidden:NO];
279 [headerContainer setHidden:NO];
Anthony Léonard4b0935f2017-12-26 12:05:22 -0500280 [chatButton setHidden:NO];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400281 [self setBackground];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500282
Olivier Soldano994971f2017-12-05 16:30:12 -0500283 using Status = lrc::api::call::Status;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400284 holdOnOffButton.image = currentCall.status == lrc::api::call::Status::PAUSED ?
285 [NSImage imageNamed:@"ic_action_holdoff.png"] : [NSImage imageNamed:@"ic_action_hold.png"];
Olivier Soldano994971f2017-12-05 16:30:12 -0500286 switch (currentCall.status) {
287 case Status::SEARCHING:
Olivier Soldano994971f2017-12-05 16:30:12 -0500288 case Status::CONNECTING:
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400289 [headerContainer setHidden:YES];
290 [outgoingPanel setHidden:NO];
291 [outgoingPhoto setHidden:NO];
292 [self setupContactInfo:outgoingPhoto];
Olivier Soldano994971f2017-12-05 16:30:12 -0500293 [loadingIndicator setHidden:NO];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400294 [controlsPanel setHidden:YES];
Olivier Soldano994971f2017-12-05 16:30:12 -0500295 break;
296 case Status::INCOMING_RINGING:
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400297 [outgoingPhoto setHidden:YES];
Olivier Soldano994971f2017-12-05 16:30:12 -0500298 [controlsPanel setHidden:YES];
299 [outgoingPanel setHidden:YES];
300 [self setupIncoming:callUid_];
301 break;
302 case Status::OUTGOING_RINGING:
303 [controlsPanel setHidden:YES];
304 [outgoingPanel setHidden:NO];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400305 [loadingIndicator setHidden:NO];
306 [headerContainer setHidden:YES];
Olivier Soldano994971f2017-12-05 16:30:12 -0500307 break;
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400308 /*case Status::CONFERENCE:
309 [self setupConference:currentCall];
310 break;*/
Olivier Soldano994971f2017-12-05 16:30:12 -0500311 case Status::PAUSED:
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400312 [self.videoMTKView fillWithBlack];
313 [self.previewView fillWithBlack];
314 [bluerBackgroundEffect setHidden:NO];
315 [backgroundImage setHidden:NO];
316 [self.previewView setHidden: YES];
317 [self.videoMTKView setHidden: YES];
318 self.previewView.stopRendering = true;
319 self.videoMTKView.stopRendering = true;
320 break;
Olivier Soldano994971f2017-12-05 16:30:12 -0500321 case Status::INACTIVE:
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400322 if(currentCall.isAudioOnly) {
323 [self setUpAudioOnlyView];
324 } else {
325 [self setUpVideoCallView];
326 }
327 break;
Olivier Soldano994971f2017-12-05 16:30:12 -0500328 case Status::IN_PROGRESS:
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400329 self.previewView.stopRendering = false;
330 self.videoMTKView.stopRendering = false;
331 [previewView fillWithBlack];
332 [self.videoMTKView fillWithBlack];
333 [self.previewView setHidden: NO];
334 [self.videoMTKView setHidden: NO];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400335 if(currentCall.isAudioOnly) {
336 [self setUpAudioOnlyView];
337 } else {
338 [self setUpVideoCallView];
339 }
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400340 break;
Olivier Soldano994971f2017-12-05 16:30:12 -0500341 case Status::CONNECTED:
Olivier Soldano994971f2017-12-05 16:30:12 -0500342 break;
343 case Status::ENDED:
344 case Status::TERMINATING:
345 case Status::INVALID:
Olivier Soldano994971f2017-12-05 16:30:12 -0500346 break;
347 }
Alexandre Lision4d9905e2016-01-22 12:01:36 -0500348}
349
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400350-(void) setUpVideoCallView {
351 [self setupContactInfo:personPhoto];
352 [timeSpentLabel setHidden:NO];
353 [outgoingPhoto setHidden:YES];
354 [headerContainer setHidden:NO];
355 [previewView setHidden: NO];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400356 [self.videoMTKView setHidden:NO];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400357 [bluerBackgroundEffect setHidden:YES];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400358 [backgroundImage setHidden:YES];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400359 [audioCallView setHidden:YES];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400360}
361
362-(void) setUpAudioOnlyView {
363 [audioCallView setHidden:NO];
364 [headerContainer setHidden:YES];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400365 [self.previewView setHidden: YES];
366 [self.videoMTKView setHidden: YES];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400367 [audioCallPhoto setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
368}
369
370-(void) setBackground {
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400371 auto* convModel = accountInfo_->conversationModel.get();
372 auto it = getConversationFromUid(convUid_, *convModel);
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400373 NSImage *image= [self getContactImageOfSize:120.0 withDefaultAvatar:NO];
374 if(image) {
Kateryna Kostiuk89398b62018-11-20 16:45:04 -0500375 CIImage * ciImage = [[CIImage alloc] initWithData:[image TIFFRepresentation]];
376 CIContext *context = [[CIContext alloc] init];
377 CIFilter *clamp = [CIFilter filterWithName:@"CIAffineClamp"];
378 [clamp setValue:[NSAffineTransform transform] forKey:@"inputTransform"];
379 [clamp setValue:ciImage forKey: kCIInputImageKey];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400380 CIFilter* bluerFilter = [CIFilter filterWithName:@"CIGaussianBlur"];
381 [bluerFilter setDefaults];
Kateryna Kostiuk89398b62018-11-20 16:45:04 -0500382 [bluerFilter setValue:[NSNumber numberWithFloat: 9] forKey:@"inputRadius"];
383 [bluerFilter setValue:[clamp valueForKey:kCIOutputImageKey] forKey: kCIInputImageKey];
384 CIImage *result = [bluerFilter valueForKey:kCIOutputImageKey];
385 CGRect extent = [result extent];
386 CGImageRef cgImage = [context createCGImage:result fromRect: [ciImage extent]];
387 NSImage *bluredImage = [[NSImage alloc] initWithCGImage:cgImage size:NSSizeFromCGSize(CGSizeMake(image.size.width, image.size.height))];
388 backgroundImage.layer.contents = bluredImage;
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400389 [backgroundImage setHidden:NO];
390 } else {
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400391 [bluerBackgroundEffect setFillColor:[NSColor ringDarkGrey]];
392 [backgroundImage setHidden:YES];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400393 }
394}
395
Kateryna Kostiuk133364b2018-10-31 11:36:08 -0400396-(NSImage *) getContactImageOfSize: (double) size withDefaultAvatar:(BOOL) shouldDrawDefault {
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400397 auto* convModel = accountInfo_->conversationModel.get();
398 auto convIt = getConversationFromUid(convUid_, *convModel);
399 if (convIt == convModel->allFilteredConversations().end()) {
400 return nil;
401 }
402 if(shouldDrawDefault) {
403 auto& imgManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
404 QVariant photo = imgManip.conversationPhoto(*convIt, *accountInfo_, QSize(size, size), NO);
405 return QtMac::toNSImage(qvariant_cast<QPixmap>(photo));
406 }
407 auto contact = accountInfo_->contactModel->getContact(convIt->participants[0]);
408 NSData *imageData = [[NSData alloc] initWithBase64EncodedString:@(contact.profileInfo.avatar.c_str()) options:NSDataBase64DecodingIgnoreUnknownCharacters];
409 return [[NSImage alloc] initWithData:imageData];
410}
411
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400412-(void) setupContactInfo:(NSImageView*)imageView
413{
414 [imageView setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
415}
416
Olivier Soldano994971f2017-12-05 16:30:12 -0500417-(void) setupIncoming:(const std::string&) callId
Alexandre Lision4d9905e2016-01-22 12:01:36 -0500418{
Olivier Soldano994971f2017-12-05 16:30:12 -0500419 if (accountInfo_ == nil)
420 return;
421
422 auto* callModel = accountInfo_->callModel.get();
423 if (not callModel->hasCall(callUid_)) {
424 return;
425 }
426
427 auto call = callModel->getCall(callUid_);
428 auto* convModel = accountInfo_->conversationModel.get();
429
430 [ringingPanel setHidden:NO];
431 [controlsPanel setHidden:YES];
432 [headerContainer setHidden:YES];
Anthony Léonard6f819752018-01-05 09:53:40 -0500433 auto it = getConversationFromUid(convUid_, *convModel);
Olivier Soldano994971f2017-12-05 16:30:12 -0500434 if (it != convModel->allFilteredConversations().end()) {
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400435 [incomingPersonPhoto setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
Anthony Léonard8ee26782018-01-05 10:23:04 -0500436 [incomingDisplayName setStringValue:bestNameForConversation(*it, *convModel)];
Olivier Soldano994971f2017-12-05 16:30:12 -0500437 }
438}
439
Olivier Soldano994971f2017-12-05 16:30:12 -0500440-(void) setupConference:(Call*) c
441{
442 [videoView setShouldAcceptInteractions:YES];
443 [self.chatButton setHidden:NO];
Olivier Soldano994971f2017-12-05 16:30:12 -0500444 [self.addParticipantButton setHidden:NO];
445 [self.transferButton setHidden:YES];
446}
447
448-(void)collapseRightView
449{
450 NSView *right = [[splitView subviews] objectAtIndex:1];
451 NSView *left = [[splitView subviews] objectAtIndex:0];
452 NSRect leftFrame = [left frame];
453 [right setHidden:YES];
454 [splitView display];
455}
456
457- (void) changeCallSelection:(std::string&)callUid
458{
459 if (accountInfo_ == nil)
460 return;
461
462 auto* callModel = accountInfo_->callModel.get();
463 if (not callModel->hasCall(callUid)) {
464 return;
465 }
466
Alexandre Lision4d9905e2016-01-22 12:01:36 -0500467 QObject::disconnect(self.selectedCallChanged);
Olivier Soldano994971f2017-12-05 16:30:12 -0500468 self.selectedCallChanged = QObject::connect(callModel,
469 &lrc::api::NewCallModel::callStatusChanged,
470 [self](const std::string callId) {
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400471 [self updateCall];
Alexandre Lision4d9905e2016-01-22 12:01:36 -0500472 });
Alexandre Lisionc5148052015-03-04 15:10:35 -0500473}
474
475-(void) connectVideoSignals
476{
Olivier Soldano994971f2017-12-05 16:30:12 -0500477 if (accountInfo_ == nil)
478 return;
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400479 [self connectRenderer];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500480}
481
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400482-(void) connectRenderer
Alexandre Lisionc5148052015-03-04 15:10:35 -0500483{
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400484 QObject::disconnect(renderConnections.frameUpdated);
485 QObject::disconnect(renderConnections.stopped);
486 QObject::disconnect(renderConnections.started);
487 renderConnections.started =
488 QObject::connect(mediaModel,
489 &lrc::api::AVModel::rendererStarted,
490 [=](const std::string& id) {
491 if (id == lrc::api::video::PREVIEW_RENDERER_ID) {
492 [self.previewView setHidden:NO];
493 self.previewView.stopRendering = false;
494 } else {
495 [self mouseIsMoving: NO];
496 self.videoMTKView.stopRendering = false;
497 [self.videoMTKView setHidden:NO];
498 [bluerBackgroundEffect setHidden:YES];
499 [backgroundImage setHidden:YES];
500 [videoView setShouldAcceptInteractions:YES];
501 }
502 QObject::disconnect(renderConnections.frameUpdated);
503 renderConnections.frameUpdated =
504 QObject::connect(mediaModel,
505 &lrc::api::AVModel::frameUpdated,
506 [=](const std::string& id) {
507 if (id == lrc::api::video::PREVIEW_RENDERER_ID) {
508 auto renderer = &mediaModel->getRenderer(lrc::api::video::PREVIEW_RENDERER_ID);
509 if(!renderer->isRendering()) {
510 return;
511 }
512 [self renderer: renderer renderFrameForPreviewView:previewView];
513
514 } else {
515 auto renderer = &mediaModel->getRenderer(id);
516 if(!renderer->isRendering()) {
517 return;
518 }
519 [self renderer:renderer renderFrameForDistantView: self.videoMTKView];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400520 }
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400521 });
Alexandre Lisionc5148052015-03-04 15:10:35 -0500522 });
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400523 renderConnections.stopped =
524 QObject::connect(mediaModel,
525 &lrc::api::AVModel::rendererStopped,
526 [=](const std::string& id) {
527 QObject::disconnect(renderConnections.frameUpdated);
528 if (id == lrc::api::video::PREVIEW_RENDERER_ID) {
529 [self.previewView setHidden:YES];
530 self.previewView.stopRendering = true;
531 } else {
532 [self mouseIsMoving: YES];
533 self.videoMTKView.stopRendering = true;
534 [self.videoMTKView setHidden:YES];
535 [bluerBackgroundEffect setHidden:NO];
536 [backgroundImage setHidden:NO];
537 [videoView setShouldAcceptInteractions:NO];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400538 }
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400539 });
540 renderConnections.frameUpdated =
541 QObject::connect(mediaModel,
542 &lrc::api::AVModel::frameUpdated,
543 [=](const std::string& id) {
544 if (id == lrc::api::video::PREVIEW_RENDERER_ID) {
545 auto renderer = &mediaModel->getRenderer(lrc::api::video::PREVIEW_RENDERER_ID);
546 if(!renderer->isRendering()) {
547 return;
548 }
549 [self renderer: renderer renderFrameForPreviewView:previewView];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500550
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400551 } else {
552 auto renderer = &mediaModel->getRenderer(id);
553 if(!renderer->isRendering()) {
554 return;
555 }
556 [self renderer:renderer renderFrameForDistantView: self.videoMTKView];
Kateryna Kostiuk732b4062018-11-28 09:59:28 -0500557 }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500558 });
559}
560
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400561-(void) renderer: (const lrc::api::video::Renderer*)renderer renderFrameForPreviewView:(CallMTKView*) view
Alexandre Lisionc5148052015-03-04 15:10:35 -0500562{
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400563 @autoreleasepool {
564 auto framePtr = renderer->currentAVFrame();
565 auto frame = framePtr.get();
566 if(!frame || !frame->width || !frame->height) {
567 return;
568 }
569 auto frameSize = CGSizeMake(frame->width, frame->height);
570 auto rotation = 0;
571 if (frame->data[3] != NULL && (CVPixelBufferRef)frame->data[3]) {
572 [view renderWithPixelBuffer:(CVPixelBufferRef)frame->data[3]
573 size: frameSize
574 rotation: rotation
575 fillFrame: true];
576 return;
577 }
578 else if (CVPixelBufferRef pixelBuffer = [self getBufferForPreviewFromFrame:frame]) {
579 [view renderWithPixelBuffer: pixelBuffer
580 size: frameSize
581 rotation: rotation
582 fillFrame: true];
583 }
584 }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500585}
586
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400587-(void) renderer: (const lrc::api::video::Renderer*)renderer renderFrameForDistantView:(CallMTKView*) view
Anthony Léonard14e7bf32017-06-08 08:13:16 -0400588{
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400589 @autoreleasepool {
590 auto framePtr = renderer->currentAVFrame();
591 auto frame = framePtr.get();
592 if(!frame || !frame->width || !frame->height) {
593 return;
594 }
595 auto frameSize = CGSizeMake(frame->width, frame->height);
596 auto rotation = 0;
597 if (auto matrix = av_frame_get_side_data(frame, AV_FRAME_DATA_DISPLAYMATRIX)) {
598 const int32_t* data = reinterpret_cast<int32_t*>(matrix->data);
599 rotation = av_display_rotation_get(data);
600 }
601 if (frame->data[3] != NULL && (CVPixelBufferRef)frame->data[3]) {
602 [view renderWithPixelBuffer: (CVPixelBufferRef)frame->data[3]
603 size: frameSize
604 rotation: rotation
605 fillFrame: false];
606 }
607 if (CVPixelBufferRef pixelBuffer = [self getBufferForDistantViewFromFrame:frame]) {
608 [view renderWithPixelBuffer: pixelBuffer
609 size: frameSize
610 rotation: rotation
611 fillFrame: false];
612 }
Kateryna Kostiukbcf24e92018-11-20 10:09:06 -0500613 }
Anthony Léonard14e7bf32017-06-08 08:13:16 -0400614}
615
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400616-(CVPixelBufferRef) getBufferForPreviewFromFrame:(const AVFrame*)frame {
617 if(!frame || !frame->data[0] || !frame->data[1]) {
618 return nil;
619 }
620 CVReturn theError;
621 bool createPool = false;
622 if (!pixelBufferPoolPreview) {
623 createPool = true;
624 } else {
625 NSDictionary* atributes = (__bridge NSDictionary*)CVPixelBufferPoolGetAttributes(pixelBufferPoolPreview);
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400626 if(!atributes)
627 atributes = (__bridge NSDictionary*)CVPixelBufferPoolGetPixelBufferAttributes(pixelBufferPoolPreview);
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400628 int width = [[atributes objectForKey:(NSString*)kCVPixelBufferWidthKey] intValue];
629 int height = [[atributes objectForKey:(NSString*)kCVPixelBufferHeightKey] intValue];
630 if (width != frame->width || height != frame->height) {
631 createPool = true;
632 }
633 }
634 if (createPool) {
635 CVPixelBufferPoolRelease(pixelBufferPoolPreview);
636 CVPixelBufferRelease(pixelBufferPreview);
637 pixelBufferPreview = nil;
638 pixelBufferPoolPreview = nil;
639 NSMutableDictionary* attributes = [NSMutableDictionary dictionary];
640 [attributes setObject:[NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange] forKey:(NSString*)kCVPixelBufferPixelFormatTypeKey];
641 [attributes setObject:[NSNumber numberWithInt:frame->width] forKey: (NSString*)kCVPixelBufferWidthKey];
642 [attributes setObject:[NSNumber numberWithInt:frame->height] forKey: (NSString*)kCVPixelBufferHeightKey];
643 [attributes setObject:@(frame->linesize[0]) forKey:(NSString*)kCVPixelBufferBytesPerRowAlignmentKey];
644 [attributes setObject:[NSDictionary dictionary] forKey:(NSString*)kCVPixelBufferIOSurfacePropertiesKey];
645 theError = CVPixelBufferPoolCreate(kCFAllocatorDefault, NULL, (__bridge CFDictionaryRef) attributes, &pixelBufferPoolPreview);
646 if (theError != kCVReturnSuccess) {
647 NSLog(@"CVPixelBufferPoolCreate Failed");
648 return nil;
649 }
650 }
651 if(!pixelBufferPreview) {
652 theError = CVPixelBufferPoolCreatePixelBuffer(NULL, pixelBufferPoolPreview, &pixelBufferPreview);
653 if(theError != kCVReturnSuccess) {
654 NSLog(@"CVPixelBufferPoolCreatePixelBuffer Failed");
655 return nil;
656 }
657 }
658 theError = CVPixelBufferLockBaseAddress(pixelBufferPreview, 0);
659 if (theError != kCVReturnSuccess) {
660 NSLog(@"lock error");
661 return nil;
662 }
663 size_t bytePerRowY = CVPixelBufferGetBytesPerRowOfPlane(pixelBufferPreview, 0);
664 size_t bytesPerRowUV = CVPixelBufferGetBytesPerRowOfPlane(pixelBufferPreview, 1);
665 void* base = CVPixelBufferGetBaseAddressOfPlane(pixelBufferPreview, 0);
666 memcpy(base, frame->data[0], bytePerRowY * frame->height);
667 base = CVPixelBufferGetBaseAddressOfPlane(pixelBufferPreview, 1);
668 memcpy(base, frame->data[1], bytesPerRowUV * frame->height/2);
669 CVPixelBufferUnlockBaseAddress(pixelBufferPreview, 0);
670 return pixelBufferPreview;
671}
672
673-(CVPixelBufferRef) getBufferForDistantViewFromFrame:(const AVFrame*)frame {
674 if(!frame || !frame->data[0] || !frame->data[1]) {
675 return nil;
676 }
677 CVReturn theError;
678 bool createPool = false;
679 if (!pixelBufferPoolDistantView){
680 createPool = true;
681 }
682 NSDictionary* atributes = (__bridge NSDictionary*)CVPixelBufferPoolGetPixelBufferAttributes(pixelBufferPoolDistantView);
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400683 if(!atributes)
684 atributes = (__bridge NSDictionary*)CVPixelBufferPoolGetPixelBufferAttributes(pixelBufferPoolDistantView);
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400685 int width = [[atributes objectForKey:(NSString*)kCVPixelBufferWidthKey] intValue];
686 int height = [[atributes objectForKey:(NSString*)kCVPixelBufferHeightKey] intValue];
687 if (width != frame->width || height != frame->height) {
688 createPool = true;
689 }
690 if (createPool) {
691 CVPixelBufferPoolRelease(pixelBufferPoolDistantView);
692 CVPixelBufferRelease(pixelBufferDistantView);
693 pixelBufferDistantView = nil;
694 pixelBufferPoolDistantView = nil;
695 NSMutableDictionary* attributes = [NSMutableDictionary dictionary];
696 [attributes setObject:[NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange] forKey:(NSString*)kCVPixelBufferPixelFormatTypeKey];
697 [attributes setObject:[NSNumber numberWithInt:frame->width] forKey: (NSString*)kCVPixelBufferWidthKey];
698 [attributes setObject:[NSNumber numberWithInt:frame->height] forKey: (NSString*)kCVPixelBufferHeightKey];
699 [attributes setObject:@(frame->linesize[0]) forKey:(NSString*)kCVPixelBufferBytesPerRowAlignmentKey];
700 [attributes setObject:[NSDictionary dictionary] forKey:(NSString*)kCVPixelBufferIOSurfacePropertiesKey];
701 theError = CVPixelBufferPoolCreate(kCFAllocatorDefault, NULL, (__bridge CFDictionaryRef) attributes, &pixelBufferPoolDistantView);
702 if (theError != kCVReturnSuccess) {
703 return nil;
704 NSLog(@"CVPixelBufferPoolCreate Failed");
705 }
706 }
707 if(!pixelBufferDistantView) {
708 theError = CVPixelBufferPoolCreatePixelBuffer(NULL, pixelBufferPoolDistantView, &pixelBufferDistantView);
709 if(theError != kCVReturnSuccess) {
710 return nil;
711 NSLog(@"CVPixelBufferPoolCreatePixelBuffer Failed");
712 }
713 }
714 theError = CVPixelBufferLockBaseAddress(pixelBufferDistantView, 0);
715 if (theError != kCVReturnSuccess) {
716 return nil;
717 NSLog(@"lock error");
718 }
719 size_t bytePerRowY = CVPixelBufferGetBytesPerRowOfPlane(pixelBufferDistantView, 0);
720 size_t bytesPerRowUV = CVPixelBufferGetBytesPerRowOfPlane(pixelBufferDistantView, 1);
721 void* base = CVPixelBufferGetBaseAddressOfPlane(pixelBufferDistantView, 0);
722 memcpy(base, frame->data[0], bytePerRowY * frame->height);
723 base = CVPixelBufferGetBaseAddressOfPlane(pixelBufferDistantView, 1);
724 uint32_t size = frame->linesize[1] * frame->height / 2;
725 uint8_t* dstData = new uint8_t[2 * size];
726 uint8_t * firstData = new uint8_t[size];
727 memcpy(firstData, frame->data[1], size);
728 uint8_t * secondData = new uint8_t[size];
729 memcpy(secondData, frame->data[2], size);
730 for (int i = 0; i < 2 * size; i++){
731 if (i % 2 == 0){
732 dstData[i] = firstData[i/2];
733 }else {
734 dstData[i] = secondData[i/2];
735 }
736 }
737 memcpy(base, dstData, bytesPerRowUV * frame->height/2);
738 CVPixelBufferUnlockBaseAddress(pixelBufferDistantView, 0);
739 free(dstData);
740 free(firstData);
741 free(secondData);
742 return pixelBufferDistantView;
743}
744
Alexandre Lisionc5148052015-03-04 15:10:35 -0500745- (void) initFrame
746{
747 [self.view setFrame:self.view.superview.bounds];
748 [self.view setHidden:YES];
749 self.view.layer.position = self.view.frame.origin;
Alexandre Lisionbb5dbcd2015-07-09 16:36:47 -0400750 [self collapseRightView];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400751 self.testView = [[NSImageView alloc] initWithFrame:self.view.frame];
752 [self.view addSubview:self.testView];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500753}
754
755# pragma private IN/OUT animations
756
Olivier Soldano994971f2017-12-05 16:30:12 -0500757-(void)uncollapseRightView
758{
759 NSView *left = [[splitView subviews] objectAtIndex:0];
760 NSView *right = [[splitView subviews] objectAtIndex:1];
761 [right setHidden:NO];
762
763 CGFloat dividerThickness = [splitView dividerThickness];
764
765 // get the different frames
766 NSRect leftFrame = [left frame];
767 NSRect rightFrame = [right frame];
768
769 leftFrame.size.width = (leftFrame.size.width - rightFrame.size.width - dividerThickness);
770 rightFrame.origin.x = leftFrame.size.width + dividerThickness;
771 [left setFrameSize:leftFrame.size];
772 [right setFrame:rightFrame];
773 [splitView display];
774
775 [self.chatVC takeFocus];
776}
777
Alexandre Lisionc5148052015-03-04 15:10:35 -0500778-(void) cleanUp
779{
Anthony Léonard5c287642018-02-21 15:54:19 -0500780 if(self.splitView.isInFullScreenMode)
781 [self.splitView exitFullScreenModeWithOptions:nil];
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400782 QObject::disconnect(renderConnections.frameUpdated);
783 QObject::disconnect(renderConnections.started);
784 QObject::disconnect(renderConnections.stopped);
Anthony Léonardc0013bb2018-02-20 16:23:02 -0500785 QObject::disconnect(self.messageConnection);
Alexandre Lision883719f2015-10-22 17:37:45 -0400786
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500787 [self.chatButton setHidden:YES];
Alexandre Lision4baba4c2016-02-11 13:00:57 -0500788 [self.chatButton setPressed:NO];
Alexandre Lision883719f2015-10-22 17:37:45 -0400789 [self collapseRightView];
Alexandre Lision57227772016-01-15 17:19:36 -0500790
791 [personLabel setStringValue:@""];
792 [timeSpentLabel setStringValue:@""];
793 [stateLabel setStringValue:@""];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400794 //audio view
795 [audioCallTime setStringValue:@""];
796 [audioCallPersonId setStringValue:@""];
797 [audioCallPersonLabel setStringValue:@""];
798 [audioCallView setHidden:YES];
799 [audioCallPhoto setImage:nil];
800 //background view
801 [bluerBackgroundEffect setHidden:NO];
802 [backgroundImage setHidden:NO];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400803 //outgoing view
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400804 [outgoingPersonLabel setStringValue:@""];
805 [outgoingStateLabel setStringValue:@""];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400806 [self.previewView setHidden:YES];
807 [self.videoMTKView setHidden:YES];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500808}
809
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400810-(void) setupCallView
811{
812 if (accountInfo_ == nil)
813 return;
814
815 auto* callModel = accountInfo_->callModel.get();
816 auto* convModel = accountInfo_->conversationModel.get();
817
818 // when call comes in we want to show the controls/header
Kateryna Kostiuk1004caa2018-03-29 13:48:07 -0400819 [self mouseIsMoving: YES];
820 [videoView setShouldAcceptInteractions: NO];
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400821
822 [self connectVideoSignals];
823 /* check if text media is already present */
824 if(not callModel->hasCall(callUid_))
825 return;
826
827 [loadingIndicator setAnimates:YES];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400828 [self updateCall];
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400829
830 /* monitor media for messaging text messaging */
831 QObject::disconnect(self.messageConnection);
832 self.messageConnection = QObject::connect(convModel,
833 &lrc::api::ConversationModel::interactionStatusUpdated,
834 [self] (std::string convUid,
835 uint64_t msgId,
836 lrc::api::interaction::Info msg) {
837 if (msg.type == lrc::api::interaction::Type::TEXT) {
838 if(not [[self splitView] isSubviewCollapsed:[[[self splitView] subviews] objectAtIndex: 1]]){
839 return;
840 }
841 [self uncollapseRightView];
842 }
843 });
Kateryna Kostiuk133364b2018-10-31 11:36:08 -0400844 //monitor for updated profile
845 QObject::disconnect(self.profileUpdatedConnection);
846 self.profileUpdatedConnection =
847 QObject::connect(accountInfo_->contactModel.get(),
848 &lrc::api::ContactModel::contactAdded,
849 [self](const std::string &contactUri) {
850 auto convIt = getConversationFromUid(convUid_, *accountInfo_->conversationModel.get());
851 if (convIt == accountInfo_->conversationModel->allFilteredConversations().end()) {
852 return;
853 }
854 if (convIt->participants.empty()) {
855 return;
856
857 }
858 auto& contact = accountInfo_->contactModel->getContact(convIt->participants[0]);
859 if (contact.profileInfo.type == lrc::api::profile::Type::RING && contact.profileInfo.uri == contactUri)
860 accountInfo_->conversationModel->makePermanent(convUid_);
861 [incomingPersonPhoto setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
862 [outgoingPhoto setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
Kateryna Kostiuk732b4062018-11-28 09:59:28 -0500863 [self.delegate conversationInfoUpdatedFor:convUid_];
Kateryna Kostiuk133364b2018-10-31 11:36:08 -0400864 if(accountInfo_->callModel.get()->getCall(callUid_).isAudioOnly) {
865 [audioCallPhoto setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
866 [self setBackground];
867 return;
868 }
869 [personPhoto setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
870 });
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400871}
872
873-(void) showWithAnimation:(BOOL)animate
874{
875 if (!animate) {
876 [self.view setHidden:NO];
877 [self setupCallView];
878 return;
879 }
880
881 CGRect frame = CGRectOffset(self.view.superview.bounds, -self.view.superview.bounds.size.width, 0);
882 [self.view setHidden:NO];
883
884 [CATransaction begin];
885 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
886 [animation setFromValue:[NSValue valueWithPoint:frame.origin]];
887 [animation setToValue:[NSValue valueWithPoint:self.view.superview.bounds.origin]];
888 [animation setDuration:0.2f];
889 [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]];
890 [CATransaction setCompletionBlock:^{
891 [self setupCallView];
892 }];
893
894 [self.view.layer addAnimation:animation forKey:animation.keyPath];
895 [CATransaction commit];
896}
897
898-(void) hideWithAnimation:(BOOL)animate
Alexandre Lisionc5148052015-03-04 15:10:35 -0500899{
Alexandre Lisionc5148052015-03-04 15:10:35 -0500900 if(self.view.frame.origin.x < 0) {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500901 return;
902 }
903
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400904 if (!animate) {
905 [self.view setHidden:YES];
906 return;
907 }
908
Alexandre Lisionc5148052015-03-04 15:10:35 -0500909 CGRect frame = CGRectOffset(self.view.frame, -self.view.frame.size.width, 0);
910 [CATransaction begin];
911 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
912 [animation setFromValue:[NSValue valueWithPoint:self.view.frame.origin]];
913 [animation setToValue:[NSValue valueWithPoint:frame.origin]];
914 [animation setDuration:0.2f];
Alexandre Lision22d615b2016-01-25 12:54:34 -0500915 [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500916
917 [CATransaction setCompletionBlock:^{
918 [self.view setHidden:YES];
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400919 // first make sure everything is disconnected
Alexandre Lisionc5148052015-03-04 15:10:35 -0500920 [self cleanUp];
Olivier Soldano994971f2017-12-05 16:30:12 -0500921// if (currentCall_) {
922// [self animateIn];
923// }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500924 }];
925 [self.view.layer addAnimation:animation forKey:animation.keyPath];
Alexandre Lisiona1c6d752015-06-23 12:27:38 -0400926
927 [self.view.layer setPosition:frame.origin];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500928 [CATransaction commit];
929}
930
931/**
932 * Debug purpose
933 */
934-(void) dumpFrame:(CGRect) frame WithName:(NSString*) name
935{
936 NSLog(@"frame %@ : %f %f %f %f \n\n",name ,frame.origin.x, frame.origin.y, frame.size.width, frame.size.height);
937}
938
Alexandre Lision58cab672015-06-09 15:25:40 -0400939#pragma mark - Button methods
940
Alexandre Lisionc5148052015-03-04 15:10:35 -0500941- (IBAction)hangUp:(id)sender {
Olivier Soldano994971f2017-12-05 16:30:12 -0500942 if (accountInfo_ == nil)
943 return;
944
945 auto* callModel = accountInfo_->callModel.get();
Olivier Soldano994971f2017-12-05 16:30:12 -0500946 callModel->hangUp(callUid_);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500947}
948
949- (IBAction)accept:(id)sender {
Olivier Soldano994971f2017-12-05 16:30:12 -0500950 if (accountInfo_ == nil)
951 return;
952
Anthony Léonardf584cc52018-01-19 12:00:19 -0500953 // If we accept a conversation with a non trusted contact, we first accept it
954 auto convIt = getConversationFromUid(convUid_, *accountInfo_->conversationModel.get());
955 if (convIt != accountInfo_->conversationModel->allFilteredConversations().end()) {
956 auto& contact = accountInfo_->contactModel->getContact(convIt->participants[0]);
957 if (contact.profileInfo.type == lrc::api::profile::Type::PENDING)
958 accountInfo_->conversationModel->makePermanent(convUid_);
959 }
960
Olivier Soldano994971f2017-12-05 16:30:12 -0500961 auto* callModel = accountInfo_->callModel.get();
962
963 callModel->accept(callUid_);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500964}
965
966- (IBAction)toggleRecording:(id)sender {
Olivier Soldano994971f2017-12-05 16:30:12 -0500967 if (accountInfo_ == nil)
968 return;
969
970 auto* callModel = accountInfo_->callModel.get();
971
972 callModel->toggleAudioRecord(callUid_);
Kateryna Kostiuk54d76972018-06-07 15:45:07 -0400973 [recordOnOffButton setPressed:!recordOnOffButton.isPressed];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500974}
975
976- (IBAction)toggleHold:(id)sender {
Olivier Soldano994971f2017-12-05 16:30:12 -0500977 if (accountInfo_ == nil)
978 return;
979
980 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiuk54d76972018-06-07 15:45:07 -0400981 auto currentCall = callModel->getCall(callUid_);
982 if (currentCall.status != lrc::api::call::Status::PAUSED) {
983 holdOnOffButton.image = [NSImage imageNamed:@"ic_action_holdoff.png"];
984 } else {
985 holdOnOffButton.image = [NSImage imageNamed:@"ic_action_hold.png"];
986 }
Olivier Soldano994971f2017-12-05 16:30:12 -0500987
988 callModel->togglePause(callUid_);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500989}
990
Alexandre Lision62005312016-01-28 15:55:16 -0500991- (IBAction)toggleAdvancedControls:(id)sender {
992 [advancedButton setPressed:!advancedButton.isPressed];
993 [advancedPanel setHidden:![advancedButton isPressed]];
994}
995
996- (IBAction)showDialpad:(id)sender {
997 AppDelegate* appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
998 [appDelegate showDialpad];
999}
1000
Alexandre Lision58cab672015-06-09 15:25:40 -04001001-(IBAction)toggleChat:(id)sender;
1002{
1003 BOOL rightViewCollapsed = [[self splitView] isSubviewCollapsed:[[[self splitView] subviews] objectAtIndex: 1]];
1004 if (rightViewCollapsed) {
1005 [self uncollapseRightView];
Alexandre Lision58cab672015-06-09 15:25:40 -04001006 } else {
1007 [self collapseRightView];
1008 }
Alexandre Lision62005312016-01-28 15:55:16 -05001009 [chatButton setPressed:rightViewCollapsed];
Alexandre Lision58cab672015-06-09 15:25:40 -04001010}
1011
Alexandre Lisiond18fa272015-06-15 11:18:03 -04001012- (IBAction)muteAudio:(id)sender
1013{
Olivier Soldano994971f2017-12-05 16:30:12 -05001014 if (accountInfo_ == nil)
1015 return;
1016
1017 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiuk54d76972018-06-07 15:45:07 -04001018 auto currentCall = callModel->getCall(callUid_);
1019 if (currentCall.audioMuted) {
1020 muteAudioButton.image = [NSImage imageNamed:@"ic_action_audio.png"];
1021 } else {
1022 muteAudioButton.image = [NSImage imageNamed:@"ic_action_mute_audio.png"];
1023 }
Olivier Soldano994971f2017-12-05 16:30:12 -05001024 callModel->toggleMedia(callUid_, lrc::api::NewCallModel::Media::AUDIO);
Alexandre Lisiond18fa272015-06-15 11:18:03 -04001025}
1026
1027- (IBAction)muteVideo:(id)sender
1028{
Olivier Soldano994971f2017-12-05 16:30:12 -05001029 if (accountInfo_ == nil)
1030 return;
Olivier Soldano994971f2017-12-05 16:30:12 -05001031 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiuk54d76972018-06-07 15:45:07 -04001032 auto currentCall = callModel->getCall(callUid_);
1033 if(!currentCall.isAudioOnly) {
1034 if (currentCall.videoMuted) {
1035 muteVideoButton.image = [NSImage imageNamed:@"ic_action_video.png"];
1036 } else {
1037 muteVideoButton.image = [NSImage imageNamed:@"ic_action_mute_video.png"];
1038 }
1039 }
Olivier Soldano994971f2017-12-05 16:30:12 -05001040 callModel->toggleMedia(callUid_, lrc::api::NewCallModel::Media::VIDEO);
Alexandre Lisiond18fa272015-06-15 11:18:03 -04001041}
Alexandre Lision883719f2015-10-22 17:37:45 -04001042
1043- (IBAction)toggleTransferView:(id)sender {
Kateryna Kostiuk1ba5a012018-12-12 17:07:38 -05001044
Alexandre Lisionf23ec5a2015-07-16 11:24:06 -04001045}
Alexandre Lisiond18fa272015-06-15 11:18:03 -04001046
Alexandre Lision89edc6a2015-11-09 11:30:47 -05001047- (IBAction)toggleAddParticipantView:(id)sender {
Kateryna Kostiuk1ba5a012018-12-12 17:07:38 -05001048
Alexandre Lision89edc6a2015-11-09 11:30:47 -05001049}
1050
Alexandre Lision2db8f472015-07-22 15:05:46 -04001051#pragma mark - NSPopOverDelegate
1052
Alexandre Lision266fca02015-09-28 14:47:05 -04001053- (void)popoverWillClose:(NSNotification *)notification
1054{
Alexandre Lision89edc6a2015-11-09 11:30:47 -05001055 if (_brokerPopoverVC != nullptr) {
1056 [_brokerPopoverVC performClose:self];
1057 _brokerPopoverVC = NULL;
Alexandre Lision883719f2015-10-22 17:37:45 -04001058 }
Alexandre Lision266fca02015-09-28 14:47:05 -04001059
Alexandre Lision2db8f472015-07-22 15:05:46 -04001060 if (self.addToContactPopover != nullptr) {
1061 [self.addToContactPopover performClose:self];
1062 self.addToContactPopover = NULL;
1063 }
Alexandre Lision883719f2015-10-22 17:37:45 -04001064
Alexandre Lision62005312016-01-28 15:55:16 -05001065 [self.transferButton setPressed:NO];
Alexandre Lision89edc6a2015-11-09 11:30:47 -05001066 [self.addParticipantButton setState:NSOffState];
Alexandre Lision883719f2015-10-22 17:37:45 -04001067}
1068
1069- (void)popoverDidClose:(NSNotification *)notification
1070{
1071 [videoView setCallDelegate:self];
Alexandre Lision2db8f472015-07-22 15:05:46 -04001072}
1073
1074#pragma mark - ContactLinkedDelegate
1075
1076- (void)contactLinked
1077{
1078 if (self.addToContactPopover != nullptr) {
1079 [self.addToContactPopover performClose:self];
1080 self.addToContactPopover = NULL;
1081 }
1082}
1083
Alexandre Lision58cab672015-06-09 15:25:40 -04001084#pragma mark - NSSplitViewDelegate
1085
1086/* 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.
1087 */
1088- (BOOL)splitView:(NSSplitView *)splitView shouldCollapseSubview:(NSView *)subview forDoubleClickOnDividerAtIndex:(NSInteger)dividerIndex;
1089{
1090 NSView* rightView = [[splitView subviews] objectAtIndex:1];
1091 return ([subview isEqual:rightView]);
1092}
1093
1094
1095- (BOOL)splitView:(NSSplitView *)splitView canCollapseSubview:(NSView *)subview;
1096{
1097 NSView* rightView = [[splitView subviews] objectAtIndex:1];
1098 return ([subview isEqual:rightView]);
1099}
1100
1101
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -04001102# pragma mark - CallnDelegate
Alexandre Lision58cab672015-06-09 15:25:40 -04001103
1104- (void) callShouldToggleFullScreen
1105{
1106 if(self.splitView.isInFullScreenMode)
1107 [self.splitView exitFullScreenModeWithOptions:nil];
1108 else {
1109 NSApplicationPresentationOptions options = NSApplicationPresentationDefault +NSApplicationPresentationAutoHideDock +
1110 NSApplicationPresentationAutoHideMenuBar + NSApplicationPresentationAutoHideToolbar;
1111 NSDictionary *opts = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInt:options],
1112 NSFullScreenModeApplicationPresentationOptions, nil];
1113
1114 [self.splitView enterFullScreenMode:[NSScreen mainScreen] withOptions:opts];
1115 }
1116}
1117
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -04001118-(void) mouseIsMoving:(BOOL) move
1119{
1120 [[controlsPanel animator] setAlphaValue:move]; // fade out
1121 [[headerContainer animator] setAlphaValue:move];
1122}
1123
Kateryna Kostiuk89398b62018-11-20 16:45:04 -05001124- (BOOL)splitView:(NSSplitView *)splitView shouldHideDividerAtIndex:(NSInteger)dividerIndex
1125{
1126 return YES;
1127}
1128
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -04001129-(void) screenShare {
1130 NSScreen *mainScreen = [NSScreen mainScreen];
1131 NSRect screenFrame = mainScreen.frame;
1132 QRect captureRect = QRect(screenFrame.origin.x, screenFrame.origin.y, screenFrame.size.width, screenFrame.size.height);
1133 mediaModel->setDisplay(0, screenFrame.origin.x, screenFrame.origin.y, screenFrame.size.width, screenFrame.size.height);
1134}
1135-(void) switchToDevice:(int)deviceID {
1136 auto devices = mediaModel->getDevices();
1137 auto device = devices[deviceID];
1138 mediaModel->switchInputTo(device);
1139}
1140
1141-(std::vector<std::string>) getDeviceList {
1142 return mediaModel->getDevices();
1143}
1144
1145-(void) switchToFile:(std::string)uri {
1146 mediaModel->setInputFile(QUrl::fromLocalFile(uri.c_str()).toLocalFile().toStdString());
1147}
1148
Alexandre Lisionc5148052015-03-04 15:10:35 -05001149@end