mirror of
https://github.com/danog/Telegram.git
synced 2024-11-26 20:14:43 +01:00
716 lines
26 KiB
Objective-C
716 lines
26 KiB
Objective-C
#import "TGShareController.h"
|
|
#import "TGShareRecipientController.h"
|
|
|
|
#import "TGColor.h"
|
|
#import "TGGeometry.h"
|
|
#import "TGScaleImage.h"
|
|
|
|
#import "TGChatModel.h"
|
|
#import "TGContactModel.h"
|
|
|
|
#import "TGShareContextSignal.h"
|
|
#import "TGSendMessageSignals.h"
|
|
#import "TGUploadMediaSignals.h"
|
|
#import "TGShareVideoConverter.h"
|
|
|
|
#import "TGSharePasscodeView.h"
|
|
#import "TGShareToolbarView.h"
|
|
#import "TGProgressAlert.h"
|
|
|
|
#import "TGItemProviderSignals.h"
|
|
|
|
#import "TGUploadedMessageContentText.h"
|
|
#import "TGUploadedMessageContentMedia.h"
|
|
#import "TGSendMessageSignals.h"
|
|
#import "TGShareContactSignals.h"
|
|
#import "TGShareLocationSignals.h"
|
|
#import "TGShareRecentPeersSignals.h"
|
|
|
|
@interface TGShareController ()
|
|
{
|
|
NSArray *_items;
|
|
SVariable *_shareContext;
|
|
TGShareContext *_currentShareContext;
|
|
SMetaDisposable *_sendMessagesDisposable;
|
|
|
|
TGSharePasscodeView *_passcodeView;
|
|
TGProgressAlert *_progressAlert;
|
|
}
|
|
@end
|
|
|
|
@implementation TGShareController
|
|
|
|
- (instancetype)init
|
|
{
|
|
self = [super initWithRootViewController:[[TGShareRecipientController alloc] init]];
|
|
if (self != nil)
|
|
{
|
|
[self.navigationBar setTintColor:TGColorWithHex(0x007ee5)];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)beginRequestWithExtensionContext:(NSExtensionContext *)context
|
|
{
|
|
[super beginRequestWithExtensionContext:context];
|
|
|
|
_items = context.inputItems;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
[_sendMessagesDisposable dispose];
|
|
}
|
|
|
|
- (void)loadView
|
|
{
|
|
[super loadView];
|
|
|
|
__weak TGShareController *weakSelf = self;
|
|
_toolbarView = [[TGShareToolbarView alloc] initWithFrame:CGRectMake(0, self.view.frame.size.height - 44.0f, self.view.frame.size.width, 44.0f)];
|
|
_toolbarView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
|
|
_toolbarView.leftPressed = ^
|
|
{
|
|
__strong TGShareController *strongSelf = weakSelf;
|
|
if (strongSelf == nil)
|
|
return;
|
|
|
|
[strongSelf dismissForCancel:true];
|
|
};
|
|
_toolbarView.rightPressed = ^
|
|
{
|
|
__strong TGShareController *strongSelf = weakSelf;
|
|
if (strongSelf == nil)
|
|
return;
|
|
|
|
if ([strongSelf.viewControllers.lastObject isKindOfClass:[TGShareRecipientController class]])
|
|
([(TGShareRecipientController *)strongSelf.viewControllers.lastObject proceed]);
|
|
};
|
|
[self.view addSubview:_toolbarView];
|
|
}
|
|
|
|
- (void)viewDidLoad
|
|
{
|
|
[super viewDidLoad];
|
|
|
|
_shareContext = [[SVariable alloc] init];
|
|
[_shareContext set:[[TGShareContextSignal shareContext] catch:^SSignal *(id error)
|
|
{
|
|
return [SSignal single:[[TGUnauthorizedShareContext alloc] init]];
|
|
}]];
|
|
}
|
|
|
|
- (void)viewWillAppear:(BOOL)animated
|
|
{
|
|
[super viewWillAppear:animated];
|
|
|
|
self.view.center = CGPointMake(self.view.center.x, self.view.center.y + self.view.frame.size.height);
|
|
|
|
__weak TGShareController *weakSelf = self;
|
|
[[_shareContext.signal deliverOn:[SQueue mainQueue]] startWithNext:^(id next)
|
|
{
|
|
__strong TGShareController *strongSelf = weakSelf;
|
|
if (strongSelf == nil)
|
|
return;
|
|
|
|
[strongSelf setShareContext:next];
|
|
}];
|
|
}
|
|
|
|
- (void)setShareContext:(id)context
|
|
{
|
|
if ([context isKindOfClass:[TGShareContext class]])
|
|
{
|
|
_currentShareContext = context;
|
|
[self animateAppearance];
|
|
|
|
if (_passcodeView != nil)
|
|
{
|
|
UIView *passcodeView = _passcodeView;
|
|
_passcodeView = nil;
|
|
|
|
[UIView animateWithDuration:0.2 delay:0.0 options:0 animations:^
|
|
{
|
|
passcodeView.frame = CGRectOffset(passcodeView.frame, 0.0f, passcodeView.frame.size.height);
|
|
} completion:^(BOOL finished)
|
|
{
|
|
[passcodeView removeFromSuperview];
|
|
}];
|
|
}
|
|
|
|
[self.recipientController setShareContext:context];
|
|
}
|
|
else if ([context isKindOfClass:[TGEncryptedShareContext class]])
|
|
{
|
|
_currentShareContext = nil;
|
|
[self animateAppearance];
|
|
|
|
TGEncryptedShareContext *encryptedShareContext = context;
|
|
|
|
if (_passcodeView == nil)
|
|
{
|
|
__weak TGShareController *weakSelf = self;
|
|
_passcodeView = [[TGSharePasscodeView alloc] initWithSimpleMode:encryptedShareContext.simplePassword cancel:^
|
|
{
|
|
__strong TGShareController *strongSelf = weakSelf;
|
|
if (strongSelf != nil)
|
|
[strongSelf dismissForCancel:true];
|
|
} verify:^(NSString *passcode, void (^result)(bool))
|
|
{
|
|
result(encryptedShareContext.verifyPassword(passcode));
|
|
} alertPresentationController:self];
|
|
|
|
_passcodeView.frame = self.view.bounds;
|
|
_passcodeView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
[self.view addSubview:_passcodeView];
|
|
|
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^
|
|
{
|
|
[_passcodeView showKeyboard];
|
|
});
|
|
}
|
|
}
|
|
else if ([context isKindOfClass:[TGUnauthorizedShareContext class]])
|
|
{
|
|
_currentShareContext = nil;
|
|
[self showAuthRequiredAlert];
|
|
}
|
|
}
|
|
|
|
- (void)sendToPeers:(NSArray *)peers models:(NSArray *)models
|
|
{
|
|
__weak TGShareController *weakSelf = self;
|
|
|
|
SSignal *itemsSignal = [SSignal complete];
|
|
NSArray *dataSignals = [TGItemProviderSignals itemSignalsForInputItems:_items];
|
|
|
|
NSInteger providerIndex = 0;
|
|
NSInteger providerCount = dataSignals.count;
|
|
|
|
for (SSignal *dataSignal in dataSignals)
|
|
{
|
|
SSignal *sendSignal = [dataSignal mapToSignal:^SSignal *(id value)
|
|
{
|
|
__strong TGShareController *strongSelf = weakSelf;
|
|
if (strongSelf == nil)
|
|
return [SSignal fail:nil];
|
|
|
|
if (![value isKindOfClass:[NSDictionary class]])
|
|
return [SSignal fail:nil];
|
|
|
|
SSignal *uploadMediaSignal = nil;
|
|
|
|
NSDictionary *description = (NSDictionary *)value;
|
|
|
|
if (description[@"image"] != nil)
|
|
{
|
|
UIImage *image = description[@"image"];
|
|
if (image != nil)
|
|
{
|
|
image = TGScaleImage(image, TGFitSize(CGSizeMake(image.size.width * image.scale, image.size.height * image.scale), CGSizeMake(1280.0f, 1280.0f)));
|
|
NSData *imageData = UIImageJPEGRepresentation(image, 0.54f);
|
|
uploadMediaSignal = [TGUploadMediaSignals uploadPhotoWithContext:strongSelf->_currentShareContext data:imageData];
|
|
}
|
|
}
|
|
else if (description[@"data"] != nil)
|
|
{
|
|
NSData *data = description[@"data"];
|
|
NSString *fileName = description[@"fileName"];
|
|
NSString *mimeType = description[@"mimeType"];
|
|
|
|
UIImage *image = [[UIImage alloc] initWithData:data];
|
|
if (image != nil)
|
|
{
|
|
bool isGif = false;
|
|
if (data.length > 4)
|
|
{
|
|
uint8_t header[4];
|
|
[data getBytes:header length:4];
|
|
if (header[0] == 'G' && header[1] == 'I' && header[2] == 'F' && header[3] == '8')
|
|
isGif = true;
|
|
}
|
|
if (isGif)
|
|
{
|
|
uploadMediaSignal = [TGUploadMediaSignals uploadFileWithContext:strongSelf->_currentShareContext data:data name:fileName == nil ? @"animation.gif" : fileName mimeType:@"image/gif" attributes:@[ [Api48_DocumentAttribute documentAttributeAnimated], [Api48_DocumentAttribute documentAttributeImageSizeWithW:@((int32_t)image.size.width) h:@((int32_t)image.size.height)] ]];
|
|
}
|
|
else
|
|
{
|
|
image = TGScaleImage(image, TGFitSize(CGSizeMake(image.size.width * image.scale, image.size.height * image.scale), CGSizeMake(1280.0f, 1280.0f)));
|
|
NSData *imageData = UIImageJPEGRepresentation(image, 0.54f);
|
|
uploadMediaSignal = [TGUploadMediaSignals uploadPhotoWithContext:strongSelf->_currentShareContext data:imageData];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
uploadMediaSignal = [TGUploadMediaSignals uploadFileWithContext:strongSelf->_currentShareContext data:data name:fileName == nil ? @"file" : fileName mimeType:mimeType == nil ? @"application/octet-stream" : mimeType attributes:@[]];
|
|
}
|
|
|
|
}
|
|
else if (description[@"video"] != nil)
|
|
{
|
|
NSURL *url = description[@"video"];
|
|
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:url options:nil];
|
|
uploadMediaSignal = [[TGShareVideoConverter convertSignalForAVAsset:asset] mapToSignal:^SSignal *(id value)
|
|
{
|
|
if ([value isKindOfClass:[NSDictionary class]])
|
|
{
|
|
NSDictionary *desc = (NSDictionary *)value;
|
|
NSError *error;
|
|
NSData *videoData = [NSData dataWithContentsOfURL:desc[@"fileUrl"] options:NSDataReadingMappedIfSafe error:&error];
|
|
if (error != nil)
|
|
return [SSignal fail:nil];
|
|
|
|
UIImage *resizedThumbnail = desc[@"previewImage"];
|
|
resizedThumbnail = TGScaleImage(resizedThumbnail, TGFitSize(resizedThumbnail.size, CGSizeMake(90, 90)));
|
|
NSData *thumbnailData = UIImageJPEGRepresentation(resizedThumbnail, 0.6);
|
|
|
|
int32_t duration = (int32_t)[desc[@"duration"] doubleValue];
|
|
CGSize dimensions = [desc[@"dimensions"] CGSizeValue];
|
|
|
|
return [[TGUploadMediaSignals uploadVideoWithContext:strongSelf->_currentShareContext data:videoData thumbData:thumbnailData duration:duration width:dimensions.width height:dimensions.height mimeType:desc[@"mimeType"]] map:^id(id value)
|
|
{
|
|
if ([value isKindOfClass:[NSNumber class]])
|
|
return @(0.5f + [value floatValue] / 2.0f);
|
|
else
|
|
return value;
|
|
}];
|
|
}
|
|
else if ([value isKindOfClass:[NSNumber class]])
|
|
{
|
|
return [SSignal single:@([value floatValue] / 2.0f)];
|
|
}
|
|
else
|
|
{
|
|
return [SSignal single:value];
|
|
}
|
|
}];
|
|
}
|
|
else if (description[@"audio"] != nil)
|
|
{
|
|
NSURL *url = description[@"audio"];
|
|
|
|
NSError *error;
|
|
NSData *audioData = [NSData dataWithContentsOfURL:url options:NSDataReadingMappedIfSafe error:&error];
|
|
if (error != nil)
|
|
return [SSignal fail:nil];
|
|
|
|
NSString *fileName = url.lastPathComponent;
|
|
|
|
NSTimeInterval duration = [description[@"duration"] doubleValue];
|
|
bool isVoice = [description[@"isVoice"] boolValue] || duration < 30;
|
|
NSString *title = description[@"title"] ? : @"";
|
|
NSString *artist = description[@"artist"] ? : @"";
|
|
|
|
int32_t flags = 0;
|
|
if (isVoice) {
|
|
flags |= (1 << 10);
|
|
}
|
|
if (title.length > 0) {
|
|
flags |= (1 << 0);
|
|
}
|
|
if (artist != nil) {
|
|
flags |= (1 << 1);
|
|
}
|
|
|
|
NSData *waveform = nil;
|
|
if (isVoice) {
|
|
waveform = [TGShareController audioWaveform:url];
|
|
}
|
|
|
|
if (waveform != nil) {
|
|
flags |= (1 << 2);
|
|
}
|
|
|
|
NSMutableArray *attributes = [[NSMutableArray alloc] init];
|
|
[attributes addObject:[Api48_DocumentAttribute_documentAttributeAudio documentAttributeAudioWithFlags:@(flags) duration:description[@"duration"] title:title performer:artist waveform:waveform]];
|
|
|
|
uploadMediaSignal = [TGUploadMediaSignals uploadFileWithContext:strongSelf->_currentShareContext data:audioData name:fileName mimeType:description[@"mimeType"] attributes:attributes];
|
|
}
|
|
else if (description[@"text"] != nil)
|
|
{
|
|
NSString *text = description[@"text"];
|
|
return [SSignal single:[[TGUploadedMessageContentText alloc] initWithText:text]];
|
|
}
|
|
else if (description[@"url"] != nil)
|
|
{
|
|
NSURL *url = (NSURL *)description[@"url"];
|
|
if ([TGShareLocationSignals isLocationURL:url])
|
|
return [TGShareLocationSignals locationMessageContentForURL:url];
|
|
else
|
|
return [SSignal single:[[TGUploadedMessageContentText alloc] initWithText:url.absoluteString]];
|
|
}
|
|
else if (description[@"contact"] != nil)
|
|
{
|
|
TGContactModel *contact = (TGContactModel *)description[@"contact"];
|
|
return [TGShareContactSignals contactMessageContentForContact:contact parentController:strongSelf];
|
|
}
|
|
|
|
if (uploadMediaSignal == nil)
|
|
return [SSignal fail:nil];
|
|
|
|
return [uploadMediaSignal mapToSignal:^SSignal *(id next)
|
|
{
|
|
__strong TGShareController *strongSelf = weakSelf;
|
|
if (strongSelf == nil)
|
|
return [SSignal fail:nil];
|
|
|
|
if ([next isKindOfClass:[Api48_InputMedia class]])
|
|
{
|
|
return [SSignal single:[[TGUploadedMessageContentMedia alloc] initWithInputMedia:next]];
|
|
}
|
|
else
|
|
{
|
|
return [SSignal single:@((providerIndex + [next floatValue]) / providerCount)];
|
|
}
|
|
}];
|
|
}];
|
|
|
|
if (sendSignal != nil)
|
|
itemsSignal = [itemsSignal then:sendSignal];
|
|
|
|
providerIndex++;
|
|
}
|
|
|
|
itemsSignal = [itemsSignal reduceLeftWithPassthrough:@[] with:^id (NSArray *currentUploadedMessageContents, id next, void (^passthrough)(id))
|
|
{
|
|
if ([next isKindOfClass:[TGUploadedMessageContent class]])
|
|
return [currentUploadedMessageContents arrayByAddingObject:next];
|
|
else
|
|
passthrough(next);
|
|
|
|
return currentUploadedMessageContents;
|
|
}];
|
|
|
|
itemsSignal = [itemsSignal mapToSignal:^SSignal *(id next)
|
|
{
|
|
if ([next respondsToSelector:@selector(floatValue)])
|
|
return [SSignal single:next];
|
|
else
|
|
{
|
|
__strong TGShareController *strongSelf = weakSelf;
|
|
if (strongSelf == nil)
|
|
return [SSignal fail:nil];
|
|
|
|
SSignal *sendMessages = [SSignal complete];
|
|
for (NSValue *peerVal in peers)
|
|
{
|
|
TGPeerId peerId;
|
|
[peerVal getValue:&peerId];
|
|
|
|
[TGShareRecentPeersSignals addRecentPeerResult:peerId];
|
|
|
|
for (id content in next)
|
|
{
|
|
if ([content isKindOfClass:[TGUploadedMessageContentText class]])
|
|
{
|
|
sendMessages = [sendMessages then:[TGSendMessageSignals sendTextMessageWithContext:strongSelf->_currentShareContext peerId:peerId users:models text:((TGUploadedMessageContentText *)content).text]];
|
|
}
|
|
else if ([content isKindOfClass:[TGUploadedMessageContentMedia class]])
|
|
{
|
|
sendMessages = [sendMessages then:[TGSendMessageSignals sendMediaWithContext:strongSelf->_currentShareContext peerId:peerId users:models inputMedia:((TGUploadedMessageContentMedia *)content).inputMedia]];
|
|
}
|
|
}
|
|
}
|
|
|
|
return sendMessages;
|
|
}
|
|
}];
|
|
|
|
if (_sendMessagesDisposable == nil)
|
|
_sendMessagesDisposable = [[SMetaDisposable alloc] init];
|
|
|
|
_progressAlert = [[TGProgressAlert alloc] initWithFrame:self.view.bounds];
|
|
_progressAlert.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
_progressAlert.text = NSLocalizedString(@"Share.Sharing", nil);
|
|
_progressAlert.alpha = 0.0f;
|
|
[self.view addSubview:_progressAlert];
|
|
[UIView animateWithDuration:0.3 animations:^
|
|
{
|
|
_progressAlert.alpha = 1.0f;
|
|
}];
|
|
|
|
_progressAlert.cancel = ^
|
|
{
|
|
__strong TGShareController *strongSelf = weakSelf;
|
|
if (strongSelf == nil)
|
|
return;
|
|
|
|
[strongSelf->_sendMessagesDisposable setDisposable:nil];
|
|
|
|
[UIView animateWithDuration:0.3 animations:^
|
|
{
|
|
strongSelf->_progressAlert.alpha = 0.0f;
|
|
} completion:^(BOOL finished)
|
|
{
|
|
[strongSelf->_progressAlert removeFromSuperview];
|
|
strongSelf->_progressAlert = nil;
|
|
}];
|
|
};
|
|
|
|
itemsSignal = [[itemsSignal then:[SSignal single:@(1.0f)]] then:[[SSignal complete] delay:0.4 onQueue:[SQueue mainQueue]]];
|
|
|
|
[_sendMessagesDisposable setDisposable:[[[itemsSignal deliverOn:[SQueue mainQueue]] onDispose:^
|
|
{
|
|
}] startWithNext:^(id next)
|
|
{
|
|
if ([next respondsToSelector:@selector(floatValue)])
|
|
{
|
|
__strong TGShareController *strongSelf = weakSelf;
|
|
if (strongSelf != nil)
|
|
[strongSelf->_progressAlert setProgress:[next floatValue] animated:true];
|
|
}
|
|
} error:^(id error)
|
|
{
|
|
NSLog(@"error: %@", error);
|
|
|
|
__strong TGShareController *strongSelf = weakSelf;
|
|
if (strongSelf == nil)
|
|
return;
|
|
|
|
[UIView animateWithDuration:0.3 animations:^
|
|
{
|
|
strongSelf->_progressAlert.alpha = 0.0f;
|
|
} completion:^(BOOL finished)
|
|
{
|
|
[strongSelf->_progressAlert removeFromSuperview];
|
|
strongSelf->_progressAlert = nil;
|
|
}];
|
|
} completed:^
|
|
{
|
|
__strong TGShareController *strongSelf = weakSelf;
|
|
if (strongSelf == nil)
|
|
return;
|
|
|
|
[strongSelf dismissForCancel:false];
|
|
}]];
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
static int32_t get_bits(uint8_t const *bytes, unsigned int bitOffset, unsigned int numBits)
|
|
{
|
|
uint8_t const *data = bytes;
|
|
numBits = (unsigned int)pow(2, numBits) - 1; //this will only work up to 32 bits, of course
|
|
data += bitOffset / 8;
|
|
bitOffset %= 8;
|
|
return (*((int*)data) >> bitOffset) & numBits;
|
|
}
|
|
|
|
static void set_bits(uint8_t *bytes, int32_t bitOffset, int32_t numBits, int32_t value) {
|
|
numBits = (unsigned int)pow(2, numBits) - 1; //this will only work up to 32 bits, of course
|
|
uint8_t *data = bytes;
|
|
data += bitOffset / 8;
|
|
bitOffset %= 8;
|
|
*((int32_t *)data) |= ((value) << bitOffset);
|
|
}
|
|
|
|
+ (NSData *)audioWaveform:(NSURL *)url {
|
|
NSDictionary *outputSettings = [NSDictionary dictionaryWithObjectsAndKeys:
|
|
[NSNumber numberWithInt:kAudioFormatLinearPCM], AVFormatIDKey,
|
|
[NSNumber numberWithFloat:44100.0], AVSampleRateKey,
|
|
[NSNumber numberWithInt:16], AVLinearPCMBitDepthKey,
|
|
[NSNumber numberWithBool:NO], AVLinearPCMIsNonInterleaved,
|
|
[NSNumber numberWithBool:NO], AVLinearPCMIsFloatKey,
|
|
[NSNumber numberWithBool:NO], AVLinearPCMIsBigEndianKey,
|
|
nil];
|
|
|
|
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil];
|
|
if (asset == nil) {
|
|
NSLog(@"asset is not defined!");
|
|
return nil;
|
|
}
|
|
|
|
NSError *assetError = nil;
|
|
AVAssetReader *iPodAssetReader = [AVAssetReader assetReaderWithAsset:asset error:&assetError];
|
|
if (assetError) {
|
|
NSLog (@"error: %@", assetError);
|
|
return nil;
|
|
}
|
|
|
|
AVAssetReaderOutput *readerOutput = [AVAssetReaderAudioMixOutput assetReaderAudioMixOutputWithAudioTracks:asset.tracks audioSettings:outputSettings];
|
|
|
|
if (! [iPodAssetReader canAddOutput: readerOutput]) {
|
|
NSLog (@"can't add reader output... die!");
|
|
return nil;
|
|
}
|
|
|
|
// add output reader to reader
|
|
[iPodAssetReader addOutput: readerOutput];
|
|
|
|
if (! [iPodAssetReader startReading]) {
|
|
NSLog(@"Unable to start reading!");
|
|
return nil;
|
|
}
|
|
|
|
NSMutableData *_waveformSamples = [[NSMutableData alloc] init];
|
|
int16_t _waveformPeak = 0;
|
|
int _waveformPeakCount = 0;
|
|
|
|
while (iPodAssetReader.status == AVAssetReaderStatusReading) {
|
|
// Check if the available buffer space is enough to hold at least one cycle of the sample data
|
|
CMSampleBufferRef nextBuffer = [readerOutput copyNextSampleBuffer];
|
|
|
|
if (nextBuffer) {
|
|
AudioBufferList abl;
|
|
CMBlockBufferRef blockBuffer = NULL;
|
|
CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(nextBuffer, NULL, &abl, sizeof(abl), NULL, NULL, kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment, &blockBuffer);
|
|
UInt64 size = CMSampleBufferGetTotalSampleSize(nextBuffer);
|
|
if (size != 0) {
|
|
int16_t *samples = (int16_t *)(abl.mBuffers[0].mData);
|
|
int count = (int)size / 2;
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
int16_t sample = samples[i];
|
|
if (sample < 0) {
|
|
sample = -sample;
|
|
}
|
|
|
|
if (_waveformPeak < sample) {
|
|
_waveformPeak = sample;
|
|
}
|
|
_waveformPeakCount++;
|
|
|
|
if (_waveformPeakCount >= 100) {
|
|
[_waveformSamples appendBytes:&_waveformPeak length:2];
|
|
_waveformPeak = 0;
|
|
_waveformPeakCount = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
CFRelease(nextBuffer);
|
|
if (blockBuffer) {
|
|
CFRelease(blockBuffer);
|
|
}
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
int16_t scaledSamples[100];
|
|
memset(scaledSamples, 0, 100 * 2);
|
|
int16_t *samples = _waveformSamples.mutableBytes;
|
|
int count = (int)_waveformSamples.length / 2;
|
|
for (int i = 0; i < count; i++) {
|
|
int16_t sample = samples[i];
|
|
int index = i * 100 / count;
|
|
if (scaledSamples[index] < sample) {
|
|
scaledSamples[index] = sample;
|
|
}
|
|
}
|
|
|
|
int16_t peak = 0;
|
|
int64_t sumSamples = 0;
|
|
for (int i = 0; i < 100; i++) {
|
|
int16_t sample = scaledSamples[i];
|
|
if (peak < sample) {
|
|
peak = sample;
|
|
}
|
|
sumSamples += sample;
|
|
}
|
|
uint16_t calculatedPeak = 0;
|
|
calculatedPeak = (uint16_t)(sumSamples * 1.8f / 100);
|
|
|
|
if (calculatedPeak < 2500) {
|
|
calculatedPeak = 2500;
|
|
}
|
|
|
|
for (int i = 0; i < 100; i++) {
|
|
uint16_t sample = (uint16_t)((int64_t)samples[i]);
|
|
if (sample > calculatedPeak) {
|
|
scaledSamples[i] = calculatedPeak;
|
|
}
|
|
}
|
|
|
|
int numSamples = 100;
|
|
int bitstreamLength = (numSamples * 5) / 8 + (((numSamples * 5) % 8) == 0 ? 0 : 1);
|
|
NSMutableData *result = [[NSMutableData alloc] initWithLength:bitstreamLength];
|
|
{
|
|
int32_t maxSample = peak;
|
|
uint16_t const *samples = (uint16_t *)scaledSamples;
|
|
uint8_t *bytes = result.mutableBytes;
|
|
|
|
for (int i = 0; i < numSamples; i++) {
|
|
int32_t value = MIN(31, ABS((int32_t)samples[i]) * 31 / maxSample);
|
|
set_bits(bytes, i * 5, 5, value & 31);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
- (TGShareRecipientController *)recipientController
|
|
{
|
|
return (TGShareRecipientController *)self.viewControllers.firstObject;
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (void)showAuthRequiredAlert
|
|
{
|
|
UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Share.AuthTitle", nil) message:NSLocalizedString(@"Share.AuthText", nil) preferredStyle:UIAlertControllerStyleAlert];
|
|
|
|
UIAlertAction *action = [UIAlertAction actionWithTitle:NSLocalizedString(@"Share.OK", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action)
|
|
{
|
|
[self _cancel];
|
|
}];
|
|
|
|
[alert addAction:action];
|
|
[self presentViewController:alert animated:true completion:nil];
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (void)dismissForCancel:(bool)forCancel
|
|
{
|
|
self.view.userInteractionEnabled = false;
|
|
|
|
[self animateDismissalWithCompletion:^
|
|
{
|
|
if (!forCancel)
|
|
[self _complete];
|
|
else
|
|
[self _cancel];
|
|
}];
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (void)_complete
|
|
{
|
|
[self.extensionContext completeRequestReturningItems:nil completionHandler:nil];
|
|
}
|
|
|
|
- (void)_cancel
|
|
{
|
|
[self.extensionContext cancelRequestWithError:[NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:nil]];
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (void)animateAppearance
|
|
{
|
|
[UIView animateWithDuration:0.3 delay:0.0 options:(7 << 16 | UIViewAnimationOptionAllowAnimatedContent) animations:^
|
|
{
|
|
self.view.center = CGPointMake(self.view.center.x, self.view.frame.size.height / 2);
|
|
} completion:^(BOOL finished)
|
|
{
|
|
|
|
}];
|
|
}
|
|
|
|
- (void)animateDismissalWithCompletion:(void (^)(void))completion
|
|
{
|
|
[UIView animateWithDuration:0.3 delay:0.0 options:(7 << 16) animations:^
|
|
{
|
|
self.view.center = CGPointMake(self.view.center.x, self.view.center.y + self.view.frame.size.height);
|
|
} completion:^(BOOL finished)
|
|
{
|
|
if (completion != nil)
|
|
completion();
|
|
}];
|
|
}
|
|
|
|
@end
|