blob: edd1ce6ce2c9ca965587f34f52126d566e07dccf [file] [log] [blame]
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -04001/*
2 * Copyright (C) 2019 Savoir-faire Linux Inc.
3 * Author: Kateryna Kostiuk <kateryna.kostiuk@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.
18 */
19
20#import "RecordFileVC.h"
21#import "AppDelegate.h"
22#import "VideoCommon.h"
23#import "views/HoverButton.h"
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -040024#import "views/NSColor+RingTheme.h"
Kateryna Kostiuk06681682020-05-07 20:50:56 -040025#import "views/RenderingView.h"
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -040026#import "NSString+Extensions.h"
27
28//lrc
29#import <video/renderer.h>
30#import <api/avmodel.h>
31
32#import <AVFoundation/AVFoundation.h>
33
34@interface RecordFileVC ()
Kateryna Kostiuk06681682020-05-07 20:50:56 -040035@property (unsafe_unretained) IBOutlet RenderingView* previewView;
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -040036
37@property (unsafe_unretained) IBOutlet NSTextField* timeLabel;
38@property (unsafe_unretained) IBOutlet NSTextField* infoLabel;
39
40@property (unsafe_unretained) IBOutlet HoverButton* recordOnOffButton;
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -040041@property (unsafe_unretained) IBOutlet NSButton *sendButton;
42@property (unsafe_unretained) IBOutlet HoverButton *fileImage;
43
44@property (assign) IBOutlet NSLayoutConstraint* timeRightConstraint;
45@property (assign) IBOutlet NSLayoutConstraint* timeTopConstraint;
46@property (assign) IBOutlet NSLayoutConstraint* timeCenterX;
47@property (assign) IBOutlet NSLayoutConstraint* timeCenterY;
48
49@property RendererConnectionsHolder* renderConnections;
50
51@end
52
53@implementation RecordFileVC
54
55CVPixelBufferPoolRef pool;
56CVPixelBufferRef pixBuf;
57BOOL recording;
58NSString *fileName;
59NSTimer* durationTimer;
60int timePassing = 0;
61bool isAudio = NO;
62
63@synthesize avModel, renderConnections,
64previewView, timeLabel, recordOnOffButton, sendButton, fileImage, infoLabel, timeRightConstraint,timeTopConstraint, timeCenterX, timeCenterY;
65
66-(id) initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil avModel:(lrc::api::AVModel*) avModel
67{
68 if (self = [self initWithNibName:nibNameOrNil bundle:nibBundleOrNil])
69 {
70 self.avModel = avModel;
71 renderConnections = [[RendererConnectionsHolder alloc] init];
72 }
73 return self;
74}
75
76- (void)loadView {
77 [super loadView];
78 [[self view] setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable];
79 [self.previewView setupView];
80 AppDelegate* appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
81 if ([appDelegate getActiveCalls].size()) {
82 [self setErrorState];
83 return;
84 }
85 [self setInitialState];
86}
87
88- (void) connectPreviewSignals {
89 [previewView fillWithBlack];
90 QObject::disconnect(renderConnections.frameUpdated);
91 renderConnections.frameUpdated =
92 QObject::connect(avModel,
93 &lrc::api::AVModel::frameUpdated,
Kateryna Kostiukc867eb92020-03-08 13:15:17 -040094 [=](const QString& id) {
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -040095 if (id != lrc::api::video::PREVIEW_RENDERER_ID) {
96 return;
97 }
98 auto renderer = &avModel->getRenderer(id);
99 if(!renderer->isRendering()) {
100 return;
101 }
102 [self renderer:renderer
103 renderFrameForView: self.previewView];
104 });
105}
106
107#pragma mark - dispaly
108
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400109-(void) renderer: (const lrc::api::video::Renderer*)renderer renderFrameForView:(RenderingView*) view
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -0400110{
111 @autoreleasepool {
112 const CGSize frameSize = [VideoCommon fillPixelBuffr:&pixBuf
113 fromRenderer:renderer
114 bufferPool:&pool];
115 if(frameSize.width == 0 || frameSize.height == 0) {
116 return;
117 }
118 CVPixelBufferRef buffer = pixBuf;
119 [view renderWithPixelBuffer: buffer
120 size: frameSize
121 rotation: 0
Kateryna Kostiukb9b9e562019-11-09 17:44:48 -0500122 fillFrame: true];
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -0400123 }
124}
125
126#pragma mark - actions
127
128- (IBAction)cancell:(NSButton *)sender {
129 [self disconnectVideo];
130 self.delegate.closeRecordingView;
131}
132
133- (IBAction)sendMessage:(NSButton *)sender {
134 NSArray* pathURL = [fileName componentsSeparatedByString: @"/"];
135 if([pathURL count] < 1) {
136 return;
137 }
138 NSString* name = [pathURL objectAtIndex: [pathURL count] - 1];
139 [self.delegate sendFile:name withFilePath:fileName];
140 self.delegate.closeRecordingView;
141}
142
143- (IBAction)togleRecord:(NSButton *)sender {
144 if (recording) {
145 [self stopRecord];
146 return;
147 }
148
149 NSString *info = NSLocalizedString(@"Press to start recording", @"Recording view explanation label");
150 infoLabel.stringValue = info;
151
152#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101400
153 if (@available(macOS 10.14, *)) {
154 NSString *noVideoPermission = NSLocalizedString(@"Video permission not granted", @"Error video permission");
155 NSString *noAudioPermission = NSLocalizedString(@"Audio permission not granted", @"Error audio permission");
156
157 AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
158 if(authStatus == AVAuthorizationStatusRestricted || authStatus == AVAuthorizationStatusDenied)
159 {
160 dispatch_async(dispatch_get_main_queue(), ^{
161 infoLabel.stringValue = noAudioPermission;
162 });
163 return;
164 }
165
166 if(authStatus == AVAuthorizationStatusNotDetermined)
167 {
168 [AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) {
169 if(!granted){
170 dispatch_async(dispatch_get_main_queue(), ^{
171 infoLabel.stringValue = noAudioPermission;
172 });
173 return;
174 }
175 [self startRecord];
176 }];
177 return;
178 }
179 if (!isAudio) {
180 AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
181 if(authStatus == AVAuthorizationStatusRestricted || authStatus == AVAuthorizationStatusDenied)
182 {
183 dispatch_async(dispatch_get_main_queue(), ^{
184 infoLabel.stringValue = noVideoPermission;
185 });
186 return;
187 }
188
189 if(authStatus == AVAuthorizationStatusNotDetermined)
190 {
191 [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
192 if(!granted){
193 dispatch_async(dispatch_get_main_queue(), ^{
194 infoLabel.stringValue = noVideoPermission;
195 });
196 return;
197 }
198 [self startRecord];
199 }];
200 return;
201 }
202 }
203 }
204#endif
205 [self startRecord];
206}
207
208-(void) stopRecord {
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400209 avModel->stopLocalRecorder(QString::fromNSString(fileName));
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -0400210 recording = false;
211 [durationTimer invalidate];
212 durationTimer = nil;
213 [self setRecordedState];
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -0400214}
215
216-(void) startRecord {
217 dispatch_async(dispatch_get_main_queue(), ^{
218 if (!isAudio) {
219 avModel->startPreview();
220 }
221 [self setRecordingState];
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400222 QString file_name = avModel->startLocalRecorder(isAudio);
223 if (file_name.isEmpty()) {
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -0400224 return;
225 }
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400226 fileName = file_name.toNSString();
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -0400227 recording = true;
228 if (durationTimer == nil)
229 durationTimer = [NSTimer scheduledTimerWithTimeInterval:1.0
230 target:self
231 selector:@selector(updateDurationLabel)
232 userInfo:nil
233 repeats:YES];
234 });
235}
236
237-(void) updateDurationLabel
238{
239 timePassing++;
240 [timeLabel setStringValue: [NSString formattedStringTimeFromSeconds: timePassing]];
241}
242
243-(void) stopRecordingView {
244 [self disconnectVideo];
245 recording = false;
246 [durationTimer invalidate];
247 durationTimer = nil;
248 [recordOnOffButton stopBlinkAnimation];
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -0400249}
250
251-(void) disconnectVideo {
252 AppDelegate* appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
253 if (![appDelegate getActiveCalls].size()) {
254 avModel->stopPreview();
255 QObject::disconnect(renderConnections.frameUpdated);
Kateryna Kostiukc867eb92020-03-08 13:15:17 -0400256 avModel->stopLocalRecorder(QString::fromNSString(fileName));
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -0400257 }
258}
259
260-(void) prepareRecordingView:(BOOL)audioOnly {
261 AppDelegate* appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
262 if ([appDelegate getActiveCalls].size()) {
263 [self setErrorState];
264 return;
265 }
266 isAudio = audioOnly;
267 [self setInitialState];
268 if (isAudio) {
269 return;
270 }
271 [previewView fillWithBlack];
272
Kateryna Kostiuk06681682020-05-07 20:50:56 -0400273 self.previewView.videoRunning = true;
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -0400274 [self connectPreviewSignals];
275 avModel->stopPreview();
276 avModel->startPreview();
277}
278
279-(void) setInitialState {
280 [recordOnOffButton setHidden:NO];
281 [infoLabel setHidden:NO];
282 [sendButton setHidden:YES];
283 [sendButton setHidden:YES];
284 [fileImage setHidden:YES];
285 [timeLabel setStringValue: @""];
286
287 fileName = @"";
288 timePassing = 0;
289
290 NSColor *color = isAudio ? [NSColor labelColor] : [NSColor whiteColor];
291 recordOnOffButton.moiuseOutsideImageColor = color;
292 recordOnOffButton.imageColor = color;
293 fileImage.buttonDisableColor = color;
294 fileImage.imageColor = color;
295 timeLabel.textColor = color;
296 infoLabel.textColor = color;
297 NSString *title = NSLocalizedString(@"Send", @"Send button title");
Kateryna Kostiuka7404812019-10-28 12:24:46 -0400298 NSString *info = NSLocalizedString(@"Press to start recording", @"Recording view explanation label");
299 infoLabel.stringValue = info;
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -0400300 NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
301 [style setAlignment:NSCenterTextAlignment];
302 NSDictionary *attrsDictionary = [NSDictionary dictionaryWithObjectsAndKeys:color, NSForegroundColorAttributeName, style, NSParagraphStyleAttributeName, nil];
303 NSAttributedString *attrString = [[NSAttributedString alloc]initWithString:title attributes:attrsDictionary];
304 [sendButton setAttributedTitle:attrString];
305
306 [previewView setHidden:isAudio];
307 auto frame = self.view.frame;
308 if (isAudio) {
309 [self.view setFrameSize: CGSizeMake(370, 160)];
310 timeRightConstraint.priority = 200;
311 timeTopConstraint.priority = 200;
312 timeCenterX.priority = 900;
313 timeCenterY.priority = 900;
314 timeCenterX.constant = 0;
315 return;
316 }
317 timeRightConstraint.priority = 900;
318 timeTopConstraint.priority = 900;
319 timeCenterX.priority = 200;
320 timeCenterY.priority = 200;
321 [self.view setFrameSize: CGSizeMake(480, 270)];
322 previewView.frame = self.view.bounds;
323}
324
325-(void) setRecordingState {
326 fileName = @"";
327 timePassing = 0;
328 [recordOnOffButton setHidden:NO];
329 [sendButton setHidden:YES];
330 [fileImage setHidden:YES];
331 [infoLabel setHidden:YES];
332 [timeLabel setStringValue: @""];
333 NSString *info = NSLocalizedString(@"Press to start recording", @"Recording view explanation label");
334 infoLabel.stringValue = info;
335 [recordOnOffButton startBlinkAnimationfrom:[NSColor buttonBlinkColorColor]
336 to:[NSColor whiteColor]
337 scaleFactor: 1
338 duration: 1.5];
339 timeCenterX.constant = 0;
340}
341
342-(void) setRecordedState {
343 [recordOnOffButton stopBlinkAnimation];
344 [recordOnOffButton setHidden:NO];
345 [sendButton setHidden:NO];
346 [fileImage setHidden:NO];
347 timeCenterX.constant = 15;
348 [infoLabel setHidden:YES];
349}
350
351//when open during call
352-(void) setErrorState {
353 NSString *info = NSLocalizedString(@"Could not record message during call", @"Recording view explanation label");
354 infoLabel.stringValue = info;
355 [infoLabel setHidden:NO];
356 [recordOnOffButton setHidden:YES];
357 infoLabel.textColor = [NSColor textColor];
358 [previewView setHidden:YES];
Kateryna Kostiuka7404812019-10-28 12:24:46 -0400359 [sendButton setHidden:YES];
360 [fileImage setHidden:YES];
361 [timeLabel setStringValue: @""];
Kateryna Kostiukfbe1b2f2019-10-07 17:32:26 -0400362 [self.view setFrameSize: CGSizeMake(370, 160)];
363}
364
365@end