Files
Swiftgram/submodules/LegacyComponents/Sources/TGVideoMessageCaptureController.m
Kylmakalle fd86110711 Version 11.3.1
Fixes

fix localeWithStrings globally (#30)

Fix badge on zoomed devices. closes #9

Hide channel bottom panel closes #27

Another attempt to fix badge on some Zoomed devices

Force System Share sheet tg://sg/debug

fixes for device badge

New Crowdin updates (#34)

* New translations sglocalizable.strings (Chinese Traditional)

* New translations sglocalizable.strings (Chinese Simplified)

* New translations sglocalizable.strings (Chinese Traditional)

Fix input panel hidden on selection (#31)

* added if check for selectionState != nil

* same order of subnodes

Revert "Fix input panel hidden on selection (#31)"

This reverts commit e8a8bb1496.

Fix input panel for channels Closes #37

Quickly share links with system's share menu

force tabbar when editing

increase height for correct animation

New translations sglocalizable.strings (Ukrainian) (#38)

Hide Post Story button

Fix 10.15.1

Fix archive option for long-tap

Enable in-app Safari

Disable some unsupported purchases

disableDeleteChatSwipeOption + refactor restart alert

Hide bot in suggestions list

Fix merge v11.0

Fix exceptions for safari webview controller

New Crowdin updates (#47)

* New translations sglocalizable.strings (Romanian)

* New translations sglocalizable.strings (French)

* New translations sglocalizable.strings (Spanish)

* New translations sglocalizable.strings (Afrikaans)

* New translations sglocalizable.strings (Arabic)

* New translations sglocalizable.strings (Catalan)

* New translations sglocalizable.strings (Czech)

* New translations sglocalizable.strings (Danish)

* New translations sglocalizable.strings (German)

* New translations sglocalizable.strings (Greek)

* New translations sglocalizable.strings (Finnish)

* New translations sglocalizable.strings (Hebrew)

* New translations sglocalizable.strings (Hungarian)

* New translations sglocalizable.strings (Italian)

* New translations sglocalizable.strings (Japanese)

* New translations sglocalizable.strings (Korean)

* New translations sglocalizable.strings (Dutch)

* New translations sglocalizable.strings (Norwegian)

* New translations sglocalizable.strings (Polish)

* New translations sglocalizable.strings (Portuguese)

* New translations sglocalizable.strings (Serbian (Cyrillic))

* New translations sglocalizable.strings (Swedish)

* New translations sglocalizable.strings (Turkish)

* New translations sglocalizable.strings (Vietnamese)

* New translations sglocalizable.strings (Indonesian)

* New translations sglocalizable.strings (Hindi)

* New translations sglocalizable.strings (Uzbek)

New Crowdin updates (#49)

* New translations sglocalizable.strings (Arabic)

* New translations sglocalizable.strings (Arabic)

New translations sglocalizable.strings (Russian) (#51)

Call confirmation

WIP Settings search

Settings Search

Localize placeholder

Update AccountUtils.swift

mark mutual contact

Align back context action to left

New Crowdin updates (#54)

* New translations sglocalizable.strings (Chinese Simplified)

* New translations sglocalizable.strings (Chinese Traditional)

* New translations sglocalizable.strings (Ukrainian)

Independent Playground app for simulator

New translations sglocalizable.strings (Ukrainian) (#55)

Playground UIKit base and controllers

Inject SwiftUI view with overflow to AsyncDisplayKit

Launch Playgound project on simulator

Create .swiftformat

Move Playground to example

Update .swiftformat

Init SwiftUIViewController

wip

New translations sglocalizable.strings (Chinese Traditional) (#57)

Xcode 16 fixes

Fix

New translations sglocalizable.strings (Italian) (#59)

New translations sglocalizable.strings (Chinese Simplified) (#63)

Force disable CallKit integration due to missing NSE Entitlement

Fix merge

Fix whole chat translator

Sweetpad config

Bump version

11.3.1 fixes

Mutual contact placement fix

Disable Video PIP swipe

Update versions.json

Fix PIP crash
2024-12-20 09:38:13 +02:00

1651 lines
56 KiB
Objective-C

#import "TGVideoMessageCaptureController.h"
#import "LegacyComponentsInternal.h"
#import <LegacyComponents/TGCameraController.h>
#import <LegacyComponents/TGImageBlur.h>
#import <LegacyComponents/TGPhotoEditorUtils.h>
#import <LegacyComponents/TGTimerTarget.h>
#import <LegacyComponents/TGImageBlur.h>
#import <LegacyComponents/TGObserverProxy.h>
#import <LegacyComponents/TGMediaAssetImageSignals.h>
#import <LegacyComponents/TGModernButton.h>
#import <LegacyComponents/TGVideoCameraGLView.h>
#import "TGVideoCameraPipeline.h"
#import <LegacyComponents/PGCameraVolumeButtonHandler.h>
#import <LegacyComponents/TGVideoMessageControls.h>
#import <LegacyComponents/TGVideoMessageRingView.h>
#import <LegacyComponents/TGVideoMessageScrubber.h>
#import <LegacyComponents/TGModernGalleryVideoView.h>
#import <LegacyComponents/TGModernConversationInputMicButton.h>
#import "TGColor.h"
#import "TGImageUtils.h"
#import "TGMediaPickerSendActionSheetController.h"
#import "TGOverlayControllerWindow.h"
#import <LegacyComponents/TGPhotoEditorSparseView.h>
const NSTimeInterval TGVideoMessageMaximumDuration = 60.0;
typedef enum
{
TGVideoMessageTransitionTypeUsual,
TGVideoMessageTransitionTypeSimplified,
TGVideoMessageTransitionTypeLegacy
} TGVideoMessageTransitionType;
@interface TGVideoMessageCaptureControllerWindow : TGOverlayControllerWindow
@property (nonatomic, assign) CGRect controlsFrame;
@property (nonatomic, assign) bool locked;
@end
@implementation TGVideoMessageCaptureControllerAssets
- (instancetype)initWithSendImage:(UIImage *)sendImage slideToCancelImage:(UIImage *)slideToCancelImage actionDelete:(UIImage *)actionDelete {
self = [super init];
if (self != nil) {
_sendImage = sendImage;
_slideToCancelImage = slideToCancelImage;
_actionDelete = actionDelete;
}
return self;
}
@end
@interface TGVideoMessageCaptureController () <TGVideoCameraPipelineDelegate, TGVideoMessageScrubberDataSource, TGVideoMessageScrubberDelegate, UIGestureRecognizerDelegate>
{
SQueue *_queue;
AVCaptureDevicePosition _preferredPosition;
bool _startWithRearCam;
TGVideoCameraPipeline *_capturePipeline;
NSURL *_url;
PGCameraVolumeButtonHandler *_buttonHandler;
bool _forStory;
bool _autorotationWasEnabled;
bool _dismissed;
bool _gpuAvailable;
bool _locked;
bool _positionChangeLocked;
bool _alreadyStarted;
CGRect _controlsFrame;
TGVideoMessageControls *_controlsView;
TGModernButton *_switchButton;
UIView *_wrapperView;
UIView *_blurView;
UIView *_fadeView;
UIView *_circleWrapperView;
UIImageView *_shadowView;
UIView *_circleView;
TGVideoCameraGLView *_previewView;
TGVideoMessageRingView *_ringView;
UIPinchGestureRecognizer *_pinchGestureRecognizer;
UIView *_separatorView;
UIImageView *_placeholderView;
TGVideoMessageShimmerView *_shimmerView;
bool _automaticDismiss;
NSTimeInterval _startTimestamp;
NSTimer *_recordingTimer;
NSTimeInterval _previousDuration;
NSUInteger _audioRecordingDurationSeconds;
NSUInteger _audioRecordingDurationMilliseconds;
id _activityHolder;
SMetaDisposable *_activityDisposable;
SMetaDisposable *_currentAudioSession;
bool _otherAudioPlaying;
id _didEnterBackgroundObserver;
bool _stopped;
id _liveUploadData;
UIImage *_thumbnailImage;
NSDictionary *_thumbnails;
NSTimeInterval _duration;
AVPlayer *_player;
id _didPlayToEndObserver;
TGModernGalleryVideoView *_videoView;
UIImageView *_muteView;
bool _muted;
SMetaDisposable *_thumbnailsDisposable;
id<LegacyComponentsContext> _context;
UIView *(^_transitionInView)();
id<TGLiveUploadInterface> _liveUploadInterface;
int32_t _slowmodeTimestamp;
UIView * (^_slowmodeView)(void);
TGVideoMessageCaptureControllerAssets *_assets;
bool _canSendSilently;
bool _canSchedule;
bool _reminder;
UIImpactFeedbackGenerator *_generator;
}
@property (nonatomic, copy) bool(^isAlreadyLocked)(void);
@end
@implementation TGVideoMessageCaptureController
# pragma mark - Swiftgram
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context forStory:(bool)forStory assets:(TGVideoMessageCaptureControllerAssets *)assets transitionInView:(UIView *(^)(void))transitionInView parentController:(TGViewController *)parentController controlsFrame:(CGRect)controlsFrame isAlreadyLocked:(bool (^)(void))isAlreadyLocked liveUploadInterface:(id<TGLiveUploadInterface>)liveUploadInterface pallete:(TGModernConversationInputMicPallete *)pallete slowmodeTimestamp:(int32_t)slowmodeTimestamp slowmodeView:(UIView *(^)(void))slowmodeView canSendSilently:(bool)canSendSilently canSchedule:(bool)canSchedule reminder:(bool)reminder startWithRearCam:(bool)startWithRearCam
{
self = [super initWithContext:context];
if (self != nil)
{
_context = context;
_forStory = forStory;
_transitionInView = [transitionInView copy];
self.isAlreadyLocked = isAlreadyLocked;
_liveUploadInterface = liveUploadInterface;
_assets = assets;
_pallete = pallete;
_canSendSilently = canSendSilently;
_canSchedule = canSchedule;
_reminder = reminder;
_slowmodeTimestamp = slowmodeTimestamp;
_slowmodeView = [slowmodeView copy];
_url = [TGVideoMessageCaptureController tempOutputPath];
_queue = [[SQueue alloc] init];
_previousDuration = 0.0;
#pragma mark - Swiftgram
if (startWithRearCam) {
_preferredPosition = AVCaptureDevicePositionBack;
} else {
_preferredPosition = AVCaptureDevicePositionFront;
}
_startWithRearCam = startWithRearCam;
self.isImportant = true;
_controlsFrame = controlsFrame;
_gpuAvailable = true;
_activityDisposable = [[SMetaDisposable alloc] init];
_currentAudioSession = [[SMetaDisposable alloc] init];
__weak TGVideoMessageCaptureController *weakSelf = self;
_didEnterBackgroundObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil queue:nil usingBlock:^(__unused NSNotification *notification)
{
__strong TGVideoMessageCaptureController *strongSelf = weakSelf;
if (strongSelf != nil && !strongSelf->_stopped)
{
strongSelf->_automaticDismiss = true;
strongSelf->_gpuAvailable = false;
[strongSelf dismiss:true];
}
}];
_thumbnailsDisposable = [[SMetaDisposable alloc] init];
TGVideoMessageCaptureControllerWindow *window = [[TGVideoMessageCaptureControllerWindow alloc] initWithManager:[_context makeOverlayWindowManager] parentController:parentController contentController:self keepKeyboard:true];
window.windowLevel = 1000000000.0f - 0.001f;
window.hidden = false;
window.controlsFrame = controlsFrame;
}
return self;
}
- (void)dealloc
{
printf("Video controller dealloc\n");
[_thumbnailsDisposable dispose];
[[NSNotificationCenter defaultCenter] removeObserver:_didEnterBackgroundObserver];
[_activityDisposable dispose];
id<SDisposable> currentAudioSession = _currentAudioSession;
[_queue dispatch:^{
[currentAudioSession dispose];
}];
}
+ (NSURL *)tempOutputPath
{
return [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSString alloc] initWithFormat:@"cam_%x.mp4", (int)arc4random()]]];
}
- (void)setPallete:(TGModernConversationInputMicPallete *)pallete {
_pallete = pallete;
if (!_alreadyStarted)
return;
TGVideoMessageTransitionType type = [self _transitionType];
if (type != TGVideoMessageTransitionTypeLegacy && ((UIVisualEffectView *)_blurView).effect != nil)
{
UIBlurEffect *effect = [UIBlurEffect effectWithStyle:self.pallete.isDark ? UIBlurEffectStyleDark : UIBlurEffectStyleLight];
((UIVisualEffectView *)_blurView).effect = effect;
}
UIColor *curtainColor = [UIColor whiteColor];
if (self.pallete != nil && self.pallete.isDark)
curtainColor = [UIColor blackColor];
_fadeView.backgroundColor = [curtainColor colorWithAlphaComponent:0.4f];
_ringView.accentColor = self.pallete != nil ? self.pallete.buttonColor : TGAccentColor();
_controlsView.pallete = self.pallete;
_separatorView.backgroundColor = self.pallete != nil ? self.pallete.borderColor : UIColorRGB(0xb2b2b2);
UIImage *switchImage = TGComponentsImageNamed(@"VideoRecordPositionSwitch");
if (self.pallete != nil)
switchImage = TGTintedImage(switchImage, self.pallete.buttonColor);
[_switchButton setImage:switchImage forState:UIControlStateNormal];
}
- (void)loadView
{
[super loadView];
self.view = [[TGPhotoEditorSparseView alloc] initWithFrame:self.view.frame];
self.view.backgroundColor = [UIColor clearColor];
CGFloat bottomOffset = self.view.frame.size.height - CGRectGetMaxY(_controlsFrame);
if (bottomOffset > 44.0) {
bottomOffset = 0.0f;
}
CGRect wrapperFrame = TGIsPad() || _forStory ? CGRectMake(0.0f, 0.0f, self.view.frame.size.width, CGRectGetMaxY(_controlsFrame) + bottomOffset) : CGRectMake(0.0f, 0.0f, self.view.frame.size.width, CGRectGetMinY(_controlsFrame));
_wrapperView = [[TGPhotoEditorSparseView alloc] initWithFrame:wrapperFrame];
_wrapperView.clipsToBounds = true;
[self.view addSubview:_wrapperView];
UIColor *curtainColor = [UIColor whiteColor];
if (self.pallete != nil && self.pallete.isDark)
curtainColor = [UIColor blackColor];
TGVideoMessageTransitionType type = [self _transitionType];
CGRect fadeFrame = CGRectMake(0.0f, 0.0f, _wrapperView.frame.size.width, _wrapperView.frame.size.height);
if (type != TGVideoMessageTransitionTypeLegacy)
{
UIBlurEffect *effect = nil;
if (type == TGVideoMessageTransitionTypeSimplified)
effect = [UIBlurEffect effectWithStyle:self.pallete.isDark ? UIBlurEffectStyleDark : UIBlurEffectStyleLight];
_blurView = [[UIVisualEffectView alloc] initWithEffect:effect];
if (!_forStory) {
[_wrapperView addSubview:_blurView];
}
if (type == TGVideoMessageTransitionTypeSimplified)
{
_blurView.alpha = 0.0f;
}
else
{
_fadeView = [[UIView alloc] initWithFrame:fadeFrame];
_fadeView.alpha = 0.0f;
_fadeView.backgroundColor = [curtainColor colorWithAlphaComponent:0.4f];
if (!_forStory) {
[_wrapperView addSubview:_fadeView];
}
}
}
else
{
_fadeView = [[UIView alloc] initWithFrame:fadeFrame];
_fadeView.alpha = 0.0f;
_fadeView.backgroundColor = [curtainColor colorWithAlphaComponent:0.6f];
if (!_forStory) {
[_wrapperView addSubview:_fadeView];
}
}
CGFloat minSide = MIN(_wrapperView.frame.size.width, _wrapperView.frame.size.height);
bool isSE = _wrapperView.frame.size.width == 320.0 || _wrapperView.frame.size.height == 320.0;
CGFloat diameter = isSE ? 216.0 : MIN(404.0, minSide - 24.0f);
CGFloat shadowSize = 21.0f;
CGFloat circleWrapperViewLength = diameter + shadowSize * 2.0;
_circleWrapperView = [[UIView alloc] initWithFrame:(CGRect){
.origin.x = (_wrapperView.bounds.size.width - circleWrapperViewLength) / 2.0f,
.origin.y = _wrapperView.bounds.size.height + circleWrapperViewLength * 0.3f,
.size.width = circleWrapperViewLength,
.size.height = circleWrapperViewLength
}];
_circleWrapperView.alpha = 0.0f;
_circleWrapperView.clipsToBounds = false;
[_wrapperView addSubview:_circleWrapperView];
_shadowView = [[UIImageView alloc] initWithImage:TGComponentsImageNamed(@"VideoMessageShadow")];
_shadowView.frame = _circleWrapperView.bounds;
[_circleWrapperView addSubview:_shadowView];
_circleView = [[UIView alloc] initWithFrame:CGRectInset(_circleWrapperView.bounds, shadowSize, shadowSize)];
_circleView.clipsToBounds = true;
_circleView.layer.cornerRadius = _circleView.frame.size.width / 2.0f;
[_circleWrapperView addSubview:_circleView];
_placeholderView = [[UIImageView alloc] initWithFrame:_circleView.bounds];
_placeholderView.backgroundColor = [UIColor blackColor];
_placeholderView.image = [TGVideoMessageCaptureController startImage];
[_circleView addSubview:_placeholderView];
_shimmerView = [[TGVideoMessageShimmerView alloc] initWithFrame:_circleView.bounds];
[_shimmerView updateAbsoluteRect:_circleView.bounds containerSize:_circleView.bounds.size];
[_circleView addSubview:_shimmerView];
if (@available(iOS 11.0, *)) {
_shadowView.accessibilityIgnoresInvertColors = true;
_placeholderView.accessibilityIgnoresInvertColors = true;
}
CGFloat ringViewLength = diameter - 8.0f;
_ringView = [[TGVideoMessageRingView alloc] initWithFrame:(CGRect){
.origin.x = (_circleWrapperView.bounds.size.width - ringViewLength) / 2.0f,
.origin.y = (_circleWrapperView.bounds.size.height - ringViewLength) / 2.0f,
.size.width = ringViewLength,
.size.height = ringViewLength
}];
_ringView.accentColor = [UIColor colorWithWhite:1.0 alpha:0.6];
[_circleWrapperView addSubview:_ringView];
CGRect controlsFrame = _controlsFrame;
_controlsView = [[TGVideoMessageControls alloc] initWithFrame:controlsFrame forStory:_forStory assets:_assets slowmodeTimestamp:_slowmodeTimestamp slowmodeView:_slowmodeView];
_controlsView.pallete = self.pallete;
_controlsView.clipsToBounds = true;
_controlsView.parent = self;
_controlsView.isAlreadyLocked = self.isAlreadyLocked;
_controlsView.controlsHeight = _controlsFrame.size.height;
__weak TGVideoMessageCaptureController *weakSelf = self;
_controlsView.cancel = ^
{
__strong TGVideoMessageCaptureController *strongSelf = weakSelf;
if (strongSelf != nil)
{
strongSelf->_automaticDismiss = true;
[strongSelf dismiss:true];
if (strongSelf.onCancel != nil)
strongSelf.onCancel();
}
};
_controlsView.deletePressed = ^
{
__strong TGVideoMessageCaptureController *strongSelf = weakSelf;
if (strongSelf != nil)
{
strongSelf->_automaticDismiss = true;
[strongSelf dismiss:true];
if (strongSelf.onCancel != nil)
strongSelf.onCancel();
};
};
_controlsView.sendPressed = ^bool
{
__strong TGVideoMessageCaptureController *strongSelf = weakSelf;
if (strongSelf != nil) {
return [strongSelf sendPressed];
} else {
return false;
}
};
_controlsView.sendLongPressed = ^bool{
__strong TGVideoMessageCaptureController *strongSelf = weakSelf;
if (strongSelf != nil) {
[strongSelf sendLongPressed];
}
return true;
};
[self.view addSubview:_controlsView];
_separatorView = [[UIView alloc] initWithFrame:CGRectMake(controlsFrame.origin.x, controlsFrame.origin.y - TGScreenPixel, controlsFrame.size.width, TGScreenPixel)];
_separatorView.backgroundColor = self.pallete != nil ? self.pallete.borderColor : UIColorRGB(0xb2b2b2);
_separatorView.userInteractionEnabled = false;
if (!_forStory) {
[self.view addSubview:_separatorView];
}
if ([TGVideoCameraPipeline cameraPositionChangeAvailable])
{
UIImage *switchImage = TGComponentsImageNamed(@"VideoRecordPositionSwitch");
if (self.pallete != nil)
switchImage = TGTintedImage(switchImage, self.pallete.buttonColor);
_switchButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 44.0f, 44.0f)];
_switchButton.alpha = 0.0f;
_switchButton.adjustsImageWhenHighlighted = false;
_switchButton.adjustsImageWhenDisabled = false;
[_switchButton setImage:switchImage forState:UIControlStateNormal];
[_switchButton addTarget:self action:@selector(changeCameraPosition) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:_switchButton];
}
_pinchGestureRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinch:)];
_pinchGestureRecognizer.delegate = self;
[self.view addGestureRecognizer:_pinchGestureRecognizer];
void (^voidBlock)(void) = ^{};
_buttonHandler = [[PGCameraVolumeButtonHandler alloc] initWithIsCameraSpecific:true eventView:self.view upButtonPressedBlock:voidBlock upButtonReleasedBlock:voidBlock downButtonPressedBlock:voidBlock downButtonReleasedBlock:voidBlock];
[self configureCamera];
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer == _pinchGestureRecognizer)
return _capturePipeline.isZoomAvailable;
return true;
}
- (void)handlePinch:(UIPinchGestureRecognizer *)gestureRecognizer
{
switch (gestureRecognizer.state)
{
case UIGestureRecognizerStateChanged:
{
CGFloat delta = (gestureRecognizer.scale - 1.0f) / 1.5f;
CGFloat value = MAX(0.0f, MIN(1.0f, _capturePipeline.zoomLevel + delta));
[_capturePipeline setZoomLevel:value];
gestureRecognizer.scale = 1.0f;
}
break;
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled:
[_capturePipeline cancelZoom];
break;
default:
break;
}
}
- (TGVideoMessageTransitionType)_transitionType
{
static dispatch_once_t onceToken;
static TGVideoMessageTransitionType type;
dispatch_once(&onceToken, ^
{
CGSize screenSize = TGScreenSize();
if (iosMajorVersion() < 8 || (NSInteger)screenSize.height == 480)
type = TGVideoMessageTransitionTypeLegacy;
else if (iosMajorVersion() == 8)
type = TGVideoMessageTransitionTypeSimplified;
else
type = TGVideoMessageTransitionTypeUsual;
});
return type;
}
- (void)setupPreviewView
{
_previewView = [[TGVideoCameraGLView alloc] initWithFrame:_circleView.bounds];
[_circleView insertSubview:_previewView belowSubview:_placeholderView];
if (@available(iOS 11.0, *)) {
_previewView.accessibilityIgnoresInvertColors = true;
}
[self captureStarted];
}
- (void)_transitionIn
{
TGVideoMessageTransitionType type = [self _transitionType];
if (type == TGVideoMessageTransitionTypeUsual)
{
UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
UIView *rootView = _transitionInView();
rootView.superview.backgroundColor = [UIColor whiteColor];
[UIView animateWithDuration:0.22 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^
{
((UIVisualEffectView *)_blurView).effect = effect;
_fadeView.alpha = 1.0f;
} completion:nil];
}
else if (type == TGVideoMessageTransitionTypeSimplified)
{
[UIView animateWithDuration:0.22 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^
{
_blurView.alpha = 1.0f;
} completion:nil];
}
else
{
[UIView animateWithDuration:0.25 animations:^
{
_fadeView.alpha = 1.0f;
}];
}
}
- (void)_transitionOut
{
TGVideoMessageTransitionType type = [self _transitionType];
if (type == TGVideoMessageTransitionTypeUsual)
{
[UIView animateWithDuration:0.22 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^
{
((UIVisualEffectView *)_blurView).effect = nil;
_fadeView.alpha = 0.0f;
} completion:nil];
}
else if (type == TGVideoMessageTransitionTypeSimplified)
{
[UIView animateWithDuration:0.22 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^
{
_blurView.alpha = 0.0f;
} completion:nil];
}
else
{
[UIView animateWithDuration:0.15 animations:^
{
_fadeView.alpha = 0.0f;
}];
}
}
- (void)viewWillAppear:(BOOL)animated
{
if (self.ignoreAppearEvents) {
return;
}
[super viewWillAppear:animated];
_capturePipeline.renderingEnabled = true;
_startTimestamp = CFAbsoluteTimeGetCurrent();
[_controlsView setShowRecordingInterface:true velocity:0.0f];
[[[LegacyComponentsGlobals provider] applicationInstance] setIdleTimerDisabled:true];
[self _transitionIn];
[self _beginAudioSession:false];
}
- (void)viewDidAppear:(BOOL)animated
{
if (self.ignoreAppearEvents) {
return;
}
[super viewDidAppear:animated];
_autorotationWasEnabled = [TGViewController autorotationAllowed];
[TGViewController disableAutorotation];
_circleWrapperView.transform = CGAffineTransformMakeScale(0.3f, 0.3f);
CGPoint targetPosition = (CGPoint){
.x = _wrapperView.frame.size.width / 2.0f,
.y = _wrapperView.frame.size.height / 2.0f - _controlsView.frame.size.height
};
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
switch (self.interfaceOrientation)
{
case UIInterfaceOrientationLandscapeLeft:
break;
case UIInterfaceOrientationLandscapeRight:
break;
default:
if (self.view.frame.size.height > self.view.frame.size.width && fabs(_wrapperView.frame.size.height - self.view.frame.size.height) < 50.0f)
targetPosition.y = _wrapperView.frame.size.height / 3.0f - 20.0f;
CGFloat minY = _circleWrapperView.bounds.size.height / 2.0f + 40.0f;
if (fabs(_wrapperView.frame.size.height - self.view.frame.size.height) > 50.0 && _wrapperView.frame.size.width == 320.0) {
minY = _circleWrapperView.bounds.size.height / 2.0f + 4.0;
}
targetPosition.y = MAX(minY, targetPosition.y);
break;
}
#pragma clang diagnostic pop
if (TGIsPad()) {
_circleWrapperView.center = targetPosition;
}
[UIView animateWithDuration:0.5 delay:0.0 usingSpringWithDamping:0.8f initialSpringVelocity:0.2f options:kNilOptions animations:^
{
if (!TGIsPad()) {
_circleWrapperView.center = targetPosition;
}
_circleWrapperView.transform = CGAffineTransformIdentity;
} completion:nil];
[UIView animateWithDuration:0.2 animations:^
{
_circleWrapperView.alpha = 1.0f;
} completion:nil];
}
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
CGRect fadeFrame = TGIsPad() || _forStory ? self.view.bounds : CGRectMake(0.0f, 0.0f, _wrapperView.frame.size.width, _wrapperView.frame.size.height);
_blurView.frame = fadeFrame;
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)__unused toInterfaceOrientation duration:(NSTimeInterval)__unused duration
{
if (TGIsPad())
{
_automaticDismiss = true;
[self dismiss:true];
}
}
- (void)dismissImmediately
{
[super dismiss];
[[[LegacyComponentsGlobals provider] applicationInstance] setIdleTimerDisabled:false];
[self stopCapture];
[self _endAudioSession];
if (_autorotationWasEnabled)
[TGViewController enableAutorotation];
if (_didDismiss) {
_didDismiss();
}
}
- (void)dismiss
{
[self dismiss:true];
}
- (void)dismiss:(bool)cancelled
{
_dismissed = cancelled;
if (self.onDismiss != nil)
self.onDismiss(_automaticDismiss, cancelled);
if (_player != nil)
[_player pause];
self.view.backgroundColor = [UIColor clearColor];
self.view.userInteractionEnabled = false;
_circleWrapperView.layer.allowsGroupOpacity = true;
[UIView animateWithDuration:0.15 animations:^
{
_circleWrapperView.alpha = 0.0f;
_switchButton.alpha = 0.0f;
}];
[self _transitionOut];
[_controlsView setShowRecordingInterface:false velocity:0.0f];
TGDispatchAfter(0.3, dispatch_get_main_queue(), ^
{
[self dismissImmediately];
});
}
- (void)complete
{
if (_stopped)
return;
[_activityDisposable dispose];
[self stopRecording:^() {
TGDispatchOnMainThread(^{
//[self dismiss:false];
[self description];
});
}];
}
- (void)buttonInteractionUpdate:(CGPoint)value
{
[_controlsView buttonInteractionUpdate:value];
}
- (void)setLocked
{
if ([self.view.window isKindOfClass:[TGVideoMessageCaptureControllerWindow class]]) {
((TGVideoMessageCaptureControllerWindow *)self.view.window).locked = true;
}
[_controlsView setLocked];
}
- (CGRect)frameForSendButton {
return [_controlsView convertRect:[_controlsView frameForSendButton] toView:self.view];
}
- (bool)stop
{
if (!_capturePipeline.isRecording)
return false;
if (_capturePipeline.videoDuration < 0.33)
return false;
if ([self.view.window isKindOfClass:[TGVideoMessageCaptureControllerWindow class]]) {
((TGVideoMessageCaptureControllerWindow *)self.view.window).locked = false;
}
_stopped = true;
_gpuAvailable = false;
_switchButton.userInteractionEnabled = false;
[_activityDisposable dispose];
[self stopRecording:^{}];
if (self.didStop != nil) {
self.didStop();
}
return true;
}
- (void)send
{
[self sendPressed];
}
- (bool)sendPressed
{
if (_slowmodeTimestamp != 0) {
int32_t timestamp = (int32_t)[[NSDate date] timeIntervalSince1970];
if (timestamp < _slowmodeTimestamp) {
if (_displaySlowmodeTooltip) {
_displaySlowmodeTooltip();
}
return false;
}
}
[self finishWithURL:_url dimensions:CGSizeMake(240.0f, 240.0f) duration:_duration liveUploadData:_liveUploadData thumbnailImage:_thumbnailImage isSilent:false scheduleTimestamp:0];
_automaticDismiss = true;
[self dismiss:false];
return true;
}
- (void)sendLongPressed {
if (iosMajorVersion() >= 10) {
if (_generator == nil) {
_generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium];
}
[_generator impactOccurred];
}
TGMediaPickerSendActionSheetController *controller = [[TGMediaPickerSendActionSheetController alloc] initWithContext:_context isDark:self.pallete.isDark sendButtonFrame:[_controlsView convertRect:[_controlsView frameForSendButton] toView:nil] canSendSilently:_canSendSilently canSendWhenOnline:false canSchedule:_canSchedule reminder:_reminder hasTimer:false];
__weak TGVideoMessageCaptureController *weakSelf = self;
controller.send = ^{
__strong TGVideoMessageCaptureController *strongSelf = weakSelf;
if (strongSelf == nil) {
return;
}
[strongSelf finishWithURL:strongSelf->_url dimensions:CGSizeMake(240.0f, 240.0f) duration:strongSelf->_duration liveUploadData:strongSelf->_liveUploadData thumbnailImage:strongSelf->_thumbnailImage isSilent:false scheduleTimestamp:0];
_automaticDismiss = true;
[strongSelf dismiss:false];
};
controller.sendSilently = ^{
__strong TGVideoMessageCaptureController *strongSelf = weakSelf;
if (strongSelf == nil) {
return;
}
[strongSelf finishWithURL:strongSelf->_url dimensions:CGSizeMake(240.0f, 240.0f) duration:strongSelf->_duration liveUploadData:strongSelf->_liveUploadData thumbnailImage:strongSelf->_thumbnailImage isSilent:true scheduleTimestamp:0];
_automaticDismiss = true;
[strongSelf dismiss:false];
};
controller.sendWhenOnline = ^{
__strong TGVideoMessageCaptureController *strongSelf = weakSelf;
if (strongSelf == nil) {
return;
}
[strongSelf finishWithURL:strongSelf->_url dimensions:CGSizeMake(240.0f, 240.0f) duration:strongSelf->_duration liveUploadData:strongSelf->_liveUploadData thumbnailImage:strongSelf->_thumbnailImage isSilent:false scheduleTimestamp:0x7ffffffe];
_automaticDismiss = true;
[strongSelf dismiss:false];
};
controller.schedule = ^{
__strong TGVideoMessageCaptureController *strongSelf = weakSelf;
if (strongSelf == nil) {
return;
}
if (strongSelf.presentScheduleController) {
strongSelf.presentScheduleController(^(int32_t time) {
__strong TGVideoMessageCaptureController *strongSelf = weakSelf;
if (strongSelf == nil) {
return;
}
[strongSelf finishWithURL:strongSelf->_url dimensions:CGSizeMake(240.0f, 240.0f) duration:strongSelf->_duration liveUploadData:strongSelf->_liveUploadData thumbnailImage:strongSelf->_thumbnailImage isSilent:false scheduleTimestamp:time];
_automaticDismiss = true;
[strongSelf dismiss:false];
});
}
};
TGOverlayControllerWindow *controllerWindow = [[TGOverlayControllerWindow alloc] initWithManager:[_context makeOverlayWindowManager] parentController:self contentController:controller];
controllerWindow.hidden = false;
}
- (void)unmutePressed
{
[self _updateMuted:false];
[[SQueue concurrentDefaultQueue] dispatch:^
{
_player.muted = false;
[self _seekToPosition:_controlsView.scrubberView.trimStartValue];
}];
}
- (void)_stop
{
[_controlsView setStopped];
[UIView animateWithDuration:0.2 animations:^
{
_switchButton.alpha = 0.0f;
_ringView.alpha = 0.0f;
} completion:^(__unused BOOL finished)
{
_ringView.hidden = true;
_switchButton.hidden = true;
}];
}
- (UIImage *)systemUnmuteButton {
static UIImage *image = nil;
if (image == nil)
{
UIGraphicsBeginImageContextWithOptions(CGSizeMake(24.0f, 24.0f), false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
UIColor *color = UIColorRGBA(0x000000, 0.4f);
CGContextSetFillColorWithColor(context, color.CGColor);
CGContextFillEllipseInRect(context, CGRectMake(0.0f, 0.0f, 24.0f, 24.0f));
UIImage *iconImage = TGComponentsImageNamed(@"VideoMessageMutedIcon.png");
[iconImage drawAtPoint:CGPointMake(CGFloor((24.0f - iconImage.size.width) / 2.0f), CGFloor((24.0f - iconImage.size.height) / 2.0f))];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
return image;
}
- (void)setupVideoView
{
_controlsView.scrubberView.trimStartValue = 0.0;
_controlsView.scrubberView.trimEndValue = _duration;
[_controlsView.scrubberView setTrimApplied:false];
[_controlsView.scrubberView reloadData];
_player = [[AVPlayer alloc] initWithURL:_url];
_player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
_player.muted = true;
_didPlayToEndObserver = [[TGObserverProxy alloc] initWithTarget:self targetSelector:@selector(playerItemDidPlayToEndTime:) name:AVPlayerItemDidPlayToEndTimeNotification object:_player.currentItem];
_videoView = [[TGModernGalleryVideoView alloc] initWithFrame: CGRectInset(_previewView.frame, -3.0, -3.0) player:_player];
[_previewView.superview insertSubview:_videoView belowSubview:_previewView];
UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(unmutePressed)];
[_videoView addGestureRecognizer:gestureRecognizer];
_muted = true;
_muteView = [[UIImageView alloc] initWithImage:[self systemUnmuteButton]];
_muteView.frame = CGRectMake(floor(CGRectGetMidX(_circleView.bounds) - 12.0f), CGRectGetMaxY(_circleView.bounds) - 24.0f - 8.0f, 24.0f, 24.0f);
[_previewView.superview addSubview:_muteView];
[_player play];
[UIView animateWithDuration:0.1 delay:0.1 options:kNilOptions animations:^
{
_previewView.alpha = 0.0f;
} completion:nil];
}
- (void)_updateMuted:(bool)muted
{
if (muted == _muted)
return;
_muted = muted;
UIView *muteButtonView = _muteView;
[muteButtonView.layer removeAllAnimations];
if ((muteButtonView.transform.a < 0.3f || muteButtonView.transform.a > 1.0f) || muteButtonView.alpha < FLT_EPSILON)
{
muteButtonView.transform = CGAffineTransformMakeScale(0.001f, 0.001f);
muteButtonView.alpha = 0.0f;
}
[UIView animateWithDuration:0.3 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState | 7 << 16 animations:^
{
muteButtonView.transform = muted ? CGAffineTransformIdentity : CGAffineTransformMakeScale(0.001f, 0.001f);
} completion:nil];
[UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^
{
muteButtonView.alpha = muted ? 1.0f : 0.0f;
} completion:nil];
}
- (void)_seekToPosition:(NSTimeInterval)position
{
CMTime targetTime = CMTimeMakeWithSeconds(MIN(position, _duration - 0.1), NSEC_PER_SEC);
[_player.currentItem seekToTime:targetTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
}
- (void)playerItemDidPlayToEndTime:(NSNotification *)__unused notification
{
[self _seekToPosition:_controlsView.scrubberView.trimStartValue];
TGDispatchOnMainThread(^
{
[self _updateMuted:true];
[[SQueue concurrentDefaultQueue] dispatch:^
{
_player.muted = true;
}];
});
}
#pragma mark -
- (void)changeCameraPosition
{
if (_positionChangeLocked)
return;
_preferredPosition = (_preferredPosition == AVCaptureDevicePositionFront) ? AVCaptureDevicePositionBack : AVCaptureDevicePositionFront;
_gpuAvailable = false;
[_previewView removeFromSuperview];
_previewView = nil;
_ringView.alpha = 0.0f;
dispatch_async(dispatch_get_main_queue(), ^
{
[UIView transitionWithView:_circleWrapperView duration:0.4f options:UIViewAnimationOptionTransitionFlipFromLeft | UIViewAnimationOptionCurveEaseOut animations:^
{
_placeholderView.hidden = false;
} completion:^(__unused BOOL finished)
{
_ringView.alpha = 1.0f;
_gpuAvailable = true;
}];
[_capturePipeline setCameraPosition:_preferredPosition];
_positionChangeLocked = true;
TGDispatchAfter(1.0, dispatch_get_main_queue(), ^
{
_positionChangeLocked = false;
});
});
}
#pragma mark -
- (void)startRecording
{
[_buttonHandler ignoreEventsFor:1.0f andDisable:false];
[_capturePipeline startRecording:_url preset:TGMediaVideoConversionPresetVideoMessage liveUpload:true];
[self startRecordingTimer];
}
- (void)stopRecording:(void (^)())completed
{
__weak TGVideoMessageCaptureController *weakSelf = self;
[_capturePipeline stopRecording:^(bool success) {
TGDispatchOnMainThread(^{
__strong TGVideoMessageCaptureController *strongSelf = weakSelf;
if (strongSelf == nil) {
return;
}
if (!success) {
if (!strongSelf->_dismissed && strongSelf.finishedWithVideo != nil) {
strongSelf.finishedWithVideo(nil, nil, 0, 0.0, CGSizeZero, nil, nil, false, 0);
}
}
});
}];
[_buttonHandler ignoreEventsFor:1.0f andDisable:true];
[_capturePipeline stopRunning];
}
- (void)finishWithURL:(NSURL *)url dimensions:(CGSize)dimensions duration:(NSTimeInterval)duration liveUploadData:(id )liveUploadData thumbnailImage:(UIImage *)thumbnailImage isSilent:(bool)isSilent scheduleTimestamp:(int32_t)scheduleTimestamp
{
if (duration < 1.0)
_dismissed = true;
CGFloat minSize = MIN(thumbnailImage.size.width, thumbnailImage.size.height);
CGFloat maxSize = MAX(thumbnailImage.size.width, thumbnailImage.size.height);
bool mirrored = !_startWithRearCam;
UIImageOrientation orientation = [self orientationForThumbnailWithTransform:_capturePipeline.videoTransform mirrored:mirrored];
UIImage *image = TGPhotoEditorCrop(thumbnailImage, nil, orientation, 0.0f, CGRectMake((maxSize - minSize) / 2.0f, 0.0f, minSize, minSize), mirrored, CGSizeMake(240.0f, 240.0f), thumbnailImage.size, true);
NSDictionary *fileDictionary = [[NSFileManager defaultManager] attributesOfItemAtPath:url.path error:NULL];
NSUInteger fileSize = (NSUInteger)[fileDictionary fileSize];
UIImage *startImage = TGSecretBlurredAttachmentImage(image, image.size, NULL, false, 0);
[TGVideoMessageCaptureController saveStartImage:startImage];
TGVideoEditAdjustments *adjustments = nil;
if (_stopped)
{
NSTimeInterval trimStartValue = _controlsView.scrubberView.trimStartValue;
NSTimeInterval trimEndValue = _controlsView.scrubberView.trimEndValue;
if (trimStartValue > DBL_EPSILON || trimEndValue < _duration - DBL_EPSILON)
{
adjustments = [TGVideoEditAdjustments editAdjustmentsWithOriginalSize:dimensions cropRect:CGRectMake(0.0f, 0.0f, dimensions.width, dimensions.height) cropOrientation:UIImageOrientationUp cropRotation:0.0 cropLockedAspectRatio:1.0 cropMirrored:false trimStartValue:trimStartValue trimEndValue:trimEndValue toolValues:nil paintingData:nil sendAsGif:false sendAsTelescope:false preset:TGMediaVideoConversionPresetVideoMessage];
duration = trimEndValue - trimStartValue;
}
if (trimStartValue > DBL_EPSILON)
{
bool generatedImage = false;
AVAsset *asset = [[AVURLAsset alloc] initWithURL:url options:nil];
if (asset != nil) {
AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:asset];
imageGenerator.maximumSize = dimensions;
imageGenerator.appliesPreferredTrackTransform = true;
CGImageRef imageRef = [imageGenerator copyCGImageAtTime:CMTimeMakeWithSeconds(trimStartValue, 24) actualTime:nil error:nil];
if (imageRef != nil) {
image = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
generatedImage = true;
}
}
if (!generatedImage) {
NSArray *thumbnail = [self thumbnailsForTimestamps:@[@(trimStartValue)]];
image = thumbnail.firstObject;
}
}
}
if (!_dismissed) {
self.finishedWithVideo(url, image, fileSize, duration, dimensions, liveUploadData, adjustments, isSilent, scheduleTimestamp);
} else {
[[NSFileManager defaultManager] removeItemAtURL:url error:NULL];
if (self.finishedWithVideo != nil) {
self.finishedWithVideo(nil, nil, 0, 0.0, CGSizeZero, nil, nil, false, 0);
}
}
}
- (UIImageOrientation)orientationForThumbnailWithTransform:(CGAffineTransform)transform mirrored:(bool)mirrored
{
CGFloat angle = atan2(transform.b, transform.a);
NSInteger degrees = (360 + (NSInteger)TGRadiansToDegrees(angle)) % 360;
switch (degrees)
{
case 90:
return mirrored ? UIImageOrientationLeft : UIImageOrientationRight;
break;
case 180:
return UIImageOrientationDown;
break;
case 270:
return mirrored ? UIImageOrientationLeft : UIImageOrientationRight;
default:
break;
}
return UIImageOrientationUp;
}
#pragma mark -
- (void)startRecordingTimer
{
[_controlsView recordingStarted];
[_controlsView setDurationString:@"0:00,00"];
self.onDuration(0);
_audioRecordingDurationSeconds = 0;
_audioRecordingDurationMilliseconds = 0.0;
_recordingTimer = [TGTimerTarget scheduledMainThreadTimerWithTarget:self action:@selector(timerEvent) interval:2.0 / 60.0 repeat:false];
}
- (void)timerEvent
{
if (_recordingTimer != nil)
{
[_recordingTimer invalidate];
_recordingTimer = nil;
}
NSTimeInterval recordingDuration = _capturePipeline.videoDuration;
if (isnan(recordingDuration))
recordingDuration = 0.0;
if (recordingDuration < _previousDuration)
recordingDuration = _previousDuration;
_previousDuration = recordingDuration;
[_ringView setValue:recordingDuration / TGVideoMessageMaximumDuration];
CFAbsoluteTime currentTime = CACurrentMediaTime();
NSUInteger currentDurationSeconds = (NSUInteger)recordingDuration;
NSUInteger currentDurationMilliseconds = (int)(recordingDuration * 100.0f) % 100;
if (currentDurationSeconds == _audioRecordingDurationSeconds && currentDurationMilliseconds == _audioRecordingDurationMilliseconds)
{
_recordingTimer = [TGTimerTarget scheduledMainThreadTimerWithTarget:self action:@selector(timerEvent) interval:MAX(0.01, _audioRecordingDurationSeconds + 2.0 / 60.0 - currentTime) repeat:false];
}
else
{
self.onDuration(recordingDuration);
_audioRecordingDurationSeconds = currentDurationSeconds;
_audioRecordingDurationMilliseconds = currentDurationMilliseconds;
[_controlsView setDurationString:[[NSString alloc] initWithFormat:@"%d:%02d,%02d", (int)_audioRecordingDurationSeconds / 60, (int)_audioRecordingDurationSeconds % 60, (int)_audioRecordingDurationMilliseconds]];
_recordingTimer = [TGTimerTarget scheduledMainThreadTimerWithTarget:self action:@selector(timerEvent) interval:2.0 / 60.0 repeat:false];
}
if (recordingDuration >= TGVideoMessageMaximumDuration)
{
[_recordingTimer invalidate];
_recordingTimer = nil;
_automaticDismiss = true;
[self stop];
if (self.onStop != nil)
self.onStop();
}
}
- (void)stopRecordingTimer
{
if (_recordingTimer != nil)
{
[_recordingTimer invalidate];
_recordingTimer = nil;
}
}
#pragma mark -
- (void)captureStarted
{
bool firstTime = !_alreadyStarted;
_alreadyStarted = true;
_switchButton.frame = CGRectMake(11.0f, _controlsFrame.origin.y - _switchButton.frame.size.height - 7.0f, _switchButton.frame.size.width, _switchButton.frame.size.height);
NSTimeInterval delay = firstTime ? 0.1 : 0.2;
[UIView animateWithDuration:0.3 delay:delay options:kNilOptions animations:^
{
_placeholderView.alpha = 0.0f;
_shimmerView.alpha = 0.0f;
_switchButton.alpha = 1.0f;
} completion:^(__unused BOOL finished)
{
_shimmerView.hidden = true;
_placeholderView.hidden = true;
_placeholderView.alpha = 1.0f;
}];
if (firstTime)
{
TGDispatchAfter(0.2, dispatch_get_main_queue(), ^
{
[self startRecording];
});
}
}
- (void)stopCapture
{
[_capturePipeline stopRunning];
}
- (void)configureCamera
{
_capturePipeline = [[TGVideoCameraPipeline alloc] initWithDelegate:self position:_preferredPosition callbackQueue:dispatch_get_main_queue() liveUploadInterface:_liveUploadInterface];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
_capturePipeline.orientation = (AVCaptureVideoOrientation)self.interfaceOrientation;
#pragma clang diagnostic pop
__weak TGVideoMessageCaptureController *weakSelf = self;
_capturePipeline.micLevel = ^(CGFloat level)
{
TGDispatchOnMainThread(^
{
__strong TGVideoMessageCaptureController *strongSelf = weakSelf;
if (strongSelf != nil && strongSelf.micLevel != nil)
strongSelf.micLevel(level);
});
};
}
#pragma mark -
- (void)capturePipeline:(TGVideoCameraPipeline *)__unused capturePipeline didStopRunningWithError:(NSError *)__unused error
{
}
- (void)capturePipeline:(TGVideoCameraPipeline *)__unused capturePipeline previewPixelBufferReadyForDisplay:(TGVideoCameraRendererBuffer *)previewPixelBuffer
{
if (!_gpuAvailable)
return;
if (!_previewView)
[self setupPreviewView];
[_previewView displayPixelBuffer:previewPixelBuffer];
}
- (void)capturePipelineDidRunOutOfPreviewBuffers:(TGVideoCameraPipeline *)__unused capturePipeline
{
if (_gpuAvailable)
[_previewView flushPixelBufferCache];
}
- (void)capturePipelineRecordingDidStart:(TGVideoCameraPipeline *)__unused capturePipeline
{
__weak TGVideoMessageCaptureController *weakSelf = self;
[_activityDisposable setDisposable:[[[SSignal complete] delay:0.3 onQueue:[SQueue mainQueue]] startStrictWithNext:nil error:nil completed:^{
__strong TGVideoMessageCaptureController *strongSelf = weakSelf;
if (strongSelf != nil && strongSelf->_requestActivityHolder) {
strongSelf->_activityHolder = strongSelf->_requestActivityHolder();
}
} file:__FILE_NAME__ line:__LINE__]];
}
- (void)capturePipelineRecordingWillStop:(TGVideoCameraPipeline *)__unused capturePipeline
{
}
- (void)capturePipelineRecordingDidStop:(TGVideoCameraPipeline *)__unused capturePipeline duration:(NSTimeInterval)duration liveUploadData:(id)liveUploadData thumbnailImage:(UIImage *)thumbnailImage thumbnails:(NSDictionary *)thumbnails
{
if (_stopped && duration > 0.33)
{
_duration = duration;
_liveUploadData = liveUploadData;
_thumbnailImage = thumbnailImage;
_thumbnails = thumbnails;
TGDispatchOnMainThread(^
{
[self _stop];
[self setupVideoView];
});
}
else
{
[self finishWithURL:_url dimensions:CGSizeMake(240.0f, 240.0f) duration:duration liveUploadData:liveUploadData thumbnailImage:thumbnailImage isSilent:false scheduleTimestamp:0];
}
}
- (void)capturePipeline:(TGVideoCameraPipeline *)__unused capturePipeline recordingDidFailWithError:(NSError *)__unused error
{
}
#pragma mark -
- (void)_beginAudioSession:(bool)speaker
{
[_queue dispatch:^
{
_otherAudioPlaying = [[AVAudioSession sharedInstance] isOtherAudioPlaying];
__weak TGVideoMessageCaptureController *weakSelf = self;
id<SDisposable> disposable = [[LegacyComponentsGlobals provider] requestAudioSession:speaker ? TGAudioSessionTypePlayAndRecordHeadphones : TGAudioSessionTypePlayAndRecord
activated:^{
__strong TGVideoMessageCaptureController *strongSelf = weakSelf;
if (strongSelf != nil) {
[strongSelf->_queue dispatch:^
{
[strongSelf->_capturePipeline startRunning];
}];
}
} interrupted:^
{
TGDispatchOnMainThread(^{
__strong TGVideoMessageCaptureController *strongSelf = weakSelf;
if (strongSelf != nil) {
strongSelf->_automaticDismiss = true;
[strongSelf complete];
}
});
}];
[_currentAudioSession setDisposable:disposable];
}];
}
- (void)_endAudioSession
{
id<SDisposable> currentAudioSession = _currentAudioSession;
[_queue dispatch:^
{
[currentAudioSession dispose];
}];
}
#pragma mark -
static UIImage *startImage = nil;
+ (NSString *)_startImagePath
{
return [[[LegacyComponentsGlobals provider] dataCachePath] stringByAppendingPathComponent:@"startImage.jpg"];
}
+ (UIImage *)startImage
{
if (startImage == nil)
startImage = [UIImage imageWithContentsOfFile:[self _startImagePath]] ? : TGComponentsImageNamed (@"VideoMessagePlaceholder.jpg");
return startImage;
}
+ (void)saveStartImage:(UIImage *)image
{
if (image == nil)
return;
[self clearStartImage];
startImage = image;
NSData *data = UIImageJPEGRepresentation(image, 0.8f);
[data writeToFile:[self _startImagePath] atomically:true];
}
+ (void)clearStartImage
{
startImage = nil;
[[NSFileManager defaultManager] removeItemAtPath:[self _startImagePath] error:NULL];
}
+ (void)requestCameraAccess:(void (^)(bool granted, bool wasNotDetermined))resultBlock
{
if (iosMajorVersion() < 7)
{
if (resultBlock != nil)
resultBlock(true, false);
return;
}
bool wasNotDetermined = ([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo] == AVAuthorizationStatusNotDetermined);
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted)
{
TGDispatchOnMainThread(^
{
if (resultBlock != nil)
resultBlock(granted, wasNotDetermined);
});
}];
}
+ (void)requestMicrophoneAccess:(void (^)(bool granted, bool wasNotDetermined))resultBlock
{
if (iosMajorVersion() < 7)
{
if (resultBlock != nil)
resultBlock(true, false);
return;
}
bool wasNotDetermined = ([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio] == AVAuthorizationStatusNotDetermined);
[[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted)
{
TGDispatchOnMainThread(^
{
if (resultBlock != nil)
resultBlock(granted, wasNotDetermined);
});
}];
}
#pragma mark - Scrubbing
- (NSTimeInterval)videoScrubberDuration:(TGVideoMessageScrubber *)__unused videoScrubber
{
return _duration;
}
- (void)videoScrubberDidBeginScrubbing:(TGVideoMessageScrubber *)__unused videoScrubber
{
}
- (void)videoScrubberDidEndScrubbing:(TGVideoMessageScrubber *)__unused videoScrubber
{
}
- (void)videoScrubber:(TGVideoMessageScrubber *)__unused videoScrubber valueDidChange:(NSTimeInterval)__unused position
{
}
#pragma mark - Trimming
- (void)videoScrubberDidBeginEditing:(TGVideoMessageScrubber *)__unused videoScrubber
{
[_player pause];
}
- (void)videoScrubberDidEndEditing:(TGVideoMessageScrubber *)videoScrubber endValueChanged:(bool)endValueChanged
{
[self updatePlayerRange:videoScrubber.trimEndValue];
if (endValueChanged)
[self _seekToPosition:videoScrubber.trimStartValue];
[_player play];
}
- (void)videoScrubber:(TGVideoMessageScrubber *)__unused videoScrubber editingStartValueDidChange:(NSTimeInterval)startValue
{
[self _seekToPosition:startValue];
}
- (void)videoScrubber:(TGVideoMessageScrubber *)__unused videoScrubber editingEndValueDidChange:(NSTimeInterval)endValue
{
[self _seekToPosition:endValue];
}
- (void)updatePlayerRange:(NSTimeInterval)trimEndValue
{
_player.currentItem.forwardPlaybackEndTime = CMTimeMakeWithSeconds(trimEndValue, NSEC_PER_SEC);
}
#pragma mark - Thumbnails
- (CGFloat)videoScrubberThumbnailAspectRatio:(TGVideoMessageScrubber *)__unused videoScrubber
{
return 1.0f;
}
- (NSArray *)videoScrubber:(TGVideoMessageScrubber *)videoScrubber evenlySpacedTimestamps:(NSInteger)count startingAt:(NSTimeInterval)startTimestamp endingAt:(NSTimeInterval)endTimestamp
{
if (endTimestamp < startTimestamp)
return nil;
if (count == 0)
return nil;
NSTimeInterval duration = [self videoScrubberDuration:videoScrubber];
if (endTimestamp > duration)
endTimestamp = duration;
NSTimeInterval interval = (endTimestamp - startTimestamp) / count;
NSMutableArray *timestamps = [[NSMutableArray alloc] init];
for (NSInteger i = 0; i < count; i++)
[timestamps addObject:@(startTimestamp + i * interval)];
return timestamps;
}
- (NSArray *)thumbnailsForTimestamps:(NSArray *)timestamps
{
NSArray *thumbnailTimestamps = [_thumbnails.allKeys sortedArrayUsingSelector:@selector(compare:)];
NSMutableArray *thumbnails = [[NSMutableArray alloc] init];
__block NSUInteger i = 1;
[timestamps enumerateObjectsUsingBlock:^(NSNumber *timestampVal, __unused NSUInteger index, __unused BOOL *stop)
{
NSTimeInterval timestamp = timestampVal.doubleValue;
NSNumber *closestTimestamp = [self closestTimestampForTimestamp:timestamp timestamps:thumbnailTimestamps start:i finalIndex:&i];
if (closestTimestamp != nil) {
[thumbnails addObject:_thumbnails[closestTimestamp]];
}
}];
return thumbnails;
}
- (NSNumber *)closestTimestampForTimestamp:(NSTimeInterval)timestamp timestamps:(NSArray *)timestamps start:(NSUInteger)start finalIndex:(NSUInteger *)finalIndex
{
if (start >= timestamps.count) {
return nil;
}
NSTimeInterval leftTimestamp = [timestamps[start - 1] doubleValue];
NSTimeInterval rightTimestamp = [timestamps[start] doubleValue];
if (fabs(leftTimestamp - timestamp) < fabs(rightTimestamp - timestamp))
{
*finalIndex = start;
return timestamps[start - 1];
}
else
{
if (start == timestamps.count - 1)
{
*finalIndex = start;
return timestamps[start];
}
return [self closestTimestampForTimestamp:timestamp timestamps:timestamps start:start + 1 finalIndex:finalIndex];
}
}
- (void)videoScrubber:(TGVideoMessageScrubber *)__unused videoScrubber requestThumbnailImagesForTimestamps:(NSArray *)timestamps size:(CGSize)__unused size isSummaryThumbnails:(bool)isSummaryThumbnails
{
if (timestamps.count == 0)
return;
NSArray *thumbnails = [self thumbnailsForTimestamps:timestamps];
[thumbnails enumerateObjectsUsingBlock:^(UIImage *image, NSUInteger index, __unused BOOL *stop)
{
if (index < timestamps.count)
[_controlsView.scrubberView setThumbnailImage:image forTimestamp:[timestamps[index] doubleValue] isSummaryThubmnail:isSummaryThumbnails];
}];
}
- (void)videoScrubberDidFinishRequestingThumbnails:(TGVideoMessageScrubber *)__unused videoScrubber
{
[_controlsView showScrubberView];
}
- (void)videoScrubberDidCancelRequestingThumbnails:(TGVideoMessageScrubber *)__unused videoScrubber
{
}
- (CGSize)videoScrubberOriginalSize:(TGVideoMessageScrubber *)__unused videoScrubber cropRect:(CGRect *)cropRect cropOrientation:(UIImageOrientation *)cropOrientation cropMirrored:(bool *)cropMirrored
{
if (cropRect != NULL)
*cropRect = CGRectMake(0.0f, 0.0f, 240.0f, 240.0f);
if (cropOrientation != NULL)
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
if (UIInterfaceOrientationIsPortrait(self.interfaceOrientation))
*cropOrientation = UIImageOrientationUp;
else if (self.interfaceOrientation == UIInterfaceOrientationLandscapeLeft)
*cropOrientation = UIImageOrientationRight;
else if (self.interfaceOrientation == UIInterfaceOrientationLandscapeRight)
*cropOrientation = UIImageOrientationLeft;
#pragma clang diagnostic pop
}
if (cropMirrored != NULL)
*cropMirrored = false;
return CGSizeMake(240.0f, 240.0f);
}
- (UIView *)extractVideoContent {
UIView *result = [_circleView snapshotViewAfterScreenUpdates:false];
result.frame = [_circleView convertRect:_circleView.bounds toView:nil];
return result;
}
- (void)hideVideoContent {
_circleWrapperView.alpha = 0.02f;
}
@end
@implementation TGVideoMessageCaptureControllerWindow
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
bool flag = [super pointInside:point withEvent:event];
if (_locked)
{
if (point.x >= self.frame.size.width - 60.0f && point.y >= self.controlsFrame.origin.y && point.y < CGRectGetMaxY(self.controlsFrame))
return false;
}
return flag;
}
@end