blob: fd9a078d3264697307c42b89e5faab1afff799ab [file] [log] [blame]
Alexandre Lisionc5148052015-03-04 15:10:35 -05001/*
Alexandre Lision9fe374b2016-01-06 10:17:31 -05002 * Copyright (C) 2015-2016 Savoir-faire Linux Inc.
Alexandre Lisionc5148052015-03-04 15:10:35 -05003 * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Alexandre Lisionc5148052015-03-04 15:10:35 -050018 */
19#import "CurrentCallVC.h"
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -040020extern "C" {
21#import "libavutil/frame.h"
22#import "libavutil/display.h"
23}
Alexandre Lisionc5148052015-03-04 15:10:35 -050024
25#import <QuartzCore/QuartzCore.h>
26
Alexandre Lision210fe212016-01-27 11:15:13 -050027///Qt
28#import <QMimeData>
29#import <QtMacExtras/qmacfunctions.h>
30#import <QtCore/qabstractitemmodel.h>
Alexandre Lision210fe212016-01-27 11:15:13 -050031#import <QPixmap>
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -040032#import <QUrl>
Alexandre Lision210fe212016-01-27 11:15:13 -050033
34///LRC
Alexandre Lisionc5148052015-03-04 15:10:35 -050035#import <video/renderer.h>
Olivier Soldano994971f2017-12-05 16:30:12 -050036#import <api/newcallmodel.h>
37#import <api/call.h>
38#import <api/conversationmodel.h>
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -040039#import <api/avmodel.h>
Alexandre Lision210fe212016-01-27 11:15:13 -050040#import <globalinstances.h>
Alexandre Lisionc5148052015-03-04 15:10:35 -050041
Alexandre Lision62005312016-01-28 15:55:16 -050042#import "AppDelegate.h"
Alexandre Lisionf47a2562015-06-15 15:48:29 -040043#import "views/ITProgressIndicator.h"
Alexandre Lision74dd47f2015-04-14 13:47:42 -040044#import "views/CallView.h"
Kateryna Kostiuk81afce72018-10-30 16:40:50 -040045#import "views/NSColor+RingTheme.h"
Alexandre Lision210fe212016-01-27 11:15:13 -050046#import "delegates/ImageManipulationDelegate.h"
Alexandre Lision16d9c0a2015-08-10 12:05:15 -040047#import "ChatVC.h"
Alexandre Lision4baba4c2016-02-11 13:00:57 -050048#import "views/IconButton.h"
Anthony Léonard6f819752018-01-05 09:53:40 -050049#import "utils.h"
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -040050#import "views/CallMTKView.h"
Kateryna Kostiukc5fd3b92019-07-24 10:12:44 -040051#import "VideoCommon.h"
Kateryna Kostiuk88a6ee82019-09-10 17:55:08 -040052#import "views/GradientView.h"
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -040053#import "views/MovableView.h"
Alexandre Lision74dd47f2015-04-14 13:47:42 -040054
Alexandre Lisionc5148052015-03-04 15:10:35 -050055@interface RendererConnectionsHolder : NSObject
56
57@property QMetaObject::Connection frameUpdated;
58@property QMetaObject::Connection started;
59@property QMetaObject::Connection stopped;
60
61@end
62
63@implementation RendererConnectionsHolder
64
65@end
66
Kateryna Kostiuk1ba5a012018-12-12 17:07:38 -050067@interface CurrentCallVC () <NSPopoverDelegate> {
Olivier Soldano994971f2017-12-05 16:30:12 -050068 std::string convUid_;
69 std::string callUid_;
70 const lrc::api::account::Info *accountInfo_;
Anthony Léonard66a050c2018-01-03 16:32:34 -050071 NSTimer* refreshDurationTimer;
Olivier Soldano994971f2017-12-05 16:30:12 -050072}
Alexandre Lisionc5148052015-03-04 15:10:35 -050073
Alexandre Lision210fe212016-01-27 11:15:13 -050074// Main container
75@property (unsafe_unretained) IBOutlet NSSplitView* splitView;
Kateryna Kostiuk89398b62018-11-20 16:45:04 -050076@property (unsafe_unretained) IBOutlet NSView* backgroundImage;
Kateryna Kostiuk81afce72018-10-30 16:40:50 -040077@property (unsafe_unretained) IBOutlet NSBox* bluerBackgroundEffect;
Alexandre Lision210fe212016-01-27 11:15:13 -050078
Alexandre Lision89edc6a2015-11-09 11:30:47 -050079// Header info
80@property (unsafe_unretained) IBOutlet NSView* headerContainer;
Alexandre Lision89edc6a2015-11-09 11:30:47 -050081@property (unsafe_unretained) IBOutlet NSTextField* timeSpentLabel;
82
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -040083// info
84@property (unsafe_unretained) IBOutlet NSStackView* infoContainer;
85@property (unsafe_unretained) IBOutlet NSImageView* contactPhoto;
86@property (unsafe_unretained) IBOutlet NSTextField* contactNameLabel;
87@property (unsafe_unretained) IBOutlet NSTextField* callStateLabel;
88@property (unsafe_unretained) IBOutlet NSTextField* contactIdLabel;
89@property (unsafe_unretained) IBOutlet IconButton* cancelCallButton;
90@property (unsafe_unretained) IBOutlet IconButton* pickUpButton;
91@property (unsafe_unretained) IBOutlet ITProgressIndicator *loadingIndicator;
Andreas Traczyk77a50d52018-05-08 17:47:31 -040092
Alexandre Lision89edc6a2015-11-09 11:30:47 -050093// Call Controls
Kateryna Kostiuk88a6ee82019-09-10 17:55:08 -040094@property (unsafe_unretained) IBOutlet GradientView* controlsPanel;
Alexandre Lision62005312016-01-28 15:55:16 -050095
Alexandre Lision4baba4c2016-02-11 13:00:57 -050096@property (unsafe_unretained) IBOutlet IconButton* holdOnOffButton;
97@property (unsafe_unretained) IBOutlet IconButton* hangUpButton;
98@property (unsafe_unretained) IBOutlet IconButton* recordOnOffButton;
Alexandre Lision4baba4c2016-02-11 13:00:57 -050099@property (unsafe_unretained) IBOutlet IconButton* muteAudioButton;
100@property (unsafe_unretained) IBOutlet IconButton* muteVideoButton;
Alexandre Lision4baba4c2016-02-11 13:00:57 -0500101@property (unsafe_unretained) IBOutlet IconButton* transferButton;
102@property (unsafe_unretained) IBOutlet IconButton* addParticipantButton;
103@property (unsafe_unretained) IBOutlet IconButton* chatButton;
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400104@property (unsafe_unretained) IBOutlet IconButton* qualityButton;
Alexandre Lisiond18fa272015-06-15 11:18:03 -0400105
Alexandre Lisionc5148052015-03-04 15:10:35 -0500106// Video
Alexandre Lision58cab672015-06-09 15:25:40 -0400107@property (unsafe_unretained) IBOutlet CallView *videoView;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400108@property (unsafe_unretained) IBOutlet CallMTKView *previewView;
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -0400109@property (unsafe_unretained) IBOutlet MovableView *movableBaseForView;
110@property (unsafe_unretained) IBOutlet NSView* hidePreviewBackground;
111@property (unsafe_unretained) IBOutlet NSButton* hidePreviewButton;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400112
113@property (unsafe_unretained) IBOutlet CallMTKView *videoMTKView;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500114
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400115@property RendererConnectionsHolder* renderConnections;
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400116@property QMetaObject::Connection videoStarted;
Alexandre Lision4d9905e2016-01-22 12:01:36 -0500117@property QMetaObject::Connection selectedCallChanged;
Alexandre Lisionb65c0272015-07-22 15:51:29 -0400118@property QMetaObject::Connection messageConnection;
119@property QMetaObject::Connection mediaAddedConnection;
Kateryna Kostiuk133364b2018-10-31 11:36:08 -0400120@property QMetaObject::Connection profileUpdatedConnection;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500121
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400122@property (strong) IBOutlet ChatVC* chatVC;
123
Alexandre Lisionc5148052015-03-04 15:10:35 -0500124@end
125
126@implementation CurrentCallVC
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400127lrc::api::AVModel* mediaModel;
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -0400128
129NSInteger const PREVIEW_WIDTH = 185;
130NSInteger const PREVIEW_HEIGHT = 130;
131NSInteger const HIDE_PREVIEW_BUTTON_MIN_SIZE = 25;
132NSInteger const HIDE_PREVIEW_BUTTON_MAX_SIZE = 35;
133NSInteger const PREVIEW_MARGIN = 20;
134
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400135@synthesize holdOnOffButton, hangUpButton, recordOnOffButton, pickUpButton, chatButton, transferButton, addParticipantButton, timeSpentLabel, muteVideoButton, muteAudioButton, controlsPanel, headerContainer, videoView, previewView, splitView, loadingIndicator, backgroundImage, bluerBackgroundEffect, hidePreviewButton, hidePreviewBackground, movableBaseForView, infoContainer, contactPhoto, contactNameLabel, callStateLabel, contactIdLabel, cancelCallButton;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500136
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400137@synthesize renderConnections;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400138CVPixelBufferPoolRef pixelBufferPoolDistantView;
139CVPixelBufferRef pixelBufferDistantView;
140CVPixelBufferPoolRef pixelBufferPoolPreview;
141CVPixelBufferRef pixelBufferPreview;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500142
Olivier Soldano994971f2017-12-05 16:30:12 -0500143-(void) setCurrentCall:(const std::string&)callUid
144 conversation:(const std::string&)convUid
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400145 account:(const lrc::api::account::Info*)account
146 avModel:(lrc::api::AVModel *)avModel;
Alexandre Lisionc5148052015-03-04 15:10:35 -0500147{
Olivier Soldano994971f2017-12-05 16:30:12 -0500148 if(account == nil)
149 return;
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400150
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400151 mediaModel = avModel;
Olivier Soldano994971f2017-12-05 16:30:12 -0500152 auto* callModel = account->callModel.get();
Alexandre Lisionc5148052015-03-04 15:10:35 -0500153
Olivier Soldano994971f2017-12-05 16:30:12 -0500154 if (not callModel->hasCall(callUid)){
155 callUid_.clear();
Alexandre Lision2db8f472015-07-22 15:05:46 -0400156 return;
157 }
Olivier Soldano994971f2017-12-05 16:30:12 -0500158 callUid_ = callUid;
159 convUid_ = convUid;
160 accountInfo_ = account;
Anthony Léonard4b0935f2017-12-26 12:05:22 -0500161 [self.chatVC setConversationUid:convUid model:account->conversationModel.get()];
Kateryna Kostiuk54d76972018-06-07 15:45:07 -0400162 auto currentCall = callModel->getCall(callUid_);
Kateryna Kostiuk88a6ee82019-09-10 17:55:08 -0400163 [self setUpButtons: currentCall isRecording: (callModel->isRecording(callUid_) || avModel->getAlwaysRecord())];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400164 [previewView setHidden: YES];
Kateryna Kostiukd76f6202018-09-11 17:15:28 -0400165 videoView.callId = callUid;
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -0400166 [self setUpPrewviewFrame];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500167}
168
Kateryna Kostiuk88a6ee82019-09-10 17:55:08 -0400169-(void) setUpButtons:(lrc::api::call::Info&)callInfo isRecording:(BOOL) isRecording {
170 muteAudioButton.image = callInfo.audioMuted ? [NSImage imageNamed:@"ic_action_mute_audio.png"] :
171 [NSImage imageNamed:@"ic_action_audio.png"];
172 muteVideoButton.image = callInfo.videoMuted ? [NSImage imageNamed:@"ic_action_mute_video.png"] :
173 [NSImage imageNamed:@"ic_action_video.png"];
174 [muteVideoButton setHidden: callInfo.isAudioOnly ? YES: NO];
175 if (isRecording) {
176 [recordOnOffButton startBlinkAnimationfrom:[NSColor buttonBlinkColorColor] to:[NSColor whiteColor] scaleFactor: 1 duration: 1.5];
177 } else {
178 [recordOnOffButton stopBlinkAnimation];
179 }
180}
181
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -0400182- (void) setUpPrewviewFrame {
183 CGPoint previewOrigin = CGPointMake(self.videoView.frame.size.width - PREVIEW_WIDTH - PREVIEW_MARGIN, PREVIEW_MARGIN);
184 movableBaseForView.frame = CGRectMake(previewOrigin.x, previewOrigin.y, PREVIEW_WIDTH, PREVIEW_HEIGHT);
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -0400185 self.movableBaseForView.movable = true;
186 previewView.frame = movableBaseForView.bounds;
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -0400187 hidePreviewBackground.frame = [self frameForExpendPreviewButton: false];;
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -0400188}
189
Alexandre Lisionc5148052015-03-04 15:10:35 -0500190- (void)awakeFromNib
191{
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400192 [self.view setWantsLayer:YES];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500193
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400194 renderConnections = [[RendererConnectionsHolder alloc] init];
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400195
Alexandre Lisionf47a2562015-06-15 15:48:29 -0400196 [loadingIndicator setColor:[NSColor whiteColor]];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400197 [loadingIndicator setNumberOfLines:200];
198 [loadingIndicator setWidthOfLine:4];
199 [loadingIndicator setLengthOfLine:4];
200 [loadingIndicator setInnerMargin:59];
Alexandre Lisionf47a2562015-06-15 15:48:29 -0400201
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -0400202 [self.videoView setCallDelegate:self];
Kateryna Kostiuk1004caa2018-03-29 13:48:07 -0400203 CGColor* color = [[[NSColor blackColor] colorWithAlphaComponent:0.2] CGColor];
204 [headerContainer.layer setBackgroundColor:color];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400205 [bluerBackgroundEffect setWantsLayer:YES];
206 bluerBackgroundEffect.alphaValue = 0.6;
Kateryna Kostiuk89398b62018-11-20 16:45:04 -0500207 [backgroundImage setWantsLayer: YES];
208 backgroundImage.layer.contentsGravity = kCAGravityResizeAspectFill;
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400209 movableBaseForView.wantsLayer = YES;
210 movableBaseForView.layer.cornerRadius = 4;
211 movableBaseForView.layer.masksToBounds = true;
212 movableBaseForView.hostingView = self.videoView;
213 [movableBaseForView setAutoresizingMask: NSViewNotSizable | NSViewMaxXMargin | NSViewMaxYMargin | NSViewMinXMargin | NSViewMinYMargin];
214 [previewView setAutoresizingMask: NSViewNotSizable | NSViewMaxXMargin | NSViewMaxYMargin | NSViewMinXMargin | NSViewMinYMargin];
215 [hidePreviewBackground setAutoresizingMask: NSViewNotSizable | NSViewMaxXMargin | NSViewMaxYMargin | NSViewMinXMargin | NSViewMinYMargin];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500216}
217
Anthony Léonard66a050c2018-01-03 16:32:34 -0500218-(void) updateDurationLabel
219{
220 if (accountInfo_ != nil) {
221 auto* callModel = accountInfo_->callModel.get();
222 if (callModel->hasCall(callUid_)) {
223 auto& callStatus = callModel->getCall(callUid_).status;
224 if (callStatus != lrc::api::call::Status::ENDED &&
225 callStatus != lrc::api::call::Status::TERMINATING &&
226 callStatus != lrc::api::call::Status::INVALID) {
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400227 [timeSpentLabel setStringValue:@(callModel->getFormattedCallDuration(callUid_).c_str())];
Anthony Léonard66a050c2018-01-03 16:32:34 -0500228 return;
229 }
230 }
231 }
232
233 // If call is not running anymore or accountInfo_ is not set for any reason
234 // we stop the refresh loop
235 [refreshDurationTimer invalidate];
236 refreshDurationTimer = nil;
Anthony Léonard66a050c2018-01-03 16:32:34 -0500237}
238
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400239-(void) updateCall
Alexandre Lisionc5148052015-03-04 15:10:35 -0500240{
Olivier Soldano994971f2017-12-05 16:30:12 -0500241 if (accountInfo_ == nil)
242 return;
Alexandre Lisionbb5dbcd2015-07-09 16:36:47 -0400243
Olivier Soldano994971f2017-12-05 16:30:12 -0500244 auto* callModel = accountInfo_->callModel.get();
245 if (not callModel->hasCall(callUid_)) {
246 return;
247 }
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500248
Olivier Soldano994971f2017-12-05 16:30:12 -0500249 auto currentCall = callModel->getCall(callUid_);
250 NSLog(@"\n status %@ \n",@(lrc::api::call::to_string(currentCall.status).c_str()));
Anthony Léonard8ee26782018-01-05 10:23:04 -0500251 auto convIt = getConversationFromUid(convUid_, *accountInfo_->conversationModel);
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400252 if (convIt != accountInfo_->conversationModel->allFilteredConversations().end()) {
253 NSString* bestName = bestNameForConversation(*convIt, *accountInfo_->conversationModel);
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400254 [contactNameLabel setStringValue:bestName];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400255 NSString* ringID = bestIDForConversation(*convIt, *accountInfo_->conversationModel);
256 if([bestName isEqualToString:ringID]) {
257 ringID = @"";
258 }
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400259 [contactIdLabel setStringValue:ringID];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400260 }
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400261 [self setupContactInfo:contactPhoto];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400262
Anthony Léonard66a050c2018-01-03 16:32:34 -0500263 [timeSpentLabel 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];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400270 [self setBackground];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500271
Olivier Soldano994971f2017-12-05 16:30:12 -0500272 using Status = lrc::api::call::Status;
Kateryna Kostiuk88a6ee82019-09-10 17:55:08 -0400273 if (currentCall.status == Status::PAUSED) {
274 [holdOnOffButton startBlinkAnimationfrom:[NSColor buttonBlinkColorColor] to:[NSColor whiteColor] scaleFactor: 1.0 duration: 1.5];
275 } else {
276 [holdOnOffButton stopBlinkAnimation];
277 }
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400278 callStateLabel.stringValue = currentCall.status == Status::INCOMING_RINGING ? @"wants to talk to you" : @(to_string(currentCall.status).c_str());
279 loadingIndicator.hidden = (currentCall.status == Status::SEARCHING ||
280 currentCall.status == Status::CONNECTING ||
281 currentCall.status == Status::OUTGOING_RINGING) ? NO : YES;
282 pickUpButton.hidden = (currentCall.status == Status::INCOMING_RINGING) ? NO : YES;
283 callStateLabel.hidden = (currentCall.status == Status::IN_PROGRESS) ? YES : NO;
284 cancelCallButton.hidden = (currentCall.status == Status::IN_PROGRESS ||
285 currentCall.status == Status::PAUSED) ? YES : NO;
286
Olivier Soldano994971f2017-12-05 16:30:12 -0500287 switch (currentCall.status) {
288 case Status::SEARCHING:
Olivier Soldano994971f2017-12-05 16:30:12 -0500289 case Status::CONNECTING:
Olivier Soldano994971f2017-12-05 16:30:12 -0500290 case Status::OUTGOING_RINGING:
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400291 case Status::INCOMING_RINGING:
292 [infoContainer setHidden: NO];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400293 [headerContainer setHidden:YES];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400294 [controlsPanel setHidden:YES];
Olivier Soldano994971f2017-12-05 16:30:12 -0500295 break;
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400296 /*case Status::CONFERENCE:
297 [self setupConference:currentCall];
298 break;*/
Olivier Soldano994971f2017-12-05 16:30:12 -0500299 case Status::PAUSED:
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400300 [infoContainer setHidden: NO];
301 [headerContainer setHidden:NO];
302 [controlsPanel setHidden:NO];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400303 [backgroundImage setHidden:NO];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400304 [bluerBackgroundEffect setHidden:NO];
305 if(!currentCall.isAudioOnly) {
306 [self.videoMTKView fillWithBlack];
307 [self.previewView fillWithBlack];
308 [hidePreviewBackground setHidden:YES];
309 [self.previewView setHidden: YES];
310 [self.videoMTKView setHidden: YES];
311 self.previewView.stopRendering = true;
312 self.videoMTKView.stopRendering = true;
313 }
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400314 break;
Olivier Soldano994971f2017-12-05 16:30:12 -0500315 case Status::INACTIVE:
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400316 if(currentCall.isAudioOnly) {
317 [self setUpAudioOnlyView];
318 } else {
319 [self setUpVideoCallView];
320 }
321 break;
Olivier Soldano994971f2017-12-05 16:30:12 -0500322 case Status::IN_PROGRESS:
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400323 [headerContainer setHidden:NO];
324 [controlsPanel setHidden:NO];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400325 if(currentCall.isAudioOnly) {
326 [self setUpAudioOnlyView];
327 } else {
328 [self setUpVideoCallView];
329 }
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400330 break;
Olivier Soldano994971f2017-12-05 16:30:12 -0500331 case Status::CONNECTED:
Olivier Soldano994971f2017-12-05 16:30:12 -0500332 break;
333 case Status::ENDED:
334 case Status::TERMINATING:
335 case Status::INVALID:
Olivier Soldano994971f2017-12-05 16:30:12 -0500336 break;
337 }
Alexandre Lision4d9905e2016-01-22 12:01:36 -0500338}
339
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400340-(void) setUpVideoCallView {
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400341 self.previewView.stopRendering = false;
342 self.videoMTKView.stopRendering = false;
343 [previewView fillWithBlack];
344 [self.videoMTKView fillWithBlack];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400345 [previewView setHidden: NO];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400346 [self.videoMTKView setHidden:NO];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400347 [hidePreviewBackground setHidden: YES];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400348 [bluerBackgroundEffect setHidden:YES];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400349 [backgroundImage setHidden:YES];
350}
351
352-(void) setUpAudioOnlyView {
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400353 [self.previewView setHidden: YES];
354 [self.videoMTKView setHidden: YES];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400355 [hidePreviewBackground setHidden: YES];
356 [bluerBackgroundEffect setHidden:NO];
357 [backgroundImage setHidden:NO];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400358}
359
360-(void) setBackground {
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400361 auto* convModel = accountInfo_->conversationModel.get();
362 auto it = getConversationFromUid(convUid_, *convModel);
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400363 NSImage *image= [self getContactImageOfSize:120.0 withDefaultAvatar:NO];
364 if(image) {
Kateryna Kostiuk89398b62018-11-20 16:45:04 -0500365 CIImage * ciImage = [[CIImage alloc] initWithData:[image TIFFRepresentation]];
366 CIContext *context = [[CIContext alloc] init];
367 CIFilter *clamp = [CIFilter filterWithName:@"CIAffineClamp"];
368 [clamp setValue:[NSAffineTransform transform] forKey:@"inputTransform"];
369 [clamp setValue:ciImage forKey: kCIInputImageKey];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400370 CIFilter* bluerFilter = [CIFilter filterWithName:@"CIGaussianBlur"];
371 [bluerFilter setDefaults];
Kateryna Kostiuk89398b62018-11-20 16:45:04 -0500372 [bluerFilter setValue:[NSNumber numberWithFloat: 9] forKey:@"inputRadius"];
373 [bluerFilter setValue:[clamp valueForKey:kCIOutputImageKey] forKey: kCIInputImageKey];
374 CIImage *result = [bluerFilter valueForKey:kCIOutputImageKey];
375 CGRect extent = [result extent];
376 CGImageRef cgImage = [context createCGImage:result fromRect: [ciImage extent]];
377 NSImage *bluredImage = [[NSImage alloc] initWithCGImage:cgImage size:NSSizeFromCGSize(CGSizeMake(image.size.width, image.size.height))];
378 backgroundImage.layer.contents = bluredImage;
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400379 [backgroundImage setHidden:NO];
380 } else {
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400381 contactNameLabel.textColor = [NSColor darkGrayColor];
382 contactNameLabel.textColor = [NSColor darkGrayColor];
383 contactIdLabel.textColor = [NSColor darkGrayColor];
384 callStateLabel.textColor = [NSColor darkGrayColor];
385 backgroundImage.layer.contents = nil;
386 [bluerBackgroundEffect setFillColor:[NSColor ringGreyHighlight]];
387 [bluerBackgroundEffect setAlphaValue:1];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400388 [backgroundImage setHidden:YES];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400389 }
390}
391
Kateryna Kostiuk133364b2018-10-31 11:36:08 -0400392-(NSImage *) getContactImageOfSize: (double) size withDefaultAvatar:(BOOL) shouldDrawDefault {
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400393 auto* convModel = accountInfo_->conversationModel.get();
394 auto convIt = getConversationFromUid(convUid_, *convModel);
395 if (convIt == convModel->allFilteredConversations().end()) {
396 return nil;
397 }
398 if(shouldDrawDefault) {
399 auto& imgManip = reinterpret_cast<Interfaces::ImageManipulationDelegate&>(GlobalInstances::pixmapManipulator());
400 QVariant photo = imgManip.conversationPhoto(*convIt, *accountInfo_, QSize(size, size), NO);
401 return QtMac::toNSImage(qvariant_cast<QPixmap>(photo));
402 }
403 auto contact = accountInfo_->contactModel->getContact(convIt->participants[0]);
404 NSData *imageData = [[NSData alloc] initWithBase64EncodedString:@(contact.profileInfo.avatar.c_str()) options:NSDataBase64DecodingIgnoreUnknownCharacters];
405 return [[NSImage alloc] initWithData:imageData];
406}
407
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400408-(void) setupContactInfo:(NSImageView*)imageView
409{
410 [imageView setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
411}
412
Kateryna Kostiukbe7e2e42019-07-26 17:44:12 -0400413-(void) setupConference
Olivier Soldano994971f2017-12-05 16:30:12 -0500414{
415 [videoView setShouldAcceptInteractions:YES];
416 [self.chatButton setHidden:NO];
Olivier Soldano994971f2017-12-05 16:30:12 -0500417 [self.addParticipantButton setHidden:NO];
418 [self.transferButton setHidden:YES];
419}
420
421-(void)collapseRightView
422{
423 NSView *right = [[splitView subviews] objectAtIndex:1];
424 NSView *left = [[splitView subviews] objectAtIndex:0];
425 NSRect leftFrame = [left frame];
426 [right setHidden:YES];
427 [splitView display];
428}
429
430- (void) changeCallSelection:(std::string&)callUid
431{
432 if (accountInfo_ == nil)
433 return;
434
435 auto* callModel = accountInfo_->callModel.get();
436 if (not callModel->hasCall(callUid)) {
437 return;
438 }
439
Alexandre Lision4d9905e2016-01-22 12:01:36 -0500440 QObject::disconnect(self.selectedCallChanged);
Olivier Soldano994971f2017-12-05 16:30:12 -0500441 self.selectedCallChanged = QObject::connect(callModel,
442 &lrc::api::NewCallModel::callStatusChanged,
443 [self](const std::string callId) {
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400444 [self updateCall];
Alexandre Lision4d9905e2016-01-22 12:01:36 -0500445 });
Alexandre Lisionc5148052015-03-04 15:10:35 -0500446}
447
448-(void) connectVideoSignals
449{
Olivier Soldano994971f2017-12-05 16:30:12 -0500450 if (accountInfo_ == nil)
451 return;
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400452 [self connectRenderer];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500453}
454
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400455-(void) connectRenderer
Alexandre Lisionc5148052015-03-04 15:10:35 -0500456{
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400457 QObject::disconnect(renderConnections.frameUpdated);
458 QObject::disconnect(renderConnections.stopped);
459 QObject::disconnect(renderConnections.started);
460 renderConnections.started =
461 QObject::connect(mediaModel,
462 &lrc::api::AVModel::rendererStarted,
463 [=](const std::string& id) {
464 if (id == lrc::api::video::PREVIEW_RENDERER_ID) {
465 [self.previewView setHidden:NO];
466 self.previewView.stopRendering = false;
467 } else {
468 [self mouseIsMoving: NO];
469 self.videoMTKView.stopRendering = false;
470 [self.videoMTKView setHidden:NO];
471 [bluerBackgroundEffect setHidden:YES];
472 [backgroundImage setHidden:YES];
473 [videoView setShouldAcceptInteractions:YES];
474 }
475 QObject::disconnect(renderConnections.frameUpdated);
476 renderConnections.frameUpdated =
477 QObject::connect(mediaModel,
478 &lrc::api::AVModel::frameUpdated,
479 [=](const std::string& id) {
480 if (id == lrc::api::video::PREVIEW_RENDERER_ID) {
481 auto renderer = &mediaModel->getRenderer(lrc::api::video::PREVIEW_RENDERER_ID);
482 if(!renderer->isRendering()) {
483 return;
484 }
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -0400485 [hidePreviewBackground setHidden: NO];
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400486 [self renderer: renderer renderFrameForPreviewView:previewView];
487
488 } else {
489 auto renderer = &mediaModel->getRenderer(id);
490 if(!renderer->isRendering()) {
491 return;
492 }
493 [self renderer:renderer renderFrameForDistantView: self.videoMTKView];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400494 }
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400495 });
Alexandre Lisionc5148052015-03-04 15:10:35 -0500496 });
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400497 renderConnections.stopped =
498 QObject::connect(mediaModel,
499 &lrc::api::AVModel::rendererStopped,
500 [=](const std::string& id) {
501 QObject::disconnect(renderConnections.frameUpdated);
502 if (id == lrc::api::video::PREVIEW_RENDERER_ID) {
503 [self.previewView setHidden:YES];
504 self.previewView.stopRendering = true;
505 } else {
506 [self mouseIsMoving: YES];
507 self.videoMTKView.stopRendering = true;
508 [self.videoMTKView setHidden:YES];
509 [bluerBackgroundEffect setHidden:NO];
510 [backgroundImage setHidden:NO];
511 [videoView setShouldAcceptInteractions:NO];
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400512 }
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400513 });
514 renderConnections.frameUpdated =
515 QObject::connect(mediaModel,
516 &lrc::api::AVModel::frameUpdated,
517 [=](const std::string& id) {
518 if (id == lrc::api::video::PREVIEW_RENDERER_ID) {
519 auto renderer = &mediaModel->getRenderer(lrc::api::video::PREVIEW_RENDERER_ID);
520 if(!renderer->isRendering()) {
521 return;
522 }
523 [self renderer: renderer renderFrameForPreviewView:previewView];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500524
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400525 } else {
526 auto renderer = &mediaModel->getRenderer(id);
527 if(!renderer->isRendering()) {
528 return;
529 }
530 [self renderer:renderer renderFrameForDistantView: self.videoMTKView];
Kateryna Kostiuk732b4062018-11-28 09:59:28 -0500531 }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500532 });
533}
534
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400535-(void) renderer: (const lrc::api::video::Renderer*)renderer renderFrameForPreviewView:(CallMTKView*) view
Alexandre Lisionc5148052015-03-04 15:10:35 -0500536{
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400537 @autoreleasepool {
538 auto framePtr = renderer->currentAVFrame();
539 auto frame = framePtr.get();
540 if(!frame || !frame->width || !frame->height) {
541 return;
542 }
543 auto frameSize = CGSizeMake(frame->width, frame->height);
544 auto rotation = 0;
545 if (frame->data[3] != NULL && (CVPixelBufferRef)frame->data[3]) {
546 [view renderWithPixelBuffer:(CVPixelBufferRef)frame->data[3]
547 size: frameSize
548 rotation: rotation
549 fillFrame: true];
550 return;
551 }
552 else if (CVPixelBufferRef pixelBuffer = [self getBufferForPreviewFromFrame:frame]) {
553 [view renderWithPixelBuffer: pixelBuffer
554 size: frameSize
555 rotation: rotation
556 fillFrame: true];
557 }
558 }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500559}
560
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400561-(void) renderer: (const lrc::api::video::Renderer*)renderer renderFrameForDistantView:(CallMTKView*) view
Anthony Léonard14e7bf32017-06-08 08:13:16 -0400562{
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 (auto matrix = av_frame_get_side_data(frame, AV_FRAME_DATA_DISPLAYMATRIX)) {
572 const int32_t* data = reinterpret_cast<int32_t*>(matrix->data);
573 rotation = av_display_rotation_get(data);
574 }
575 if (frame->data[3] != NULL && (CVPixelBufferRef)frame->data[3]) {
576 [view renderWithPixelBuffer: (CVPixelBufferRef)frame->data[3]
577 size: frameSize
578 rotation: rotation
579 fillFrame: false];
580 }
581 if (CVPixelBufferRef pixelBuffer = [self getBufferForDistantViewFromFrame:frame]) {
582 [view renderWithPixelBuffer: pixelBuffer
583 size: frameSize
584 rotation: rotation
585 fillFrame: false];
586 }
Kateryna Kostiukbcf24e92018-11-20 10:09:06 -0500587 }
Anthony Léonard14e7bf32017-06-08 08:13:16 -0400588}
589
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400590-(CVPixelBufferRef) getBufferForPreviewFromFrame:(const AVFrame*)frame {
Kateryna Kostiukc5fd3b92019-07-24 10:12:44 -0400591 [VideoCommon fillPixelBuffr:&pixelBufferPreview fromFrame:frame bufferPool:&pixelBufferPoolPreview];
592 CVPixelBufferRef buffer = pixelBufferPreview;
593 return buffer;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400594}
595
596-(CVPixelBufferRef) getBufferForDistantViewFromFrame:(const AVFrame*)frame {
Kateryna Kostiukc5fd3b92019-07-24 10:12:44 -0400597 [VideoCommon fillPixelBuffr:&pixelBufferDistantView fromFrame:frame bufferPool:&pixelBufferPoolDistantView];
598 CVPixelBufferRef buffer = pixelBufferDistantView;
599 return buffer;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400600}
601
Alexandre Lisionc5148052015-03-04 15:10:35 -0500602- (void) initFrame
603{
604 [self.view setFrame:self.view.superview.bounds];
605 [self.view setHidden:YES];
606 self.view.layer.position = self.view.frame.origin;
Alexandre Lisionbb5dbcd2015-07-09 16:36:47 -0400607 [self collapseRightView];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500608}
609
610# pragma private IN/OUT animations
611
Olivier Soldano994971f2017-12-05 16:30:12 -0500612-(void)uncollapseRightView
613{
614 NSView *left = [[splitView subviews] objectAtIndex:0];
615 NSView *right = [[splitView subviews] objectAtIndex:1];
616 [right setHidden:NO];
617
618 CGFloat dividerThickness = [splitView dividerThickness];
619
620 // get the different frames
621 NSRect leftFrame = [left frame];
622 NSRect rightFrame = [right frame];
623
624 leftFrame.size.width = (leftFrame.size.width - rightFrame.size.width - dividerThickness);
625 rightFrame.origin.x = leftFrame.size.width + dividerThickness;
626 [left setFrameSize:leftFrame.size];
627 [right setFrame:rightFrame];
628 [splitView display];
629
630 [self.chatVC takeFocus];
631}
632
Alexandre Lisionc5148052015-03-04 15:10:35 -0500633-(void) cleanUp
634{
Anthony Léonard5c287642018-02-21 15:54:19 -0500635 if(self.splitView.isInFullScreenMode)
636 [self.splitView exitFullScreenModeWithOptions:nil];
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400637 QObject::disconnect(renderConnections.frameUpdated);
638 QObject::disconnect(renderConnections.started);
639 QObject::disconnect(renderConnections.stopped);
Anthony Léonardc0013bb2018-02-20 16:23:02 -0500640 QObject::disconnect(self.messageConnection);
Alexandre Lision883719f2015-10-22 17:37:45 -0400641
Alexandre Lision4baba4c2016-02-11 13:00:57 -0500642 [self.chatButton setPressed:NO];
Alexandre Lision883719f2015-10-22 17:37:45 -0400643 [self collapseRightView];
Alexandre Lision57227772016-01-15 17:19:36 -0500644
Alexandre Lision57227772016-01-15 17:19:36 -0500645 [timeSpentLabel setStringValue:@""];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400646 [contactIdLabel setStringValue:@""];
647 [contactNameLabel setStringValue:@""];
648 [contactPhoto setImage:nil];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400649 //background view
650 [bluerBackgroundEffect setHidden:NO];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400651 [bluerBackgroundEffect setFillColor:[NSColor darkGrayColor]];
652 [bluerBackgroundEffect setAlphaValue:0.6];
Kateryna Kostiuk81afce72018-10-30 16:40:50 -0400653 [backgroundImage setHidden:NO];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400654 backgroundImage.layer.contents = nil;
Kateryna Kostiuk00dcbff2019-07-11 15:42:13 -0400655 [self.previewView setHidden:YES];
656 [self.videoMTKView setHidden:YES];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400657
658 contactNameLabel.textColor = [NSColor highlightColor];
659 contactNameLabel.textColor = [NSColor highlightColor];
660 contactIdLabel.textColor = [NSColor highlightColor];
661 callStateLabel.textColor = [NSColor highlightColor];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500662}
663
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400664-(void) setupCallView
665{
666 if (accountInfo_ == nil)
667 return;
668
669 auto* callModel = accountInfo_->callModel.get();
670 auto* convModel = accountInfo_->conversationModel.get();
671
672 // when call comes in we want to show the controls/header
Kateryna Kostiuk1004caa2018-03-29 13:48:07 -0400673 [self mouseIsMoving: YES];
674 [videoView setShouldAcceptInteractions: NO];
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400675
676 [self connectVideoSignals];
677 /* check if text media is already present */
678 if(not callModel->hasCall(callUid_))
679 return;
680
681 [loadingIndicator setAnimates:YES];
Andreas Traczyk77a50d52018-05-08 17:47:31 -0400682 [self updateCall];
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400683
684 /* monitor media for messaging text messaging */
685 QObject::disconnect(self.messageConnection);
686 self.messageConnection = QObject::connect(convModel,
687 &lrc::api::ConversationModel::interactionStatusUpdated,
688 [self] (std::string convUid,
689 uint64_t msgId,
690 lrc::api::interaction::Info msg) {
691 if (msg.type == lrc::api::interaction::Type::TEXT) {
692 if(not [[self splitView] isSubviewCollapsed:[[[self splitView] subviews] objectAtIndex: 1]]){
693 return;
694 }
695 [self uncollapseRightView];
696 }
697 });
Kateryna Kostiuk133364b2018-10-31 11:36:08 -0400698 //monitor for updated profile
699 QObject::disconnect(self.profileUpdatedConnection);
700 self.profileUpdatedConnection =
701 QObject::connect(accountInfo_->contactModel.get(),
702 &lrc::api::ContactModel::contactAdded,
703 [self](const std::string &contactUri) {
704 auto convIt = getConversationFromUid(convUid_, *accountInfo_->conversationModel.get());
705 if (convIt == accountInfo_->conversationModel->allFilteredConversations().end()) {
706 return;
707 }
708 if (convIt->participants.empty()) {
709 return;
710
711 }
712 auto& contact = accountInfo_->contactModel->getContact(convIt->participants[0]);
713 if (contact.profileInfo.type == lrc::api::profile::Type::RING && contact.profileInfo.uri == contactUri)
714 accountInfo_->conversationModel->makePermanent(convUid_);
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400715 [contactPhoto setImage: [self getContactImageOfSize:120.0 withDefaultAvatar:YES]];
Kateryna Kostiuk732b4062018-11-28 09:59:28 -0500716 [self.delegate conversationInfoUpdatedFor:convUid_];
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400717 [self setBackground];
Kateryna Kostiuk133364b2018-10-31 11:36:08 -0400718 });
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400719}
720
721-(void) showWithAnimation:(BOOL)animate
722{
723 if (!animate) {
724 [self.view setHidden:NO];
725 [self setupCallView];
726 return;
727 }
728
729 CGRect frame = CGRectOffset(self.view.superview.bounds, -self.view.superview.bounds.size.width, 0);
730 [self.view setHidden:NO];
731
732 [CATransaction begin];
733 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
734 [animation setFromValue:[NSValue valueWithPoint:frame.origin]];
735 [animation setToValue:[NSValue valueWithPoint:self.view.superview.bounds.origin]];
736 [animation setDuration:0.2f];
737 [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]];
738 [CATransaction setCompletionBlock:^{
739 [self setupCallView];
740 }];
741
742 [self.view.layer addAnimation:animation forKey:animation.keyPath];
743 [CATransaction commit];
744}
745
746-(void) hideWithAnimation:(BOOL)animate
Alexandre Lisionc5148052015-03-04 15:10:35 -0500747{
Alexandre Lisionc5148052015-03-04 15:10:35 -0500748 if(self.view.frame.origin.x < 0) {
Alexandre Lisionc5148052015-03-04 15:10:35 -0500749 return;
750 }
751
Andreas Traczykecdc4fa2018-03-22 16:11:47 -0400752 if (!animate) {
753 [self.view setHidden:YES];
754 return;
755 }
756
Alexandre Lisionc5148052015-03-04 15:10:35 -0500757 CGRect frame = CGRectOffset(self.view.frame, -self.view.frame.size.width, 0);
758 [CATransaction begin];
759 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
760 [animation setFromValue:[NSValue valueWithPoint:self.view.frame.origin]];
761 [animation setToValue:[NSValue valueWithPoint:frame.origin]];
762 [animation setDuration:0.2f];
Alexandre Lision22d615b2016-01-25 12:54:34 -0500763 [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500764
765 [CATransaction setCompletionBlock:^{
766 [self.view setHidden:YES];
Alexandre Lisionef6333a2015-03-24 12:30:31 -0400767 // first make sure everything is disconnected
Alexandre Lisionc5148052015-03-04 15:10:35 -0500768 [self cleanUp];
Olivier Soldano994971f2017-12-05 16:30:12 -0500769// if (currentCall_) {
770// [self animateIn];
771// }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500772 }];
773 [self.view.layer addAnimation:animation forKey:animation.keyPath];
Alexandre Lisiona1c6d752015-06-23 12:27:38 -0400774
775 [self.view.layer setPosition:frame.origin];
Alexandre Lisionc5148052015-03-04 15:10:35 -0500776 [CATransaction commit];
777}
778
779/**
780 * Debug purpose
781 */
782-(void) dumpFrame:(CGRect) frame WithName:(NSString*) name
783{
784 NSLog(@"frame %@ : %f %f %f %f \n\n",name ,frame.origin.x, frame.origin.y, frame.size.width, frame.size.height);
785}
786
Alexandre Lision58cab672015-06-09 15:25:40 -0400787#pragma mark - Button methods
788
Alexandre Lisionc5148052015-03-04 15:10:35 -0500789- (IBAction)hangUp:(id)sender {
Olivier Soldano994971f2017-12-05 16:30:12 -0500790 if (accountInfo_ == nil)
791 return;
792
793 auto* callModel = accountInfo_->callModel.get();
Olivier Soldano994971f2017-12-05 16:30:12 -0500794 callModel->hangUp(callUid_);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500795}
796
797- (IBAction)accept:(id)sender {
Olivier Soldano994971f2017-12-05 16:30:12 -0500798 if (accountInfo_ == nil)
799 return;
800
Anthony Léonardf584cc52018-01-19 12:00:19 -0500801 // If we accept a conversation with a non trusted contact, we first accept it
802 auto convIt = getConversationFromUid(convUid_, *accountInfo_->conversationModel.get());
803 if (convIt != accountInfo_->conversationModel->allFilteredConversations().end()) {
804 auto& contact = accountInfo_->contactModel->getContact(convIt->participants[0]);
805 if (contact.profileInfo.type == lrc::api::profile::Type::PENDING)
806 accountInfo_->conversationModel->makePermanent(convUid_);
807 }
808
Olivier Soldano994971f2017-12-05 16:30:12 -0500809 auto* callModel = accountInfo_->callModel.get();
810
811 callModel->accept(callUid_);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500812}
813
814- (IBAction)toggleRecording:(id)sender {
Olivier Soldano994971f2017-12-05 16:30:12 -0500815 if (accountInfo_ == nil)
816 return;
817
818 auto* callModel = accountInfo_->callModel.get();
819
820 callModel->toggleAudioRecord(callUid_);
Kateryna Kostiuk88a6ee82019-09-10 17:55:08 -0400821 if (callModel->isRecording(callUid_)) {
822 [recordOnOffButton startBlinkAnimationfrom:[NSColor buttonBlinkColorColor] to:[NSColor whiteColor] scaleFactor: 1 duration: 1.5];
823 } else {
824 [recordOnOffButton stopBlinkAnimation];
825 }
Alexandre Lisionc5148052015-03-04 15:10:35 -0500826}
827
828- (IBAction)toggleHold:(id)sender {
Olivier Soldano994971f2017-12-05 16:30:12 -0500829 if (accountInfo_ == nil)
830 return;
831
832 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiuk54d76972018-06-07 15:45:07 -0400833 auto currentCall = callModel->getCall(callUid_);
Olivier Soldano994971f2017-12-05 16:30:12 -0500834
835 callModel->togglePause(callUid_);
Alexandre Lisionc5148052015-03-04 15:10:35 -0500836}
837
Alexandre Lision62005312016-01-28 15:55:16 -0500838- (IBAction)showDialpad:(id)sender {
839 AppDelegate* appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
840 [appDelegate showDialpad];
841}
842
Alexandre Lision58cab672015-06-09 15:25:40 -0400843-(IBAction)toggleChat:(id)sender;
844{
845 BOOL rightViewCollapsed = [[self splitView] isSubviewCollapsed:[[[self splitView] subviews] objectAtIndex: 1]];
846 if (rightViewCollapsed) {
847 [self uncollapseRightView];
Alexandre Lision58cab672015-06-09 15:25:40 -0400848 } else {
849 [self collapseRightView];
850 }
Alexandre Lision62005312016-01-28 15:55:16 -0500851 [chatButton setPressed:rightViewCollapsed];
Alexandre Lision58cab672015-06-09 15:25:40 -0400852}
853
Alexandre Lisiond18fa272015-06-15 11:18:03 -0400854- (IBAction)muteAudio:(id)sender
855{
Olivier Soldano994971f2017-12-05 16:30:12 -0500856 if (accountInfo_ == nil)
857 return;
858
859 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiuk54d76972018-06-07 15:45:07 -0400860 auto currentCall = callModel->getCall(callUid_);
861 if (currentCall.audioMuted) {
862 muteAudioButton.image = [NSImage imageNamed:@"ic_action_audio.png"];
863 } else {
864 muteAudioButton.image = [NSImage imageNamed:@"ic_action_mute_audio.png"];
865 }
Olivier Soldano994971f2017-12-05 16:30:12 -0500866 callModel->toggleMedia(callUid_, lrc::api::NewCallModel::Media::AUDIO);
Alexandre Lisiond18fa272015-06-15 11:18:03 -0400867}
868
869- (IBAction)muteVideo:(id)sender
870{
Olivier Soldano994971f2017-12-05 16:30:12 -0500871 if (accountInfo_ == nil)
872 return;
Olivier Soldano994971f2017-12-05 16:30:12 -0500873 auto* callModel = accountInfo_->callModel.get();
Kateryna Kostiuk54d76972018-06-07 15:45:07 -0400874 auto currentCall = callModel->getCall(callUid_);
875 if(!currentCall.isAudioOnly) {
876 if (currentCall.videoMuted) {
877 muteVideoButton.image = [NSImage imageNamed:@"ic_action_video.png"];
878 } else {
879 muteVideoButton.image = [NSImage imageNamed:@"ic_action_mute_video.png"];
880 }
881 }
Olivier Soldano994971f2017-12-05 16:30:12 -0500882 callModel->toggleMedia(callUid_, lrc::api::NewCallModel::Media::VIDEO);
Alexandre Lisiond18fa272015-06-15 11:18:03 -0400883}
Alexandre Lision883719f2015-10-22 17:37:45 -0400884
885- (IBAction)toggleTransferView:(id)sender {
Kateryna Kostiuk1ba5a012018-12-12 17:07:38 -0500886
Alexandre Lisionf23ec5a2015-07-16 11:24:06 -0400887}
Alexandre Lisiond18fa272015-06-15 11:18:03 -0400888
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500889- (IBAction)toggleAddParticipantView:(id)sender {
Kateryna Kostiuk9e9f7822019-09-27 11:29:45 -0400890
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500891}
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -0400892- (IBAction)hidePreview:(id)sender {
893 CGRect previewFrame = previewView.frame;
894 CGRect newPreviewFrame, bcHidePreviewFrame;
895 if (previewFrame.size.width > HIDE_PREVIEW_BUTTON_MAX_SIZE) {
896 self.movableBaseForView.movable = false;
897 newPreviewFrame = self.getVideoPreviewCollapsedSize;
898 bcHidePreviewFrame = [self frameForExpendPreviewButton: true];
899 hidePreviewButton.image = [NSImage imageNamed: NSImageNameTouchBarEnterFullScreenTemplate];
900 } else {
901 self.movableBaseForView.movable = true;
902 newPreviewFrame = CGRectMake(0, 0, PREVIEW_WIDTH, PREVIEW_HEIGHT);
903 bcHidePreviewFrame = [self frameForExpendPreviewButton: false];
904 hidePreviewButton.image = [NSImage imageNamed: NSImageNameTouchBarExitFullScreenTemplate];
905 }
906 [NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull context) {
907 context.duration = 0.2f;
908 context.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut];
909 previewView.animator.frame = newPreviewFrame;
910 } completionHandler: nil];
911 hidePreviewBackground.frame = bcHidePreviewFrame;
912}
Alexandre Lision89edc6a2015-11-09 11:30:47 -0500913
Alexandre Lision58cab672015-06-09 15:25:40 -0400914#pragma mark - NSSplitViewDelegate
915
916/* 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.
917 */
918- (BOOL)splitView:(NSSplitView *)splitView shouldCollapseSubview:(NSView *)subview forDoubleClickOnDividerAtIndex:(NSInteger)dividerIndex;
919{
920 NSView* rightView = [[splitView subviews] objectAtIndex:1];
921 return ([subview isEqual:rightView]);
922}
923
924
925- (BOOL)splitView:(NSSplitView *)splitView canCollapseSubview:(NSView *)subview;
926{
927 NSView* rightView = [[splitView subviews] objectAtIndex:1];
928 return ([subview isEqual:rightView]);
929}
930
931
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -0400932# pragma mark - CallnDelegate
Alexandre Lision58cab672015-06-09 15:25:40 -0400933
934- (void) callShouldToggleFullScreen
935{
936 if(self.splitView.isInFullScreenMode)
937 [self.splitView exitFullScreenModeWithOptions:nil];
938 else {
939 NSApplicationPresentationOptions options = NSApplicationPresentationDefault +NSApplicationPresentationAutoHideDock +
940 NSApplicationPresentationAutoHideMenuBar + NSApplicationPresentationAutoHideToolbar;
941 NSDictionary *opts = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInt:options],
942 NSFullScreenModeApplicationPresentationOptions, nil];
943
944 [self.splitView enterFullScreenMode:[NSScreen mainScreen] withOptions:opts];
945 }
946}
947
Alexandre Lisiona1eee3c2015-08-10 13:44:51 -0400948-(void) mouseIsMoving:(BOOL) move
949{
950 [[controlsPanel animator] setAlphaValue:move]; // fade out
951 [[headerContainer animator] setAlphaValue:move];
952}
953
Kateryna Kostiuk89398b62018-11-20 16:45:04 -0500954- (BOOL)splitView:(NSSplitView *)splitView shouldHideDividerAtIndex:(NSInteger)dividerIndex
955{
956 return YES;
957}
958
Kateryna Kostiuk5d90c3b2019-07-18 12:03:52 -0400959-(void) screenShare {
960 NSScreen *mainScreen = [NSScreen mainScreen];
961 NSRect screenFrame = mainScreen.frame;
962 QRect captureRect = QRect(screenFrame.origin.x, screenFrame.origin.y, screenFrame.size.width, screenFrame.size.height);
963 mediaModel->setDisplay(0, screenFrame.origin.x, screenFrame.origin.y, screenFrame.size.width, screenFrame.size.height);
964}
965-(void) switchToDevice:(int)deviceID {
966 auto devices = mediaModel->getDevices();
967 auto device = devices[deviceID];
968 mediaModel->switchInputTo(device);
969}
970
971-(std::vector<std::string>) getDeviceList {
972 return mediaModel->getDevices();
973}
974
975-(void) switchToFile:(std::string)uri {
976 mediaModel->setInputFile(QUrl::fromLocalFile(uri.c_str()).toLocalFile().toStdString());
977}
978
Kateryna Kostiuk6891d4f2019-09-19 17:44:33 -0400979-(CGRect) getVideoPreviewCollapsedSize {
980 CGPoint origin;
981 switch (movableBaseForView.closestCorner) {
982 case TOP_LEFT:
983 origin = CGPointMake(0, movableBaseForView.frame.size.height - HIDE_PREVIEW_BUTTON_MAX_SIZE);
984 break;
985 case BOTTOM_LEFT:
986 origin = CGPointMake(0, 0);
987 break;
988 case TOP_RIGHT:
989 origin = CGPointMake(movableBaseForView.frame.size.width - HIDE_PREVIEW_BUTTON_MAX_SIZE, movableBaseForView.frame.size.height - HIDE_PREVIEW_BUTTON_MAX_SIZE);
990 break;
991 case BOTTOM_RIGHT:
992 origin = CGPointMake(movableBaseForView.frame.size.width - HIDE_PREVIEW_BUTTON_MAX_SIZE, 0);
993 break;
994 }
995 return CGRectMake(origin.x, origin.y, HIDE_PREVIEW_BUTTON_MAX_SIZE, HIDE_PREVIEW_BUTTON_MAX_SIZE);
996}
997
998-(CGRect) frameForExpendPreviewButton:(BOOL)collapsed {
999 CGFloat size = collapsed ? HIDE_PREVIEW_BUTTON_MAX_SIZE : HIDE_PREVIEW_BUTTON_MIN_SIZE;
1000 CGPoint origin = CGPointMake(self.previewView.frame.size.width - size,
1001 self.previewView.frame.size.height - size);
1002 return CGRectMake(origin.x, origin.y, size, size);
1003}
1004
Alexandre Lisionc5148052015-03-04 15:10:35 -05001005@end