mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Add 'submodules/LegacyComponents/' from commit 'd559434616'
git-subtree-dir: submodules/LegacyComponents git-subtree-mainline:6086305304git-subtree-split:d559434616
This commit is contained in:
commit
d153fe0f21
1462 changed files with 206636 additions and 0 deletions
25
submodules/LegacyComponents/.gitignore
vendored
Normal file
25
submodules/LegacyComponents/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
fastlane/README.md
|
||||
fastlane/report.xml
|
||||
fastlane/test_output/*
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
xcuserdata
|
||||
*.xccheckout
|
||||
*.xcscmblueprint
|
||||
*.moved-aside
|
||||
DerivedData
|
||||
*.hmap
|
||||
*.ipa
|
||||
*.xcuserstate
|
||||
.DS_Store
|
||||
*.dSYM
|
||||
*.dSYM.zip
|
||||
*.ipa
|
||||
*/xcuserdata/*
|
||||
LegacyComponents.xcodeproj/*
|
||||
380
submodules/LegacyComponents/BUCK
Normal file
380
submodules/LegacyComponents/BUCK
Normal file
|
|
@ -0,0 +1,380 @@
|
|||
load('//tools:buck_utils.bzl', 'config_with_updated_linker_flags', 'combined_config', 'configs_with_config')
|
||||
load('//tools:buck_defs.bzl', 'SHARED_CONFIGS', 'EXTENSION_LIB_SPECIFIC_CONFIG')
|
||||
|
||||
apple_resource(
|
||||
name = "LegacyComponentsResources",
|
||||
dirs = [
|
||||
"LegacyComponents/Resources/LegacyComponentsResources.bundle",
|
||||
],
|
||||
)
|
||||
|
||||
apple_library(
|
||||
name = 'LegacyComponents',
|
||||
srcs = glob([
|
||||
'LegacyComponents/*.m',
|
||||
'LegacyComponents/*.mm',
|
||||
'LegacyComponents/*.c',
|
||||
'LegacyComponents/*.cpp',
|
||||
]),
|
||||
headers = glob([
|
||||
'LegacyComponents/*.h',
|
||||
]),
|
||||
header_namespace = 'LegacyComponents',
|
||||
exported_headers = [
|
||||
'LegacyComponents/LegacyComponentsGlobals.h',
|
||||
'LegacyComponents/LegacyComponentsContext.h',
|
||||
'LegacyComponents/TGLocalization.h',
|
||||
'LegacyComponents/TGPluralization.h',
|
||||
'LegacyComponents/TGStringUtils.h',
|
||||
'LegacyComponents/TGPhoneUtils.h',
|
||||
'LegacyComponents/NSObject+TGLock.h',
|
||||
'LegacyComponents/RMPhoneFormat.h',
|
||||
'LegacyComponents/NSInputStream+TL.h',
|
||||
'LegacyComponents/TGFont.h',
|
||||
'LegacyComponents/TGImageUtils.h',
|
||||
'LegacyComponents/TGDateUtils.h',
|
||||
'LegacyComponents/Freedom.h',
|
||||
'LegacyComponents/FreedomUIKit.h',
|
||||
'LegacyComponents/TGHacks.h',
|
||||
'LegacyComponents/TGImageBlur.h',
|
||||
'LegacyComponents/UIDevice+PlatformInfo.h',
|
||||
'LegacyComponents/TGObserverProxy.h',
|
||||
'LegacyComponents/TGModernCache.h',
|
||||
'LegacyComponents/TGMemoryImageCache.h',
|
||||
'LegacyComponents/LegacyComponentsAccessChecker.h',
|
||||
'LegacyComponents/TGTimerTarget.h',
|
||||
'LegacyComponents/TGKeyCommand.h',
|
||||
'LegacyComponents/TGKeyCommandController.h',
|
||||
'LegacyComponents/TGWeakDelegate.h',
|
||||
'LegacyComponents/TGCache.h',
|
||||
'LegacyComponents/TGLiveUploadInterface.h',
|
||||
|
||||
'LegacyComponents/JNWSpringAnimation.h',
|
||||
'LegacyComponents/POPAnimationEvent.h',
|
||||
'LegacyComponents/POPAnimationTracer.h',
|
||||
'LegacyComponents/POPAnimation.h',
|
||||
'LegacyComponents/POPBasicAnimation.h',
|
||||
'LegacyComponents/POPCustomAnimation.h',
|
||||
'LegacyComponents/POPDecayAnimation.h',
|
||||
'LegacyComponents/POPPropertyAnimation.h',
|
||||
'LegacyComponents/POPSpringAnimation.h',
|
||||
'LegacyComponents/POPGeometry.h',
|
||||
'LegacyComponents/POPAnimatableProperty.h',
|
||||
|
||||
'LegacyComponents/lmdb.h',
|
||||
'LegacyComponents/PSLMDBTable.h',
|
||||
'LegacyComponents/PSLMDBKeyValueStore.h',
|
||||
'LegacyComponents/PSLMDBKeyValueReaderWriter.h',
|
||||
'LegacyComponents/PSLMDBKeyValueCursor.h',
|
||||
|
||||
'LegacyComponents/PSCoding.h',
|
||||
'LegacyComponents/PSData.h',
|
||||
'LegacyComponents/PSKeyValueCoder.h',
|
||||
'LegacyComponents/PSKeyValueDecoder.h',
|
||||
'LegacyComponents/PSKeyValueEncoder.h',
|
||||
'LegacyComponents/PSKeyValueReader.h',
|
||||
'LegacyComponents/PSKeyValueStore.h',
|
||||
'LegacyComponents/PSKeyValueWriter.h',
|
||||
|
||||
'LegacyComponents/TGPeerIdAdapter.h',
|
||||
'LegacyComponents/TGUser.h',
|
||||
'LegacyComponents/TGBotInfo.h',
|
||||
'LegacyComponents/TGBotComandInfo.h',
|
||||
'LegacyComponents/TGConversation.h',
|
||||
|
||||
'LegacyComponents/TGModernConversationAssociatedInputPanel.h',
|
||||
'LegacyComponents/TGModernConversationHashtagsAssociatedPanel.h',
|
||||
'LegacyComponents/TGModernConversationMentionsAssociatedPanel.h',
|
||||
'LegacyComponents/TGModernConversationAlphacodeAssociatedPanel.h',
|
||||
'LegacyComponents/TGSuggestionContext.h',
|
||||
'LegacyComponents/TGAlphacode.h',
|
||||
|
||||
'LegacyComponents/TGTextCheckingResult.h',
|
||||
'LegacyComponents/TGChannelBannedRights.h',
|
||||
'LegacyComponents/TGChannelAdminRights.h',
|
||||
'LegacyComponents/TGDatabaseMessageDraft.h',
|
||||
'LegacyComponents/TGMessageGroup.h',
|
||||
'LegacyComponents/TGMessageHole.h',
|
||||
'LegacyComponents/TGMessageViewCountContentProperty.h',
|
||||
'LegacyComponents/TGAuthorSignatureMediaAttachment.h',
|
||||
'LegacyComponents/TGWebDocument.h',
|
||||
'LegacyComponents/TGInvoiceMediaAttachment.h',
|
||||
'LegacyComponents/TGGameMediaAttachment.h',
|
||||
'LegacyComponents/TGViaUserAttachment.h',
|
||||
'LegacyComponents/TGBotContextResultAttachment.h',
|
||||
'LegacyComponents/TGMessageEntity.h',
|
||||
'LegacyComponents/TGMessageEntityBold.h',
|
||||
'LegacyComponents/TGMessageEntityBotCommand.h',
|
||||
'LegacyComponents/TGMessageEntityCode.h',
|
||||
'LegacyComponents/TGMessageEntityEmail.h',
|
||||
'LegacyComponents/TGMessageEntityHashtag.h',
|
||||
'LegacyComponents/TGMessageEntityItalic.h',
|
||||
'LegacyComponents/TGMessageEntityMention.h',
|
||||
'LegacyComponents/TGMessageEntityMentionName.h',
|
||||
'LegacyComponents/TGMessageEntityPre.h',
|
||||
'LegacyComponents/TGMessageEntityTextUrl.h',
|
||||
'LegacyComponents/TGMessageEntityUrl.h',
|
||||
'LegacyComponents/TGMessageEntitiesAttachment.h',
|
||||
'LegacyComponents/TGBotReplyMarkup.h',
|
||||
'LegacyComponents/TGBotReplyMarkupButton.h',
|
||||
'LegacyComponents/TGBotReplyMarkupRow.h',
|
||||
'LegacyComponents/TGReplyMarkupAttachment.h',
|
||||
'LegacyComponents/TGInstantPage.h',
|
||||
'LegacyComponents/TGWebPageMediaAttachment.h',
|
||||
'LegacyComponents/TGAudioMediaAttachment.h',
|
||||
'LegacyComponents/TGAudioWaveform.h',
|
||||
'LegacyComponents/TGStickerPackReference.h',
|
||||
'LegacyComponents/TGDocumentAttributeFilename.h',
|
||||
'LegacyComponents/TGDocumentAttributeImageSize.h',
|
||||
'LegacyComponents/TGDocumentAttributeSticker.h',
|
||||
'LegacyComponents/TGDocumentAttributeVideo.h',
|
||||
'LegacyComponents/TGDocumentAttributeAnimated.h',
|
||||
'LegacyComponents/TGDocumentAttributeAudio.h',
|
||||
'LegacyComponents/TGDocumentMediaAttachment.h',
|
||||
'LegacyComponents/TGUnsupportedMediaAttachment.h',
|
||||
'LegacyComponents/TGForwardedMessageMediaAttachment.h',
|
||||
'LegacyComponents/TGContactMediaAttachment.h',
|
||||
'LegacyComponents/TGVideoInfo.h',
|
||||
'LegacyComponents/TGVideoMediaAttachment.h',
|
||||
'LegacyComponents/TGLocalMessageMetaMediaAttachment.h',
|
||||
'LegacyComponents/TGLocationMediaAttachment.h',
|
||||
'LegacyComponents/TGImageMediaAttachment.h',
|
||||
'LegacyComponents/TGMediaAttachment.h',
|
||||
'LegacyComponents/TGImageInfo.h',
|
||||
'LegacyComponents/TGMediaOriginInfo.h',
|
||||
'LegacyComponents/TGMessage.h',
|
||||
'LegacyComponents/TGStickerPack.h',
|
||||
'LegacyComponents/TGStickerAssociation.h',
|
||||
'LegacyComponents/TGPhotoMaskPosition.h',
|
||||
|
||||
'LegacyComponents/ActionStage.h',
|
||||
'LegacyComponents/ASActor.h',
|
||||
'LegacyComponents/ASHandle.h',
|
||||
'LegacyComponents/ASQueue.h',
|
||||
'LegacyComponents/ASWatcher.h',
|
||||
'LegacyComponents/SGraphListNode.h',
|
||||
'LegacyComponents/SGraphNode.h',
|
||||
'LegacyComponents/SGraphObjectNode.h',
|
||||
|
||||
'LegacyComponents/TGLabel.h',
|
||||
'LegacyComponents/TGToolbarButton.h',
|
||||
'LegacyComponents/UIScrollView+TGHacks.h',
|
||||
'LegacyComponents/TGAnimationBlockDelegate.h',
|
||||
'LegacyComponents/TGBackdropView.h',
|
||||
'LegacyComponents/UIImage+TG.h',
|
||||
'LegacyComponents/TGStaticBackdropAreaData.h',
|
||||
'LegacyComponents/TGStaticBackdropImageData.h',
|
||||
'LegacyComponents/TGImageLuminanceMap.h',
|
||||
'LegacyComponents/TGFullscreenContainerView.h',
|
||||
'LegacyComponents/TGDoubleTapGestureRecognizer.h',
|
||||
'LegacyComponents/TGModernButton.h',
|
||||
'LegacyComponents/TGModernToolbarButton.h',
|
||||
'LegacyComponents/TGModernBackToolbarButton.h',
|
||||
'LegacyComponents/UIControl+HitTestEdgeInsets.h',
|
||||
'LegacyComponents/TGMenuView.h',
|
||||
'LegacyComponents/TGImageView.h',
|
||||
'LegacyComponents/UICollectionView+Utils.h',
|
||||
'LegacyComponents/TGMessageImageViewOverlayView.h',
|
||||
'LegacyComponents/TGLetteredAvatarView.h',
|
||||
'LegacyComponents/TGGradientLabel.h',
|
||||
'LegacyComponents/TGRemoteImageView.h',
|
||||
|
||||
'LegacyComponents/TGProgressSpinnerView.h',
|
||||
'LegacyComponents/TGProgressWindow.h',
|
||||
|
||||
'LegacyComponents/TGMenuSheetController.h',
|
||||
'LegacyComponents/TGMenuSheetButtonItemView.h',
|
||||
'LegacyComponents/TGMenuSheetCollectionView.h',
|
||||
'LegacyComponents/TGMenuSheetItemView.h',
|
||||
'LegacyComponents/TGMenuSheetTitleItemView.h',
|
||||
'LegacyComponents/TGMenuSheetView.h',
|
||||
|
||||
'LegacyComponents/HPGrowingTextView.h',
|
||||
'LegacyComponents/HPTextViewInternal.h',
|
||||
'LegacyComponents/TGInputTextTag.h',
|
||||
|
||||
'LegacyComponents/TGStickerKeyboardTabPanel.h',
|
||||
|
||||
'LegacyComponents/TGItemPreviewController.h',
|
||||
'LegacyComponents/TGItemPreviewView.h',
|
||||
'LegacyComponents/TGItemMenuSheetPreviewView.h',
|
||||
|
||||
'LegacyComponents/TGImageManager.h',
|
||||
'LegacyComponents/TGDataResource.h',
|
||||
'LegacyComponents/TGImageDataSource.h',
|
||||
'LegacyComponents/TGImageManagerTask.h',
|
||||
|
||||
'LegacyComponents/TGRTLScreenEdgePanGestureRecognizer.h',
|
||||
'LegacyComponents/TGPopoverController.h',
|
||||
'LegacyComponents/TGNavigationController.h',
|
||||
'LegacyComponents/TGNavigationBar.h',
|
||||
'LegacyComponents/TGViewController.h',
|
||||
'LegacyComponents/TGViewController+TGRecursiveEnumeration.h',
|
||||
'LegacyComponents/TGOverlayController.h',
|
||||
'LegacyComponents/TGOverlayControllerWindow.h',
|
||||
|
||||
'LegacyComponents/TGMediaAssetsLibrary.h',
|
||||
'LegacyComponents/TGMediaAssetsModernLibrary.h',
|
||||
'LegacyComponents/TGMediaAsset.h',
|
||||
'LegacyComponents/TGMediaAssetFetchResult.h',
|
||||
'LegacyComponents/TGMediaAssetFetchResultChange.h',
|
||||
'LegacyComponents/TGMediaAssetGroup.h',
|
||||
'LegacyComponents/TGMediaAssetMoment.h',
|
||||
'LegacyComponents/TGMediaAssetMomentList.h',
|
||||
'LegacyComponents/TGMediaAssetImageSignals.h',
|
||||
'LegacyComponents/TGMediaSelectionContext.h',
|
||||
'LegacyComponents/TGMediaEditingContext.h',
|
||||
|
||||
'LegacyComponents/TGModernGalleryZoomableItemViewContent.h',
|
||||
'LegacyComponents/TGModernGalleryZoomableScrollView.h',
|
||||
'LegacyComponents/TGModernGalleryZoomableScrollViewSwipeGestureRecognizer.h',
|
||||
'LegacyComponents/TGModernGalleryVideoView.h',
|
||||
'LegacyComponents/TGModernGalleryScrollView.h',
|
||||
'LegacyComponents/TGModernGalleryItem.h',
|
||||
'LegacyComponents/TGModernGalleryItemView.h',
|
||||
'LegacyComponents/TGModernGalleryImageItem.h',
|
||||
'LegacyComponents/TGModernGalleryImageItemView.h',
|
||||
'LegacyComponents/TGModernGalleryImageItemImageView.h',
|
||||
'LegacyComponents/TGModernGalleryEditableItemView.h',
|
||||
'LegacyComponents/TGModernGallerySelectableItem.h',
|
||||
'LegacyComponents/TGModernGalleryDefaultFooterAccessoryView.h',
|
||||
'LegacyComponents/TGModernGalleryDefaultFooterView.h',
|
||||
'LegacyComponents/TGModernGalleryDefaultHeaderView.h',
|
||||
'LegacyComponents/TGModernGalleryDefaultInterfaceView.h',
|
||||
'LegacyComponents/TGModernGalleryInterfaceView.h',
|
||||
'LegacyComponents/TGModernGalleryImageItemContainerView.h',
|
||||
'LegacyComponents/TGModernGalleryZoomableItemView.h',
|
||||
'LegacyComponents/TGModernGalleryModel.h',
|
||||
'LegacyComponents/TGModernGalleryTransitionView.h',
|
||||
'LegacyComponents/TGModernGalleryView.h',
|
||||
'LegacyComponents/TGModernGalleryContainerView.h',
|
||||
'LegacyComponents/TGModernGalleryEmbeddedStickersHeaderView.h',
|
||||
'LegacyComponents/TGModernGalleryController.h',
|
||||
|
||||
'LegacyComponents/TGPhotoToolbarView.h',
|
||||
'LegacyComponents/TGMediaPickerGalleryModel.h',
|
||||
'LegacyComponents/TGMediaPickerGalleryInterfaceView.h',
|
||||
'LegacyComponents/TGPhotoEditorController.h',
|
||||
'LegacyComponents/TGMediaAvatarEditorTransition.h',
|
||||
|
||||
'LegacyComponents/TGPhotoEditorUtils.h',
|
||||
'LegacyComponents/PGPhotoEditorValues.h',
|
||||
'LegacyComponents/TGVideoEditAdjustments.h',
|
||||
'LegacyComponents/AVURLAsset+TGMediaItem.h',
|
||||
'LegacyComponents/UIImage+TGMediaEditableItem.h',
|
||||
'LegacyComponents/TGMediaVideoConverter.h',
|
||||
'LegacyComponents/TGGifConverter.h',
|
||||
|
||||
'LegacyComponents/TGPhotoEditorAnimation.h',
|
||||
|
||||
'LegacyComponents/TGPaintingData.h',
|
||||
'LegacyComponents/TGPaintUtils.h',
|
||||
'LegacyComponents/TGPhotoPaintEntity.h',
|
||||
'LegacyComponents/TGPhotoPaintStickerEntity.h',
|
||||
'LegacyComponents/TGPaintUndoManager.h',
|
||||
|
||||
'LegacyComponents/PGCamera.h',
|
||||
'LegacyComponents/PGCameraCaptureSession.h',
|
||||
'LegacyComponents/PGCameraDeviceAngleSampler.h',
|
||||
'LegacyComponents/PGCameraMomentSegment.h',
|
||||
'LegacyComponents/PGCameraMomentSession.h',
|
||||
'LegacyComponents/PGCameraMovieWriter.h',
|
||||
'LegacyComponents/PGCameraShotMetadata.h',
|
||||
'LegacyComponents/PGCameraVolumeButtonHandler.h',
|
||||
'LegacyComponents/TGCameraPreviewView.h',
|
||||
'LegacyComponents/TGCameraMainPhoneView.h',
|
||||
'LegacyComponents/TGCameraMainTabletView.h',
|
||||
'LegacyComponents/TGCameraMainView.h',
|
||||
'LegacyComponents/TGCameraFlashActiveView.h',
|
||||
'LegacyComponents/TGCameraFlashControl.h',
|
||||
'LegacyComponents/TGCameraFlipButton.h',
|
||||
'LegacyComponents/TGCameraInterfaceAssets.h',
|
||||
'LegacyComponents/TGCameraModeControl.h',
|
||||
'LegacyComponents/TGCameraSegmentsView.h',
|
||||
'LegacyComponents/TGCameraShutterButton.h',
|
||||
'LegacyComponents/TGCameraTimeCodeView.h',
|
||||
'LegacyComponents/TGCameraZoomView.h',
|
||||
'LegacyComponents/TGCameraPhotoPreviewController.h',
|
||||
'LegacyComponents/TGCameraController.h',
|
||||
'LegacyComponents/TGCameraCapturedPhoto.h',
|
||||
'LegacyComponents/TGCameraCapturedVideo.h',
|
||||
'LegacyComponents/TGPhotoVideoEditor.h',
|
||||
|
||||
'LegacyComponents/TGModernConversationTitleActivityIndicator.h',
|
||||
'LegacyComponents/TGEmbedPIPButton.h',
|
||||
'LegacyComponents/TGEmbedPIPPullArrowView.h',
|
||||
'LegacyComponents/TGEmbedPlayerState.h',
|
||||
'LegacyComponents/TGAttachmentCameraView.h',
|
||||
'LegacyComponents/TGMediaAvatarMenuMixin.h',
|
||||
'LegacyComponents/TGPassportAttachMenu.h',
|
||||
'LegacyComponents/TGPassportScanController.h',
|
||||
'LegacyComponents/TGPassportOCR.h',
|
||||
'LegacyComponents/TGPassportMRZ.h',
|
||||
'LegacyComponents/TGPasscodeEntryController.h',
|
||||
'LegacyComponents/TGPassportICloud.h',
|
||||
'LegacyComponents/TGEmbedPlayerView.h',
|
||||
'LegacyComponents/TGWallpaperInfo.h',
|
||||
'LegacyComponents/LegacyHTTPRequestOperation.h',
|
||||
|
||||
'LegacyComponents/TGAttachmentCarouselItemView.h',
|
||||
'LegacyComponents/TGMediaAssetsController.h',
|
||||
|
||||
'LegacyComponents/TGLocationVenue.h',
|
||||
'LegacyComponents/TGLocationMapViewController.h',
|
||||
'LegacyComponents/TGLocationPickerController.h',
|
||||
'LegacyComponents/TGLocationViewController.h',
|
||||
'LegacyComponents/TGListsTableView.h',
|
||||
'LegacyComponents/TGSearchBar.h',
|
||||
'LegacyComponents/TGSearchDisplayMixin.h',
|
||||
|
||||
'LegacyComponents/TGPhotoEditorSliderView.h',
|
||||
|
||||
'LegacyComponents/TGClipboardGalleryMixin.h',
|
||||
'LegacyComponents/TGClipboardGalleryPhotoItem.h',
|
||||
'LegacyComponents/TGVideoMessageCaptureController.h',
|
||||
'LegacyComponents/TGModernConversationInputMicButton.h',
|
||||
|
||||
'LegacyComponents/TGLocationPulseView.h',
|
||||
'LegacyComponents/TGLocationWavesView.h',
|
||||
'LegacyComponents/TGLocationLiveElapsedView.h',
|
||||
'LegacyComponents/TGLocationLiveSessionItemView.h',
|
||||
|
||||
'LegacyComponents/TGTooltipView.h',
|
||||
|
||||
'LegacyComponents/TGCheckButtonView.h',
|
||||
'LegacyComponents/TGBuiltinWallpaperInfo.h',
|
||||
'LegacyComponents/TGRemoteWallpaperInfo.h',
|
||||
'LegacyComponents/TGColorWallpaperInfo.h',
|
||||
'LegacyComponents/TGCustomImageWallpaperInfo.h',
|
||||
'LegacyComponents/TGClipboardMenu.h',
|
||||
'LegacyComponents/TGImagePickerController.h',
|
||||
'LegacyComponents/TGLegacyCameraController.h',
|
||||
'LegacyComponents/TGWallpaperController.h',
|
||||
'LegacyComponents/TGProxyWindow.h',
|
||||
'LegacyComponents/TGIconSwitchView.h',
|
||||
|
||||
'LegacyComponents/TGModernGalleryEditableItem.h',
|
||||
'LegacyComponents/TGPhotoEditorButton.h',
|
||||
|
||||
'LegacyComponents/TGActionMediaAttachment.h',
|
||||
'LegacyComponents/TGReplyMessageMediaAttachment.h',
|
||||
'LegacyComponents/TGMessageEntityPhone.h',
|
||||
'LegacyComponents/TGMessageEntityCashtag.h',
|
||||
'LegacyComponents/TGPIPAblePlayerView.h',
|
||||
'LegacyComponents/TGEmbedPlayerControls.h',
|
||||
],
|
||||
modular = True,
|
||||
configs = configs_with_config(combined_config([SHARED_CONFIGS, EXTENSION_LIB_SPECIFIC_CONFIG])),
|
||||
compiler_flags = ['-w'],
|
||||
preprocessor_flags = ['-fobjc-arc'],
|
||||
visibility = ['PUBLIC'],
|
||||
deps = [
|
||||
':LegacyComponentsResources',
|
||||
'//submodules/SSignalKit:SSignalKit',
|
||||
],
|
||||
frameworks = [
|
||||
'$SDKROOT/System/Library/Frameworks/Foundation.framework',
|
||||
'$SDKROOT/System/Library/Frameworks/UIKit.framework',
|
||||
'$SDKROOT/System/Library/Frameworks/Vision.framework',
|
||||
],
|
||||
)
|
||||
36
submodules/LegacyComponents/LegacyComponents/ASActor.h
Normal file
36
submodules/LegacyComponents/LegacyComponents/ASActor.h
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import <LegacyComponents/ASHandle.h>
|
||||
|
||||
@interface ASActor : NSObject
|
||||
|
||||
+ (void)registerActorClass:(Class)requestBuilderClass;
|
||||
|
||||
+ (ASActor *)requestBuilderForGenericPath:(NSString *)genericPath path:(NSString *)path;
|
||||
|
||||
+ (NSString *)genericPath;
|
||||
|
||||
@property (nonatomic, strong) NSString *path;
|
||||
|
||||
@property (nonatomic, strong) NSString *requestQueueName;
|
||||
@property (nonatomic, strong) NSDictionary *storedOptions;
|
||||
|
||||
@property (nonatomic) bool requiresAuthorization;
|
||||
|
||||
@property (nonatomic) NSTimeInterval cancelTimeout;
|
||||
@property (nonatomic, strong) id cancelToken;
|
||||
@property (nonatomic, strong) NSMutableArray *multipleCancelTokens;
|
||||
@property (nonatomic) bool cancelled;
|
||||
|
||||
- (id)initWithPath:(NSString *)path;
|
||||
- (void)prepare:(NSDictionary *)options;
|
||||
- (void)execute:(NSDictionary *)options;
|
||||
- (void)cancel;
|
||||
|
||||
- (void)addCancelToken:(id)token;
|
||||
|
||||
- (void)watcherJoined:(ASHandle *)watcherHandle options:(NSDictionary *)options waitingInActorQueue:(bool)waitingInActorQueue;
|
||||
|
||||
- (void)handleRequestProblem;
|
||||
|
||||
@end
|
||||
121
submodules/LegacyComponents/LegacyComponents/ASActor.m
Normal file
121
submodules/LegacyComponents/LegacyComponents/ASActor.m
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
#import "ASActor.h"
|
||||
|
||||
#import "ActionStage.h"
|
||||
|
||||
#import "LegacyComponentsInternal.h"
|
||||
|
||||
static NSMutableDictionary *registeredRequestBuilders()
|
||||
{
|
||||
static NSMutableDictionary *dict = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^
|
||||
{
|
||||
dict = [[NSMutableDictionary alloc] init];
|
||||
});
|
||||
return dict;
|
||||
}
|
||||
|
||||
@implementation ASActor
|
||||
|
||||
+ (void)registerActorClass:(Class)requestBuilderClass
|
||||
{
|
||||
NSString *genericPath = [requestBuilderClass genericPath];
|
||||
if (genericPath == nil || genericPath.length == 0)
|
||||
{
|
||||
TGLegacyLog(@"Error: ASActor::registerActorClass: genericPath is nil");
|
||||
return;
|
||||
}
|
||||
|
||||
[registeredRequestBuilders() setObject:requestBuilderClass forKey:genericPath];
|
||||
}
|
||||
|
||||
+ (ASActor *)requestBuilderForGenericPath:(NSString *)genericPath path:(NSString *)path
|
||||
{
|
||||
Class builderClass = [registeredRequestBuilders() objectForKey:genericPath];
|
||||
if (builderClass != nil)
|
||||
{
|
||||
ASActor *builderInstance = [[builderClass alloc] initWithPath:path];
|
||||
return builderInstance;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
+ (NSString *)genericPath
|
||||
{
|
||||
TGLegacyLog(@"Error: ASActor::genericPath: no default implementation provided");
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
@synthesize path = _path;
|
||||
|
||||
@synthesize requestQueueName = _requestQueueName;
|
||||
@synthesize storedOptions = _storedOptions;
|
||||
|
||||
@synthesize requiresAuthorization = _requiresAuthorization;
|
||||
|
||||
@synthesize cancelTimeout = _cancelTimeout;
|
||||
@synthesize cancelToken = _cancelToken;
|
||||
@synthesize cancelled = _cancelled;
|
||||
|
||||
/*#if TARGET_IPHONE_SIMULATOR
|
||||
static int instanceCount = 0;
|
||||
#endif*/
|
||||
|
||||
- (id)initWithPath:(NSString *)path
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil)
|
||||
{
|
||||
_cancelTimeout = 0;
|
||||
_path = path;
|
||||
|
||||
/*#if TARGET_IPHONE_SIMULATOR
|
||||
instanceCount++;
|
||||
|
||||
TGLegacyLog(@"%d actors (++)", instanceCount);
|
||||
#endif*/
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
/*#if TARGET_IPHONE_SIMULATOR
|
||||
instanceCount--;
|
||||
|
||||
TGLegacyLog(@"%d actors (--)", instanceCount);
|
||||
#endif*/
|
||||
}
|
||||
|
||||
- (void)prepare:(NSDictionary *)__unused options
|
||||
{
|
||||
}
|
||||
|
||||
- (void)execute:(NSDictionary *)__unused options
|
||||
{
|
||||
TGLegacyLog(@"Error: ASActor::execute: no default implementation provided");
|
||||
}
|
||||
|
||||
- (void)cancel
|
||||
{
|
||||
self.cancelled = true;
|
||||
}
|
||||
|
||||
- (void)addCancelToken:(id)token
|
||||
{
|
||||
if (_multipleCancelTokens == nil)
|
||||
_multipleCancelTokens = [[NSMutableArray alloc] init];
|
||||
|
||||
[_multipleCancelTokens addObject:token];
|
||||
}
|
||||
|
||||
- (void)handleRequestProblem
|
||||
{
|
||||
}
|
||||
|
||||
- (void)watcherJoined:(ASHandle *)__unused watcherHandle options:(NSDictionary *)__unused options waitingInActorQueue:(bool)__unused waitingInActorQueue
|
||||
{
|
||||
}
|
||||
|
||||
@end
|
||||
21
submodules/LegacyComponents/LegacyComponents/ASHandle.h
Normal file
21
submodules/LegacyComponents/LegacyComponents/ASHandle.h
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
|
||||
@protocol ASWatcher;
|
||||
|
||||
@interface ASHandle : NSObject
|
||||
|
||||
@property (nonatomic, weak) id<ASWatcher> delegate;
|
||||
@property (nonatomic) bool releaseOnMainThread;
|
||||
|
||||
- (id)initWithDelegate:(id<ASWatcher>)delegate;
|
||||
- (id)initWithDelegate:(id<ASWatcher>)delegate releaseOnMainThread:(bool)releaseOnMainThread;
|
||||
- (void)reset;
|
||||
|
||||
- (bool)hasDelegate;
|
||||
|
||||
- (void)requestAction:(NSString *)action options:(id)options;
|
||||
- (void)receiveActorMessage:(NSString *)path messageType:(NSString *)messageType message:(id)message;
|
||||
- (void)notifyResourceDispatched:(NSString *)path resource:(id)resource;
|
||||
- (void)notifyResourceDispatched:(NSString *)path resource:(id)resource arguments:(id)arguments;
|
||||
|
||||
@end
|
||||
130
submodules/LegacyComponents/LegacyComponents/ASHandle.m
Normal file
130
submodules/LegacyComponents/LegacyComponents/ASHandle.m
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
#import "ASHandle.h"
|
||||
|
||||
#import <LegacyComponents/LegacyComponents.h>
|
||||
|
||||
#import "ASWatcher.h"
|
||||
|
||||
@interface ASHandle ()
|
||||
{
|
||||
TG_SYNCHRONIZED_DEFINE(_delegate);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASHandle
|
||||
|
||||
@synthesize delegate = _delegate;
|
||||
@synthesize releaseOnMainThread = _releaseOnMainThread;
|
||||
|
||||
- (id)initWithDelegate:(id<ASWatcher>)delegate
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil)
|
||||
{
|
||||
TG_SYNCHRONIZED_INIT(_delegate);
|
||||
|
||||
_delegate = delegate;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithDelegate:(id<ASWatcher>)delegate releaseOnMainThread:(bool)releaseOnMainThread
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil)
|
||||
{
|
||||
TG_SYNCHRONIZED_INIT(_delegate);
|
||||
|
||||
_delegate = delegate;
|
||||
_releaseOnMainThread = releaseOnMainThread;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)reset
|
||||
{
|
||||
TG_SYNCHRONIZED_BEGIN(_delegate);
|
||||
_delegate = nil;
|
||||
TG_SYNCHRONIZED_END(_delegate);
|
||||
}
|
||||
|
||||
- (bool)hasDelegate
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
TG_SYNCHRONIZED_BEGIN(_delegate);
|
||||
result = _delegate != nil;
|
||||
TG_SYNCHRONIZED_END(_delegate);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (id<ASWatcher>)delegate
|
||||
{
|
||||
id<ASWatcher> result = nil;
|
||||
|
||||
TG_SYNCHRONIZED_BEGIN(_delegate);
|
||||
result = _delegate;
|
||||
TG_SYNCHRONIZED_END(_delegate);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)setDelegate:(id<ASWatcher>)delegate
|
||||
{
|
||||
TG_SYNCHRONIZED_BEGIN(_delegate);
|
||||
_delegate = delegate;
|
||||
TG_SYNCHRONIZED_END(_delegate);
|
||||
}
|
||||
|
||||
- (void)requestAction:(NSString *)action options:(id)options
|
||||
{
|
||||
__strong id<ASWatcher> delegate = self.delegate;
|
||||
if (delegate != nil && [delegate respondsToSelector:@selector(actionStageActionRequested:options:)])
|
||||
[delegate actionStageActionRequested:action options:options];
|
||||
|
||||
if (_releaseOnMainThread && ![NSThread isMainThread])
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^
|
||||
{
|
||||
[delegate class];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)receiveActorMessage:(NSString *)path messageType:(NSString *)messageType message:(id)message
|
||||
{
|
||||
__strong id<ASWatcher> delegate = self.delegate;
|
||||
if (delegate != nil && [delegate respondsToSelector:@selector(actorMessageReceived:messageType:message:)])
|
||||
[delegate actorMessageReceived:path messageType:messageType message:message];
|
||||
|
||||
if (_releaseOnMainThread && ![NSThread isMainThread])
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^
|
||||
{
|
||||
[delegate class];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)notifyResourceDispatched:(NSString *)path resource:(id)resource
|
||||
{
|
||||
[self notifyResourceDispatched:path resource:resource arguments:nil];
|
||||
}
|
||||
|
||||
- (void)notifyResourceDispatched:(NSString *)path resource:(id)resource arguments:(id)arguments
|
||||
{
|
||||
__strong id<ASWatcher> delegate = self.delegate;
|
||||
if (delegate != nil && [delegate respondsToSelector:@selector(actionStageResourceDispatched:resource:arguments:)])
|
||||
[delegate actionStageResourceDispatched:path resource:resource arguments:arguments];
|
||||
|
||||
if (_releaseOnMainThread && ![NSThread isMainThread])
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^
|
||||
{
|
||||
[delegate class];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
15
submodules/LegacyComponents/LegacyComponents/ASQueue.h
Normal file
15
submodules/LegacyComponents/LegacyComponents/ASQueue.h
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface ASQueue : NSObject
|
||||
|
||||
- (instancetype)initWithName:(const char *)name;
|
||||
|
||||
+ (ASQueue *)mainQueue;
|
||||
|
||||
- (dispatch_queue_t)nativeQueue;
|
||||
|
||||
- (bool)isCurrentQueue;
|
||||
- (void)dispatchOnQueue:(dispatch_block_t)block;
|
||||
- (void)dispatchOnQueue:(dispatch_block_t)block synchronous:(bool)synchronous;
|
||||
|
||||
@end
|
||||
100
submodules/LegacyComponents/LegacyComponents/ASQueue.m
Normal file
100
submodules/LegacyComponents/LegacyComponents/ASQueue.m
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
|
||||
|
||||
#import "ASQueue.h"
|
||||
|
||||
@interface ASQueue ()
|
||||
{
|
||||
bool _isMainQueue;
|
||||
dispatch_queue_t _queue;
|
||||
|
||||
const char *_name;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASQueue
|
||||
|
||||
- (instancetype)initWithName:(const char *)name
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil)
|
||||
{
|
||||
_name = name;
|
||||
|
||||
_queue = dispatch_queue_create(_name, 0);
|
||||
dispatch_queue_set_specific(_queue, _name, (void *)_name, NULL);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
#if !OS_OBJECT_HAVE_OBJC_SUPPORT
|
||||
dispatch_release(_queue);
|
||||
#endif
|
||||
_queue = nil;
|
||||
}
|
||||
|
||||
+ (ASQueue *)mainQueue
|
||||
{
|
||||
static ASQueue *queue = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^
|
||||
{
|
||||
queue = [[ASQueue alloc] init];
|
||||
queue->_queue = dispatch_get_main_queue();
|
||||
queue->_isMainQueue = true;
|
||||
});
|
||||
return queue;
|
||||
}
|
||||
|
||||
- (dispatch_queue_t)nativeQueue
|
||||
{
|
||||
return _queue;
|
||||
}
|
||||
|
||||
- (bool)isCurrentQueue
|
||||
{
|
||||
if (_queue == nil)
|
||||
return false;
|
||||
|
||||
if (_isMainQueue)
|
||||
return [NSThread isMainThread];
|
||||
else
|
||||
return dispatch_get_specific(_name) == _name;
|
||||
}
|
||||
|
||||
- (void)dispatchOnQueue:(dispatch_block_t)block
|
||||
{
|
||||
[self dispatchOnQueue:block synchronous:false];
|
||||
}
|
||||
|
||||
- (void)dispatchOnQueue:(dispatch_block_t)block synchronous:(bool)synchronous
|
||||
{
|
||||
if (block == nil)
|
||||
return;
|
||||
|
||||
if (_queue != nil)
|
||||
{
|
||||
if (_isMainQueue)
|
||||
{
|
||||
if ([NSThread isMainThread])
|
||||
block();
|
||||
else if (synchronous)
|
||||
dispatch_sync(_queue, block);
|
||||
else
|
||||
dispatch_async(_queue, block);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dispatch_get_specific(_name) == _name)
|
||||
block();
|
||||
else if (synchronous)
|
||||
dispatch_sync(_queue, block);
|
||||
else
|
||||
dispatch_async(_queue, block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
20
submodules/LegacyComponents/LegacyComponents/ASWatcher.h
Normal file
20
submodules/LegacyComponents/LegacyComponents/ASWatcher.h
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import <LegacyComponents/ASHandle.h>
|
||||
#import <LegacyComponents/SGraphNode.h>
|
||||
|
||||
@protocol ASWatcher <NSObject>
|
||||
|
||||
@required
|
||||
|
||||
@property (nonatomic, strong, readonly) ASHandle *actionHandle;
|
||||
|
||||
@optional
|
||||
|
||||
- (void)actorCompleted:(int)status path:(NSString *)path result:(id)result;
|
||||
- (void)actorReportedProgress:(NSString *)path progress:(float)progress;
|
||||
- (void)actionStageResourceDispatched:(NSString *)path resource:(id)resource arguments:(id)arguments;
|
||||
- (void)actionStageActionRequested:(NSString *)action options:(id)options;
|
||||
- (void)actorMessageReceived:(NSString *)path messageType:(NSString *)messageType message:(id)message;
|
||||
|
||||
@end
|
||||
16
submodules/LegacyComponents/LegacyComponents/AVAsset+CBExtension.h
Executable file
16
submodules/LegacyComponents/LegacyComponents/AVAsset+CBExtension.h
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
//
|
||||
// AVAsset+Extension.h
|
||||
// CoubPlayer
|
||||
//
|
||||
// Created by Pavel Tikhonenko on 19/10/14.
|
||||
// Copyright (c) 2014 Pavel Tikhonenko. All rights reserved.
|
||||
//
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
@interface AVAsset (CBExtension)
|
||||
|
||||
@property (nonatomic, readonly) AVAssetTrack *anyVideoTrack;
|
||||
@property (nonatomic, readonly) AVAssetTrack *anyAudioTrack;
|
||||
|
||||
@end
|
||||
25
submodules/LegacyComponents/LegacyComponents/AVAsset+CBExtension.m
Executable file
25
submodules/LegacyComponents/LegacyComponents/AVAsset+CBExtension.m
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// AVAsset+Extension.m
|
||||
// CoubPlayer
|
||||
//
|
||||
// Created by Pavel Tikhonenko on 19/10/14.
|
||||
// Copyright (c) 2014 Pavel Tikhonenko. All rights reserved.
|
||||
//
|
||||
|
||||
#import "AVAsset+CBExtension.h"
|
||||
|
||||
@implementation AVAsset (CBExtension)
|
||||
|
||||
- (AVAssetTrack *)anyVideoTrack
|
||||
{
|
||||
NSArray *videoTracks = [self tracksWithMediaType:AVMediaTypeVideo];
|
||||
return [videoTracks count] ? videoTracks[0] : nil;
|
||||
}
|
||||
|
||||
- (AVAssetTrack *)anyAudioTrack
|
||||
{
|
||||
NSArray *audioTracks = [self tracksWithMediaType:AVMediaTypeAudio];
|
||||
return [audioTracks count] ? audioTracks[0] : nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <LegacyComponents/TGMediaSelectionContext.h>
|
||||
#import <LegacyComponents/TGMediaEditingContext.h>
|
||||
|
||||
@interface AVURLAsset (TGMediaItem) <TGMediaSelectableItem, TGMediaEditableItem>
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
#import "AVURLAsset+TGMediaItem.h"
|
||||
#import <LegacyComponents/TGMediaAssetImageSignals.h>
|
||||
|
||||
#import <LegacyComponents/TGPhotoEditorUtils.h>
|
||||
|
||||
@implementation AVURLAsset (TGMediaItem)
|
||||
|
||||
- (bool)isVideo
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
- (NSString *)uniqueIdentifier
|
||||
{
|
||||
return self.URL.absoluteString;
|
||||
}
|
||||
|
||||
- (CGSize)originalSize
|
||||
{
|
||||
AVAssetTrack *track = self.tracks.firstObject;
|
||||
return CGRectApplyAffineTransform((CGRect){ CGPointZero, track.naturalSize }, track.preferredTransform).size;
|
||||
}
|
||||
|
||||
- (SSignal *)thumbnailImageSignal
|
||||
{
|
||||
CGFloat thumbnailImageSide = TGPhotoThumbnailSizeForCurrentScreen().width;
|
||||
CGSize size = TGScaleToSize(self.originalSize, CGSizeMake(thumbnailImageSide, thumbnailImageSide));
|
||||
|
||||
return [TGMediaAssetImageSignals videoThumbnailForAVAsset:self size:size timestamp:kCMTimeZero];
|
||||
}
|
||||
|
||||
- (SSignal *)screenImageSignal:(NSTimeInterval)__unused position
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (SSignal *)originalImageSignal:(NSTimeInterval)__unused position
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
82
submodules/LegacyComponents/LegacyComponents/ActionStage.h
Normal file
82
submodules/LegacyComponents/LegacyComponents/ActionStage.h
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import <LegacyComponents/ASWatcher.h>
|
||||
#import <LegacyComponents/ASActor.h>
|
||||
|
||||
#import <LegacyComponents/SGraphObjectNode.h>
|
||||
|
||||
typedef enum {
|
||||
ASStatusSuccess = 0,
|
||||
ASStatusFailed = -1
|
||||
} ASStatus;
|
||||
|
||||
#ifdef DEBUG
|
||||
#define dispatchOnStageQueue dispatchOnStageQueueDebug:__FILE__ line:__LINE__ block
|
||||
#endif
|
||||
|
||||
@class ActionStage;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
ActionStage *ActionStageInstance();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
TGActorRequestChangePriority = 1
|
||||
} TGActorRequestFlags;
|
||||
|
||||
@interface ActionStage : NSObject
|
||||
|
||||
- (dispatch_queue_t)globalStageDispatchQueue;
|
||||
#ifdef DEBUG
|
||||
- (void)dispatchOnStageQueueDebug:(const char *)function line:(int)line block:(dispatch_block_t)block;
|
||||
#else
|
||||
- (void)dispatchOnStageQueue:(dispatch_block_t)block;
|
||||
#endif
|
||||
|
||||
- (NSFileManager *)globalFileManager;
|
||||
|
||||
- (bool)isCurrentQueueStageQueue;
|
||||
|
||||
- (void)cancelActorTimeout:(NSString *)path;
|
||||
|
||||
- (NSString *)genericStringForParametrizedPath:(NSString *)path;
|
||||
|
||||
- (void)requestActor:(NSString *)path options:(NSDictionary *)options flags:(int)flags watcher:(id<ASWatcher>)watcher;
|
||||
- (void)requestActor:(NSString *)path options:(NSDictionary *)options watcher:(id<ASWatcher>)watcher;
|
||||
- (void)changeActorPriority:(NSString *)path;
|
||||
|
||||
- (NSArray *)rejoinActionsWithGenericPathNow:(NSString *)genericPath prefix:(NSString *)prefix watcher:(id<ASWatcher>)watcher;
|
||||
- (bool)isExecutingActorsWithGenericPath:(NSString *)genericPath;
|
||||
- (bool)isExecutingActorsWithPathPrefix:(NSString *)pathPrefix;
|
||||
- (NSArray *)executingActorsWithPathPrefix:(NSString *)pathPrefix;
|
||||
- (ASActor *)executingActorWithPath:(NSString *)path;
|
||||
|
||||
- (void)watchForPath:(NSString *)path watcher:(id<ASWatcher>)watcher;
|
||||
- (void)watchForPaths:(NSArray *)paths watcher:(id<ASWatcher>)watcher;
|
||||
- (void)watchForGenericPath:(NSString *)path watcher:(id<ASWatcher>)watcher;
|
||||
- (void)watchForMessagesToWatchersAtGenericPath:(NSString *)genericPath watcher:(id<ASWatcher>)watcher;
|
||||
- (void)removeWatcherByHandle:(ASHandle *)actionHandle;
|
||||
- (void)removeWatcher:(id<ASWatcher>)watcher;
|
||||
- (void)removeWatcherByHandle:(ASHandle *)actionHandle fromPath:(NSString *)path;
|
||||
- (void)removeWatcher:(id<ASWatcher>)watcher fromPath:(NSString *)path;
|
||||
- (void)removeAllWatchersFromPath:(NSString *)path;
|
||||
|
||||
- (bool)requestActorStateNow:(NSString *)path;
|
||||
|
||||
- (void)dispatchResource:(NSString *)path resource:(id)resource arguments:(id)arguments;
|
||||
- (void)dispatchResource:(NSString *)path resource:(id)resource;
|
||||
|
||||
- (void)actionCompleted:(NSString *)action result:(id)result;
|
||||
- (void)dispatchMessageToWatchers:(NSString *)path messageType:(NSString *)messageType message:(id)message;
|
||||
- (void)actionFailed:(NSString *)action reason:(int)reason;
|
||||
- (void)nodeRetrieved:(NSString *)path node:(SGraphNode *)node;
|
||||
- (void)nodeRetrieveProgress:(NSString *)path progress:(float)progress;
|
||||
- (void)nodeRetrieveFailed:(NSString *)path;
|
||||
|
||||
@end
|
||||
1242
submodules/LegacyComponents/LegacyComponents/ActionStage.mm
Normal file
1242
submodules/LegacyComponents/LegacyComponents/ActionStage.mm
Normal file
File diff suppressed because it is too large
Load diff
66
submodules/LegacyComponents/LegacyComponents/CBAssetDownloadManager.h
Executable file
66
submodules/LegacyComponents/LegacyComponents/CBAssetDownloadManager.h
Executable file
|
|
@ -0,0 +1,66 @@
|
|||
//
|
||||
// Created by Tikhonenko Pavel on 23/05/2014.
|
||||
// Copyright (c) 2014 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "CBCoubAsset.h"
|
||||
|
||||
@protocol CBDownloadOperationDelegate;
|
||||
|
||||
typedef NS_ENUM(NSInteger, CBDownloadProcessType)
|
||||
{
|
||||
CBDownloadProcessTypeCoub,
|
||||
CBDownloadProcessTypeChunks,
|
||||
};
|
||||
|
||||
@interface CBAssetDownloadManager : NSObject
|
||||
|
||||
- (void)downloadCoubWithoutChunks:(id<CBCoubAsset>)coub
|
||||
tag:(NSInteger)tag
|
||||
withDelegate:(id<CBDownloadOperationDelegate>)delegate
|
||||
downloadSucces:(void (^)(id<CBCoubAsset> coub, NSInteger tag))success downloadFailure:(void (^)(id<CBCoubAsset> coub, NSInteger tag, NSError *error))failure;
|
||||
|
||||
- (void)downloadCoub:(id<CBCoubAsset>)coub
|
||||
tag:(NSInteger)tag
|
||||
withDelegate:(id<CBDownloadOperationDelegate>)delegate
|
||||
withChunks:(BOOL)withChunks
|
||||
downloadSucces:(void (^)(id<CBCoubAsset> coub, NSInteger tag))success downloadFailure:(void (^)(id<CBCoubAsset> coub, NSInteger tag, NSError *error))failure;
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Downloading video and first chunk
|
||||
|
||||
//- (void)downloadCoub:(id<CBCoubAsset>)coub
|
||||
// tag:(NSInteger)tag
|
||||
// withDelegate:(id<CBDownloadOperationDelegate>)delegate
|
||||
// downloadSucces:(void (^)(id<CBCoubAsset> coub, NSInteger tag))success downloadFailure:(void (^)(id<CBCoubAsset> coub, NSInteger tag, NSError *error))failure;
|
||||
|
||||
- (void)downloadNextCoub:(id<CBCoubAsset>)coub
|
||||
tag:(NSInteger)tag
|
||||
downloadSucces:(void (^)(id<CBCoubAsset> coub, NSInteger tag))success downloadFailure:(void (^)(id<CBCoubAsset> coub, NSInteger tag, NSError *error))failure;
|
||||
|
||||
- (void)cancelDownloadingForCoub:(id<CBCoubAsset>)coub;
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Downloading remaining chunks
|
||||
|
||||
- (void)downloadChunkWithCoub:(id<CBCoubAsset>)coub
|
||||
tag:(NSInteger)tag
|
||||
chunkIdx:(NSInteger)chunkIdx
|
||||
downloadSucces:(void (^)(id<CBCoubAsset> coub, NSInteger tag))success
|
||||
downloadFailure:(void (^)(id<CBCoubAsset> coub, NSInteger tag, NSError *error))failure;
|
||||
|
||||
- (void)downloadNextChunkWithCoub:(id<CBCoubAsset>)coub
|
||||
downloadSucces:(void (^)(id<CBCoubAsset> coub, NSInteger tag))success
|
||||
downloadFailure:(void (^)(id<CBCoubAsset> coub, NSInteger tag, NSError *error))failure;
|
||||
//- (void)downloadRemainingChunksWithCoub:(id<CBCoubAsset>)coub
|
||||
// tag:(NSInteger)tag
|
||||
// downloadChunkSucces:(void (^)(id<CBCoubAsset> coub, NSInteger tag, NSInteger chunkIdx))chunkDownloaded
|
||||
// downloadSucces:(void (^)(id<CBCoubAsset> coub, NSInteger tag))success
|
||||
// downloadFailure:(void (^)(id<CBCoubAsset> coub, NSInteger tag, NSError *error))failure;
|
||||
|
||||
- (void)stopAllDownloads;
|
||||
|
||||
+ (instancetype)sharedManager;
|
||||
|
||||
@end
|
||||
290
submodules/LegacyComponents/LegacyComponents/CBAssetDownloadManager.m
Executable file
290
submodules/LegacyComponents/LegacyComponents/CBAssetDownloadManager.m
Executable file
|
|
@ -0,0 +1,290 @@
|
|||
//
|
||||
// Created by Tikhonenko Pavel on 23/05/2014.
|
||||
// Copyright (c) 2014 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CBAssetDownloadManager.h"
|
||||
#import "CBCoubDownloadOperation.h"
|
||||
#import "CBChunkDownloadOperation.h"
|
||||
#import "CBDownloadOperation.h"
|
||||
#import "CBDownloadOperationDelegate.h"
|
||||
|
||||
|
||||
@interface CBAssetDownloadManager ()
|
||||
|
||||
@property (nonatomic, strong) CBCoubDownloadOperation *currentCoubDownloadingOperation;
|
||||
@property (nonatomic, strong) CBCoubDownloadOperation *pausedCoubDownloadingOperation;
|
||||
@property (nonatomic, strong) CBCoubDownloadOperation *nextCoubDownloadingOperation;
|
||||
@property (nonatomic, strong) CBChunkDownloadOperation *currentChunksDownloadingOperation;
|
||||
|
||||
- (void)resumeCoubDownloading;
|
||||
- (void)destroyDownloadOperation:(id<CBDownloadOperation>)downloadOperation;
|
||||
|
||||
- (id<CBDownloadOperation>)operationWithType:(CBDownloadProcessType)type
|
||||
coub:(id<CBCoubAsset>)coub tag:(NSInteger)tag
|
||||
downloadSucces:(void (^)(id<CBCoubAsset> coub, NSInteger tag))success
|
||||
downloadFailure:(void (^)(id<CBCoubAsset> coub, NSInteger tag, NSError *error))failure;
|
||||
@end
|
||||
|
||||
@implementation CBAssetDownloadManager
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if(self)
|
||||
{
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)downloadCoubWithoutChunks:(id<CBCoubAsset>)coub
|
||||
tag:(NSInteger)tag
|
||||
withDelegate:(id<CBDownloadOperationDelegate>)delegate
|
||||
downloadSucces:(void (^)(id<CBCoubAsset> coub, NSInteger tag))success downloadFailure:(void (^)(id<CBCoubAsset> coub, NSInteger tag, NSError *error))failure
|
||||
{
|
||||
if(_currentChunksDownloadingOperation)
|
||||
{
|
||||
//_pausedCoubDownloadingOperation = _currentChunksDownloadingOperation;
|
||||
[self destroyDownloadOperation:_currentChunksDownloadingOperation];
|
||||
_currentChunksDownloadingOperation = nil;
|
||||
}
|
||||
|
||||
[self downloadCoub:coub tag:tag withDelegate:delegate queuePriority:NSOperationQueuePriorityNormal withChunks:NO downloadSucces:success downloadFailure:failure];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Downloading video and first chunk
|
||||
|
||||
- (void)downloadCoub:(id<CBCoubAsset>)coub
|
||||
tag:(NSInteger)tag
|
||||
withDelegate:(id<CBDownloadOperationDelegate>)delegate
|
||||
withChunks:(BOOL)withChunks
|
||||
downloadSucces:(void (^)(id<CBCoubAsset> coub, NSInteger tag))success downloadFailure:(void (^)(id<CBCoubAsset> coub, NSInteger tag, NSError *error))failure
|
||||
{
|
||||
if(_currentChunksDownloadingOperation)
|
||||
{
|
||||
//_pausedCoubDownloadingOperation = _currentChunksDownloadingOperation;
|
||||
[self destroyDownloadOperation:_currentChunksDownloadingOperation];
|
||||
_currentChunksDownloadingOperation = nil;
|
||||
}
|
||||
|
||||
[self downloadCoub:coub tag:tag withDelegate:delegate queuePriority:NSOperationQueuePriorityNormal withChunks:withChunks downloadSucces:success downloadFailure:failure];
|
||||
}
|
||||
|
||||
- (void)downloadCoub:(id<CBCoubAsset>)coub
|
||||
tag:(NSInteger)tag
|
||||
withDelegate:(id<CBDownloadOperationDelegate>)delegate
|
||||
queuePriority:(NSOperationQueuePriority)queuePriority
|
||||
withChunks:(BOOL)withChunks
|
||||
downloadSucces:(void (^)(id<CBCoubAsset> coub, NSInteger tag))success downloadFailure:(void (^)(id<CBCoubAsset> coub, NSInteger tag, NSError *error))failure
|
||||
{
|
||||
if(_currentCoubDownloadingOperation && _currentCoubDownloadingOperation.coub == coub && !_currentChunksDownloadingOperation.comleted)
|
||||
{
|
||||
_currentCoubDownloadingOperation.operationViewDelegate = delegate;
|
||||
[_currentCoubDownloadingOperation setClientSuccess:success];
|
||||
[_currentCoubDownloadingOperation setClientFailure:failure];
|
||||
return;
|
||||
}
|
||||
|
||||
if(_nextCoubDownloadingOperation && _nextCoubDownloadingOperation.coub == coub && !_nextCoubDownloadingOperation.comleted)
|
||||
{
|
||||
[self destroyDownloadOperation:_nextCoubDownloadingOperation];
|
||||
}
|
||||
|
||||
if(_currentCoubDownloadingOperation)
|
||||
[self destroyDownloadOperation:_currentCoubDownloadingOperation];
|
||||
|
||||
id<CBDownloadOperation> downloadOperation = [self operationWithType:CBDownloadProcessTypeCoub
|
||||
coub:coub tag:tag downloadSucces:success downloadFailure:failure];
|
||||
downloadOperation.queuePriority = queuePriority;
|
||||
|
||||
_currentCoubDownloadingOperation = downloadOperation;
|
||||
_currentCoubDownloadingOperation.chunkDownloadingNeeded = withChunks;
|
||||
[_currentCoubDownloadingOperation setOperationViewDelegate:delegate];
|
||||
[_currentCoubDownloadingOperation start];
|
||||
}
|
||||
|
||||
- (void)downloadNextCoub:(id<CBCoubAsset>)coub
|
||||
tag:(NSInteger)tag
|
||||
downloadSucces:(void (^)(id<CBCoubAsset> coub,NSInteger tag))success downloadFailure:(void (^)(id<CBCoubAsset> coub,NSInteger tag, NSError *error))failure
|
||||
{
|
||||
if(_currentChunksDownloadingOperation && _currentChunksDownloadingOperation.coub == coub && !_currentChunksDownloadingOperation.comleted)
|
||||
{
|
||||
failure(self, 0, nil);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if(_nextCoubDownloadingOperation)
|
||||
[self destroyDownloadOperation:_nextCoubDownloadingOperation];
|
||||
|
||||
|
||||
id<CBDownloadOperation> downloadOperation = [self operationWithType:CBDownloadProcessTypeCoub
|
||||
coub:coub tag:tag downloadSucces:success downloadFailure:failure];
|
||||
downloadOperation.queuePriority = NSOperationQueuePriorityVeryLow;
|
||||
|
||||
_nextCoubDownloadingOperation = downloadOperation;
|
||||
[_nextCoubDownloadingOperation setOperationViewDelegate:nil];
|
||||
[_nextCoubDownloadingOperation start];
|
||||
}
|
||||
|
||||
- (void)cancelDownloadingForCoub:(id<CBCoubAsset>)coub
|
||||
{
|
||||
if(_currentCoubDownloadingOperation && _currentCoubDownloadingOperation.coub == coub)
|
||||
[self destroyDownloadOperation:_currentCoubDownloadingOperation];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Downloading remaining chunks
|
||||
|
||||
- (void)downloadChunkWithCoub:(id<CBCoubAsset>)coub
|
||||
tag:(NSInteger)tag
|
||||
chunkIdx:(NSInteger)chunkIdx
|
||||
downloadSucces:(void (^)(id<CBCoubAsset> coub, NSInteger tag))success
|
||||
downloadFailure:(void (^)(id<CBCoubAsset> coub, NSInteger tag, NSError *error))failure
|
||||
{
|
||||
[self destroyDownloadOperation:_currentChunksDownloadingOperation];
|
||||
|
||||
_currentChunksDownloadingOperation = [self operationWithType:CBDownloadProcessTypeChunks coub:coub
|
||||
tag:tag downloadSucces:success downloadFailure:failure];
|
||||
|
||||
__weak typeof(self) wSelf = self;
|
||||
|
||||
_currentChunksDownloadingOperation.chunkIdx = chunkIdx;
|
||||
[_currentChunksDownloadingOperation setCompletionBlock:^(id<CBDownloadOperation> process, NSError *error) {
|
||||
[wSelf destroyDownloadOperation:process];
|
||||
[wSelf resumeCoubDownloading];
|
||||
}];
|
||||
|
||||
[_currentChunksDownloadingOperation start];
|
||||
}
|
||||
|
||||
- (void)downloadNextChunkWithCoub:(id<CBCoubAsset>)coub
|
||||
downloadSucces:(void (^)(id<CBCoubAsset> coub, NSInteger tag))success
|
||||
downloadFailure:(void (^)(id<CBCoubAsset> coub, NSInteger tag, NSError *error))failure
|
||||
{
|
||||
NSInteger chunkIdx = 0;
|
||||
[self downloadChunkWithCoub:coub tag:chunkIdx chunkIdx:chunkIdx downloadSucces:success downloadFailure:failure];
|
||||
}
|
||||
|
||||
- (void)downloadRemainingChunksWithCoub:(id<CBCoubAsset>)coub tag:(NSInteger)tag
|
||||
downloadChunkSucces:(void (^)(id<CBCoubAsset> coub, NSInteger tag, NSInteger chunkIdx))chunkDownloaded
|
||||
downloadSucces:(void (^)(id<CBCoubAsset> coub,NSInteger tag))success
|
||||
downloadFailure:(void (^)(id<CBCoubAsset> coub, NSInteger tag, NSError *error))failure
|
||||
{
|
||||
[self destroyDownloadOperation:_currentChunksDownloadingOperation];
|
||||
|
||||
_currentChunksDownloadingOperation = [self operationWithType:CBDownloadProcessTypeChunks coub:coub
|
||||
tag:tag downloadSucces:success downloadFailure:failure];
|
||||
|
||||
__weak typeof(self) wSelf = self;
|
||||
|
||||
_currentChunksDownloadingOperation.chunkDownloadedBlock = chunkDownloaded;
|
||||
[_currentChunksDownloadingOperation setCompletionBlock:^(id<CBDownloadOperation> process, NSError *error) {
|
||||
[wSelf destroyDownloadOperation:process];
|
||||
[wSelf resumeCoubDownloading];
|
||||
}];
|
||||
|
||||
[_currentChunksDownloadingOperation start];
|
||||
}
|
||||
|
||||
- (void)stopAllDownloads
|
||||
{
|
||||
[self destroyCurrentOperations];
|
||||
|
||||
_pausedCoubDownloadingOperation = nil;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark private method
|
||||
|
||||
- (void)resumeCoubDownloading
|
||||
{
|
||||
if(_pausedCoubDownloadingOperation)
|
||||
{
|
||||
_currentCoubDownloadingOperation = _pausedCoubDownloadingOperation;
|
||||
[_currentCoubDownloadingOperation start];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)destroyCurrentOperations
|
||||
{
|
||||
[self destroyDownloadOperation:_currentCoubDownloadingOperation];
|
||||
|
||||
[self destroyDownloadOperation:_currentChunksDownloadingOperation];
|
||||
|
||||
//_pausedCoubDownloadingOperation = nil;
|
||||
}
|
||||
|
||||
- (void)destroyDownloadOperation:(id<CBDownloadOperation>)downloadOperation
|
||||
{
|
||||
if(downloadOperation)
|
||||
[downloadOperation cancel];
|
||||
|
||||
if(downloadOperation == _currentCoubDownloadingOperation)
|
||||
_currentCoubDownloadingOperation = nil;
|
||||
|
||||
if(downloadOperation == _currentChunksDownloadingOperation)
|
||||
_currentChunksDownloadingOperation = nil;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Factory method
|
||||
|
||||
- (id<CBDownloadOperation>)operationWithType:(CBDownloadProcessType)type
|
||||
coub:(id<CBCoubAsset>)coub tag:(NSInteger)tag
|
||||
downloadSucces:(void (^)(id<CBCoubAsset>, NSInteger tag))success
|
||||
downloadFailure:(void (^)(id<CBCoubAsset>, NSInteger tag, NSError *error))failure
|
||||
{
|
||||
id<CBDownloadOperation> downloadOperation;
|
||||
|
||||
switch(type)
|
||||
{
|
||||
case CBDownloadProcessTypeCoub:
|
||||
{
|
||||
downloadOperation = [CBCoubDownloadOperation new];
|
||||
break;
|
||||
}
|
||||
|
||||
case CBDownloadProcessTypeChunks:
|
||||
{
|
||||
downloadOperation = [CBChunkDownloadOperation new];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[downloadOperation setTag:tag];
|
||||
[downloadOperation setCoub:coub];
|
||||
[downloadOperation setClientSuccess:success];
|
||||
[downloadOperation setClientFailure:failure];
|
||||
|
||||
__weak typeof(self) wSelf = self;
|
||||
|
||||
[downloadOperation setCompletionBlock:^(id<CBCoubAsset> operation, NSError *error) {
|
||||
[wSelf destroyDownloadOperation:operation];
|
||||
[wSelf resumeCoubDownloading];
|
||||
}];
|
||||
|
||||
return downloadOperation;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Singleton implementation
|
||||
|
||||
static CBAssetDownloadManager *instance = nil;
|
||||
|
||||
+ (instancetype)sharedManager
|
||||
{
|
||||
if(instance != nil) return instance;
|
||||
|
||||
static dispatch_once_t onceToken = 0;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [[self alloc] init];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
@end
|
||||
14
submodules/LegacyComponents/LegacyComponents/CBChunkDownloadOperation.h
Executable file
14
submodules/LegacyComponents/LegacyComponents/CBChunkDownloadOperation.h
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
//
|
||||
// Created by Tikhonenko Pavel on 23/05/2014.
|
||||
// Copyright (c) 2014 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CBGenericDownloadOperation.h"
|
||||
|
||||
@interface CBChunkDownloadOperation : CBGenericDownloadOperation
|
||||
|
||||
@property (nonatomic, assign) NSInteger chunkIdx;
|
||||
|
||||
@property(nonatomic, copy) void (^chunkDownloadedBlock)(id<CBCoubAsset>, NSInteger tag, NSInteger chunkIdx);
|
||||
|
||||
@end
|
||||
65
submodules/LegacyComponents/LegacyComponents/CBChunkDownloadOperation.m
Executable file
65
submodules/LegacyComponents/LegacyComponents/CBChunkDownloadOperation.m
Executable file
|
|
@ -0,0 +1,65 @@
|
|||
//
|
||||
// Created by Tikhonenko Pavel on 23/05/2014.
|
||||
// Copyright (c) 2014 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CBChunkDownloadOperation.h"
|
||||
|
||||
#import "LegacyComponentsInternal.h"
|
||||
#import "LegacyHTTPRequestOperation.h"
|
||||
|
||||
@implementation CBChunkDownloadOperation
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
- (void)start
|
||||
{
|
||||
[super start];
|
||||
[self downloadChunk];
|
||||
}
|
||||
|
||||
- (void)downloadChunk
|
||||
{
|
||||
NSURL *localURL = [self.coub localAudioChunkWithIdx:_chunkIdx];
|
||||
|
||||
NSURL *remoteURL = [self.coub remoteAudioChunkWithIdx:_chunkIdx+1];
|
||||
|
||||
NSURLRequest *downloadRequest = [NSURLRequest requestWithURL:remoteURL];
|
||||
|
||||
self.downloadOperation = [[LegacyComponentsGlobals provider] makeHTTPRequestOperationWithRequest:downloadRequest];
|
||||
self.downloadOperation.queuePriority = self.queuePriority;
|
||||
self.downloadOperation.outputStream = [NSOutputStream outputStreamToFileAtPath:localURL.path append:NO];
|
||||
//[self.downloadOperation setShouldExecuteAsBackgroundTaskWithExpirationHandler:nil];
|
||||
|
||||
__weak typeof(self) wSelf = self;
|
||||
|
||||
[self.downloadOperation setCompletionBlockWithSuccess:^(__unused id operation, __unused id responseObject) {
|
||||
[wSelf successChunkDownload];
|
||||
} failure:^(__unused id operation, __unused NSError *error) {
|
||||
[wSelf failureDownloadWithError:error];
|
||||
}];
|
||||
|
||||
[self.downloadOperation start];
|
||||
}
|
||||
|
||||
- (void)successChunkDownload
|
||||
{
|
||||
//NSLog(@"successChunkDownload");
|
||||
|
||||
[self successDownload];
|
||||
}
|
||||
|
||||
- (instancetype)clone
|
||||
{
|
||||
CBChunkDownloadOperation *clone = [CBChunkDownloadOperation new];
|
||||
[clone setTag:self.tag];
|
||||
[clone setCoub:self.coub];
|
||||
[clone setCompletionBlock:self.completionBlock];
|
||||
[clone setClientSuccess:self.clientSuccess];
|
||||
[clone setClientFailure:self.clientFailure];
|
||||
[clone setChunkDownloadedBlock:self.chunkDownloadedBlock];
|
||||
return clone;
|
||||
}
|
||||
|
||||
@end
|
||||
33
submodules/LegacyComponents/LegacyComponents/CBConstance.h
Executable file
33
submodules/LegacyComponents/LegacyComponents/CBConstance.h
Executable file
|
|
@ -0,0 +1,33 @@
|
|||
//
|
||||
// CBConstance.h
|
||||
// CoubPlayer
|
||||
//
|
||||
// Created by Pavel Tikhonenko on 19/10/14.
|
||||
// Copyright (c) 2014 Pavel Tikhonenko. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef void (^CBSuccessBlock)(id result);
|
||||
//typedef void (^CBSuccessWithUserInfoBlock)(id result, id userInfo);
|
||||
typedef void (^CBFailureBlock)(NSError *error);
|
||||
|
||||
extern NSString * const kCBServerURL;
|
||||
|
||||
extern NSString * const CBCoubLoopErrorDomain;
|
||||
enum {
|
||||
CBCoubLoopErrorUnknown = 1,
|
||||
CBCoubLoopErrorNoSuchFile,
|
||||
CBCoubLoopErrorUnsupportedAudioFormat,
|
||||
CBCoubLoopErrorUnreadableAudioTrack,
|
||||
CBCoubLoopErrorNoVideoTracks,
|
||||
CBCoubLoopErrorCanceled,
|
||||
};
|
||||
|
||||
|
||||
extern NSString *const CBPlayerInterruptionDidBeginNotification;
|
||||
extern NSString *const CBPlayerInterruptionDidEndNotification;
|
||||
|
||||
@interface CBConstance : NSObject
|
||||
|
||||
@end
|
||||
21
submodules/LegacyComponents/LegacyComponents/CBConstance.m
Executable file
21
submodules/LegacyComponents/LegacyComponents/CBConstance.m
Executable file
|
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// CBConstance.m
|
||||
// CoubPlayer
|
||||
//
|
||||
// Created by Pavel Tikhonenko on 19/10/14.
|
||||
// Copyright (c) 2014 Pavel Tikhonenko. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CBConstance.h"
|
||||
|
||||
NSString *const kCBServerURL = @"http://coub.com";
|
||||
|
||||
NSString *const CBCoubLoopErrorDomain = @"com.coub.loop.ErrorDomain";
|
||||
|
||||
NSString *const CBPlayerInterruptionDidBeginNotification = @"CBPlayerInterruptionDidBeginNotification";
|
||||
NSString *const CBPlayerInterruptionDidEndNotification = @"CBPlayerInterruptionDidEndNotification";
|
||||
|
||||
|
||||
@implementation CBConstance
|
||||
|
||||
@end
|
||||
25
submodules/LegacyComponents/LegacyComponents/CBCoubAsset.h
Executable file
25
submodules/LegacyComponents/LegacyComponents/CBCoubAsset.h
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// Created by Tikhonenko Pavel on 04/04/2014.
|
||||
// Copyright (c) 2014 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "CBCoubPlayerContance.h"
|
||||
|
||||
@protocol CBCoubAsset<NSObject>
|
||||
|
||||
@property(nonatomic, readonly) NSString *assetId; //permalink
|
||||
@property(nonatomic, readonly) NSURL *localVideoFileURL;
|
||||
@property(nonatomic, readonly) CBCoubAudioType audioType;
|
||||
@property(nonatomic, readonly) NSURL *externalAudioURL; //may be nil
|
||||
@property(nonatomic, readonly) NSURL *largeImageURL;
|
||||
|
||||
- (BOOL)failedDownloadChunk;
|
||||
|
||||
- (NSURL *)remoteVideoFileURL;
|
||||
|
||||
- (NSURL *)localAudioChunkWithIdx:(NSInteger)idx;
|
||||
- (NSURL *)remoteAudioChunkWithIdx:(NSInteger)idx;
|
||||
|
||||
|
||||
@end
|
||||
21
submodules/LegacyComponents/LegacyComponents/CBCoubAudioSource.h
Executable file
21
submodules/LegacyComponents/LegacyComponents/CBCoubAudioSource.h
Executable file
|
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// CBCoubAudioSource.h
|
||||
// Coub
|
||||
//
|
||||
// Created by Tikhonenko Pavel on 18/11/2013.
|
||||
// Copyright (c) 2013 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface CBCoubAudioSource : NSObject
|
||||
|
||||
@property (nonatomic, strong) NSString *cover;
|
||||
@property (nonatomic, strong) NSString *title;
|
||||
@property (nonatomic, strong) NSString *artist;
|
||||
@property (nonatomic, strong) NSString *itunesURL;
|
||||
@property (nonatomic, strong) NSString *songName;
|
||||
|
||||
+ (CBCoubAudioSource *)sourceFromData:(NSDictionary *)dict;
|
||||
|
||||
@end
|
||||
32
submodules/LegacyComponents/LegacyComponents/CBCoubAudioSource.m
Executable file
32
submodules/LegacyComponents/LegacyComponents/CBCoubAudioSource.m
Executable file
|
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// CBCoubAudioSource.m
|
||||
// Coub
|
||||
//
|
||||
// Created by Tikhonenko Pavel on 18/11/2013.
|
||||
// Copyright (c) 2013 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CBCoubAudioSource.h"
|
||||
|
||||
@implementation CBCoubAudioSource
|
||||
|
||||
+ (CBCoubAudioSource *)sourceFromData:(NSDictionary *)dict
|
||||
{
|
||||
if(!dict.count) return nil;
|
||||
|
||||
|
||||
NSDictionary *meta = dict[@"meta"];
|
||||
|
||||
CBCoubAudioSource *source = [[CBCoubAudioSource alloc] init];
|
||||
source.cover = dict[@"image"];
|
||||
source.itunesURL = dict[@"url"];
|
||||
source.songName = meta[@"title"];
|
||||
source.title = source.songName;
|
||||
source.artist = meta[@"artist"];
|
||||
if(source.title == nil || source.itunesURL == nil)
|
||||
return nil;
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
@end
|
||||
23
submodules/LegacyComponents/LegacyComponents/CBCoubAuthorVO.h
Executable file
23
submodules/LegacyComponents/LegacyComponents/CBCoubAuthorVO.h
Executable file
|
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// CBCoubAuthorVO.h
|
||||
// Coub
|
||||
//
|
||||
// Created by Tikhonenko Pavel on 21/11/2013.
|
||||
// Copyright (c) 2013 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface CBCoubAuthorVO : NSObject
|
||||
|
||||
@property (nonatomic, strong) NSString *avatarURL;
|
||||
@property (nonatomic, strong) NSString *largeAvatarURL;
|
||||
@property (nonatomic, assign) NSInteger followersCount;
|
||||
@property (nonatomic, assign) NSInteger viewsCount;
|
||||
@property (nonatomic, strong) NSString *userId;
|
||||
@property (nonatomic, strong) NSString *name;
|
||||
@property (nonatomic, strong) NSString *permalink;
|
||||
|
||||
+ (CBCoubAuthorVO *)coubAuthorWithAttributes:(NSDictionary *)attributes;
|
||||
+ (CBCoubAuthorVO *)currentUser;
|
||||
@end
|
||||
42
submodules/LegacyComponents/LegacyComponents/CBCoubAuthorVO.m
Executable file
42
submodules/LegacyComponents/LegacyComponents/CBCoubAuthorVO.m
Executable file
|
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// CBCoubAuthorVO.m
|
||||
// Coub
|
||||
//
|
||||
// Created by Tikhonenko Pavel on 21/11/2013.
|
||||
// Copyright (c) 2013 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CBCoubAuthorVO.h"
|
||||
|
||||
@implementation CBCoubAuthorVO
|
||||
|
||||
+ (CBCoubAuthorVO *)coubAuthorWithAttributes:(NSDictionary *)attributes
|
||||
{
|
||||
CBCoubAuthorVO *author = [CBCoubAuthorVO new];
|
||||
author.avatarURL = attributes[@"avatar"];
|
||||
author.largeAvatarURL = attributes[@"large_avatar"];
|
||||
author.followersCount = [attributes[@"followers_count"] integerValue];
|
||||
author.userId = [attributes[@"id"] stringValue];
|
||||
author.name = attributes[@"name"];
|
||||
author.permalink = attributes[@"permalink"];
|
||||
author.viewsCount = [attributes[@"views_count"] integerValue];
|
||||
//author.viewsCount;
|
||||
return author;
|
||||
}
|
||||
|
||||
//+ (CBCoubAuthorVO *)currentUser
|
||||
//{
|
||||
// CBUserNew *curUser = [CBUserNew currentUser];
|
||||
//
|
||||
// CBCoubAuthorVO *author = [CBCoubAuthorVO new];
|
||||
// author.avatarURL = curUser.mediumAvatar;
|
||||
// author.largeAvatarURL = curUser.largeAvatar;
|
||||
// author.followersCount = curUser.followerCount;
|
||||
// author.userId = curUser.coubID;
|
||||
// author.name = curUser.fullName;
|
||||
// author.permalink = curUser.permalink;
|
||||
// author.viewsCount = curUser.viewsCount;
|
||||
// return author;
|
||||
//}
|
||||
|
||||
@end
|
||||
12
submodules/LegacyComponents/LegacyComponents/CBCoubDownloadOperation.h
Executable file
12
submodules/LegacyComponents/LegacyComponents/CBCoubDownloadOperation.h
Executable file
|
|
@ -0,0 +1,12 @@
|
|||
//
|
||||
// Created by Tikhonenko Pavel on 23/05/2014.
|
||||
// Copyright (c) 2014 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CBGenericDownloadOperation.h"
|
||||
|
||||
@interface CBCoubDownloadOperation : CBGenericDownloadOperation
|
||||
|
||||
@property (nonatomic, assign) NSInteger neededChunkCount;
|
||||
|
||||
@end
|
||||
190
submodules/LegacyComponents/LegacyComponents/CBCoubDownloadOperation.m
Executable file
190
submodules/LegacyComponents/LegacyComponents/CBCoubDownloadOperation.m
Executable file
|
|
@ -0,0 +1,190 @@
|
|||
//
|
||||
// Created by Tikhonenko Pavel on 23/05/2014.
|
||||
// Copyright (c) 2014 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CBCoubDownloadOperation.h"
|
||||
#import "CBDownloadOperationDelegate.h"
|
||||
|
||||
#import "LegacyComponentsInternal.h"
|
||||
|
||||
@implementation CBCoubDownloadOperation
|
||||
{
|
||||
BOOL _isVideoDownloaded;
|
||||
NSInteger _currentChunkIdx;
|
||||
}
|
||||
|
||||
- (void)start
|
||||
{
|
||||
[super start];
|
||||
|
||||
_neededChunkCount = NSNotFound;
|
||||
_currentChunkIdx = 0;
|
||||
|
||||
[self downloadVideo];
|
||||
}
|
||||
|
||||
- (void)downloadVideo
|
||||
{
|
||||
NSURL *remoteURL = [self.coub remoteVideoFileURL];
|
||||
NSURL *localURL = [self.coub localVideoFileURL];
|
||||
|
||||
_isVideoDownloaded = [[NSFileManager defaultManager] fileExistsAtPath:[localURL path]];
|
||||
if(_isVideoDownloaded)
|
||||
{
|
||||
[self successVideoDownload];
|
||||
return;
|
||||
}else{
|
||||
//KALog(@"video doesn't %i", _currentChunkIdx);
|
||||
}
|
||||
|
||||
NSURLRequest *downloadRequest = [NSURLRequest requestWithURL:remoteURL];
|
||||
|
||||
__weak typeof(self) wSelf = self;
|
||||
|
||||
self.downloadOperation = [[LegacyComponentsGlobals provider] makeHTTPRequestOperationWithRequest:downloadRequest];
|
||||
self.downloadOperation.queuePriority = self.queuePriority;
|
||||
self.downloadOperation.outputStream = [NSOutputStream outputStreamToFileAtPath:localURL.path append:NO];
|
||||
//[self.downloadOperation setShouldExecuteAsBackgroundTaskWithExpirationHandler:nil];
|
||||
[self.downloadOperation setDownloadProgressBlock:^(__unused NSInteger bytesRead, NSInteger totalBytesRead, NSInteger totalBytesExpectedToRead) {
|
||||
float progress = totalBytesRead / (float) totalBytesExpectedToRead;
|
||||
[wSelf progressDownload:progress];
|
||||
}];
|
||||
|
||||
[self.downloadOperation setCompletionBlockWithSuccess:^(__unused id operation, __unused id responseObject) {
|
||||
[wSelf successVideoDownload];
|
||||
} failure:^(__unused id operation, NSError *error) {
|
||||
|
||||
if([[NSFileManager defaultManager] fileExistsAtPath:[localURL path]])
|
||||
[[NSFileManager defaultManager] removeItemAtPath:[localURL path] error:nil];
|
||||
|
||||
[wSelf failureDownloadWithError:error];
|
||||
}];
|
||||
|
||||
[self.downloadOperation start];
|
||||
}
|
||||
|
||||
- (void)downloadChunk
|
||||
{
|
||||
NSURL *localURL = [self.coub localAudioChunkWithIdx:_currentChunkIdx];
|
||||
|
||||
//int fileExist = [[CBLibrary sharedLibrary] isCoubChunkDownloadedByPermalink:self.coub.permalink idx:_currentChunkIdx];
|
||||
int fileExist = [[NSFileManager defaultManager] fileExistsAtPath:[localURL path]];
|
||||
|
||||
if(fileExist || (_neededChunkCount == NSNotFound && _currentChunkIdx > 1))
|
||||
{
|
||||
[self successDownload];
|
||||
return;
|
||||
}
|
||||
|
||||
NSURL *remoteURL = [self.coub remoteAudioChunkWithIdx:_currentChunkIdx + 1];
|
||||
NSURLRequest *downloadRequest = [NSURLRequest requestWithURL:remoteURL];
|
||||
|
||||
//NSLog(@"chunk idx = %i, remoteURL = %@", _currentChunkIdx, remoteURL);
|
||||
|
||||
__weak typeof(self) wSelf = self;
|
||||
|
||||
self.downloadOperation = [[LegacyComponentsGlobals provider] makeHTTPRequestOperationWithRequest:downloadRequest];
|
||||
self.downloadOperation.queuePriority = self.queuePriority;
|
||||
self.downloadOperation.outputStream = [NSOutputStream outputStreamToFileAtPath:localURL.path append:NO];
|
||||
//[self.downloadOperation setShouldExecuteAsBackgroundTaskWithExpirationHandler:nil];
|
||||
[self.downloadOperation setDownloadProgressBlock:^(__unused NSInteger bytesRead, NSInteger totalBytesRead, NSInteger totalBytesExpectedToRead) {
|
||||
|
||||
if(wSelf.neededChunkCount == NSNotFound)
|
||||
{
|
||||
//NSLog(@"totalBytesExpectedToRead = %llu", totalBytesExpectedToRead);
|
||||
|
||||
wSelf.neededChunkCount = MIN((int) ((1024*1024)/totalBytesExpectedToRead), 4);
|
||||
wSelf.neededChunkCount = MAX(wSelf.neededChunkCount, 1);
|
||||
}
|
||||
|
||||
float progress = totalBytesRead / (float) totalBytesExpectedToRead;
|
||||
[wSelf progressDownload:progress];
|
||||
}];
|
||||
|
||||
[self.downloadOperation setCompletionBlockWithSuccess:^(__unused id operation, __unused id responseObject) {
|
||||
//[wSelf.coub setFailedDownloadChunk:NO];
|
||||
[wSelf successChunkDownload];
|
||||
} failure:^(__unused id operation, NSError *error) {
|
||||
|
||||
if([[NSFileManager defaultManager] fileExistsAtPath:[localURL path]])
|
||||
[[NSFileManager defaultManager] removeItemAtPath:[localURL path] error:nil];
|
||||
|
||||
if(error.code == -1011)
|
||||
{
|
||||
//NSLog(@"failed download");
|
||||
|
||||
//[wSelf.coub setFailedDownloadChunk:YES];
|
||||
[wSelf successDownload];
|
||||
|
||||
return;
|
||||
}
|
||||
//NSLog(@"failed download unknown");
|
||||
|
||||
[wSelf failureDownloadWithError:error];
|
||||
}];
|
||||
|
||||
[self.downloadOperation start];
|
||||
}
|
||||
|
||||
- (void)successVideoDownload
|
||||
{
|
||||
//[[CBLibrary sharedLibrary] markCoubAsset:self.coub asDownloaded:YES];
|
||||
|
||||
_isVideoDownloaded = YES;
|
||||
|
||||
if(self.starting)
|
||||
{
|
||||
if(self.chunkDownloadingNeeded && [self.coub audioType] == CBCoubAudioTypeExternal)
|
||||
[self downloadChunk];
|
||||
else
|
||||
[super successDownload];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)successDownload
|
||||
{
|
||||
_currentChunkIdx++;
|
||||
|
||||
if([self.coub audioType] != CBCoubAudioTypeExternal || _currentChunkIdx >= MIN(4, _neededChunkCount))
|
||||
{
|
||||
[super successDownload];
|
||||
}
|
||||
else
|
||||
[self downloadChunk];
|
||||
}
|
||||
|
||||
- (void)successChunkDownload
|
||||
{
|
||||
[self successDownload];
|
||||
}
|
||||
|
||||
- (void)progressDownload:(float)progress
|
||||
{
|
||||
if(self.starting && self.operationViewDelegate)
|
||||
{
|
||||
float newProgress;
|
||||
|
||||
if(_isVideoDownloaded)
|
||||
{
|
||||
newProgress = .5f + (_currentChunkIdx * 1/(float)_neededChunkCount + progress/(float)_neededChunkCount)*.5f;
|
||||
}else{
|
||||
newProgress = progress/2.0f;
|
||||
}
|
||||
[self.operationViewDelegate downloadDidReachProgress:newProgress];
|
||||
}
|
||||
}
|
||||
|
||||
- (instancetype)clone
|
||||
{
|
||||
CBCoubDownloadOperation *clone = [CBCoubDownloadOperation new];
|
||||
[clone setOperationViewDelegate:self.operationViewDelegate];
|
||||
[clone setTag:self.tag];
|
||||
[clone setCoub:self.coub];
|
||||
[clone setCompletionBlock:self.completionBlock];
|
||||
[clone setClientSuccess:self.clientSuccess];
|
||||
[clone setClientFailure:self.clientFailure];
|
||||
return clone;
|
||||
}
|
||||
|
||||
@end
|
||||
41
submodules/LegacyComponents/LegacyComponents/CBCoubLoopCompositionMaker.h
Executable file
41
submodules/LegacyComponents/LegacyComponents/CBCoubLoopCompositionMaker.h
Executable file
|
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// Created by Tikhonenko Pavel on 26/11/2013.
|
||||
// Copyright (c) 2013 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "CBCoubAsset.h"
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
@class CBCoubLoopCompositionMaker;
|
||||
@protocol CBCoubAsset;
|
||||
|
||||
@protocol CBCoubLoopDelegate<NSObject>
|
||||
|
||||
@required
|
||||
- (void)coubLoopDidFinishPreparing:(CBCoubLoopCompositionMaker *)loop;
|
||||
- (void)coubLoop:(CBCoubLoopCompositionMaker *)loop didFailToLoadWithError:(NSError *)error;
|
||||
|
||||
@end
|
||||
|
||||
@interface CBCoubLoopCompositionMaker : NSObject
|
||||
|
||||
@property (nonatomic, weak) id<CBCoubLoopDelegate> delegate;
|
||||
@property (nonatomic, strong) id<CBCoubAsset> asset;
|
||||
|
||||
@property (nonatomic, strong) AVAsset *videoAsset;
|
||||
@property (nonatomic, strong) NSError *error;
|
||||
@property (nonatomic, readonly) BOOL hasAudio;
|
||||
|
||||
@property (nonatomic, assign, getter=isLoopReady) BOOL loopReady;
|
||||
|
||||
- (void)prepareLoop;
|
||||
- (void)cancelPrepareLoop;
|
||||
|
||||
- (void)notifyObservers;
|
||||
|
||||
+ (instancetype)coubLoopWithAsset:(id<CBCoubAsset>)asset;
|
||||
|
||||
@end
|
||||
322
submodules/LegacyComponents/LegacyComponents/CBCoubLoopCompositionMaker.m
Executable file
322
submodules/LegacyComponents/LegacyComponents/CBCoubLoopCompositionMaker.m
Executable file
|
|
@ -0,0 +1,322 @@
|
|||
//
|
||||
// Created by Tikhonenko Pavel on 26/11/2013.
|
||||
// Copyright (c) 2013 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
#import <CoreMedia/CoreMedia.h>
|
||||
#import "CBCoubLoopCompositionMaker.h"
|
||||
#import "CBCoubAsset.h"
|
||||
#import "CBConstance.h"
|
||||
#import "AVAsset+CBExtension.h"
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark CBCoubLoopOperation
|
||||
|
||||
static AVURLAsset *gDigitalSilenceAsset = nil;
|
||||
|
||||
@interface CBCoubLoopOperation : NSOperation
|
||||
|
||||
- (id)initWithCoubAsset:(id<CBCoubAsset>)asset loop:(CBCoubLoopCompositionMaker *)loop;
|
||||
|
||||
- (void)prepareOperation;
|
||||
- (void)makeLoop;
|
||||
- (AVComposition *)makeLoopComposition;
|
||||
|
||||
- (BOOL)checkAssetURL:(NSURL *)assetURL;
|
||||
|
||||
- (void)completeWithError:(NSError *)error;
|
||||
|
||||
@end
|
||||
|
||||
@implementation CBCoubLoopOperation
|
||||
{
|
||||
@private
|
||||
id<CBCoubAsset> _asset;
|
||||
CBCoubLoopCompositionMaker *_loop;
|
||||
AVAssetExportSession *_exportSession;
|
||||
|
||||
NSURL *_assetURL;
|
||||
BOOL _hasExternalAudio;
|
||||
}
|
||||
|
||||
- (id)initWithCoubAsset:(id<CBCoubAsset>)asset loop:(CBCoubLoopCompositionMaker *)loop
|
||||
{
|
||||
self = [super init];
|
||||
if(self)
|
||||
{
|
||||
_asset = asset;
|
||||
_loop = loop;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)main
|
||||
{
|
||||
if([self isCancelled])
|
||||
return;
|
||||
|
||||
[self prepareOperation];
|
||||
}
|
||||
|
||||
- (void)prepareOperation
|
||||
{
|
||||
if(![self checkAssetURL:_asset.localVideoFileURL])
|
||||
return;
|
||||
|
||||
_assetURL = _asset.localVideoFileURL;
|
||||
_hasExternalAudio = _asset.externalAudioURL != nil;
|
||||
|
||||
[self makeLoop];
|
||||
}
|
||||
|
||||
- (void)makeLoop
|
||||
{
|
||||
AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:_assetURL options:@{AVURLAssetPreferPreciseDurationAndTimingKey : @YES}];
|
||||
// _loop.videoAsset = avAsset;
|
||||
// [self completeWithError:nil];
|
||||
// return;
|
||||
if(_asset.audioType == CBCoubAudioTypeInternal)
|
||||
{
|
||||
AVComposition *composition = [self makeLoopComposition];
|
||||
if(![self isCancelled])
|
||||
{
|
||||
_loop.videoAsset = composition;
|
||||
[self completeWithError:nil];
|
||||
}else{
|
||||
|
||||
}
|
||||
|
||||
}else{
|
||||
_loop.videoAsset = avAsset;
|
||||
[self completeWithError:nil];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
- (AVComposition *)makeLoopComposition
|
||||
{
|
||||
AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:_assetURL options:@{AVURLAssetPreferPreciseDurationAndTimingKey : @YES}];
|
||||
NSArray *videoTracks = [avAsset tracksWithMediaType:AVMediaTypeVideo];
|
||||
if([videoTracks count] == 0)
|
||||
{
|
||||
|
||||
//TODO: remove cache file and download again
|
||||
|
||||
NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain
|
||||
code:CBCoubLoopErrorNoVideoTracks
|
||||
userInfo:_assetURL ? @{NSURLErrorKey : _assetURL} : nil];
|
||||
|
||||
[self completeWithError:error];
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
if([self isCancelled])
|
||||
return nil;
|
||||
|
||||
AVAssetTrack *originalVideoTrack = videoTracks[0];
|
||||
AVAssetTrack *originalAudioTrack = _hasExternalAudio ? nil : avAsset.anyAudioTrack;
|
||||
|
||||
if(originalVideoTrack == nil)
|
||||
{
|
||||
NSError *error = [NSError errorWithDomain:CBCoubLoopErrorDomain
|
||||
code:CBCoubLoopErrorNoVideoTracks
|
||||
userInfo:nil];
|
||||
[self completeWithError:error];
|
||||
return nil;
|
||||
}
|
||||
|
||||
CMTimeRange videoTrackTimeRange = originalVideoTrack.timeRange;
|
||||
// videoTrackTimeRange.start.value = 1;
|
||||
// videoTrackTimeRange.duration.value -= 2;
|
||||
|
||||
CMTimeRange audioTrackTimeRange = originalAudioTrack ? originalAudioTrack.timeRange : kCMTimeRangeZero;
|
||||
// if(originalAudioTrack)
|
||||
// {
|
||||
// audioTrackTimeRange.start.value = 1;
|
||||
// audioTrackTimeRange.duration.value -= 2;
|
||||
// }
|
||||
|
||||
// Calculate the minimum duration of our composition
|
||||
CMTime minimumCompositionDuration = CMTimeMake(60*audioTrackTimeRange.start.timescale, audioTrackTimeRange.start.timescale);
|
||||
|
||||
if(!_hasExternalAudio)
|
||||
{
|
||||
if(originalAudioTrack)
|
||||
audioTrackTimeRange = CMTimeRangeGetIntersection(audioTrackTimeRange, videoTrackTimeRange);
|
||||
}
|
||||
|
||||
AVAssetTrack *silence = nil;
|
||||
CMTimeRange silenceTimeRange = kCMTimeRangeZero;
|
||||
|
||||
if([self isCancelled])
|
||||
return nil;
|
||||
|
||||
AVMutableComposition *composition = [AVMutableComposition composition];
|
||||
AVMutableCompositionTrack *videoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
|
||||
AVMutableCompositionTrack *audioTrack = originalAudioTrack ? [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid] : nil;
|
||||
|
||||
CMTime videoTrackDuration = kCMTimeZero;
|
||||
NSError *error = nil;
|
||||
while(CMTIME_COMPARE_INLINE(videoTrackDuration, <, minimumCompositionDuration))
|
||||
{
|
||||
if([self isCancelled])
|
||||
return nil;
|
||||
|
||||
if(![videoTrack insertTimeRange:videoTrackTimeRange ofTrack:originalVideoTrack atTime:videoTrackDuration error:&error])
|
||||
{
|
||||
// KAObjectLogError(@"Failed to insert %@ of video track %@ at %@: %@", CBStringFromTimeRange(videoTrackTimeRange), originalVideoTrack, CBStringFromTime(videoTrackDuration), error);
|
||||
[self completeWithError:error];
|
||||
return nil;
|
||||
}
|
||||
if(audioTrack)
|
||||
{
|
||||
if(![audioTrack insertTimeRange:audioTrackTimeRange ofTrack:originalAudioTrack atTime:videoTrackDuration error:&error])
|
||||
{
|
||||
|
||||
// KAObjectLogError(@"Failed to insert %@ of audio track %@ at %@: %@", CBStringFromTimeRange(audioTrackTimeRange), originalAudioTrack, CBStringFromTime(videoTrackDuration), error);
|
||||
[self completeWithError:error];
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
if(silence)
|
||||
{
|
||||
if(![audioTrack insertTimeRange:silenceTimeRange ofTrack:silence atTime:CMTimeAdd(videoTrackDuration, audioTrackTimeRange.duration) error:&error])
|
||||
{
|
||||
[self completeWithError:error];
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
videoTrackDuration = CMTimeAdd(videoTrackDuration, videoTrackTimeRange.duration);
|
||||
}
|
||||
|
||||
videoTrack.preferredTransform = originalVideoTrack.preferredTransform;
|
||||
return composition;
|
||||
}
|
||||
|
||||
- (BOOL)checkAssetURL:(NSURL *)assetURL
|
||||
{
|
||||
if (assetURL == nil || ([assetURL isFileURL] && ![[NSFileManager defaultManager] fileExistsAtPath: [assetURL path]]))
|
||||
{
|
||||
NSLog(@"File doesn't exist: %@", assetURL);
|
||||
//TODO: remove cache file and download again
|
||||
|
||||
NSError *error = [NSError errorWithDomain:CBCoubLoopErrorDomain
|
||||
code:CBCoubLoopErrorNoSuchFile
|
||||
userInfo:assetURL ? @{NSURLErrorKey : assetURL} : nil];
|
||||
[self completeWithError:error];
|
||||
return NO;
|
||||
}else{
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)cancel
|
||||
{
|
||||
[_exportSession cancelExport];
|
||||
[super cancel];
|
||||
}
|
||||
|
||||
- (void)completeWithError:(NSError *)error
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(), ^
|
||||
{
|
||||
[_exportSession cancelExport];
|
||||
|
||||
_loop.error = error;
|
||||
[_loop notifyObservers];
|
||||
});
|
||||
}
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark CBCoubLoop
|
||||
|
||||
static NSOperationQueue *gOperationQueue = nil;
|
||||
|
||||
@implementation CBCoubLoopCompositionMaker
|
||||
{
|
||||
@private
|
||||
CBCoubLoopOperation *_operation;
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if(!gOperationQueue)
|
||||
{
|
||||
gOperationQueue = [NSOperationQueue new];
|
||||
[gOperationQueue setMaxConcurrentOperationCount:1];
|
||||
}
|
||||
}
|
||||
|
||||
- (id)initWithAsset:(id<CBCoubAsset>)asset
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if(self)
|
||||
{
|
||||
_asset = asset;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)hasAudio
|
||||
{
|
||||
return !(_asset.audioType == CBCoubAudioTypeNone);
|
||||
}
|
||||
|
||||
- (void)prepareLoop
|
||||
{
|
||||
if(gOperationQueue.operations.count)
|
||||
[gOperationQueue cancelAllOperations];
|
||||
|
||||
if(!_operation)
|
||||
{
|
||||
self.error = nil;
|
||||
|
||||
CBCoubLoopOperation *operation = [[CBCoubLoopOperation alloc] initWithCoubAsset:_asset loop:self];
|
||||
[gOperationQueue addOperation:operation];
|
||||
|
||||
_operation = operation;
|
||||
//[_operation start];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)cancelPrepareLoop
|
||||
{
|
||||
self.videoAsset = nil;
|
||||
|
||||
[_operation cancel];
|
||||
_operation = nil;
|
||||
|
||||
self.error = [NSError errorWithDomain:CBCoubLoopErrorDomain
|
||||
code:CBCoubLoopErrorCanceled
|
||||
userInfo:nil];
|
||||
[_delegate coubLoop:self didFailToLoadWithError:self.error];
|
||||
|
||||
}
|
||||
|
||||
- (void)notifyObservers
|
||||
{
|
||||
NSAssert([NSThread isMainThread], @"%s must be called on the main thread", __PRETTY_FUNCTION__);
|
||||
|
||||
if(self.error)
|
||||
[_delegate coubLoop:self didFailToLoadWithError:self.error];
|
||||
else
|
||||
{
|
||||
_loopReady = YES;
|
||||
[_delegate coubLoopDidFinishPreparing:self];
|
||||
}
|
||||
|
||||
_operation = nil;
|
||||
}
|
||||
|
||||
+ (instancetype)coubLoopWithAsset:(id<CBCoubAsset>)asset
|
||||
{
|
||||
CBCoubLoopCompositionMaker *instance = [[CBCoubLoopCompositionMaker alloc] initWithAsset:asset];
|
||||
return instance;
|
||||
}
|
||||
|
||||
@end
|
||||
70
submodules/LegacyComponents/LegacyComponents/CBCoubNew.h
Executable file
70
submodules/LegacyComponents/LegacyComponents/CBCoubNew.h
Executable file
|
|
@ -0,0 +1,70 @@
|
|||
//
|
||||
// CBCoubNew.h
|
||||
// Coub
|
||||
//
|
||||
// Created by Tikhonenko Pavel on 18/11/2013.
|
||||
// Copyright (c) 2013 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "CBCoubAuthorVO.h"
|
||||
#import "CBCoubAudioSource.h"
|
||||
#import "CBCoubVideoSource.h"
|
||||
#import "CBCoubAsset.h"
|
||||
|
||||
@interface CBCoubNew : NSObject<CBCoubAsset>
|
||||
|
||||
@property(nonatomic, strong) NSString *coubID;
|
||||
@property(nonatomic, strong) NSString *permalink;
|
||||
@property(nonatomic, strong) NSString *originalPermalink;
|
||||
@property(nonatomic, strong) NSString *visibility; // kCBCoubVisibility*, see above
|
||||
@property(nonatomic, assign) BOOL isDone;
|
||||
|
||||
@property(nonatomic, strong) CBCoubAuthorVO *author;
|
||||
@property(nonatomic, strong) CBCoubAuthorVO *recouber;
|
||||
|
||||
@property(nonatomic, strong) NSString *title;
|
||||
@property(nonatomic, strong) NSDate *creationDate;
|
||||
@property(nonatomic, strong) NSDate *originalCreationDate;
|
||||
@property(nonatomic, strong) NSArray *tags; //CBTagNew
|
||||
|
||||
//Stats
|
||||
@property(nonatomic, assign) NSUInteger viewCount;
|
||||
@property(nonatomic, assign) NSUInteger likeCount;
|
||||
@property(nonatomic, assign) NSUInteger recoubCount;
|
||||
@property(nonatomic, assign) BOOL liked; // whether the current user likes the coub; does not update likeCount
|
||||
@property(nonatomic, assign) BOOL recoubed;
|
||||
@property(nonatomic, assign) BOOL cotd;
|
||||
@property(nonatomic, assign) BOOL flagged;
|
||||
@property(nonatomic, assign) BOOL deleted;
|
||||
|
||||
@property(nonatomic, assign) CBCoubAudioType audioType;
|
||||
|
||||
@property(nonatomic, strong) NSString *externalDownloadType;
|
||||
@property(nonatomic, strong) NSString *externalDownloadSource; // youtube/vimeo URL
|
||||
|
||||
//@property (nonatomic, readonly) NSOrderedSet *recoubersOthenThanCurrentUser;
|
||||
@property (nonatomic, readonly) NSURL *coubWebViewURL;
|
||||
@property(nonatomic, readonly) NSURL *mediumImageURL;
|
||||
@property(nonatomic, readonly) NSURL *largeImageURL;
|
||||
@property(nonatomic, readonly) BOOL isRecoub;
|
||||
@property(nonatomic, readonly) BOOL isMyCoub;
|
||||
|
||||
@property(nonatomic, retain) NSURL *customLocalVideoFileURL;
|
||||
@property(nonatomic, retain) NSString *remoteVideoLocation;
|
||||
@property(nonatomic, retain) NSString *remoteAudioLocation;
|
||||
@property(nonatomic, retain) NSString *remoteAudioLocationPattern;
|
||||
@property(nonatomic, retain) NSString *mediumPicture;
|
||||
@property(nonatomic, retain) NSString *largePicture;
|
||||
@property(nonatomic, retain) NSString *creationDateAsString;
|
||||
@property(nonatomic, retain) NSString *originalCreationDateAsString;
|
||||
|
||||
@property(nonatomic, readonly) NSURL *remoteVideoFileURL;
|
||||
|
||||
@property(nonatomic, assign) BOOL isCoubSourcesAvailable;
|
||||
@property(nonatomic, strong) CBCoubAudioSource *audioSource;
|
||||
@property(nonatomic, strong) CBCoubVideoSource *videoSource;
|
||||
|
||||
+ (CBCoubNew *)coubWithAttributes:(NSDictionary *)attributes;
|
||||
|
||||
@end
|
||||
199
submodules/LegacyComponents/LegacyComponents/CBCoubNew.m
Executable file
199
submodules/LegacyComponents/LegacyComponents/CBCoubNew.m
Executable file
|
|
@ -0,0 +1,199 @@
|
|||
//
|
||||
// CBCoubNew.m
|
||||
// Coub
|
||||
//
|
||||
// Created by Tikhonenko Pavel on 18/11/2013.
|
||||
// Copyright (c) 2013 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CBCoubNew.h"
|
||||
//#import "Formatters.h"
|
||||
//#import "NSDictionary+Extensions.h"
|
||||
#import "CBLibrary.h"
|
||||
#import "CBJSONCoubMapper.h"
|
||||
#import "CBConstance.h"
|
||||
|
||||
@interface CBCoubNew ()
|
||||
{
|
||||
BOOL _failedDownloadChunks;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation CBCoubNew
|
||||
|
||||
//- (void)setNaturalVideoSize:(CGSize)size
|
||||
//{
|
||||
// _naturalVideoSize = size;
|
||||
//}
|
||||
|
||||
- (BOOL)isDraft
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
//- (NSDate *)creationDate
|
||||
//{
|
||||
// if(!_creationDate)
|
||||
// _creationDate = [[NSDateFormatter sharedCoubJSONDateFormatter] dateFromString:_creationDateAsString];
|
||||
//
|
||||
// return _creationDate;
|
||||
//}
|
||||
|
||||
//- (NSDate *)originalCreationDate
|
||||
//{
|
||||
// if(!_originalCreationDate)
|
||||
// _originalCreationDate = [[NSDateFormatter sharedCoubJSONDateFormatter] dateFromString:_originalCreationDateAsString];
|
||||
//
|
||||
// return _originalCreationDate;
|
||||
//}
|
||||
|
||||
- (NSURL *)mediumImageURL
|
||||
{
|
||||
NSString *remoteImageFilePath = self.mediumPicture;
|
||||
if(remoteImageFilePath)
|
||||
return [NSURL URLWithString:[remoteImageFilePath hasPrefix:@"http://"] ? remoteImageFilePath : [[@"http://" stringByAppendingString:kCBServerURL] stringByAppendingPathComponent:remoteImageFilePath]];
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
- (NSURL *)largeImageURL
|
||||
{
|
||||
NSString *remoteImageFilePath = self.largePicture;
|
||||
if(remoteImageFilePath)
|
||||
return [NSURL URLWithString:[remoteImageFilePath hasPrefix:@"http://"] ? remoteImageFilePath : [[@"http://" stringByAppendingString:kCBServerURL] stringByAppendingPathComponent:remoteImageFilePath]];
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)isRecoub
|
||||
{
|
||||
return _recouber != nil ? YES : NO;
|
||||
}
|
||||
|
||||
//- (CBCoubStatusFlags)statusFlags
|
||||
//{
|
||||
// CBCoubStatusFlags statusFlags = 0;
|
||||
//
|
||||
// if([_visibility isEqualToString:kCBCoubVisibilityFriends])
|
||||
// statusFlags |= CBCoubStatusFriendsOnly;
|
||||
// else if([_visibility isEqualToString:kCBCoubVisibilityPrivate])
|
||||
// statusFlags |= CBCoubStatusPrivate;
|
||||
// else if([_visibility isEqualToString:kCBCoubVisibilityUnlisted])
|
||||
// statusFlags |= CBCoubStatusUnlisted;
|
||||
// switch(_audioType)
|
||||
// {
|
||||
// case CBCoubAudioTypeExternal:
|
||||
// statusFlags |= CBCoubStatusExternalAudio;
|
||||
// break;
|
||||
// case CBCoubAudioTypeInternal:
|
||||
// statusFlags |= CBCoubStatusHasAudioTrack;
|
||||
// break;
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// return statusFlags;
|
||||
//}
|
||||
|
||||
- (NSURL *)remoteVideoFileURL
|
||||
{
|
||||
NSURL *url = nil;
|
||||
|
||||
NSString *remoteFilePath = self.remoteVideoLocation;
|
||||
if([remoteFilePath isKindOfClass:[NSString class]] && remoteFilePath.length > 0)
|
||||
{
|
||||
url = [NSURL URLWithString:[remoteFilePath hasPrefix:@"http://"] ? remoteFilePath : [[@"http://" stringByAppendingString:kCBServerURL] stringByAppendingPathComponent:remoteFilePath]];
|
||||
if(!url)
|
||||
[NSException raise:NSInternalInconsistencyException format:@"Could not make a URL from \"%@\"", remoteFilePath];
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
- (NSURL *)externalAudioURL
|
||||
{
|
||||
NSURL *url = nil;
|
||||
|
||||
NSString *remoteFilePath = self.remoteAudioLocation;
|
||||
if([remoteFilePath isKindOfClass:[NSString class]] && remoteFilePath.length > 0)
|
||||
{
|
||||
url = [NSURL URLWithString:[remoteFilePath hasPrefix:@"http://"] ? remoteFilePath : [[@"http://" stringByAppendingString:kCBServerURL] stringByAppendingPathComponent:remoteFilePath]];
|
||||
if(!url)
|
||||
[NSException raise:NSInternalInconsistencyException format:@"Could not make a URL from \"%@\"", remoteFilePath];
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
- (NSURL *)localVideoFileURL
|
||||
{
|
||||
if (self.customLocalVideoFileURL != nil)
|
||||
return self.customLocalVideoFileURL;
|
||||
|
||||
if(self.permalink == nil)
|
||||
return nil;
|
||||
|
||||
|
||||
NSString *fileNameExtension = [self.remoteVideoLocation pathExtension] ?: @"mp4";
|
||||
NSString *localFileName = [[@"coub " stringByAppendingString:self.permalink] stringByAppendingPathExtension:fileNameExtension];
|
||||
NSString *path = [[CBLibrary sharedLibrary].mediaDirectory.path stringByAppendingPathComponent:localFileName];
|
||||
return [NSURL fileURLWithPath:path isDirectory:NO];
|
||||
}
|
||||
|
||||
- (NSURL *)localAudioFileURL
|
||||
{
|
||||
if(self.permalink == nil)
|
||||
return nil;
|
||||
|
||||
NSString *fileNameExtension = @"m4a";
|
||||
NSString *localFileName = [[@"coub " stringByAppendingString:self.permalink] stringByAppendingPathExtension:fileNameExtension];
|
||||
return [NSURL fileURLWithPath:[[CBLibrary sharedLibrary].mediaDirectory.path stringByAppendingPathComponent:localFileName] isDirectory:NO];
|
||||
}
|
||||
|
||||
- (NSString *)assetId
|
||||
{
|
||||
return self.permalink;
|
||||
}
|
||||
|
||||
- (BOOL)isAudioAvailable
|
||||
{
|
||||
return (_audioType != CBCoubAudioTypeNone && !_failedDownloadChunks);
|
||||
}
|
||||
|
||||
- (BOOL)isEqualToCoub:(CBCoubNew *)coub
|
||||
{
|
||||
return [coub.permalink isEqualToString:self.permalink];
|
||||
}
|
||||
|
||||
+ (CBCoubNew *)coubWithAttributes:(NSDictionary *)attributes
|
||||
{
|
||||
return [CBJSONCoubMapper coubFromJSONObject:attributes];
|
||||
}
|
||||
|
||||
- (NSURL *)remoteAudioChunkWithIdx:(NSInteger)idx
|
||||
{
|
||||
//NSLog(@"remoteAudioChunkWithIdx = %@ ", [NSURL URLWithString:[NSString stringWithFormat:_remoteAudioLocationPattern, idx]]);
|
||||
|
||||
//NOTE: sometimes _remoteAudioLocationPattern
|
||||
return [NSURL URLWithString:[NSString stringWithFormat:_remoteAudioLocationPattern, idx]];
|
||||
}
|
||||
|
||||
- (NSURL *)localAudioChunkWithIdx:(NSInteger)idx
|
||||
{
|
||||
//NSLog(@"localAudioChunkWithIdx = %i", idx);
|
||||
|
||||
NSString *fileNameExtension = @"mp3";
|
||||
NSString *fileName = [[NSString stringWithFormat:@"coub mp3 chunk %i ", idx] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
|
||||
NSString *localFileName = [[fileName stringByAppendingString:self.permalink] stringByAppendingPathExtension:fileNameExtension];
|
||||
|
||||
return [NSURL fileURLWithPath:[[CBLibrary sharedLibrary].mediaDirectory.path stringByAppendingPathComponent:localFileName] isDirectory:NO];
|
||||
}
|
||||
|
||||
- (BOOL)failedDownloadChunk
|
||||
{
|
||||
return _failedDownloadChunks;
|
||||
}
|
||||
|
||||
- (void)setFailedDownloadChunk:(BOOL)failed
|
||||
{
|
||||
_failedDownloadChunks = failed;
|
||||
}
|
||||
|
||||
@end
|
||||
96
submodules/LegacyComponents/LegacyComponents/CBCoubPlayer.h
Executable file
96
submodules/LegacyComponents/LegacyComponents/CBCoubPlayer.h
Executable file
|
|
@ -0,0 +1,96 @@
|
|||
//
|
||||
// CBCoubPlayer.h
|
||||
// Coub
|
||||
//
|
||||
// Created by Pavel Tikhonenko on 12/08/14.
|
||||
// Copyright (c) 2014 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class CBCoubPlayer;
|
||||
@protocol CBCoubAsset;
|
||||
@class AVPlayerLayer;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
CBCoubPlayerStateReadyToPlay,
|
||||
CBCoubPlayerStateRunning = 1,
|
||||
CBCoubPlayerStatePlaying = (1 << 1) | CBCoubPlayerStateRunning,
|
||||
CBCoubPlayerStatePaused = (1 << 2) | CBCoubPlayerStatePlaying,
|
||||
CBCoubPlayerStateInterrupted = (1 << 3) | CBCoubPlayerStatePlaying,
|
||||
CBCoubPlayerStateStopped = (1 << 4),
|
||||
CBCoubPlayerStatePseudoStopped = (1 << 5) | CBCoubPlayerStateStopped,
|
||||
CBCoubPlayerStateError = (1 << 6) | CBCoubPlayerStateStopped,
|
||||
CBCoubPlayerStatePrepairing = (1 << 7) | CBCoubPlayerStateRunning,
|
||||
CBCoubPlayerStateUnknown = NSNotFound,
|
||||
}
|
||||
CBCoubPlayerState;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
CBCoubPlayerVideoPlayMethodDefault,
|
||||
CBCoubPlayerVideoPlayMethodStream
|
||||
}
|
||||
CBCoubPlayerVideoPlayMethod;
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark CBCoubPlayerDelegate
|
||||
|
||||
@protocol CBCoubPlayerDelegate <NSObject>
|
||||
@required
|
||||
- (void)playerReadyToPlay:(CBCoubPlayer *)player;
|
||||
- (void)playerDidStartPlaying:(CBCoubPlayer *)player;
|
||||
- (void)playerDidPause:(CBCoubPlayer *)player withUserAction:(BOOL)isUserAction;
|
||||
- (void)playerDidResume:(CBCoubPlayer *)player;
|
||||
- (void)playerDidStop:(CBCoubPlayer *)player;
|
||||
- (void)playerDidFail:(CBCoubPlayer *)player error:(NSError *)error;
|
||||
- (void)player:(CBCoubPlayer *)player didReachProgressWhileDownloading:(float)progress;
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark CBCoubPlayer
|
||||
|
||||
@interface CBCoubPlayer : NSObject
|
||||
|
||||
@property (nonatomic, weak) id<CBCoubPlayerDelegate> delegate;
|
||||
|
||||
/// indicate whether player is playing
|
||||
@property(nonatomic, readonly) BOOL isPlaying;
|
||||
/// indicate whether player was paused
|
||||
@property(nonatomic, readonly) BOOL isPaused;
|
||||
/// indicate whether player was interrupted by system
|
||||
@property(nonatomic, readonly) BOOL isInterrupted;
|
||||
/// If Yes then this instance is current/active player
|
||||
@property(nonatomic, readonly) BOOL isActivePlayer;
|
||||
/// set/get current player state
|
||||
@property(nonatomic, assign) CBCoubPlayerState state;
|
||||
|
||||
/// set/get whether player playback only video
|
||||
@property(nonatomic, assign) BOOL withoutAudio; //Default NO
|
||||
|
||||
//Temp later move back to private var
|
||||
@property(nonatomic, strong) id<CBCoubAsset> asset;
|
||||
|
||||
@property(nonatomic, assign) CBCoubPlayerVideoPlayMethod videoPlayMethod;
|
||||
|
||||
|
||||
|
||||
- (instancetype)initWithVideoLayer:(AVPlayerLayer *)layer;
|
||||
|
||||
- (void)playAsset:(id<CBCoubAsset>)asset;
|
||||
- (void)pause;
|
||||
- (void)resume;
|
||||
- (void)stop;
|
||||
|
||||
// pause video and mute audio
|
||||
- (void)pseudoStop;
|
||||
|
||||
- (BOOL)isVideoLayerPlaying:(AVPlayerLayer *)layer;
|
||||
- (BOOL)coubPlaying:(id<CBCoubAsset>)coub;
|
||||
- (BOOL)coubPrepairing:(id<CBCoubAsset>)coub;
|
||||
|
||||
+ (instancetype)activePlayer;
|
||||
+ (instancetype)setActivePlayer:(CBCoubPlayer *)player;
|
||||
|
||||
@end
|
||||
585
submodules/LegacyComponents/LegacyComponents/CBCoubPlayer.m
Executable file
585
submodules/LegacyComponents/LegacyComponents/CBCoubPlayer.m
Executable file
|
|
@ -0,0 +1,585 @@
|
|||
//
|
||||
// CBCoubPlayer.m
|
||||
// Coub
|
||||
//
|
||||
// Created by Pavel Tikhonenko on 12/08/14.
|
||||
// Copyright (c) 2014 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import "CBCoubPlayer.h"
|
||||
#import "CBCoubAsset.h"
|
||||
#import "CBVideoPlayer.h"
|
||||
#import "CBCoubLoopCompositionMaker.h"
|
||||
#import "CBAssetDownloadManager.h"
|
||||
#import "STKAudioPlayer.h"
|
||||
#import "CBLibrary.h"
|
||||
#import "CBConstance.h"
|
||||
#import "CBDownloadOperationDelegate.h"
|
||||
|
||||
static CBCoubPlayer *gActivePlayer = nil;
|
||||
static NSLock *gLock = nil;
|
||||
static BOOL gPlaybackIsInterrupted = NO;
|
||||
|
||||
@interface CBCoubPlayer ()<CBDownloadOperationDelegate, STKAudioPlayerDelegate, CBCoubLoopDelegate>
|
||||
{
|
||||
BOOL _shouldPlayWhenReady;
|
||||
BOOL _shouldResumeWhenAppIsActive;
|
||||
BOOL _shouldResumeWhenInterruptionEnds;
|
||||
BOOL _shouldPlayAfterStop;
|
||||
NSInteger _currentPlayingChunk;
|
||||
double _startTime;
|
||||
}
|
||||
|
||||
|
||||
@property(nonatomic, strong) CBVideoPlayer *videoPlayer;
|
||||
@property(nonatomic, strong) STKAudioPlayer *audioPlayer;
|
||||
@property(nonatomic, strong) CBCoubLoopCompositionMaker *loopMaker;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@implementation CBCoubPlayer
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if(!gLock)
|
||||
{
|
||||
NSError *categoryError = NULL;
|
||||
NSError *activeError = NULL;
|
||||
|
||||
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&categoryError];
|
||||
[[AVAudioSession sharedInstance] setActive:YES error:&activeError];
|
||||
|
||||
Float32 bufferLength = 0.1;
|
||||
AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareIOBufferDuration, sizeof(bufferLength), &bufferLength);
|
||||
|
||||
gLock = [[NSLock alloc] init];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pauseActivePlayer:) name:UIApplicationWillResignActiveNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resumeActivePlayer:) name:UIApplicationDidBecomeActiveNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pauseActivePlayer:) name:CBPlayerInterruptionDidBeginNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resumeActivePlayer:) name:CBPlayerInterruptionDidEndNotification object:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (instancetype)initWithVideoLayer:(AVPlayerLayer *)layer
|
||||
{
|
||||
return [self initWithVideoLayer:layer videoPlayMethod:CBCoubPlayerVideoPlayMethodDefault];
|
||||
}
|
||||
|
||||
- (instancetype)initWithVideoLayer:(AVPlayerLayer *)layer videoPlayMethod:(CBCoubPlayerVideoPlayMethod)videoPlayMethod
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if(self)
|
||||
{
|
||||
_currentPlayingChunk = -1;
|
||||
_videoPlayMethod = videoPlayMethod;
|
||||
_state = CBCoubPlayerStateUnknown;
|
||||
|
||||
[self createVideoPlayerWithLayer:layer];
|
||||
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Getter/Setter
|
||||
|
||||
- (BOOL)isPlaying
|
||||
{
|
||||
return _state == CBCoubPlayerStatePlaying;
|
||||
}
|
||||
|
||||
- (BOOL)isPaused
|
||||
{
|
||||
return _state == CBCoubPlayerStatePaused;
|
||||
}
|
||||
|
||||
- (BOOL)isActivePlayer
|
||||
{
|
||||
return (gActivePlayer == self);
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Public methods
|
||||
|
||||
- (void)playAsset:(id<CBCoubAsset>)asset
|
||||
{
|
||||
NSLog(@"playAsset");
|
||||
|
||||
if(_state == CBCoubPlayerStateRunning && self.asset == asset)
|
||||
return;
|
||||
|
||||
if(gActivePlayer == self && self.asset == asset && gActivePlayer.state == CBCoubPlayerStatePlaying)
|
||||
return;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"interruptDownloading" object:nil];
|
||||
|
||||
//_startTime = [CBUtils timestamp];
|
||||
|
||||
if(gActivePlayer == self)
|
||||
{
|
||||
[_videoPlayer stop];
|
||||
[_audioPlayer dispose];
|
||||
}else{
|
||||
[self stopActivePlayer];
|
||||
[self becomeActivePlayer];
|
||||
}
|
||||
|
||||
gPlaybackIsInterrupted = NO; //NOTE: note note note
|
||||
_shouldPlayWhenReady = YES;
|
||||
self.state = CBCoubPlayerStateRunning;
|
||||
|
||||
self.asset = asset;
|
||||
|
||||
[self downloadMediaAssetsWithCompletion:^(__unused id result) {
|
||||
[self prepareLoopCompostion];
|
||||
}failure:^(NSError *error) {
|
||||
[self failPlaybackWithError:error];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)pause
|
||||
{
|
||||
_shouldPlayWhenReady = NO;
|
||||
|
||||
if(!self.isPlaying)
|
||||
return;
|
||||
|
||||
self.state = CBCoubPlayerStatePaused;
|
||||
|
||||
[_videoPlayer pause];
|
||||
|
||||
if(_asset.audioType == CBCoubAudioTypeExternal)
|
||||
[_audioPlayer pause];
|
||||
|
||||
if([_delegate respondsToSelector:@selector(playerDidPause:withUserAction:)])
|
||||
[_delegate playerDidPause:self withUserAction:YES];
|
||||
}
|
||||
|
||||
- (void)resume
|
||||
{
|
||||
_shouldPlayWhenReady = YES;
|
||||
|
||||
// if(_state == CBCoubPlayerStateReadyToPlay)
|
||||
// {
|
||||
// [self startPlayingIfPossible];
|
||||
// }
|
||||
if(self.state == CBCoubPlayerStatePaused){
|
||||
self.state = STKAudioPlayerStatePlaying;
|
||||
|
||||
[_videoPlayer play];
|
||||
|
||||
if(_asset.audioType == CBCoubAudioTypeExternal)
|
||||
[_audioPlayer resume];
|
||||
}
|
||||
|
||||
if([_delegate respondsToSelector:@selector(playerDidResume:)])
|
||||
[_delegate playerDidResume:self];
|
||||
}
|
||||
|
||||
- (void)stop
|
||||
{
|
||||
_shouldPlayWhenReady = NO;
|
||||
|
||||
if(self.state == CBCoubPlayerStateStopped || self.state == NSNotFound)
|
||||
return;
|
||||
|
||||
//KAObjectLog(@"stop player");
|
||||
|
||||
//[[CBAssetDownloadManager sharedManager] cancelDownloadingForCoub:_asset];
|
||||
|
||||
if([_delegate respondsToSelector:@selector(playerDidStop:)])
|
||||
[_delegate playerDidStop:self];
|
||||
|
||||
// [_audioPlayer pause];
|
||||
if(_asset.audioType == CBCoubAudioTypeExternal)
|
||||
{
|
||||
_audioPlayer.delegate = nil;
|
||||
[_audioPlayer dispose];
|
||||
_audioPlayer = nil;
|
||||
}
|
||||
|
||||
[_videoPlayer stop];
|
||||
|
||||
// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
//
|
||||
// });
|
||||
|
||||
self.state = CBCoubPlayerStateStopped;
|
||||
|
||||
[self resignActivePlayer];
|
||||
}
|
||||
|
||||
- (void)pseudoStop
|
||||
{
|
||||
_shouldPlayWhenReady = NO;
|
||||
|
||||
if(self.isPlaying)
|
||||
{
|
||||
if(_asset.audioType == CBCoubAudioTypeExternal)
|
||||
[_audioPlayer pause];
|
||||
else
|
||||
[_videoPlayer pause];
|
||||
}
|
||||
|
||||
self.state = CBCoubPlayerStatePseudoStopped;
|
||||
}
|
||||
|
||||
- (void)stopActivePlayer
|
||||
{
|
||||
[gActivePlayer stop];
|
||||
}
|
||||
|
||||
- (void)resetCurrentPlayer
|
||||
{
|
||||
self.state = NSNotFound;
|
||||
_shouldPlayWhenReady = YES;
|
||||
_currentPlayingChunk = -1;
|
||||
[self.loopMaker cancelPrepareLoop];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Private methods
|
||||
|
||||
- (void)createVideoPlayerWithLayer:(AVPlayerLayer *)layer
|
||||
{
|
||||
self.videoPlayer = [[CBVideoPlayer alloc] initWithVideoLayer:layer];
|
||||
}
|
||||
|
||||
- (void)prepareVideoPlayer
|
||||
{
|
||||
[_videoPlayer prepareWithAVAsset:self.loopMaker.videoAsset completion:^(NSError *error) {
|
||||
if(error)
|
||||
[self failPlaybackWithError:error];
|
||||
else{
|
||||
if([_delegate respondsToSelector:@selector(playerReadyToPlay:)])
|
||||
[_delegate playerReadyToPlay:self];
|
||||
|
||||
[self startPlayingIfPossible];
|
||||
}
|
||||
}];
|
||||
|
||||
[self.loopMaker cancelPrepareLoop];
|
||||
}
|
||||
|
||||
- (void)playVideoPlayer
|
||||
{
|
||||
[_videoPlayer play];
|
||||
|
||||
if(_shouldPlayWhenReady == NO)
|
||||
[self pause];
|
||||
}
|
||||
|
||||
- (void)createAudioPlayer
|
||||
{
|
||||
self.audioPlayer = [[STKAudioPlayer alloc] init];
|
||||
_audioPlayer.delegate = self;
|
||||
}
|
||||
|
||||
- (void)prepareAudioPlayer
|
||||
{
|
||||
_currentPlayingChunk = -1;
|
||||
|
||||
// STKAudioPlayerState state = _audioPlayer.state;
|
||||
|
||||
[self startPlayingFirstAudioChunk];
|
||||
}
|
||||
|
||||
- (void)startPlayingFirstAudioChunk
|
||||
{
|
||||
// NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
// NSDictionary *attributes = [fileManager attributesOfItemAtPath:[_asset localAudioChunkWithIdx:0].path error:nil];
|
||||
|
||||
_shouldPlayAfterStop = NO;
|
||||
|
||||
NSURL * url = [_asset localAudioChunkWithIdx:0];
|
||||
|
||||
if (url) {
|
||||
[_audioPlayer playURL:url];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)startPlayingIfPossible
|
||||
{
|
||||
if(self.state == CBCoubPlayerStatePlaying)
|
||||
return;
|
||||
|
||||
self.state = CBCoubPlayerStateReadyToPlay;
|
||||
|
||||
if(gPlaybackIsInterrupted && _shouldPlayWhenReady)
|
||||
[self pauseWhileInterrupted];
|
||||
|
||||
if(!_shouldPlayWhenReady)
|
||||
return;
|
||||
|
||||
if(!_withoutAudio && _asset.audioType == CBCoubAudioTypeExternal && _audioPlayer.state != STKAudioPlayerStatePlaying)
|
||||
{
|
||||
[self createAudioPlayer];
|
||||
[self prepareAudioPlayer];
|
||||
return;
|
||||
}
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"resumeDownloading" object:nil];
|
||||
|
||||
self.state = CBCoubPlayerStatePlaying;
|
||||
|
||||
if([_delegate respondsToSelector:@selector(playerDidStartPlaying:)])
|
||||
[_delegate playerDidStartPlaying:self];
|
||||
|
||||
|
||||
[self playVideoPlayer];
|
||||
|
||||
// NSNumber *number = [NSNumber numberWithDouble:[CBUtils timestamp] - _startTime];
|
||||
// NSString *curPlace = [CBAnalyticManager sharedManager].currentPlayerPlace;
|
||||
// NSString *assetId = _asset.assetId;
|
||||
//
|
||||
// NSDictionary *dict = @{@"loading_time":number,
|
||||
// @"coubId":assetId,
|
||||
// @"place": curPlace};
|
||||
//
|
||||
// [[CBAnalyticManager sharedManager] track:@"player_started"
|
||||
// properties:dict
|
||||
// method:CBAnalyticManagerMethodCES];
|
||||
}
|
||||
|
||||
- (void)downloadMediaAssetsWithCompletion:(CBSuccessBlock)success failure:(CBFailureBlock)failure
|
||||
{
|
||||
[[CBAssetDownloadManager sharedManager] downloadCoub:_asset tag:-1
|
||||
withDelegate:self
|
||||
withChunks:!_withoutAudio
|
||||
downloadSucces:^(__unused id<CBCoubAsset> coub, __unused NSInteger tag) {
|
||||
success(nil);
|
||||
}downloadFailure:^(__unused id<CBCoubAsset> coub, __unused NSInteger tag, NSError *error) {
|
||||
failure(error);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)downloadChunk:(NSInteger)idx
|
||||
{
|
||||
if(_asset.audioType != CBCoubAudioTypeExternal)
|
||||
return;
|
||||
|
||||
[[CBAssetDownloadManager sharedManager] downloadChunkWithCoub:_asset tag:NSNotFound chunkIdx:idx downloadSucces:^(id<CBCoubAsset> __unused coub, NSInteger __unused tag) {
|
||||
[_audioPlayer queueURL:[_asset localAudioChunkWithIdx:idx]];
|
||||
} downloadFailure:^(__unused id<CBCoubAsset> coub, __unused NSInteger tag, __unused NSError *error) {
|
||||
|
||||
}];
|
||||
|
||||
// [[CBAssetDownloadManager sharedManager] downloadNextChunkWithCoub:_asset downloadSucces:^(id<CBDownloadAsset> coub, NSInteger tag) {
|
||||
// [_audioPlayer queueURL:[_asset localAudioChunkWithIdx:tag]];
|
||||
// } downloadFailure:^(id<CBDownloadAsset> coub, NSInteger tag, NSError *error) {
|
||||
//
|
||||
// }];
|
||||
}
|
||||
|
||||
- (void)prepareLoopCompostion
|
||||
{
|
||||
if(self.state == CBCoubPlayerStateStopped)
|
||||
return;
|
||||
|
||||
self.loopMaker = [CBCoubLoopCompositionMaker coubLoopWithAsset:_asset];
|
||||
_loopMaker.delegate = self;
|
||||
[_loopMaker prepareLoop];
|
||||
}
|
||||
|
||||
- (void)failPlaybackWithError:(NSError *)error
|
||||
{
|
||||
if(self.state == CBCoubPlayerStateStopped)
|
||||
return;
|
||||
|
||||
self.state = CBCoubPlayerStateError;
|
||||
|
||||
if([_delegate respondsToSelector:@selector(playerDidFail:error:)])
|
||||
[_delegate playerDidFail:self error:error];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)becomeActivePlayer
|
||||
{
|
||||
[gLock lock];
|
||||
if(gActivePlayer != self)
|
||||
{
|
||||
//[gActivePlayer pause];
|
||||
gActivePlayer = self;
|
||||
}
|
||||
[gLock unlock];
|
||||
}
|
||||
|
||||
|
||||
- (void)resignActivePlayer
|
||||
{
|
||||
[gLock lock];
|
||||
if(gActivePlayer == self && self.isPlaying == NO)
|
||||
gActivePlayer = nil;
|
||||
[gLock unlock];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Audio Player Delegate methods
|
||||
|
||||
- (void)audioPlayer:(STKAudioPlayer *)audioPlayer stateChanged:(STKAudioPlayerState)state previousState:(STKAudioPlayerState)previousState
|
||||
{
|
||||
if(state == STKAudioPlayerStateStopped && _shouldPlayAfterStop)
|
||||
{
|
||||
[self startPlayingFirstAudioChunk];
|
||||
return;
|
||||
}
|
||||
|
||||
if(state == STKAudioPlayerStatePlaying && self.state != CBCoubPlayerStatePlaying)
|
||||
{
|
||||
//KAObjectLog(@"STKAudioPlayerStatePlaying");
|
||||
|
||||
[self startPlayingIfPossible];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)audioPlayer:(STKAudioPlayer *)audioPlayer unexpectedError:(STKAudioPlayerErrorCode)errorCode
|
||||
{
|
||||
[self failPlaybackWithError:nil];
|
||||
}
|
||||
|
||||
- (void)audioPlayer:(STKAudioPlayer *)audioPlayer didStartPlayingQueueItemId:(NSObject *)queueItemId
|
||||
{
|
||||
if(_currentPlayingChunk == 3)
|
||||
_currentPlayingChunk = -1;
|
||||
|
||||
_currentPlayingChunk++;
|
||||
|
||||
NSInteger nextItem = _currentPlayingChunk+1;
|
||||
nextItem = nextItem > 3 ? 0 : nextItem;
|
||||
|
||||
if([[CBLibrary sharedLibrary] isCoubChunkDownloadedByPermalink:_asset.assetId idx:nextItem])
|
||||
[_audioPlayer queueURL:[_asset localAudioChunkWithIdx:nextItem]];
|
||||
else
|
||||
[self downloadChunk:nextItem];
|
||||
}
|
||||
|
||||
- (void)audioPlayer:(STKAudioPlayer *)audioPlayer didFinishBufferingSourceWithQueueItemId:(NSObject *)queueItemId
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
- (void)audioPlayer:(STKAudioPlayer *)audioPlayer didFinishPlayingQueueItemId:(NSObject *)queueItemId withReason:(STKAudioPlayerStopReason)stopReason andProgress:(double)progress andDuration:(double)duration
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
- (void)audioPlayer:(STKAudioPlayer *)audioPlayer logInfo:(NSString *)line
|
||||
{
|
||||
//KAObjectLog(@"%@", line);
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Download Manager Delegate methods
|
||||
|
||||
- (void)downloadDidReachProgress:(float)progress
|
||||
{
|
||||
if([_delegate respondsToSelector:@selector(player:didReachProgressWhileDownloading:)])
|
||||
[_delegate player:self didReachProgressWhileDownloading:progress];
|
||||
}
|
||||
|
||||
- (void)downloadHasBeenCancelledWithError:(NSError *)error
|
||||
{
|
||||
if(error == nil)
|
||||
return;
|
||||
|
||||
[self failPlaybackWithError:error];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Loop Composition Maker Delegate methods
|
||||
|
||||
- (void)coubLoopDidFinishPreparing:(CBCoubLoopCompositionMaker *)loop
|
||||
{
|
||||
[self prepareVideoPlayer];
|
||||
}
|
||||
|
||||
- (void)coubLoop:(CBCoubLoopCompositionMaker *)loop didFailToLoadWithError:(NSError *)error
|
||||
{
|
||||
if(error.code == CBCoubLoopErrorCanceled)
|
||||
return;
|
||||
|
||||
[self failPlaybackWithError:error];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
+ (void)pauseActivePlayer:(NSNotification *)notification
|
||||
{
|
||||
//KAObjectLog(@"pauseActivePlayer %p", gActivePlayer);
|
||||
|
||||
if([[notification name] isEqualToString:UIApplicationWillResignActiveNotification])
|
||||
{
|
||||
[gActivePlayer pauseWhileInBackground];
|
||||
}else
|
||||
{
|
||||
gPlaybackIsInterrupted = YES;
|
||||
[gActivePlayer pauseWhileInterrupted];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+ (void)resumeActivePlayer:(NSNotification *)notification
|
||||
{
|
||||
//KAObjectLog(@"resumeActivePlayer %p", gActivePlayer);
|
||||
|
||||
NSLog(@"resumeActivePlayer");
|
||||
|
||||
if([[notification name] isEqualToString:UIApplicationDidBecomeActiveNotification])
|
||||
{
|
||||
[gActivePlayer resumeIfPausedWhileInBackground];
|
||||
}else
|
||||
{
|
||||
gPlaybackIsInterrupted = NO;
|
||||
[gActivePlayer resumeIfPausedWhileInterrupted];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)pauseWhileInBackground
|
||||
{
|
||||
_shouldResumeWhenAppIsActive = self.isPlaying || _shouldPlayWhenReady;
|
||||
[self pause];
|
||||
}
|
||||
|
||||
|
||||
- (void)resumeIfPausedWhileInBackground
|
||||
{
|
||||
NSLog(@"resumeIfPausedWhileInBackground");
|
||||
|
||||
if(_shouldResumeWhenAppIsActive)
|
||||
[self resume];
|
||||
}
|
||||
|
||||
|
||||
- (void)pauseWhileInterrupted
|
||||
{
|
||||
_shouldResumeWhenInterruptionEnds = self.isPlaying || _shouldPlayWhenReady;
|
||||
[self pause];
|
||||
}
|
||||
|
||||
|
||||
- (void)resumeIfPausedWhileInterrupted
|
||||
{
|
||||
if(_shouldResumeWhenInterruptionEnds)
|
||||
[self resume];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
+ (instancetype)activePlayer
|
||||
{
|
||||
return gActivePlayer;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
_audioPlayer.delegate = nil;
|
||||
[_audioPlayer dispose];
|
||||
}
|
||||
|
||||
@end
|
||||
20
submodules/LegacyComponents/LegacyComponents/CBCoubPlayerContance.h
Executable file
20
submodules/LegacyComponents/LegacyComponents/CBCoubPlayerContance.h
Executable file
|
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// CBCoubPlayerContance.h
|
||||
// CoubPlayer
|
||||
//
|
||||
// Created by Pavel Tikhonenko on 19/10/14.
|
||||
// Copyright (c) 2014 Pavel Tikhonenko. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef NS_ENUM(UInt16, CBCoubAudioType)
|
||||
{
|
||||
CBCoubAudioTypeNone = 0,
|
||||
CBCoubAudioTypeInternal,
|
||||
CBCoubAudioTypeExternal,
|
||||
};
|
||||
|
||||
@interface CBCoubPlayerContance : NSObject
|
||||
|
||||
@end
|
||||
13
submodules/LegacyComponents/LegacyComponents/CBCoubPlayerContance.m
Executable file
13
submodules/LegacyComponents/LegacyComponents/CBCoubPlayerContance.m
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
//
|
||||
// CBCoubPlayerContance.m
|
||||
// CoubPlayer
|
||||
//
|
||||
// Created by Pavel Tikhonenko on 19/10/14.
|
||||
// Copyright (c) 2014 Pavel Tikhonenko. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CBCoubPlayerContance.h"
|
||||
|
||||
@implementation CBCoubPlayerContance
|
||||
|
||||
@end
|
||||
20
submodules/LegacyComponents/LegacyComponents/CBCoubVideoSource.h
Executable file
20
submodules/LegacyComponents/LegacyComponents/CBCoubVideoSource.h
Executable file
|
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// CBCoubVideoSource.h
|
||||
// Coub
|
||||
//
|
||||
// Created by Tikhonenko Pavel on 18/11/2013.
|
||||
// Copyright (c) 2013 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface CBCoubVideoSource : NSObject
|
||||
|
||||
@property (nonatomic, strong) NSString *yourtubeURL;
|
||||
@property (nonatomic, strong) NSString *title;
|
||||
@property (nonatomic, strong) NSString *thumbnail;
|
||||
@property (nonatomic, readonly) BOOL isYouTube;
|
||||
|
||||
+ (CBCoubVideoSource *)sourceFromData:(NSDictionary *)dict;
|
||||
|
||||
@end
|
||||
33
submodules/LegacyComponents/LegacyComponents/CBCoubVideoSource.m
Executable file
33
submodules/LegacyComponents/LegacyComponents/CBCoubVideoSource.m
Executable file
|
|
@ -0,0 +1,33 @@
|
|||
//
|
||||
// CBCoubVideoSource.m
|
||||
// Coub
|
||||
//
|
||||
// Created by Tikhonenko Pavel on 18/11/2013.
|
||||
// Copyright (c) 2013 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CBCoubVideoSource.h"
|
||||
|
||||
@implementation CBCoubVideoSource
|
||||
|
||||
- (BOOL)isYouTube
|
||||
{
|
||||
return [_yourtubeURL rangeOfString:@"youtube"].length != 0;
|
||||
}
|
||||
|
||||
+ (CBCoubVideoSource *)sourceFromData:(NSDictionary *)dict
|
||||
{
|
||||
if(!dict.count) return nil;
|
||||
|
||||
CBCoubVideoSource *source = [[CBCoubVideoSource alloc] init];
|
||||
source.yourtubeURL = dict[@"url"];
|
||||
source.title = dict[@"title"];
|
||||
source.thumbnail = dict[@"image"];
|
||||
|
||||
if(source.yourtubeURL == nil)
|
||||
return nil;
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
@end
|
||||
37
submodules/LegacyComponents/LegacyComponents/CBDownloadOperation.h
Executable file
37
submodules/LegacyComponents/LegacyComponents/CBDownloadOperation.h
Executable file
|
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// CBDownloadOperation.h
|
||||
// CoubPlayer
|
||||
//
|
||||
// Created by Pavel Tikhonenko on 19/10/14.
|
||||
// Copyright (c) 2014 Pavel Tikhonenko. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
@protocol CBCoubAsset;
|
||||
@protocol CBDownloadOperationDelegate;
|
||||
|
||||
@protocol CBDownloadOperation<NSObject>
|
||||
|
||||
@required
|
||||
- (void)setTag:(NSInteger)tag;
|
||||
- (NSInteger)tag;
|
||||
|
||||
- (void)setCoub:(id<CBCoubAsset>)coub;
|
||||
- (id<CBCoubAsset>)coub;
|
||||
|
||||
- (void)setQueuePriority:(NSOperationQueuePriority)queuePriority;
|
||||
- (NSOperationQueuePriority)queuePriority;
|
||||
|
||||
//- (void)setDelegate:(id<CBDownloadOperationDelegate>)delegate;
|
||||
|
||||
- (void)setClientSuccess:(void (^)(id<CBCoubAsset> operation, NSInteger tag))success;
|
||||
- (void)setClientFailure:(void (^)(id<CBCoubAsset> operation, NSInteger tag, NSError *error))failure;
|
||||
|
||||
- (void)setCompletionBlock:(void (^)(id<CBCoubAsset> operation, NSError *error))completion;
|
||||
|
||||
- (void)start;
|
||||
- (void)cancel;
|
||||
|
||||
- (instancetype)clone;
|
||||
|
||||
@end
|
||||
17
submodules/LegacyComponents/LegacyComponents/CBDownloadOperationDelegate.h
Executable file
17
submodules/LegacyComponents/LegacyComponents/CBDownloadOperationDelegate.h
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
//
|
||||
// CBDownloadOperationDelegate.h
|
||||
// CoubPlayer
|
||||
//
|
||||
// Created by Pavel Tikhonenko on 19/10/14.
|
||||
// Copyright (c) 2014 Pavel Tikhonenko. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@protocol CBDownloadOperationDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
- (void)downloadDidReachProgress:(float)progress;
|
||||
- (void)downloadHasBeenCancelledWithError:(NSError *)error;
|
||||
|
||||
@end
|
||||
28
submodules/LegacyComponents/LegacyComponents/CBGenericDownloadOperation.h
Executable file
28
submodules/LegacyComponents/LegacyComponents/CBGenericDownloadOperation.h
Executable file
|
|
@ -0,0 +1,28 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
#import "CBDownloadOperation.h"
|
||||
#import "CBCoubAsset.h"
|
||||
#import "CBDownloadOperationDelegate.h"
|
||||
#import "LegacyHTTPRequestOperation.h"
|
||||
|
||||
@interface CBGenericDownloadOperation : NSObject <CBDownloadOperation>
|
||||
|
||||
@property (nonatomic, strong) NSOperation<LegacyHTTPRequestOperation> *downloadOperation;
|
||||
@property (nonatomic, readwrite) NSOperationQueuePriority queuePriority;
|
||||
|
||||
@property (nonatomic, assign) NSInteger tag;
|
||||
@property (nonatomic, assign) BOOL starting;
|
||||
@property (nonatomic, assign) BOOL comleted;
|
||||
@property (nonatomic, assign) BOOL chunkDownloadingNeeded;
|
||||
|
||||
@property (nonatomic, weak) id<CBCoubAsset> coub;
|
||||
@property (nonatomic, weak) id<CBDownloadOperationDelegate> operationViewDelegate;
|
||||
|
||||
@property(nonatomic, copy) void (^clientSuccess)(id<CBCoubAsset>, NSInteger tag);
|
||||
@property(nonatomic, copy) void (^clientFailure)(id<CBCoubAsset>, NSInteger tag, NSError *error);
|
||||
|
||||
@property (nonatomic, copy) void (^completionBlock)(id<CBDownloadOperation> process, NSError *error);
|
||||
|
||||
//protected
|
||||
- (void)successDownload;
|
||||
- (void)failureDownloadWithError:(NSError *)error;
|
||||
@end
|
||||
89
submodules/LegacyComponents/LegacyComponents/CBGenericDownloadOperation.m
Executable file
89
submodules/LegacyComponents/LegacyComponents/CBGenericDownloadOperation.m
Executable file
|
|
@ -0,0 +1,89 @@
|
|||
//
|
||||
// Created by Tikhonenko Pavel on 27/05/2014.
|
||||
// Copyright (c) 2014 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CBGenericDownloadOperation.h"
|
||||
#import "CBCoubAsset.h"
|
||||
|
||||
@implementation CBGenericDownloadOperation
|
||||
{
|
||||
NSOperationQueuePriority _queuePriority;
|
||||
}
|
||||
|
||||
- (NSOperationQueuePriority)queuePriority
|
||||
{
|
||||
return _queuePriority;
|
||||
}
|
||||
|
||||
- (void)setQueuePriority:(NSOperationQueuePriority)queuePriority
|
||||
{
|
||||
_queuePriority = queuePriority;
|
||||
|
||||
if(_downloadOperation)
|
||||
_downloadOperation.queuePriority = queuePriority;
|
||||
}
|
||||
|
||||
- (void)start
|
||||
{
|
||||
self.starting = YES;
|
||||
}
|
||||
|
||||
- (void)cancel
|
||||
{
|
||||
self.starting = NO;
|
||||
|
||||
NSError *cancelError = nil;
|
||||
|
||||
if (!_downloadOperation.isFinished && !_downloadOperation.isCancelled)
|
||||
{
|
||||
//cancelError = [NSError errorWithDomain:kCBAssetDownloadManagerErrorDomain code:99 userInfo:nil];
|
||||
[_downloadOperation cancel];
|
||||
}
|
||||
|
||||
[_operationViewDelegate downloadHasBeenCancelledWithError:cancelError];
|
||||
|
||||
self.clientSuccess = nil;
|
||||
self.clientFailure = nil;
|
||||
}
|
||||
|
||||
- (void)successDownload
|
||||
{
|
||||
if(self.starting)
|
||||
{
|
||||
self.starting = NO;
|
||||
self.comleted = YES;
|
||||
|
||||
if(self.operationViewDelegate != nil)[self.operationViewDelegate downloadHasBeenCancelledWithError:nil];
|
||||
if(self.clientSuccess != nil) self.clientSuccess(self.coub, self.tag);
|
||||
if(self.completionBlock != nil) self.completionBlock(self, nil);
|
||||
|
||||
self.downloadOperation = nil;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void)failureDownloadWithError:(NSError *)error
|
||||
{
|
||||
if(self.starting)
|
||||
{
|
||||
self.starting = NO;
|
||||
self.comleted = YES;
|
||||
|
||||
if(self.operationViewDelegate != nil)[self.operationViewDelegate downloadHasBeenCancelledWithError:error];
|
||||
if(self.clientFailure != nil) self.clientFailure(self.coub, self.tag, error);
|
||||
if(self.completionBlock != nil) self.completionBlock(self, error);
|
||||
}
|
||||
}
|
||||
|
||||
- (instancetype)clone
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
self.completionBlock = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
18
submodules/LegacyComponents/LegacyComponents/CBJSONCoubMapper.h
Executable file
18
submodules/LegacyComponents/LegacyComponents/CBJSONCoubMapper.h
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
//
|
||||
// Created by Tikhonenko Pavel on 29/11/2013.
|
||||
// Copyright (c) 2013 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class CBCoubNew;
|
||||
|
||||
|
||||
@interface CBJSONCoubMapper : NSObject
|
||||
|
||||
+ (CBCoubNew *)updateCoubFromCoub:(CBCoubNew *)newCoub coub:(CBCoubNew *)coub;
|
||||
+ (CBCoubNew *)updateCoubFromJSONObject:(NSDictionary *)jsonObj coub:(CBCoubNew *)coub;
|
||||
+ (CBCoubNew *)coubFromJSONObject:(NSDictionary *)jsonObj;
|
||||
|
||||
@end
|
||||
254
submodules/LegacyComponents/LegacyComponents/CBJSONCoubMapper.m
Executable file
254
submodules/LegacyComponents/LegacyComponents/CBJSONCoubMapper.m
Executable file
|
|
@ -0,0 +1,254 @@
|
|||
//
|
||||
// Created by Tikhonenko Pavel on 29/11/2013.
|
||||
// Copyright (c) 2013 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
#import "CBJSONCoubMapper.h"
|
||||
#import "CBCoubNew.h"
|
||||
#import "NSDictionary+CBExtensions.h"
|
||||
#import "CBCoubVideoSource.h"
|
||||
#import "CBCoubAudioSource.h"
|
||||
#import "CBTagNew.h"
|
||||
|
||||
|
||||
|
||||
@implementation CBJSONCoubMapper
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
+ (CBCoubNew *)updateCoubFromCoub:(CBCoubNew *)newCoub coub:(CBCoubNew *)coub
|
||||
{
|
||||
coub.title = newCoub.title;
|
||||
coub.liked = newCoub.liked;
|
||||
coub.visibility = newCoub.visibility;
|
||||
coub.recoubed = newCoub.recoubed;
|
||||
coub.likeCount = newCoub.likeCount;
|
||||
coub.recoubCount = newCoub.recoubCount;
|
||||
coub.cotd = newCoub.cotd;
|
||||
coub.flagged = newCoub.flagged;
|
||||
coub.creationDate = coub.creationDate;
|
||||
coub.creationDateAsString = coub.creationDateAsString;
|
||||
|
||||
// if(newCoub.isDone)
|
||||
// coub.state = CBCoubDraftCompleted;
|
||||
|
||||
return coub;
|
||||
}
|
||||
|
||||
+ (CBCoubNew *)updateCoubFromJSONObject:(NSDictionary *)attributes coub:(CBCoubNew *)coub
|
||||
{
|
||||
static NSDictionary *kCoubJSONKeys = nil;
|
||||
if(!kCoubJSONKeys)
|
||||
{
|
||||
kCoubJSONKeys = @{
|
||||
//@"permalink" : @"permalink",
|
||||
@"title" : @"title",
|
||||
@"visibility_type" : @"visibility",
|
||||
//@"created_at" : @"creationDateAsString",
|
||||
//@"external_download" : @"externalDownloadAsDictionary",
|
||||
@"like" : @"liked",
|
||||
@"recoub" : @"recoubed",
|
||||
@"likes_count" : @"likeCount",
|
||||
@"views_count" : @"viewCount",
|
||||
@"recoubs_count" : @"recoubCount",
|
||||
@"cotd" : @"cotd",
|
||||
@"flag" : @"flagged",
|
||||
};
|
||||
}
|
||||
|
||||
if ([attributes[@"title"] isKindOfClass:[NSString class]])
|
||||
{
|
||||
NSString *title = attributes[@"title"];
|
||||
coub.title = title;
|
||||
}
|
||||
|
||||
NSDictionary *recoubInfo = nil;
|
||||
|
||||
if(attributes[@"permalink"])
|
||||
coub.permalink = attributes[@"permalink"];
|
||||
|
||||
if(attributes[@"id"])
|
||||
coub.coubID = [attributes[@"id"] stringValue];
|
||||
|
||||
coub.originalPermalink = attributes[@"permalink"];
|
||||
coub.originalCreationDateAsString = attributes[@"created_at"];
|
||||
|
||||
if (coub.author == nil)
|
||||
{
|
||||
coub.author = [CBCoubAuthorVO coubAuthorWithAttributes:attributes[@"user"]];
|
||||
//coub.authorCD = [CBUserNewuserWithAttributes:attributes[@"user"]];
|
||||
}
|
||||
|
||||
if (coub.author.name.length == 0)
|
||||
{
|
||||
NSDictionary *channel = attributes[@"channel"];
|
||||
if ([channel respondsToSelector:@selector(objectForKey:)])
|
||||
coub.author.name = channel[@"title"];
|
||||
}
|
||||
|
||||
if(attributes[@"is_done"])
|
||||
coub.isDone = [attributes[@"is_done"] boolValue];
|
||||
|
||||
NSDictionary *fileVersions = attributes[@"file_versions"];
|
||||
NSString *remoteVideoLocation = attributes[@"explicitVideoLocation"];
|
||||
if (remoteVideoLocation)
|
||||
{
|
||||
remoteVideoLocation = [remoteVideoLocation stringByReplacingOccurrencesOfString:@"muted_mp4_med_" withString:@"mp4_med_"];
|
||||
remoteVideoLocation = [remoteVideoLocation stringByReplacingOccurrencesOfString:@"_muted_med" withString:@"_med"];
|
||||
coub.remoteVideoLocation = remoteVideoLocation;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (fileVersions)
|
||||
remoteVideoLocation = fileVersions[@"iphone"][@"url"];
|
||||
if(!remoteVideoLocation)
|
||||
remoteVideoLocation = attributes[@"file"];
|
||||
if(!remoteVideoLocation || [remoteVideoLocation isEqual:[NSNull null]])
|
||||
remoteVideoLocation = fileVersions[@"html5"][@"video"][@"med"][@"url"];
|
||||
|
||||
if (remoteVideoLocation)
|
||||
{
|
||||
if ([remoteVideoLocation rangeOfString:@"muted_mp4"].location != NSNotFound)
|
||||
{
|
||||
remoteVideoLocation = [remoteVideoLocation stringByReplacingOccurrencesOfString:@"muted_mp4_med_" withString:@"mp4_med_"];
|
||||
remoteVideoLocation = [remoteVideoLocation stringByReplacingOccurrencesOfString:@"_muted_med" withString:@"_med"];
|
||||
coub.remoteVideoLocation = remoteVideoLocation;
|
||||
}
|
||||
else
|
||||
{
|
||||
NSRange r1 = [remoteVideoLocation rangeOfString:@"iphone_"];
|
||||
NSRange r2 = [remoteVideoLocation rangeOfString:@"_iphone"];
|
||||
|
||||
if (r1.location == NSNotFound)
|
||||
{
|
||||
r1 = [remoteVideoLocation rangeOfString:@"gifv_"];
|
||||
r2 = [remoteVideoLocation rangeOfString:@"_gifv"];
|
||||
}
|
||||
|
||||
if (r1.location != NSNotFound)
|
||||
{
|
||||
NSInteger loc = r1.length+r1.location;
|
||||
NSString *someVideoMetadataString = [remoteVideoLocation substringWithRange:NSMakeRange(loc, r2.location - loc)];
|
||||
|
||||
remoteVideoLocation = fileVersions[@"web"][@"template"];
|
||||
NSRange r3 = [remoteVideoLocation rangeOfString:@"%{"];
|
||||
|
||||
remoteVideoLocation = [remoteVideoLocation substringToIndex:r3.location];
|
||||
remoteVideoLocation = [NSString stringWithFormat:@"%@mp4_med_size_%@_med.mp4", remoteVideoLocation, someVideoMetadataString];
|
||||
coub.remoteVideoLocation = remoteVideoLocation;
|
||||
}
|
||||
else if ([remoteVideoLocation rangeOfString:@"mp4_med_size_"].location != NSNotFound)
|
||||
{
|
||||
coub.remoteVideoLocation = remoteVideoLocation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NSString *remoteAudioLocation = [attributes[@"audio_versions"] coubURIFromVersionTemplateWithPreferredSubstitutions:@[@"low", @"mid", @"high"]];
|
||||
if(!remoteAudioLocation)
|
||||
remoteAudioLocation = attributes[@"audio_file_url"];
|
||||
if(!remoteAudioLocation || [remoteAudioLocation isEqual:[NSNull null]])
|
||||
remoteAudioLocation = fileVersions[@"mobile"][@"mp3"];
|
||||
if(remoteAudioLocation)
|
||||
coub.remoteAudioLocation = remoteAudioLocation;
|
||||
|
||||
NSDictionary *chunks = attributes[@"audio_versions"][@"chunks"];
|
||||
|
||||
if(chunks)
|
||||
{
|
||||
NSString *audioTemplate = chunks[@"template"];
|
||||
audioTemplate = [audioTemplate stringByReplacingOccurrencesOfString:@"%{version}" withString:@"low"];
|
||||
audioTemplate = [audioTemplate stringByReplacingOccurrencesOfString:@"%{chunk}" withString:@"%i"];
|
||||
coub.remoteAudioLocationPattern = audioTemplate;
|
||||
}
|
||||
|
||||
// big, med, small, ios_large
|
||||
//KALog(@"ff=%@", [attributes[@"first_frame_versions"][@"versions"] componentsJoinedByString: @", "]);
|
||||
NSString *largePictureLocation = [attributes[@"first_frame_versions"] coubURIFromVersionTemplateWithPreferredSubstitutions:@[@"med", @"big", @"ios_large"]];
|
||||
if(largePictureLocation)
|
||||
coub.largePicture = largePictureLocation;
|
||||
|
||||
// micro, tiny, age_restricted, ios_large, ios_mosaic, big, med, small
|
||||
NSString *mediumPictureLocation = [attributes[@"image_versions"] coubURIFromVersionTemplateWithPreferredSubstitutions:@[@"ios_mosaic", @"small", @"med"]];
|
||||
if(!mediumPictureLocation)
|
||||
mediumPictureLocation = attributes[@"picture"]; // short JSON
|
||||
if(mediumPictureLocation)
|
||||
coub.mediumPicture = mediumPictureLocation;
|
||||
|
||||
// Determine the type of audio based on the values of "has_sound" and "audio_file_url"
|
||||
if(coub.remoteAudioLocation && ([coub.remoteAudioLocation isKindOfClass:[NSString class]] && [coub.remoteAudioLocation length] < 8))
|
||||
coub.remoteAudioLocation = nil;
|
||||
|
||||
if(attributes[@"visibility_type"])
|
||||
{
|
||||
coub.audioType = coub.remoteAudioLocationPattern ? CBCoubAudioTypeExternal : ([attributes[@"has_sound"] boolValue] ? CBCoubAudioTypeInternal : CBCoubAudioTypeNone);
|
||||
}
|
||||
// else a short JSON doesn't have any information on sound
|
||||
|
||||
NSDictionary *mediaBlock = attributes[@"media_blocks"];
|
||||
|
||||
BOOL isCoubAudioSourceAvailable = mediaBlock[@"audio_track"] != nil;
|
||||
BOOL isCoubVideoSourceAvailable = mediaBlock[@"external_video"] != nil;
|
||||
|
||||
if(isCoubVideoSourceAvailable)
|
||||
coub.videoSource = [CBCoubVideoSource sourceFromData:mediaBlock[@"external_video"]];
|
||||
if(isCoubAudioSourceAvailable)
|
||||
coub.audioSource = [CBCoubAudioSource sourceFromData:mediaBlock[@"audio_track"]];
|
||||
|
||||
if(coub.audioSource || coub.videoSource)
|
||||
coub.isCoubSourcesAvailable = YES;
|
||||
|
||||
NSDictionary *externalDownloadInfo = attributes[@"external_download"];
|
||||
if([externalDownloadInfo isKindOfClass:[NSDictionary class]])
|
||||
{
|
||||
coub.externalDownloadType = externalDownloadInfo[@"type"];
|
||||
coub.externalDownloadSource = externalDownloadInfo[@"url"];
|
||||
}else
|
||||
{
|
||||
// Coub API may return a boolean "false" as "external_download" value
|
||||
coub.externalDownloadType = nil;
|
||||
coub.externalDownloadSource = nil;
|
||||
}
|
||||
|
||||
NSArray *tags = attributes[@"tags"];
|
||||
if(tags)
|
||||
{
|
||||
NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:tags.count];
|
||||
[tags enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
|
||||
{
|
||||
[mutableArray addObject:[CBTagNew tagWithAttributes:obj]];
|
||||
}];
|
||||
|
||||
coub.tags = [NSArray arrayWithArray:mutableArray];
|
||||
}
|
||||
|
||||
|
||||
|
||||
if(recoubInfo)
|
||||
{
|
||||
coub.recouber = [CBCoubAuthorVO coubAuthorWithAttributes:recoubInfo[@"user"]];
|
||||
coub.liked = [recoubInfo[@"like"] boolValue];
|
||||
coub.recoubed = [recoubInfo[@"recoub"] boolValue];
|
||||
coub.flagged = [recoubInfo[@"flagged"] boolValue];
|
||||
}
|
||||
|
||||
if(!coub.coubID)
|
||||
[NSException raise:NSInternalInconsistencyException format:@"No coub id found in %@", attributes];
|
||||
if(!coub.permalink)
|
||||
[NSException raise:NSInternalInconsistencyException format:@"No coub permalink found in %@", attributes];
|
||||
|
||||
NSArray *size = attributes[@"dimensions"][@"small"];
|
||||
|
||||
//coub.naturalVideoSize = CGSizeMake([size[0] floatValue], [size[1] floatValue]);
|
||||
return coub;
|
||||
}
|
||||
|
||||
+ (CBCoubNew *)coubFromJSONObject:(NSDictionary *)jsonObj
|
||||
{
|
||||
return [self updateCoubFromJSONObject:jsonObj coub:[CBCoubNew new]];
|
||||
}
|
||||
|
||||
@end
|
||||
36
submodules/LegacyComponents/LegacyComponents/CBLibrary.h
Executable file
36
submodules/LegacyComponents/LegacyComponents/CBLibrary.h
Executable file
|
|
@ -0,0 +1,36 @@
|
|||
//
|
||||
// CBLibrary.h
|
||||
// Coub
|
||||
//
|
||||
// Created by Konstantin Anoshkin on 26.06.12.
|
||||
// Copyright 2012 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "CBCoubAsset.h"
|
||||
|
||||
#if TARGET_IPHONE_SIMULATOR
|
||||
// On iPhone Simulator NSTemporaryDirectory() returns a Mac OS X temporary directory which is outside our application sandbox.
|
||||
// For consistency's sake we want to make it behave as on an honest-to-goodness iPhone device.
|
||||
NSString *CBTemporaryDirectory (void);
|
||||
#define NSTemporaryDirectory() CBTemporaryDirectory()
|
||||
#endif
|
||||
|
||||
NSString *CBDocumentsDirectory (void);
|
||||
NSString *CBCachesDirectory (void);
|
||||
|
||||
@interface CBLibrary : NSObject
|
||||
|
||||
+ (CBLibrary *)sharedLibrary;
|
||||
|
||||
- (void)markCoubAsset:(id<CBCoubAsset>)coub asDownloaded:(BOOL)downloaded;
|
||||
- (BOOL)isCoubDownloadedByPermalink:(NSString *)permalink;
|
||||
|
||||
- (void)markCoubChunk:(id<CBCoubAsset>)coub idx:(NSInteger)idx asDownloaded:(BOOL)downloaded;
|
||||
- (BOOL)isCoubChunkDownloadedByPermalink:(NSString *)permalink idx:(NSInteger)idx;
|
||||
|
||||
- (void)cleanUpMediaCache;
|
||||
|
||||
@property (strong, nonatomic) NSURL *mediaDirectory;
|
||||
|
||||
@end
|
||||
236
submodules/LegacyComponents/LegacyComponents/CBLibrary.m
Executable file
236
submodules/LegacyComponents/LegacyComponents/CBLibrary.m
Executable file
|
|
@ -0,0 +1,236 @@
|
|||
//
|
||||
// CBLibrary.m
|
||||
// Coub
|
||||
//
|
||||
// Created by Konstantin Anoshkin on 26.06.12.
|
||||
// Copyright 2012 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
#define CBLIBRARY_IMPLEMENTATION_FILE
|
||||
|
||||
#import "CBLibrary.h"
|
||||
#import <sys/xattr.h>
|
||||
|
||||
|
||||
#if TARGET_IPHONE_SIMULATOR
|
||||
NSString *CBTemporaryDirectory (void)
|
||||
{
|
||||
return [NSHomeDirectory() stringByAppendingPathComponent: @"tmp"];
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
NSString *CBDocumentsDirectory (void)
|
||||
{
|
||||
static NSString *sPath = nil;
|
||||
if (!sPath)
|
||||
sPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] copy];
|
||||
return sPath;
|
||||
}
|
||||
|
||||
|
||||
NSString *CBCachesDirectory (void)
|
||||
{
|
||||
static NSString *sPath = nil;
|
||||
if (!sPath) {
|
||||
sPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] copy];
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath: sPath])
|
||||
[[NSFileManager defaultManager] createDirectoryAtPath: sPath withIntermediateDirectories: YES attributes: nil error: NULL];
|
||||
}
|
||||
return sPath;
|
||||
}
|
||||
|
||||
|
||||
NSString *CBMediaDirectory (void)
|
||||
{
|
||||
static NSString *sPath = nil;
|
||||
if (!sPath) {
|
||||
sPath = [[[NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent: @"Media"] copy];
|
||||
}
|
||||
return sPath;
|
||||
}
|
||||
|
||||
NSString *const kCBLibraryDatabaseFileNameExtension = @"sqlite";
|
||||
NSString *const kCBLibraryCurrentUserID = @"currentUserID";
|
||||
|
||||
|
||||
@implementation CBLibrary
|
||||
{
|
||||
@private
|
||||
NSURL *_mediaDirectory;
|
||||
NSMutableDictionary *_cachedMediaFiles;
|
||||
}
|
||||
|
||||
|
||||
+ (CBLibrary *)sharedLibrary
|
||||
{
|
||||
static id sSharedInstance = nil;
|
||||
static dispatch_once_t onceToken = 0;
|
||||
dispatch_once(&onceToken, ^
|
||||
{
|
||||
sSharedInstance = [[self alloc] init];
|
||||
});
|
||||
return sSharedInstance;
|
||||
}
|
||||
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if(self)
|
||||
{
|
||||
_cachedMediaFiles = [NSMutableDictionary new];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Paths & URLs
|
||||
|
||||
- (void)setMediaDirectory:(NSURL *)mediaDirectory
|
||||
{
|
||||
if (_mediaDirectory && ![mediaDirectory.absoluteString isEqualToString:_mediaDirectory.absoluteString]) {
|
||||
[self cleanUpMediaCache];
|
||||
}
|
||||
|
||||
if (mediaDirectory && ![mediaDirectory.absoluteString isEqualToString:_mediaDirectory.absoluteString]) {
|
||||
_mediaDirectory = mediaDirectory;
|
||||
[self createMediaDirectory];
|
||||
} else {
|
||||
_mediaDirectory = mediaDirectory;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSURL *)mediaDirectory
|
||||
{
|
||||
if(!_mediaDirectory) {
|
||||
_mediaDirectory = [NSURL fileURLWithPath:CBMediaDirectory() isDirectory:YES];
|
||||
[self createMediaDirectory];
|
||||
}
|
||||
return _mediaDirectory;
|
||||
}
|
||||
|
||||
- (void)createMediaDirectory
|
||||
{
|
||||
NSError *error = nil;
|
||||
NSString *mediaDirectoryPath = [[self mediaDirectory] path];
|
||||
if(![[NSFileManager defaultManager] fileExistsAtPath:mediaDirectoryPath])
|
||||
{
|
||||
if(![[NSFileManager defaultManager] createDirectoryAtPath:mediaDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error])
|
||||
{
|
||||
NSLog(@"*** Could not recreate Media directory, %@", error);
|
||||
return;
|
||||
}
|
||||
|
||||
if(&NSURLIsExcludedFromBackupKey)
|
||||
{
|
||||
if(![[self mediaDirectory] setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:&error])
|
||||
NSLog(@"*** Failed to set NSURLIsExcludedFromBackupKey for %@, %@", [self mediaDirectory], error);
|
||||
}else
|
||||
{
|
||||
// Set the Do Not Backup extended attribute, http://developer.apple.com/library/ios/#qa/qa1719/_index.html
|
||||
|
||||
|
||||
u_int8_t attrValue = 1;
|
||||
if(setxattr([mediaDirectoryPath fileSystemRepresentation], "com.apple.MobileBackup", &attrValue, sizeof(attrValue), 0, 0))
|
||||
{
|
||||
//KAObjectLogError(@"setxattr(%@) failed: %s (%d)", mediaDirectoryPath, strerror(errno), errno);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
- (void)markCoubAsset:(id<CBCoubAsset>)coub asDownloaded:(BOOL)downloaded
|
||||
{
|
||||
if(downloaded)
|
||||
{
|
||||
if([self isCoubDownloadedByPermalink:coub.assetId])
|
||||
return;
|
||||
|
||||
NSError *error = nil;
|
||||
NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:[coub.localVideoFileURL path] error:&error];
|
||||
if(attrs)
|
||||
{
|
||||
_cachedMediaFiles[coub.assetId] = [NSMutableDictionary dictionaryWithDictionary:@{@"coub" : coub, @"downloadedChunks": @0}];
|
||||
}else
|
||||
{
|
||||
//KAObjectLogError(@"Can't get attributes at %@: %@", coub.localVideoFileURL, error);
|
||||
}
|
||||
}else
|
||||
{
|
||||
[_cachedMediaFiles removeObjectForKey:coub.assetId];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isCoubDownloadedByPermalink:(NSString *)permalink
|
||||
{
|
||||
return _cachedMediaFiles[permalink] != nil;
|
||||
}
|
||||
|
||||
- (void)markCoubChunk:(id<CBCoubAsset>)coub idx:(NSInteger)idx asDownloaded:(BOOL)downloaded
|
||||
{
|
||||
//NSLog(@"markCoubChunk %i", idx);
|
||||
|
||||
NSMutableDictionary *mediaFile = _cachedMediaFiles[coub.assetId];
|
||||
|
||||
if(mediaFile)
|
||||
{
|
||||
NSInteger downloadedChunks = ((NSNumber *)mediaFile[@"downloadedChunks"]).integerValue;
|
||||
NSInteger normalIdx = 1<<idx;
|
||||
|
||||
if(downloaded)
|
||||
downloadedChunks |= normalIdx;
|
||||
else
|
||||
downloadedChunks &= normalIdx;
|
||||
|
||||
mediaFile[@"downloadedChunks"] = [NSNumber numberWithInteger:downloadedChunks];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
- (BOOL)isCoubChunkDownloadedByPermalink:(NSString *)permalink idx:(NSInteger)idx
|
||||
{
|
||||
NSMutableDictionary *mediaFile = _cachedMediaFiles[permalink];
|
||||
|
||||
if(mediaFile == nil)
|
||||
return NO;
|
||||
|
||||
idx = 1<<idx;
|
||||
|
||||
NSInteger downloadedChunks = ((NSNumber *)mediaFile[@"downloadedChunks"]).integerValue;
|
||||
|
||||
BOOL downloaded = (downloadedChunks & idx) == idx;
|
||||
return downloaded;
|
||||
}
|
||||
|
||||
- (void)cleanUpMediaCache
|
||||
{
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
NSString *directory = self.mediaDirectory.path;
|
||||
NSError *error = nil;
|
||||
|
||||
for(NSString *file in [fm contentsOfDirectoryAtPath:directory error:&error])
|
||||
{
|
||||
BOOL (^isMedia)(void) = ^BOOL(void){
|
||||
BOOL isMp3 = [file rangeOfString:@"mp3"].length != 0;
|
||||
BOOL isMp4 = [file rangeOfString:@"mp4"].length != 0;
|
||||
|
||||
return isMp3 || isMp4;
|
||||
};
|
||||
|
||||
if(!isMedia()) continue;
|
||||
|
||||
|
||||
|
||||
BOOL success = [fm removeItemAtPath:[NSString stringWithFormat:@"%@/%@", directory, file] error:&error];
|
||||
if(!success || error) {}
|
||||
}
|
||||
|
||||
[_cachedMediaFiles removeAllObjects];
|
||||
}
|
||||
|
||||
@end
|
||||
13
submodules/LegacyComponents/LegacyComponents/CBPlayerLayerView.h
Executable file
13
submodules/LegacyComponents/LegacyComponents/CBPlayerLayerView.h
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
//
|
||||
// CBPlayerLayerView.h
|
||||
// Coub
|
||||
//
|
||||
// Created by Tikhonenko Pavel on 23/11/2013.
|
||||
// Copyright (c) 2013 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface CBPlayerLayerView : UIView
|
||||
|
||||
@end
|
||||
20
submodules/LegacyComponents/LegacyComponents/CBPlayerLayerView.m
Executable file
20
submodules/LegacyComponents/LegacyComponents/CBPlayerLayerView.m
Executable file
|
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// CBPlayerLayerView.m
|
||||
// Coub
|
||||
//
|
||||
// Created by Tikhonenko Pavel on 23/11/2013.
|
||||
// Copyright (c) 2013 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CBPlayerLayerView.h"
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
@implementation CBPlayerLayerView
|
||||
|
||||
+ (Class)layerClass
|
||||
{
|
||||
return [AVPlayerLayer class];
|
||||
}
|
||||
|
||||
@end
|
||||
27
submodules/LegacyComponents/LegacyComponents/CBPlayerView.h
Executable file
27
submodules/LegacyComponents/LegacyComponents/CBPlayerView.h
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// CBPlayerView.h
|
||||
// CoubPlayer
|
||||
//
|
||||
// Created by Pavel Tikhonenko on 17/10/14.
|
||||
// Copyright (c) 2014 Pavel Tikhonenko. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
#import "CBCoubPlayer.h"
|
||||
#import "CBPlayerLayerView.h"
|
||||
|
||||
@interface CBPlayerView : UIView <CBCoubPlayerDelegate>
|
||||
|
||||
@property (nonatomic, readonly) UIImageView *preview;
|
||||
@property (nonatomic, readonly) CBPlayerLayerView *videoPlayerView;
|
||||
|
||||
- (void)play;
|
||||
- (void)stop;
|
||||
|
||||
@end
|
||||
|
||||
//@interface CBPlayerLayerView : UIView
|
||||
//@end
|
||||
101
submodules/LegacyComponents/LegacyComponents/CBPlayerView.m
Executable file
101
submodules/LegacyComponents/LegacyComponents/CBPlayerView.m
Executable file
|
|
@ -0,0 +1,101 @@
|
|||
//
|
||||
// CBPlayerView.m
|
||||
// CoubPlayer
|
||||
//
|
||||
// Created by Pavel Tikhonenko on 17/10/14.
|
||||
// Copyright (c) 2014 Pavel Tikhonenko. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CBPlayerView.h"
|
||||
|
||||
|
||||
|
||||
@implementation CBPlayerView
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
|
||||
if(self)
|
||||
{
|
||||
[self setupView];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
- (void)setupView
|
||||
{
|
||||
self.clipsToBounds = YES;
|
||||
|
||||
_preview = [[UIImageView alloc] initWithFrame:self.bounds];
|
||||
_preview.contentMode = UIViewContentModeScaleAspectFill;
|
||||
[self addSubview:_preview];
|
||||
|
||||
_videoPlayerView = [[CBPlayerLayerView alloc] initWithFrame:self.bounds];
|
||||
_videoPlayerView.hidden = YES;
|
||||
((AVPlayerLayer *) _videoPlayerView.layer).videoGravity = AVLayerVideoGravityResizeAspectFill;
|
||||
[self addSubview:_videoPlayerView];
|
||||
}
|
||||
|
||||
- (void)setContentMode:(UIViewContentMode)contentMode
|
||||
{
|
||||
[super setContentMode:contentMode];
|
||||
|
||||
_preview.contentMode = contentMode;
|
||||
((AVPlayerLayer *)_videoPlayerView.layer).videoGravity = (contentMode == UIViewContentModeScaleAspectFit) ? AVLayerVideoGravityResizeAspect : AVLayerVideoGravityResizeAspectFill;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
|
||||
CGRect bounds = self.bounds;
|
||||
|
||||
_videoPlayerView.frame = bounds;
|
||||
_preview.frame = bounds;
|
||||
}
|
||||
|
||||
- (void)play
|
||||
{
|
||||
_videoPlayerView.hidden = NO;
|
||||
}
|
||||
|
||||
- (void)stop
|
||||
{
|
||||
_videoPlayerView.hidden = YES;
|
||||
}
|
||||
|
||||
//- (void) layoutSubviews
|
||||
//{
|
||||
// // We need manual aspect fill/fit layout if we want fluid frame animations
|
||||
// CGRect bounds = self.bounds;
|
||||
// //_preview.frame = bounds;
|
||||
//
|
||||
// if (self.contentMode == UIViewContentModeScaleAspectFit) {
|
||||
// CGRect viewFrame = bounds;
|
||||
//
|
||||
// CGSize size = _player.loop.videoTrackSize;
|
||||
// if (size.width && size.height) {
|
||||
// CGFloat scale = fminf(viewFrame.size.width / size.width, viewFrame.size.height / size.height);
|
||||
// size.width = roundf(size.width * scale);
|
||||
// size.height = roundf(size.height * scale);
|
||||
// viewFrame = CGRectInset(viewFrame, 0.5f * (viewFrame.size.width - size.width), 0.5f * (viewFrame.size.height - size.height));
|
||||
// }
|
||||
//
|
||||
// _videoPlayerView.frame = viewFrame;
|
||||
// } else
|
||||
// _videoPlayerView.frame = bounds;
|
||||
//
|
||||
// CGPoint center = (CGPoint) { CGRectGetMidX(bounds), CGRectGetMidY(bounds) };
|
||||
// _spinner.center = center;
|
||||
// _reloadButton.center = center;
|
||||
//}
|
||||
|
||||
@end
|
||||
|
||||
//#pragma mark -
|
||||
//#pragma mark CBPlayerLayerView
|
||||
//
|
||||
//@implementation CBPlayerLayerView
|
||||
//+ (Class)layerClass { return [AVPlayerLayer class]; }
|
||||
//@end
|
||||
18
submodules/LegacyComponents/LegacyComponents/CBTagNew.h
Executable file
18
submodules/LegacyComponents/LegacyComponents/CBTagNew.h
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
//
|
||||
// Created by Tikhonenko Pavel on 30/11/2013.
|
||||
// Copyright (c) 2013 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
|
||||
@interface CBTagNew : NSObject
|
||||
@property (nonatomic, strong) NSNumber *tagId;
|
||||
@property (nonatomic, strong) NSString *title;
|
||||
@property (nonatomic, strong) NSString *value;
|
||||
@property (nonatomic, readonly) NSString *hashTag;
|
||||
|
||||
+ (instancetype)tagWithAttributes:(NSDictionary *)attributes;
|
||||
|
||||
@end
|
||||
29
submodules/LegacyComponents/LegacyComponents/CBTagNew.m
Executable file
29
submodules/LegacyComponents/LegacyComponents/CBTagNew.m
Executable file
|
|
@ -0,0 +1,29 @@
|
|||
//
|
||||
// Created by Tikhonenko Pavel on 30/11/2013.
|
||||
// Copyright (c) 2013 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
#import "CBTagNew.h"
|
||||
|
||||
|
||||
@implementation CBTagNew
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
- (NSString *)hashTag
|
||||
{
|
||||
return [@"#" stringByAppendingString:_title];
|
||||
}
|
||||
|
||||
+ (instancetype)tagWithAttributes:(NSDictionary *)attributes
|
||||
{
|
||||
CBTagNew *tag = [CBTagNew new];
|
||||
tag.tagId = attributes[@"id"];
|
||||
tag.title = attributes[@"title"];
|
||||
tag.value = attributes[@"value"];
|
||||
return tag;
|
||||
}
|
||||
|
||||
@end
|
||||
36
submodules/LegacyComponents/LegacyComponents/CBVideoPlayer.h
Executable file
36
submodules/LegacyComponents/LegacyComponents/CBVideoPlayer.h
Executable file
|
|
@ -0,0 +1,36 @@
|
|||
//
|
||||
// CBVideoPlayer.h
|
||||
// Coub
|
||||
//
|
||||
// Created by Pavel Tikhonenko on 12/08/14.
|
||||
// Copyright (c) 2014 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
typedef enum
|
||||
{
|
||||
CBVideoPlayerStatusInited,
|
||||
CBVideoPlayerStatusPrepairing = 1,
|
||||
CBVideoPlayerStatusReadyToPlay = 1 << 1,
|
||||
CBVideoPlayerStatusFailed = 1 << 2,
|
||||
CBVideoPlayerStatusUnknown = NSNotFound
|
||||
|
||||
}
|
||||
CBVideoPlayerStatus;
|
||||
|
||||
@interface CBVideoPlayer : NSObject
|
||||
|
||||
@property (nonatomic, assign) CBVideoPlayerStatus status;
|
||||
|
||||
- (id)initWithVideoLayer:(AVPlayerLayer *)layer;
|
||||
|
||||
- (void)prepareWithAVAsset:(AVAsset *)asset completion:(void (^)(NSError *error))completion;
|
||||
- (void)stopPrepairing;
|
||||
|
||||
- (void)play;
|
||||
- (void)pause;
|
||||
- (void)stop;
|
||||
|
||||
@end
|
||||
193
submodules/LegacyComponents/LegacyComponents/CBVideoPlayer.m
Executable file
193
submodules/LegacyComponents/LegacyComponents/CBVideoPlayer.m
Executable file
|
|
@ -0,0 +1,193 @@
|
|||
//
|
||||
// CBVideoPlayer.m
|
||||
// Coub
|
||||
//
|
||||
// Created by Pavel Tikhonenko on 12/08/14.
|
||||
// Copyright (c) 2014 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CBVideoPlayer.h"
|
||||
|
||||
static void *kPlayerItemContext = (void *) 1;
|
||||
static void *kPlayerStatusContext = (void *) 2;
|
||||
|
||||
static void *kPlayerLayerReadyToDisplayContext = (void *) 4;
|
||||
|
||||
@interface CBVideoPlayer ()
|
||||
|
||||
@property (nonatomic, strong) AVPlayerLayer *layer;
|
||||
@property (nonatomic, strong) AVPlayer *videoPlayer;
|
||||
@property (nonatomic, strong) AVPlayerItem *nextItem;
|
||||
@property (nonatomic, assign) BOOL hasBeenReseted;
|
||||
|
||||
@property (nonatomic, copy) void (^prepairingCompletion)(NSError *error);
|
||||
|
||||
@end
|
||||
|
||||
@implementation CBVideoPlayer
|
||||
|
||||
- (id)initWithVideoLayer:(AVPlayerLayer *)layer
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if(self)
|
||||
{
|
||||
self.status = CBVideoPlayerStatusUnknown;
|
||||
self.hasBeenReseted = YES;
|
||||
|
||||
self.layer = layer;
|
||||
|
||||
[self createVideoPlayer];
|
||||
|
||||
|
||||
self.status = CBVideoPlayerStatusInited;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Public methods
|
||||
|
||||
- (void)prepareWithAVAsset:(AVAsset *)asset completion:(void (^)(NSError *error))completion
|
||||
{
|
||||
self.prepairingCompletion = completion;
|
||||
|
||||
self.status = CBVideoPlayerStatusPrepairing;
|
||||
|
||||
if(!_hasBeenReseted)
|
||||
[self resetPlayer];
|
||||
|
||||
_hasBeenReseted = NO;
|
||||
|
||||
[self prepareVideoPlayer];
|
||||
[self prepareVideoLayer];
|
||||
|
||||
AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:asset];
|
||||
[self.videoPlayer replaceCurrentItemWithPlayerItem:item];
|
||||
|
||||
}
|
||||
|
||||
- (void)play
|
||||
{
|
||||
[_videoPlayer play];
|
||||
}
|
||||
|
||||
- (void)pause
|
||||
{
|
||||
_videoPlayer.rate = 0;
|
||||
}
|
||||
|
||||
- (void)stop
|
||||
{
|
||||
_videoPlayer.rate = 0;
|
||||
|
||||
[self resetPlayer];
|
||||
[self.videoPlayer replaceCurrentItemWithPlayerItem:nil];
|
||||
}
|
||||
|
||||
- (void)stopPrepairing
|
||||
{
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Private methods
|
||||
|
||||
- (void)createVideoPlayer
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(restartAVPlayer:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
|
||||
|
||||
self.videoPlayer = [AVPlayer new];
|
||||
_videoPlayer.actionAtItemEnd = AVPlayerActionAtItemEndNone;
|
||||
|
||||
}
|
||||
|
||||
- (void)prepareVideoPlayer
|
||||
{
|
||||
[_videoPlayer addObserver:self forKeyPath:@"currentItem" options:NSKeyValueObservingOptionNew context:kPlayerItemContext];
|
||||
[_videoPlayer addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:kPlayerStatusContext];
|
||||
}
|
||||
|
||||
- (void)prepareVideoLayer
|
||||
{
|
||||
[_layer addObserver:self forKeyPath:@"readyForDisplay" options:NSKeyValueObservingOptionNew context:kPlayerLayerReadyToDisplayContext];
|
||||
}
|
||||
|
||||
- (void)resetPlayer
|
||||
{
|
||||
@try{
|
||||
[_videoPlayer removeObserver:self forKeyPath:@"currentItem" context:kPlayerItemContext];
|
||||
}@catch(id anException){}
|
||||
|
||||
@try{
|
||||
[_videoPlayer removeObserver:self forKeyPath:@"status" context:kPlayerStatusContext];
|
||||
}@catch(id anException){}
|
||||
|
||||
@try{
|
||||
[_layer removeObserver:self forKeyPath:@"readyForDisplay" context:kPlayerLayerReadyToDisplayContext];
|
||||
}@catch(id anException){}
|
||||
}
|
||||
|
||||
- (void)prerollPlayer
|
||||
{
|
||||
[_videoPlayer prerollAtRate:1 completionHandler:^(BOOL finished) {
|
||||
[_layer setPlayer:self.videoPlayer];
|
||||
}];
|
||||
}
|
||||
- (void)completeVideoPrepairing
|
||||
{
|
||||
[self resetPlayer];
|
||||
self.prepairingCompletion(nil);
|
||||
}
|
||||
|
||||
- (void)restartAVPlayer:(NSNotification *)notification
|
||||
{
|
||||
AVPlayerItem *playerItem = [notification object];
|
||||
if (playerItem == _videoPlayer.currentItem)
|
||||
[_videoPlayer seekToTime:kCMTimeZero];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Observe value
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
|
||||
{
|
||||
AVPlayer *player = (AVPlayer *) object;
|
||||
|
||||
if(context == kPlayerLayerReadyToDisplayContext)
|
||||
{
|
||||
[self completeVideoPrepairing];
|
||||
}
|
||||
|
||||
if(context == kPlayerStatusContext || context == kPlayerItemContext)
|
||||
{
|
||||
//KAObjectLog(@"%@ status: %i", player == self.videoPlayer ? @"videoPlayer" : @"audioPlayer", player.status);
|
||||
|
||||
NSLog(@"status: %i", player.status);
|
||||
if(context == kPlayerItemContext && player.currentItem == nil && _nextItem)
|
||||
return;
|
||||
|
||||
if(context == kPlayerItemContext && player.currentItem == nil)
|
||||
return;
|
||||
|
||||
if(player.status == AVPlayerStatusReadyToPlay)
|
||||
{
|
||||
[self prerollPlayer];
|
||||
}else if(player.status == AVPlayerStatusFailed)
|
||||
{
|
||||
//[self.delegate playerInitialzeProccess:self completeWithError:[NSError errorWithDomain:@"com.coub.player" code:99 userInfo:nil]];
|
||||
|
||||
}//else if(player.status == AVPlayerStatusUnknown)
|
||||
//KAObjectLog(@"%@ AVPlayerStatusUnknown: %@", player == self.videoPlayer ? @"videoPlayer" : @"audioPlayer", player.error);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
@try{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
|
||||
}@catch(id anException){}
|
||||
|
||||
[self resetPlayer];
|
||||
}
|
||||
@end
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
//
|
||||
// FLAnimatedImage.h
|
||||
// Flipboard
|
||||
//
|
||||
// Created by Raphael Schaad on 7/8/13.
|
||||
// Copyright (c) 2013-2014 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@protocol FLAnimatedImageDebugDelegate;
|
||||
|
||||
|
||||
//
|
||||
// An `FLAnimatedImage`'s job is to deliver frames in a highly performant way and works in conjunction with `FLAnimatedImageView`.
|
||||
// It subclasses `NSObject` and not `UIImage` because it's only an "image" in the sense that a sea lion is a lion.
|
||||
// It tries to intelligently choose the frame cache size depending on the image and memory situation with the goal to lower CPU usage for smaller ones, lower memory usage for larger ones and always deliver frames for high performant play-back.
|
||||
// Note: `posterImage`, `size`, `loopCount`, `delayTimes` and `frameCount` don't change after successful initialization.
|
||||
//
|
||||
@interface FLAnimatedImage : NSObject
|
||||
|
||||
@property (nonatomic, strong) UIImage *(^imageDrawingBlock)(UIImage *);
|
||||
|
||||
@property (nonatomic, strong, readonly) UIImage *posterImage; // Guaranteed to be loaded; usually equivalent to `-imageLazilyCachedAtIndex:0`
|
||||
@property (nonatomic, assign, readonly) CGSize size; // The `.posterImage`'s `.size`
|
||||
|
||||
@property (nonatomic, assign, readonly) NSUInteger loopCount; // 0 means repeating the animation indefinitely
|
||||
@property (nonatomic, strong, readonly) NSArray *delayTimes; // Of type `NSTimeInterval` boxed in `NSNumber`s
|
||||
@property (nonatomic, assign, readonly) NSUInteger frameCount; // Number of valid frames; equal to `[.delayTimes count]`
|
||||
|
||||
@property (nonatomic, assign, readonly) NSUInteger frameCacheSizeCurrent; // Current size of intelligently chosen buffer window; can range in the interval [1..frameCount]
|
||||
@property (nonatomic, assign) NSUInteger frameCacheSizeMax; // Allow to cap the cache size; 0 means no specific limit (default)
|
||||
|
||||
// Intended to be called from main thread synchronously; will return immediately.
|
||||
// If the result isn't cached, will return `nil`; the caller should then pause playback, not increment frame counter and keep polling.
|
||||
// After an initial loading time, depending on `frameCacheSize`, frames should be available immediately from the cache.
|
||||
- (UIImage *)imageLazilyCachedAtIndex:(NSUInteger)index;
|
||||
|
||||
// Pass either a `UIImage` or an `FLAnimatedImage` and get back its size
|
||||
+ (CGSize)sizeForImage:(id)image;
|
||||
|
||||
// Designated initializer
|
||||
// On success, returns a new `FLAnimatedImage` with all fields populated, on failure returns `nil` and an error will be logged.
|
||||
- (instancetype)initWithAnimatedGIFData:(NSData *)data imageDrawingBlock:(UIImage *(^)(UIImage *))imageDrawingBlock;
|
||||
|
||||
@property (nonatomic, strong, readonly) NSData *data; // The data the receiver was initialized with; read-only
|
||||
|
||||
#if DEBUG
|
||||
// Only intended to report internal state for debugging
|
||||
@property (nonatomic, weak) id<FLAnimatedImageDebugDelegate> debug_delegate;
|
||||
@property (nonatomic, strong) NSMutableDictionary *debug_info; // To track arbitrary data (e.g. original URL, loading durations, cache hits, etc.)
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface FLWeakProxy : NSProxy
|
||||
|
||||
+ (instancetype)weakProxyForObject:(id)targetObject;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#if DEBUG
|
||||
@protocol FLAnimatedImageDebugDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
|
||||
- (void)debug_animatedImage:(FLAnimatedImage *)animatedImage didUpdateCachedFrames:(NSIndexSet *)indexesOfFramesInCache;
|
||||
- (void)debug_animatedImage:(FLAnimatedImage *)animatedImage didRequestCachedFrame:(NSUInteger)index;
|
||||
- (CGFloat)debug_animatedImagePredrawingSlowdownFactor:(FLAnimatedImage *)animatedImage;
|
||||
|
||||
@end
|
||||
#endif
|
||||
740
submodules/LegacyComponents/LegacyComponents/FLAnimatedImage.m
Normal file
740
submodules/LegacyComponents/LegacyComponents/FLAnimatedImage.m
Normal file
|
|
@ -0,0 +1,740 @@
|
|||
//
|
||||
// FLAnimatedImage.m
|
||||
// Flipboard
|
||||
//
|
||||
// Created by Raphael Schaad on 7/8/13.
|
||||
// Copyright (c) 2013-2014 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
#import "FLAnimatedImage.h"
|
||||
#import <ImageIO/ImageIO.h>
|
||||
#import <MobileCoreServices/MobileCoreServices.h>
|
||||
#import <CoreGraphics/CoreGraphics.h>
|
||||
|
||||
|
||||
// From vm_param.h, define for iOS 8.0 or higher to build on device.
|
||||
#ifndef BYTE_SIZE
|
||||
#define BYTE_SIZE 8 // byte size in bits
|
||||
#endif
|
||||
|
||||
#define MEGABYTE (1024 * 1024)
|
||||
|
||||
|
||||
// An animated image's data size (dimensions * frameCount) category; its value is the max allowed memory (in MB).
|
||||
// E.g.: A 100x200px GIF with 30 frames is ~2.3MB in our pixel format and would fall into the `FLAnimatedImageDataSizeCategoryAll` category.
|
||||
typedef NS_ENUM(NSUInteger, FLAnimatedImageDataSizeCategory) {
|
||||
FLAnimatedImageDataSizeCategoryAll = 10, // All frames permanently in memory (be nice to the CPU)
|
||||
FLAnimatedImageDataSizeCategoryDefault = 75, // A frame cache of default size in memory (usually real-time performance and keeping low memory profile)
|
||||
FLAnimatedImageDataSizeCategoryOnDemand = 250, // Only keep one frame at the time in memory (easier on memory, slowest performance)
|
||||
FLAnimatedImageDataSizeCategoryUnsupported // Even for one frame too large, computer says no.
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSUInteger, FLAnimatedImageFrameCacheSize) {
|
||||
FLAnimatedImageFrameCacheSizeNoLimit = 0, // 0 means no specific limit
|
||||
FLAnimatedImageFrameCacheSizeLowMemory = 1, // The minimum frame cache size; this will produce frames on-demand.
|
||||
FLAnimatedImageFrameCacheSizeGrowAfterMemoryWarning = 2, // If we can produce the frames faster than we consume, one frame ahead will already result in a stutter-free playback.
|
||||
FLAnimatedImageFrameCacheSizeDefault = 5 // Build up a comfy buffer window to cope with CPU hiccups etc.
|
||||
};
|
||||
|
||||
|
||||
@interface FLAnimatedImage ()
|
||||
{
|
||||
// Use old school ivar instead of property for retained non-object types (CF type, dispatch "object") to avoid ARC confusion: http://stackoverflow.com/questions/9684972/strong-property-with-attribute-nsobject-for-a-cf-type-doesnt-retain/9690656#9690656
|
||||
CGImageSourceRef _imageSource;
|
||||
// Note: Only if the deployment target is iOS 6.0 or higher, dispatch objects are declared as "true objects" and participate in ARC, etc.; See <os/object.h> or https://github.com/AFNetworking/AFNetworking/pull/517 for details.
|
||||
dispatch_queue_t _serialQueue;
|
||||
}
|
||||
|
||||
@property (nonatomic, assign, readonly) NSUInteger frameCacheSizeOptimal; // The optimal number of frames to cache based on image size & number of frames; never changes
|
||||
@property (nonatomic, assign) NSUInteger frameCacheSizeMaxInternal; // Allow to cap the cache size e.g. when memory warnings occur; 0 means no specific limit (default)
|
||||
@property (nonatomic, assign) NSUInteger requestedFrameIndex; // Most recently requested frame index
|
||||
@property (nonatomic, assign, readonly) NSUInteger posterImageFrameIndex; // Index of non-purgable poster image; never changes
|
||||
@property (nonatomic, strong, readonly) NSMutableArray *cachedFrames; // Uncached frame indexes hold `NSNull`
|
||||
@property (nonatomic, strong, readonly) NSMutableIndexSet *cachedFrameIndexes; // Indexes of cached frames
|
||||
@property (nonatomic, strong, readonly) NSMutableIndexSet *requestedFrameIndexes; // Indexes of frames that are currently produced in the background
|
||||
@property (nonatomic, strong, readonly) NSIndexSet *allFramesIndexSet; // Default index set with the full range of indexes; never changes
|
||||
@property (nonatomic, assign) NSUInteger memoryWarningCount;
|
||||
|
||||
// The weak proxy is used to break retain cycles with delayed actions from memory warnings.
|
||||
// We are lying about the actual type here to gain static type checking and eliminate casts.
|
||||
// The actual type of the object is `FLWeakProxy`. Lazily instantiated since it is not typically needed.
|
||||
@property (nonatomic, strong, readonly) FLAnimatedImage *weakProxy;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation FLAnimatedImage
|
||||
|
||||
#pragma mark - Accessors
|
||||
#pragma mark Public
|
||||
|
||||
// This is the definite value the frame cache needs to size itself to.
|
||||
- (NSUInteger)frameCacheSizeCurrent
|
||||
{
|
||||
NSUInteger frameCacheSizeCurrent = self.frameCacheSizeOptimal;
|
||||
|
||||
// If set, respect the caps.
|
||||
if (self.frameCacheSizeMax > FLAnimatedImageFrameCacheSizeNoLimit) {
|
||||
frameCacheSizeCurrent = MIN(frameCacheSizeCurrent, self.frameCacheSizeMax);
|
||||
}
|
||||
|
||||
if (self.frameCacheSizeMaxInternal > FLAnimatedImageFrameCacheSizeNoLimit) {
|
||||
frameCacheSizeCurrent = MIN(frameCacheSizeCurrent, self.frameCacheSizeMaxInternal);
|
||||
}
|
||||
|
||||
return frameCacheSizeCurrent;
|
||||
}
|
||||
|
||||
|
||||
- (void)setFrameCacheSizeMax:(NSUInteger)frameCacheSizeMax
|
||||
{
|
||||
if (_frameCacheSizeMax != frameCacheSizeMax) {
|
||||
|
||||
// Remember whether the new cap will cause the current cache size to shrink; then we'll make sure to purge from the cache if needed.
|
||||
BOOL willFrameCacheSizeShrink = (frameCacheSizeMax < self.frameCacheSizeCurrent);
|
||||
|
||||
// Update the value
|
||||
_frameCacheSizeMax = frameCacheSizeMax;
|
||||
|
||||
if (willFrameCacheSizeShrink) {
|
||||
[self purgeFrameCacheIfNeeded];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Private
|
||||
|
||||
- (void)setFrameCacheSizeMaxInternal:(NSUInteger)frameCacheSizeMaxInternal
|
||||
{
|
||||
if (_frameCacheSizeMaxInternal != frameCacheSizeMaxInternal) {
|
||||
|
||||
// Remember whether the new cap will cause the current cache size to shrink; then we'll make sure to purge from the cache if needed.
|
||||
BOOL willFrameCacheSizeShrink = (frameCacheSizeMaxInternal < self.frameCacheSizeCurrent);
|
||||
|
||||
// Update the value
|
||||
_frameCacheSizeMaxInternal = frameCacheSizeMaxInternal;
|
||||
|
||||
if (willFrameCacheSizeShrink) {
|
||||
[self purgeFrameCacheIfNeeded];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Explicit synthesizing for `readonly` property with overridden getter.
|
||||
@synthesize weakProxy = _weakProxy;
|
||||
|
||||
- (FLAnimatedImage *)weakProxy
|
||||
{
|
||||
if (!_weakProxy) {
|
||||
_weakProxy = (id)[FLWeakProxy weakProxyForObject:self];
|
||||
}
|
||||
|
||||
return _weakProxy;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Life Cycle
|
||||
|
||||
- (id)init
|
||||
{
|
||||
NSLog(@"Error: Use `-initWithAnimatedGIFData:` and supply the animated GIF data as an argument to initialize an object of type `FLAnimatedImage`.");
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
- (instancetype)initWithAnimatedGIFData:(NSData *)data imageDrawingBlock:(UIImage *(^)(UIImage *))imageDrawingBlock
|
||||
{
|
||||
// Early return if no data supplied!
|
||||
BOOL hasData = ([data length] > 0);
|
||||
if (!hasData) {
|
||||
NSLog(@"Error: No animated GIF data supplied.");
|
||||
return nil;
|
||||
}
|
||||
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.imageDrawingBlock = imageDrawingBlock;
|
||||
// Do one-time initializations of `readonly` properties directly to ivar to prevent implicit actions and avoid need for private `readwrite` property overrides.
|
||||
|
||||
// Keep a strong reference to `data` and expose it read-only publicly.
|
||||
// However, we will use the `_imageSource` as handler to the image data throughout our life cycle.
|
||||
_data = data;
|
||||
|
||||
// Initialize internal data structures
|
||||
// We'll fill in the initial `NSNull` values below, when we loop through all frames.
|
||||
_cachedFrames = [[NSMutableArray alloc] init];
|
||||
_cachedFrameIndexes = [[NSMutableIndexSet alloc] init];
|
||||
_requestedFrameIndexes = [[NSMutableIndexSet alloc] init];
|
||||
|
||||
// Note: We could leverage `CGImageSourceCreateWithURL` too to add a second initializer `-initWithAnimatedGIFContentsOfURL:`.
|
||||
_imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
|
||||
// Early return on failure!
|
||||
if (!_imageSource) {
|
||||
NSLog(@"Error: Failed to `CGImageSourceCreateWithData` for animated GIF data %@", data);
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Early return if not GIF!
|
||||
CFStringRef imageSourceContainerType = CGImageSourceGetType(_imageSource);
|
||||
BOOL isGIFData = UTTypeConformsTo(imageSourceContainerType, kUTTypeGIF);
|
||||
if (!isGIFData) {
|
||||
NSLog(@"Error: Supplied data is of type %@ and doesn't seem to be GIF data %@", imageSourceContainerType, data);
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Get `LoopCount`
|
||||
// Note: 0 means repeating the animation indefinitely.
|
||||
// Image properties example:
|
||||
// {
|
||||
// FileSize = 314446;
|
||||
// "{GIF}" = {
|
||||
// HasGlobalColorMap = 1;
|
||||
// LoopCount = 0;
|
||||
// };
|
||||
// }
|
||||
NSDictionary *imageProperties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(_imageSource, NULL);
|
||||
_loopCount = [[[imageProperties objectForKey:(id)kCGImagePropertyGIFDictionary] objectForKey:(id)kCGImagePropertyGIFLoopCount] unsignedIntegerValue];
|
||||
|
||||
// Iterate through frame images
|
||||
size_t imageCount = CGImageSourceGetCount(_imageSource);
|
||||
NSMutableArray *delayTimesMutable = [NSMutableArray arrayWithCapacity:imageCount];
|
||||
for (size_t i = 0; i < imageCount; i++) {
|
||||
CGImageRef frameImageRef = CGImageSourceCreateImageAtIndex(_imageSource, i, NULL);
|
||||
if (frameImageRef) {
|
||||
UIImage *frameImage = [UIImage imageWithCGImage:frameImageRef];
|
||||
// Check for valid `frameImage` before parsing its properties as frames can be corrupted (and `frameImage` even `nil` when `frameImageRef` was valid).
|
||||
if (frameImage) {
|
||||
// Set poster image
|
||||
if (!self.posterImage) {
|
||||
if (_imageDrawingBlock)
|
||||
_posterImage = _imageDrawingBlock(frameImage);
|
||||
else
|
||||
_posterImage = frameImage;
|
||||
// Set its size to proxy our size.
|
||||
_size = _posterImage.size;
|
||||
// Remember index of poster image so we never purge it; also add it to the cache.
|
||||
_posterImageFrameIndex = i;
|
||||
self.cachedFrames[self.posterImageFrameIndex] = self.posterImage;
|
||||
[self.cachedFrameIndexes addIndex:self.posterImageFrameIndex];
|
||||
} else {
|
||||
// Placeholder indicates that we don't have a cached frame.
|
||||
// We use an array instead of a dictionary for slightly faster access.
|
||||
self.cachedFrames[i] = [NSNull null];
|
||||
}
|
||||
|
||||
// Get `DelayTime`
|
||||
// Note: It's not in (1/100) of a second like still falsly described in the documentation as per iOS 7 but in seconds stored as `kCFNumberFloat32Type`.
|
||||
// Frame properties example:
|
||||
// {
|
||||
// ColorModel = RGB;
|
||||
// Depth = 8;
|
||||
// PixelHeight = 960;
|
||||
// PixelWidth = 640;
|
||||
// "{GIF}" = {
|
||||
// DelayTime = "0.4";
|
||||
// UnclampedDelayTime = "0.4";
|
||||
// };
|
||||
// }
|
||||
|
||||
NSDictionary *frameProperties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(_imageSource, i, NULL);
|
||||
NSDictionary *framePropertiesGIF = [frameProperties objectForKey:(id)kCGImagePropertyGIFDictionary];
|
||||
|
||||
// Try to use the unclamped delay time; fall back to the normal delay time.
|
||||
NSNumber *delayTime = [framePropertiesGIF objectForKey:(id)kCGImagePropertyGIFUnclampedDelayTime];
|
||||
if (!delayTime) {
|
||||
delayTime = [framePropertiesGIF objectForKey:(id)kCGImagePropertyGIFDelayTime];
|
||||
}
|
||||
// If we don't get a delay time from the properties, fall back to `kDelayTimeIntervalDefault` or carry over the preceding frame's value.
|
||||
const NSTimeInterval kDelayTimeIntervalDefault = 0.1;
|
||||
if (!delayTime) {
|
||||
if (i == 0) {
|
||||
NSLog(@"Verbose: Falling back to default delay time for first frame %@ because none found in GIF properties %@", frameImage, frameProperties);
|
||||
delayTime = @(kDelayTimeIntervalDefault);
|
||||
} else {
|
||||
NSLog(@"Verbose: Falling back to preceding delay time for frame %zu %@ because none found in GIF properties %@", i, frameImage, frameProperties);
|
||||
delayTime = delayTimesMutable[i - 1];
|
||||
}
|
||||
}
|
||||
// Support frame delays as low as `kDelayTimeIntervalMinimum`, with anything below being rounded up to `kDelayTimeIntervalDefault` for legacy compatibility.
|
||||
// This is how the fastest browsers do it as per 2012: http://nullsleep.tumblr.com/post/16524517190/animated-gif-minimum-frame-delay-browser-compatibility
|
||||
const NSTimeInterval kDelayTimeIntervalMinimum = 0.02;
|
||||
// To support the minimum even when rounding errors occur, use an epsilon when comparing. We downcast to float because that's what we get for delayTime from ImageIO.
|
||||
if ([delayTime floatValue] < ((float)kDelayTimeIntervalMinimum - FLT_EPSILON)) {
|
||||
NSLog(@"Verbose: Rounding frame %zu's `delayTime` from %f up to default %f (minimum supported: %f).", i, [delayTime floatValue], kDelayTimeIntervalDefault, kDelayTimeIntervalMinimum);
|
||||
delayTime = @(kDelayTimeIntervalDefault);
|
||||
}
|
||||
delayTimesMutable[i] = delayTime;
|
||||
} else {
|
||||
NSLog(@"Verbose: Dropping frame %zu because valid `CGImageRef` %@ did result in `nil`-`UIImage`.", i, frameImageRef);
|
||||
}
|
||||
CFRelease(frameImageRef);
|
||||
} else {
|
||||
NSLog(@"Verbose: Dropping frame %zu because failed to `CGImageSourceCreateImageAtIndex` with image source %@", i, _imageSource);
|
||||
}
|
||||
}
|
||||
_delayTimes = [delayTimesMutable copy];
|
||||
_frameCount = [_delayTimes count];
|
||||
|
||||
if (self.frameCount == 0) {
|
||||
NSLog(@"Error: Failed to create any valid frames for GIF with properties %@", imageProperties);
|
||||
return nil;
|
||||
} else if (self.frameCount == 1) {
|
||||
// Warn when we only have a single frame but return a valid GIF.
|
||||
NSLog(@"Verbose: Created valid GIF but with only a single frame. Image properties: %@", imageProperties);
|
||||
} else {
|
||||
// We have multiple frames, rock on!
|
||||
}
|
||||
|
||||
// Calculate the optimal frame cache size: try choosing a larger buffer window depending on the predicted image size.
|
||||
// It's only dependent on the image size & number of frames and never changes.
|
||||
CGFloat animatedImageDataSize = CGImageGetBytesPerRow(self.posterImage.CGImage) * self.size.height * self.frameCount / MEGABYTE;
|
||||
if (animatedImageDataSize <= FLAnimatedImageDataSizeCategoryAll) {
|
||||
_frameCacheSizeOptimal = self.frameCount;
|
||||
} else if (animatedImageDataSize <= FLAnimatedImageDataSizeCategoryDefault) {
|
||||
// This value doesn't depend on device memory much because if we're not keeping all frames in memory we will always be decoding 1 frame up ahead per 1 frame that gets played and at this point we might as well just keep a small buffer just large enough to keep from running out of frames.
|
||||
_frameCacheSizeOptimal = FLAnimatedImageFrameCacheSizeDefault;
|
||||
} else {
|
||||
// The predicted size exceeds the limits to build up a cache and we go into low memory mode from the beginning.
|
||||
_frameCacheSizeOptimal = FLAnimatedImageFrameCacheSizeLowMemory;
|
||||
}
|
||||
// In any case, cap the optimal cache size at the frame count.
|
||||
_frameCacheSizeOptimal = MIN(_frameCacheSizeOptimal, self.frameCount);
|
||||
|
||||
// Convenience/minor performance optimization; keep an index set handy with the full range to return in `-frameIndexesToCache`.
|
||||
_allFramesIndexSet = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, self.frameCount)];
|
||||
|
||||
// System Memory Warnings Notification Handler
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
|
||||
if (_weakProxy) {
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget:_weakProxy];
|
||||
}
|
||||
|
||||
if (_imageSource) {
|
||||
CFRelease(_imageSource);
|
||||
}
|
||||
|
||||
// Needed for deployment target iOS 5.0
|
||||
#if ((__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_6_0) || (!defined(__IPHONE_6_0)))
|
||||
if (_serialQueue) {
|
||||
dispatch_release(_serialQueue);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Public Methods
|
||||
|
||||
// See header for more details.
|
||||
// Note: both consumer and producer are throttled: consumer by frame timings and producer by the available memory (max buffer window size).
|
||||
- (UIImage *)imageLazilyCachedAtIndex:(NSUInteger)index
|
||||
{
|
||||
// Early return if the requested index is beyond bounds.
|
||||
// Note: We're comparing an index with a count and need to bail on greater than or equal to.
|
||||
if (index >= self.frameCount) {
|
||||
NSLog(@"Error: Skipping requested frame %lu beyond bounds (total frame count: %lu) for animated image: %@", (unsigned long)index, (unsigned long)self.frameCount, self);
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Remember requested frame index, this influences what we should cache next.
|
||||
self.requestedFrameIndex = index;
|
||||
|
||||
// Quick check to avoid doing any work if we already have all possible frames cached, a common case.
|
||||
if ([self.cachedFrameIndexes count] < self.frameCount) {
|
||||
// If we have frames that should be cached but aren't and aren't requested yet, request them.
|
||||
// Exclude existing cached frames, frames already requested, and specially cached poster image.
|
||||
NSMutableIndexSet *frameIndexesToAddToCacheMutable = [[self frameIndexesToCache] mutableCopy];
|
||||
[frameIndexesToAddToCacheMutable removeIndexes:self.cachedFrameIndexes];
|
||||
[frameIndexesToAddToCacheMutable removeIndexes:self.requestedFrameIndexes];
|
||||
[frameIndexesToAddToCacheMutable removeIndex:self.posterImageFrameIndex];
|
||||
NSIndexSet *frameIndexesToAddToCache = [frameIndexesToAddToCacheMutable copy];
|
||||
|
||||
// Asynchronously add frames to our cache.
|
||||
if ([frameIndexesToAddToCache count] > 0) {
|
||||
[self addFrameIndexesToCache:frameIndexesToAddToCache];
|
||||
}
|
||||
}
|
||||
|
||||
// Get the specified image. Watch out for `NSNull` placeholders.
|
||||
UIImage *image = nil;
|
||||
id tryImage = self.cachedFrames[index];
|
||||
if ([tryImage isKindOfClass:[UIImage class]]) {
|
||||
image = tryImage;
|
||||
}
|
||||
|
||||
// Purge if needed based on the current playhead position.
|
||||
[self purgeFrameCacheIfNeeded];
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
|
||||
// Only called once from `-imageLazilyCachedAtIndex` but factored into its own method for logical grouping.
|
||||
- (void)addFrameIndexesToCache:(NSIndexSet *)frameIndexesToAddToCache
|
||||
{
|
||||
// Order matters. First, iterate over the indexes starting from the requested frame index.
|
||||
// Then, if there are any indexes before the requested frame index, do those.
|
||||
NSRange firstRange = NSMakeRange(self.requestedFrameIndex, self.frameCount - self.requestedFrameIndex);
|
||||
NSRange secondRange = NSMakeRange(0, self.requestedFrameIndex);
|
||||
if (firstRange.length + secondRange.length != self.frameCount) {
|
||||
NSLog(@"Error: Two-part frame cache range doesn't equal full range.");
|
||||
}
|
||||
|
||||
// Add to the requested list before we actually kick them off, so they don't get into the queue twice.
|
||||
[self.requestedFrameIndexes addIndexes:frameIndexesToAddToCache];
|
||||
|
||||
// Lazily create dedicated isolation queue.
|
||||
if (!_serialQueue) {
|
||||
_serialQueue = dispatch_queue_create("com.flipboard.framecachingqueue", DISPATCH_QUEUE_SERIAL);
|
||||
}
|
||||
|
||||
// Start streaming requested frames in the background into the cache.
|
||||
// Avoid capturing self in the block as there's no reason to keep doing work if the animated image went away.
|
||||
FLAnimatedImage * __weak weakSelf = self;
|
||||
dispatch_async(_serialQueue, ^{
|
||||
// Produce and cache next needed frame.
|
||||
void (^frameRangeBlock)(NSRange, BOOL *) = ^(NSRange range, __unused BOOL *stop) {
|
||||
// Iterate through contiguous indexes; can be faster than `enumerateIndexesInRange:options:usingBlock:`.
|
||||
__strong FLAnimatedImage *strongSelf = weakSelf;
|
||||
for (NSUInteger i = range.location; i < NSMaxRange(range); i++) {
|
||||
UIImage *image = [strongSelf predrawnImageAtIndex:i];
|
||||
// The results get returned one by one as soon as they're ready (and not in batch).
|
||||
// The benefits of having the first frames as quick as possible outweigh building up a buffer to cope with potential hiccups when the CPU suddenly gets busy.
|
||||
if (image && strongSelf) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
strongSelf.cachedFrames[i] = image;
|
||||
[strongSelf.cachedFrameIndexes addIndex:i];
|
||||
[strongSelf.requestedFrameIndexes removeIndex:i];
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Guard against crashing on 0-length ranges with an 'NSRangeException' "last range index (-1) beyond bounds" (Apple's error message is bogus here).
|
||||
// This is only needed on iOS 5, iPad only, running on device and only for the range {0,0} but regardless of whether the index set is mutable or immutable or what the indexes in the set are (can even be empty).
|
||||
if (firstRange.length > 0) {
|
||||
[frameIndexesToAddToCache enumerateRangesInRange:firstRange options:0 usingBlock:frameRangeBlock];
|
||||
}
|
||||
if (secondRange.length > 0) {
|
||||
[frameIndexesToAddToCache enumerateRangesInRange:secondRange options:0 usingBlock:frameRangeBlock];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+ (CGSize)sizeForImage:(id)image
|
||||
{
|
||||
CGSize imageSize = CGSizeZero;
|
||||
|
||||
// Early return for nil
|
||||
if (!image) {
|
||||
return imageSize;
|
||||
}
|
||||
|
||||
if ([image isKindOfClass:[UIImage class]]) {
|
||||
UIImage *uiImage = (UIImage *)image;
|
||||
imageSize = uiImage.size;
|
||||
} else if ([image isKindOfClass:[FLAnimatedImage class]]) {
|
||||
FLAnimatedImage *animatedImage = (FLAnimatedImage *)image;
|
||||
imageSize = animatedImage.size;
|
||||
} else {
|
||||
// Bear trap to capture bad images; we have seen crashers cropping up on iOS 7.
|
||||
NSLog(@"Error: `image` isn't of expected types `UIImage` or `FLAnimatedImage`: %@", image);
|
||||
}
|
||||
|
||||
return imageSize;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Private Methods
|
||||
#pragma mark Frame Loading
|
||||
|
||||
- (UIImage *)predrawnImageAtIndex:(NSUInteger)index
|
||||
{
|
||||
// It's very important to use the cached `_imageSource` since the random access to a frame with `CGImageSourceCreateImageAtIndex` turns from an O(1) into an O(n) operation when re-initializing the image source every time.
|
||||
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(_imageSource, index, NULL);
|
||||
UIImage *image = [UIImage imageWithCGImage:imageRef];
|
||||
CFRelease(imageRef);
|
||||
|
||||
if (_imageDrawingBlock)
|
||||
image = _imageDrawingBlock(image);
|
||||
else
|
||||
{
|
||||
// Loading in the image object is only half the work, the displaying image view would still have to synchronosly wait and decode the image, so we go ahead and do that here on the background thread.
|
||||
image = [[self class] predrawnImageFromImage:image];
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Frame Caching
|
||||
|
||||
- (NSIndexSet *)frameIndexesToCache
|
||||
{
|
||||
NSIndexSet *indexesToCache = nil;
|
||||
// Quick check to avoid building the index set if the number of frames to cache equals the total frame count.
|
||||
if (self.frameCacheSizeCurrent == self.frameCount) {
|
||||
indexesToCache = self.allFramesIndexSet;
|
||||
} else {
|
||||
NSMutableIndexSet *indexesToCacheMutable = [[NSMutableIndexSet alloc] init];
|
||||
|
||||
// Add indexes to the set in two separate blocks- the first starting from the requested frame index, up to the limit or the end.
|
||||
// The second, if needed, the remaining number of frames beginning at index zero.
|
||||
NSUInteger firstLength = MIN(self.frameCacheSizeCurrent, self.frameCount - self.requestedFrameIndex);
|
||||
NSRange firstRange = NSMakeRange(self.requestedFrameIndex, firstLength);
|
||||
[indexesToCacheMutable addIndexesInRange:firstRange];
|
||||
NSUInteger secondLength = self.frameCacheSizeCurrent - firstLength;
|
||||
if (secondLength > 0) {
|
||||
NSRange secondRange = NSMakeRange(0, secondLength);
|
||||
[indexesToCacheMutable addIndexesInRange:secondRange];
|
||||
}
|
||||
// Double check our math, before we add the poster image index which may increase it by one.
|
||||
if ([indexesToCacheMutable count] != self.frameCacheSizeCurrent) {
|
||||
NSLog(@"Error: Number of frames to cache doesn't equal expected cache size.");
|
||||
}
|
||||
|
||||
[indexesToCacheMutable addIndex:self.posterImageFrameIndex];
|
||||
|
||||
indexesToCache = [indexesToCacheMutable copy];
|
||||
}
|
||||
|
||||
return indexesToCache;
|
||||
}
|
||||
|
||||
|
||||
- (void)purgeFrameCacheIfNeeded
|
||||
{
|
||||
// Purge frames that are currently cached but don't need to be.
|
||||
// But not if we're still under the number of frames to cache.
|
||||
// This way, if all frames are allowed to be cached (the common case), we can skip all the `NSIndexSet` math below.
|
||||
if ([self.cachedFrameIndexes count] > self.frameCacheSizeCurrent) {
|
||||
NSMutableIndexSet *indexesToPurge = [self.cachedFrameIndexes mutableCopy];
|
||||
[indexesToPurge removeIndexes:[self frameIndexesToCache]];
|
||||
[indexesToPurge enumerateRangesUsingBlock:^(NSRange range, __unused BOOL *stop) {
|
||||
// Iterate through contiguous indexes; can be faster than `enumerateIndexesInRange:options:usingBlock:`.
|
||||
for (NSUInteger i = range.location; i < NSMaxRange(range); i++) {
|
||||
[self.cachedFrameIndexes removeIndex:i];
|
||||
self.cachedFrames[i] = [NSNull null];
|
||||
// Note: Don't `CGImageSourceRemoveCacheAtIndex` on the image source for frames that we don't want cached any longer to maintain O(1) time access.
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void)growFrameCacheSizeAfterMemoryWarning:(NSNumber *)frameCacheSize
|
||||
{
|
||||
self.frameCacheSizeMaxInternal = [frameCacheSize unsignedIntegerValue];
|
||||
NSLog(@"Verbose: Grew frame cache size max to %lu after memory warning for animated image: %@", (unsigned long)self.frameCacheSizeMaxInternal, self);
|
||||
|
||||
// Schedule resetting the frame cache size max completely after a while.
|
||||
const NSTimeInterval kResetDelay = 3.0;
|
||||
[self.weakProxy performSelector:@selector(resetFrameCacheSizeMaxInternal) withObject:nil afterDelay:kResetDelay];
|
||||
}
|
||||
|
||||
|
||||
- (void)resetFrameCacheSizeMaxInternal
|
||||
{
|
||||
self.frameCacheSizeMaxInternal = FLAnimatedImageFrameCacheSizeNoLimit;
|
||||
NSLog(@"Verbose: Reset frame cache size max (current frame cache size: %lu) for animated image: %@", (unsigned long)self.frameCacheSizeCurrent, self);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark System Memory Warnings Notification Handler
|
||||
|
||||
- (void)didReceiveMemoryWarning:(NSNotification *)__unused notification
|
||||
{
|
||||
// Hold (and use!) a strong reference to self, since `NSNotificationCenter` no longer strongly references the observer.
|
||||
// This is another example of the lame fallout from the LLVM change in Xcode 5.1.
|
||||
FLAnimatedImage *strongSelf = self;
|
||||
|
||||
strongSelf.memoryWarningCount++;
|
||||
|
||||
// If we were about to grow larger, but got rapped on our knuckles by the system again, cancel.
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget:strongSelf.weakProxy selector:@selector(growFrameCacheSizeAfterMemoryWarning:) object:@(FLAnimatedImageFrameCacheSizeGrowAfterMemoryWarning)];
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget:strongSelf.weakProxy selector:@selector(resetFrameCacheSizeMaxInternal) object:nil];
|
||||
|
||||
// Go down to the minimum and by that implicitly immediately purge from the cache if needed to not get jettisoned by the system and start producing frames on-demand.
|
||||
NSLog(@"Verbose: Attempt setting frame cache size max to %lu (previous was %lu) after memory warning #%lu for animated image: %@", (unsigned long)FLAnimatedImageFrameCacheSizeLowMemory, (unsigned long)strongSelf.frameCacheSizeMaxInternal, (unsigned long)strongSelf.memoryWarningCount, strongSelf);
|
||||
strongSelf.frameCacheSizeMaxInternal = FLAnimatedImageFrameCacheSizeLowMemory;
|
||||
|
||||
// Schedule growing larger again after a while, but cap our attempts to prevent a periodic sawtooth wave (ramps upward and then sharply drops) of memory usage.
|
||||
//
|
||||
// [mem]^ (2) (5) (6) 1) Loading frames for the first time
|
||||
// (*)| , , , 2) Mem warning #1; purge cache
|
||||
// | /| (4)/| /| 3) Grow cache size a bit after a while, if no mem warning occurs
|
||||
// | / | _/ | _/ | 4) Try to grow cache size back to optimum after a while, if no mem warning occurs
|
||||
// |(1)/ |_/ |/ |__(7) 5) Mem warning #2; purge cache
|
||||
// |__/ (3) 6) After repetition of (3) and (4), mem warning #3; purge cache
|
||||
// +----------------------> 7) After 3 mem warnings, stay at minimum cache size
|
||||
// [t]
|
||||
// *) The mem high water mark before we get warned might change for every cycle.
|
||||
//
|
||||
const NSUInteger kGrowAttemptsMax = 2;
|
||||
const NSTimeInterval kGrowDelay = 2.0;
|
||||
if ((strongSelf.memoryWarningCount - 1) <= kGrowAttemptsMax) {
|
||||
[strongSelf.weakProxy performSelector:@selector(growFrameCacheSizeAfterMemoryWarning:) withObject:@(FLAnimatedImageFrameCacheSizeGrowAfterMemoryWarning) afterDelay:kGrowDelay];
|
||||
}
|
||||
|
||||
// Note: It's not possible to get the level of a memory warning with a public API: http://stackoverflow.com/questions/2915247/iphone-os-memory-warnings-what-do-the-different-levels-mean/2915477#2915477
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Image Decoding
|
||||
|
||||
// Decodes the image's data and draws it off-screen fully in memory; it's thread-safe and hence can be called on a background thread.
|
||||
// On success, the returned object is a new `UIImage` instance with the same content as the one passed in.
|
||||
// On failure, the returned object is the unchanged passed in one; the data will not be predrawn in memory though and an error will be logged.
|
||||
// First inspired by & good Karma to: https://gist.github.com/steipete/1144242
|
||||
+ (UIImage *)predrawnImageFromImage:(UIImage *)imageToPredraw
|
||||
{
|
||||
// Always use a device RGB color space for simplicity and predictability what will be going on.
|
||||
CGColorSpaceRef colorSpaceDeviceRGBRef = CGColorSpaceCreateDeviceRGB();
|
||||
// Early return on failure!
|
||||
if (!colorSpaceDeviceRGBRef) {
|
||||
NSLog(@"Error: Failed to `CGColorSpaceCreateDeviceRGB` for image %@", imageToPredraw);
|
||||
return imageToPredraw;
|
||||
}
|
||||
|
||||
// Even when the image doesn't have transparency, we have to add the extra channel because Quartz doesn't support other pixel formats than 32 bpp/8 bpc for RGB:
|
||||
// kCGImageAlphaNoneSkipFirst, kCGImageAlphaNoneSkipLast, kCGImageAlphaPremultipliedFirst, kCGImageAlphaPremultipliedLast
|
||||
// (source: docs "Quartz 2D Programming Guide > Graphics Contexts > Table 2-1 Pixel formats supported for bitmap graphics contexts")
|
||||
size_t numberOfComponents = CGColorSpaceGetNumberOfComponents(colorSpaceDeviceRGBRef) + 1; // 4: RGB + A
|
||||
|
||||
// "In iOS 4.0 and later, and OS X v10.6 and later, you can pass NULL if you want Quartz to allocate memory for the bitmap." (source: docs)
|
||||
void *data = NULL;
|
||||
size_t width = (size_t)imageToPredraw.size.width;
|
||||
size_t height = (size_t)imageToPredraw.size.height;
|
||||
size_t bitsPerComponent = CHAR_BIT;
|
||||
|
||||
size_t bitsPerPixel = (bitsPerComponent * numberOfComponents);
|
||||
size_t bytesPerPixel = (bitsPerPixel / BYTE_SIZE);
|
||||
size_t bytesPerRow = (bytesPerPixel * width);
|
||||
|
||||
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault;
|
||||
|
||||
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageToPredraw.CGImage);
|
||||
// If the alpha info doesn't match to one of the supported formats (see above), pick a reasonable supported one.
|
||||
// "For bitmaps created in iOS 3.2 and later, the drawing environment uses the premultiplied ARGB format to store the bitmap data." (source: docs)
|
||||
if (alphaInfo == kCGImageAlphaNone || alphaInfo == kCGImageAlphaOnly) {
|
||||
alphaInfo = kCGImageAlphaNoneSkipFirst;
|
||||
} else if (alphaInfo == kCGImageAlphaFirst) {
|
||||
alphaInfo = kCGImageAlphaPremultipliedFirst;
|
||||
} else if (alphaInfo == kCGImageAlphaLast) {
|
||||
alphaInfo = kCGImageAlphaPremultipliedLast;
|
||||
}
|
||||
// "The constants for specifying the alpha channel information are declared with the `CGImageAlphaInfo` type but can be passed to this parameter safely." (source: docs)
|
||||
bitmapInfo |= alphaInfo;
|
||||
|
||||
// Create our own graphics context to draw to; `UIGraphicsGetCurrentContext`/`UIGraphicsBeginImageContextWithOptions` doesn't create a new context but returns the current one which isn't thread-safe (e.g. main thread could use it at the same time).
|
||||
// Note: It's not worth caching the bitmap context for multiple frames ("unique key" would be `width`, `height` and `hasAlpha`), it's ~50% slower. Time spent in libRIP's `CGSBlendBGRA8888toARGB8888` suddenly shoots up -- not sure why.
|
||||
CGContextRef bitmapContextRef = CGBitmapContextCreate(data, width, height, bitsPerComponent, bytesPerRow, colorSpaceDeviceRGBRef, bitmapInfo);
|
||||
CGColorSpaceRelease(colorSpaceDeviceRGBRef);
|
||||
// Early return on failure!
|
||||
if (!bitmapContextRef) {
|
||||
NSLog(@"Error: Failed to `CGBitmapContextCreate` with color space %@ and parameters (width: %zu height: %zu bitsPerComponent: %zu bytesPerRow: %zu) for image %@", colorSpaceDeviceRGBRef, width, height, bitsPerComponent, bytesPerRow, imageToPredraw);
|
||||
return imageToPredraw;
|
||||
}
|
||||
|
||||
// Draw image in bitmap context and create image by preserving receiver's properties.
|
||||
CGContextDrawImage(bitmapContextRef, CGRectMake(0.0, 0.0, imageToPredraw.size.width, imageToPredraw.size.height), imageToPredraw.CGImage);
|
||||
CGImageRef predrawnImageRef = CGBitmapContextCreateImage(bitmapContextRef);
|
||||
UIImage *predrawnImage = [UIImage imageWithCGImage:predrawnImageRef scale:imageToPredraw.scale orientation:imageToPredraw.imageOrientation];
|
||||
CGImageRelease(predrawnImageRef);
|
||||
CGContextRelease(bitmapContextRef);
|
||||
|
||||
// Early return on failure!
|
||||
if (!predrawnImage) {
|
||||
NSLog(@"Error: Failed to `imageWithCGImage:scale:orientation:` with image ref %@ created with color space %@ and bitmap context %@ and properties and properties (scale: %f orientation: %ld) for image %@", predrawnImageRef, colorSpaceDeviceRGBRef, bitmapContextRef, imageToPredraw.scale, (long)imageToPredraw.imageOrientation, imageToPredraw);
|
||||
return imageToPredraw;
|
||||
}
|
||||
|
||||
return predrawnImage;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Description
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
NSString *description = [super description];
|
||||
|
||||
description = [description stringByAppendingFormat:@" size=%@", NSStringFromCGSize(self.size)];
|
||||
description = [description stringByAppendingFormat:@" frameCount=%lu", (unsigned long)self.frameCount];
|
||||
|
||||
return description;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark - FLWeakProxy
|
||||
|
||||
@interface FLWeakProxy ()
|
||||
|
||||
@property (nonatomic, weak) id target;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation FLWeakProxy
|
||||
|
||||
#pragma mark Life Cycle
|
||||
|
||||
// This is the designated creation method of an `FLWeakProxy` and
|
||||
// as a subclass of `NSProxy` it doesn't respond to or need `-init`.
|
||||
+ (instancetype)weakProxyForObject:(id)targetObject
|
||||
{
|
||||
FLWeakProxy *weakProxy = [FLWeakProxy alloc];
|
||||
weakProxy.target = targetObject;
|
||||
return weakProxy;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Forwarding Messages
|
||||
|
||||
- (id)forwardingTargetForSelector:(SEL)__unused selector
|
||||
{
|
||||
// Keep it lightweight: access the ivar directly
|
||||
return _target;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - NSWeakProxy Method Overrides
|
||||
#pragma mark Handling Unimplemented Methods
|
||||
|
||||
- (void)forwardInvocation:(NSInvocation *)invocation
|
||||
{
|
||||
// Fallback for when target is nil. Don't do anything, just return 0/NULL/nil.
|
||||
// The method signature we've received to get here is just a dummy to keep `doesNotRecognizeSelector:` from firing.
|
||||
// We can't really handle struct return types here because we don't know the length.
|
||||
void *nullPointer = NULL;
|
||||
[invocation setReturnValue:&nullPointer];
|
||||
}
|
||||
|
||||
|
||||
- (NSMethodSignature *)methodSignatureForSelector:(SEL)__unused selector
|
||||
{
|
||||
// We only get here if `forwardingTargetForSelector:` returns nil.
|
||||
// In that case, our weak target has been reclaimed. Return a dummy method signature to keep `doesNotRecognizeSelector:` from firing.
|
||||
// We'll emulate the Obj-c messaging nil behavior by setting the return value to nil in `forwardInvocation:`, but we'll assume that the return value is `sizeof(void *)`.
|
||||
// Other libraries handle this situation by making use of a global method signature cache, but that seems heavier than necessary and has issues as well.
|
||||
// See https://www.mikeash.com/pyblog/friday-qa-2010-02-26-futures.html and https://github.com/steipete/PSTDelegateProxy/issues/1 for examples of using a method signature cache.
|
||||
return [NSObject instanceMethodSignatureForSelector:@selector(init)];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright (C) 2007 Apple Inc. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
|
||||
* its contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef FloatConversion_h
|
||||
#define FloatConversion_h
|
||||
|
||||
#include <CoreGraphics/CGBase.h>
|
||||
|
||||
namespace WebCore {
|
||||
|
||||
template<typename T>
|
||||
float narrowPrecisionToFloat(T);
|
||||
|
||||
template<>
|
||||
inline float narrowPrecisionToFloat(double number)
|
||||
{
|
||||
return static_cast<float>(number);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
CGFloat narrowPrecisionToCGFloat(T);
|
||||
|
||||
template<>
|
||||
inline CGFloat narrowPrecisionToCGFloat(double number)
|
||||
{
|
||||
return static_cast<CGFloat>(number);
|
||||
}
|
||||
|
||||
} // namespace WebCore
|
||||
|
||||
#endif // FloatConversion_h
|
||||
54
submodules/LegacyComponents/LegacyComponents/Freedom.h
Normal file
54
submodules/LegacyComponents/LegacyComponents/Freedom.h
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
#ifndef Freedom_h
|
||||
#define Freedom_h
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
const char *string;
|
||||
uint32_t key;
|
||||
} FreedomIdentifier;
|
||||
|
||||
char *copyFreedomIdentifierValue(FreedomIdentifier identifier);
|
||||
|
||||
extern FreedomIdentifier FreedomIdentifierEmpty;
|
||||
|
||||
typedef struct {
|
||||
uint32_t name;
|
||||
IMP imp;
|
||||
FreedomIdentifier newIdentifier;
|
||||
FreedomIdentifier newEncoding;
|
||||
} FreedomDecoration;
|
||||
|
||||
typedef struct {
|
||||
ptrdiff_t offset;
|
||||
int bit;
|
||||
} FreedomBitfield;
|
||||
|
||||
void freedomInit();
|
||||
bool freedomInitialized();
|
||||
|
||||
Class freedomClass(uint32_t name);
|
||||
Class freedomMakeClass(Class superclass, Class subclass);
|
||||
ptrdiff_t freedomIvarOffset(Class targetClass, uint32_t name);
|
||||
FreedomBitfield freedomIvarBitOffset(Class targetClass, uint32_t fieldName, uint32_t bitfieldName);
|
||||
FreedomBitfield freedomIvarBitOffset2(Class targetClass, uint32_t fieldName, uint32_t bitfieldName);
|
||||
void freedomSetBitfield(void *object, FreedomBitfield bitfield, int value);
|
||||
int freedomGetBitfield(void *object, FreedomBitfield bitfield);
|
||||
void freedomDumpBitfields(Class targetClass, void *object, uint32_t fieldName);
|
||||
|
||||
IMP freedomNativeImpl(Class targetClass, SEL selector);
|
||||
void freedomClassAutoDecorate(uint32_t name, FreedomDecoration *classDecorations, int numClassDecorations, FreedomDecoration *instanceDecorations, int numInstanceDecorations);
|
||||
void freedomClassAutoDecorateExplicit(Class c, uint32_t name, FreedomDecoration *classDecorations, int numClassDecorations, FreedomDecoration *instanceDecorations, int numInstanceDecorations);
|
||||
IMP freedomImpl(id target, uint32_t name, SEL *selector);
|
||||
IMP freedomImplInstancesOfClass(Class targetClass, uint32_t name, SEL *selector);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
675
submodules/LegacyComponents/LegacyComponents/Freedom.mm
Normal file
675
submodules/LegacyComponents/LegacyComponents/Freedom.mm
Normal file
|
|
@ -0,0 +1,675 @@
|
|||
#import "Freedom.h"
|
||||
|
||||
#import <objc/runtime.h>
|
||||
#import <objc/message.h>
|
||||
|
||||
#import <map>
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#define FORCE_INLINE __attribute__((always_inline))
|
||||
|
||||
static inline uint32_t rotl32 ( uint32_t x, int8_t r )
|
||||
{
|
||||
return (x << r) | (x >> (32 - r));
|
||||
}
|
||||
|
||||
#define ROTL32(x,y) rotl32(x,y)
|
||||
|
||||
static FORCE_INLINE uint32_t getblock ( const uint32_t * p, int i )
|
||||
{
|
||||
return p[i];
|
||||
}
|
||||
|
||||
static FORCE_INLINE uint32_t fmix ( uint32_t h )
|
||||
{
|
||||
h ^= h >> 16;
|
||||
h *= 0x85ebca6b;
|
||||
h ^= h >> 13;
|
||||
h *= 0xc2b2ae35;
|
||||
h ^= h >> 16;
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
static void MurmurHash3_x86_32 ( const void * key, int len,
|
||||
uint32_t seed, void * out )
|
||||
{
|
||||
const uint8_t * data = (const uint8_t*)key;
|
||||
const int nblocks = len / 4;
|
||||
|
||||
uint32_t h1 = seed;
|
||||
|
||||
const uint32_t c1 = 0xcc9e2d51;
|
||||
const uint32_t c2 = 0x1b873593;
|
||||
|
||||
//----------
|
||||
// body
|
||||
|
||||
const uint32_t * blocks = (const uint32_t *)(data + nblocks*4);
|
||||
|
||||
for(int i = -nblocks; i; i++)
|
||||
{
|
||||
uint32_t k1 = getblock(blocks,i);
|
||||
|
||||
k1 *= c1;
|
||||
k1 = ROTL32(k1,15);
|
||||
k1 *= c2;
|
||||
|
||||
h1 ^= k1;
|
||||
h1 = ROTL32(h1,13);
|
||||
h1 = h1*5+0xe6546b64;
|
||||
}
|
||||
|
||||
//----------
|
||||
// tail
|
||||
|
||||
const uint8_t * tail = (const uint8_t*)(data + nblocks*4);
|
||||
|
||||
uint32_t k1 = 0;
|
||||
|
||||
switch(len & 3)
|
||||
{
|
||||
case 3: k1 ^= tail[2] << 16;
|
||||
case 2: k1 ^= tail[1] << 8;
|
||||
case 1: k1 ^= tail[0];
|
||||
k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1;
|
||||
};
|
||||
|
||||
//----------
|
||||
// finalization
|
||||
|
||||
h1 ^= len;
|
||||
|
||||
h1 = fmix(h1);
|
||||
|
||||
*(uint32_t*)out = h1;
|
||||
}
|
||||
|
||||
static int32_t murMurHashBytes32(void *bytes, int length)
|
||||
{
|
||||
int32_t result = 0;
|
||||
MurmurHash3_x86_32(bytes, length, -137723950, &result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static const char *freedomDecoratedClass = "freedomDecoratedClass";
|
||||
|
||||
static int (*freedom_getClassList)(Class *, int) = NULL;
|
||||
static const char * (*freedom_class_getName)(Class cls) = NULL;
|
||||
static bool freedom_initialized = false;
|
||||
|
||||
FreedomIdentifier FreedomIdentifierEmpty = (FreedomIdentifier){ .string = NULL, .key = 0 };
|
||||
|
||||
char *copyFreedomIdentifierValue(FreedomIdentifier identifier)
|
||||
{
|
||||
if (identifier.string == NULL)
|
||||
return NULL;
|
||||
|
||||
int length = (int)strlen(identifier.string) / 2;
|
||||
char *buf = (char *)malloc(length + 1);
|
||||
buf[length] = 0;
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
int b = 0;
|
||||
sscanf(identifier.string + i * 2, "%02x", &b);
|
||||
buf[i] = ((char)b) ^ (((uint8_t *)&identifier.key)[i % 4]);
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
Class freedomClass(uint32_t name)
|
||||
{
|
||||
static std::map<uint32_t, __unsafe_unretained Class> classMap;
|
||||
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^
|
||||
{
|
||||
int classCount = freedom_getClassList(NULL, 0);
|
||||
if (classCount > 0)
|
||||
{
|
||||
__unsafe_unretained Class *classList = (Class *)malloc(classCount * sizeof(Class));
|
||||
objc_getClassList(classList, classCount);
|
||||
|
||||
for (int i = 0; i < classCount; i++)
|
||||
{
|
||||
const char *className = freedom_class_getName(classList[i]);
|
||||
uint32_t hashName = (uint32_t)murMurHashBytes32((void *)className, (int)strlen(className));
|
||||
//NSLog(@"0x%" PRIx32 " -> %s", hashName, className);
|
||||
classMap[hashName] = classList[i];
|
||||
}
|
||||
|
||||
free(classList);
|
||||
}
|
||||
freedom_initialized = true;
|
||||
});
|
||||
|
||||
auto it = classMap.find(name);
|
||||
if (it != classMap.end())
|
||||
return it->second;
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
Class freedomMakeClass(Class superclass, Class subclass)
|
||||
{
|
||||
if (superclass == Nil || subclass == Nil)
|
||||
return nil;
|
||||
|
||||
int32_t randomId = 0;
|
||||
arc4random_buf(&randomId, 4);
|
||||
Class decoratedClass = objc_allocateClassPair(superclass, [[NSString alloc] initWithFormat:@"Decorated%" PRIx32 "", randomId].UTF8String, 0);
|
||||
|
||||
unsigned int count = 0;
|
||||
Method *methodList = class_copyMethodList(subclass, &count);
|
||||
if (methodList != NULL)
|
||||
{
|
||||
for (unsigned int i = 0; i < count; i++)
|
||||
{
|
||||
class_addMethod(decoratedClass, method_getName(methodList[i]), method_getImplementation(methodList[i]), method_getTypeEncoding(methodList[i]));
|
||||
}
|
||||
|
||||
free(methodList);
|
||||
}
|
||||
|
||||
objc_registerClassPair(decoratedClass);
|
||||
|
||||
return decoratedClass;
|
||||
}
|
||||
|
||||
ptrdiff_t freedomIvarOffset(Class targetClass, uint32_t name)
|
||||
{
|
||||
ptrdiff_t result = -1;
|
||||
|
||||
unsigned int count = 0;
|
||||
Ivar *ivarList = class_copyIvarList(targetClass, &count);
|
||||
|
||||
if (ivarList != NULL)
|
||||
{
|
||||
for (unsigned int i = 0; i < count; i++)
|
||||
{
|
||||
const char *ivarName = ivar_getName(ivarList[i]);
|
||||
uint32_t hashName = (uint32_t)murMurHashBytes32((void *)ivarName, (int)strlen(ivarName));
|
||||
//NSLog(@"%s -> 0x%x", ivarName, hashName);
|
||||
if (hashName == name)
|
||||
{
|
||||
result = ivar_getOffset(ivarList[i]);
|
||||
}
|
||||
}
|
||||
|
||||
free(ivarList);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool consumeToken(const char *desc, int length, int &offset, const char *token)
|
||||
{
|
||||
size_t tokenLength = strlen(token);
|
||||
if (offset + (int)tokenLength > length)
|
||||
return false;
|
||||
|
||||
if (strncmp(desc + offset, token, tokenLength))
|
||||
return false;
|
||||
|
||||
offset += tokenLength;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool consumeNumber(const char *desc, int length, int &offset, int &number)
|
||||
{
|
||||
if (offset + 1 > length)
|
||||
return false;
|
||||
|
||||
int result = 0;
|
||||
|
||||
for (int i = offset; i < length; i++)
|
||||
{
|
||||
if (desc[i] < '0' || desc[i] > '9')
|
||||
{
|
||||
if (i != offset)
|
||||
{
|
||||
number = result;
|
||||
offset = i;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
result = result * 10 + (desc[i] - '0');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool consumeField(const char *desc, int length, int &offset, uint32_t &outName, int &outBitSize)
|
||||
{
|
||||
if (!consumeToken(desc, length, offset, "\""))
|
||||
return false;
|
||||
|
||||
for (int i = offset; i < length; i++)
|
||||
{
|
||||
if (desc[i] == '"')
|
||||
{
|
||||
outName = (uint32_t)murMurHashBytes32((void *)(desc + offset), i - offset);
|
||||
|
||||
char buf[i - offset + 1];
|
||||
memcpy(buf, desc + offset, i - offset);
|
||||
buf[i - offset] = 0;
|
||||
|
||||
int newOffset = i + 1;
|
||||
if (consumeToken(desc, length, newOffset, "b"))
|
||||
{
|
||||
int fieldLength = 0;
|
||||
if (consumeNumber(desc, length, newOffset, fieldLength))
|
||||
{
|
||||
offset = newOffset;
|
||||
outBitSize = fieldLength;
|
||||
//NSLog(@"%s", buf);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
else if (consumeToken(desc, length, newOffset, "I"))
|
||||
{
|
||||
offset = newOffset;
|
||||
outBitSize = sizeof(unsigned int) * 8;
|
||||
//NSLog(@"%s", buf);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int freedomBitfieldOffsetInBits(const char *desc, uint32_t name)
|
||||
{
|
||||
size_t length = strlen(desc);
|
||||
int offset = 0;
|
||||
|
||||
if (!consumeToken(desc, (int)length, offset, "{?="))
|
||||
return -1;
|
||||
|
||||
int currentBitOffset = 0;
|
||||
while (true)
|
||||
{
|
||||
uint32_t fieldName = 0;
|
||||
int bitLength = 0;
|
||||
if (consumeField(desc, (int)length, offset, fieldName, bitLength))
|
||||
{
|
||||
if (fieldName == name)
|
||||
return currentBitOffset;
|
||||
|
||||
currentBitOffset += bitLength;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void freedomDumpBitfieldsByDescription(void *address, const char *desc)
|
||||
{
|
||||
size_t length = strlen(desc);
|
||||
int offset = 0;
|
||||
|
||||
if (!consumeToken(desc, (int)length, offset, "{?="))
|
||||
return;
|
||||
|
||||
int currentBitOffset = 0;
|
||||
while (true)
|
||||
{
|
||||
uint32_t fieldName = 0;
|
||||
int bitLength = 0;
|
||||
if (consumeField(desc, (int)length, offset, fieldName, bitLength))
|
||||
{
|
||||
if (bitLength == 1)
|
||||
{
|
||||
uint8_t *value = ((uint8_t *)address) + currentBitOffset / 8 + (currentBitOffset % 8 == 0 ? 0 : 1);
|
||||
NSLog(@" : %d", ((*value) & ((uint8_t)(1 << (8 - currentBitOffset % 8)))) != 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
currentBitOffset += bitLength;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
FreedomBitfield freedomIvarBitOffset(Class targetClass, uint32_t fieldName, uint32_t bitfieldName)
|
||||
{
|
||||
FreedomBitfield result = {.offset = -1, .bit = -1 };
|
||||
|
||||
unsigned int count = 0;
|
||||
Ivar *ivarList = class_copyIvarList(targetClass, &count);
|
||||
|
||||
if (ivarList != NULL)
|
||||
{
|
||||
for (unsigned int i = 0; i < count; i++)
|
||||
{
|
||||
const char *ivarName = ivar_getName(ivarList[i]);
|
||||
uint32_t hashName = (uint32_t)murMurHashBytes32((void *)ivarName, (int)strlen(ivarName));
|
||||
//NSLog(@"%s -> 0x%x", ivarName, hashName);
|
||||
if (hashName == fieldName)
|
||||
{
|
||||
ptrdiff_t ivarOffset = ivar_getOffset(ivarList[i]);
|
||||
int bitOffset = freedomBitfieldOffsetInBits(ivar_getTypeEncoding(ivarList[i]), bitfieldName);
|
||||
if (bitOffset >= 0)
|
||||
{
|
||||
result.offset = ivarOffset + bitOffset / 8 + (bitOffset % 8 == 0 ? 0 : 1);
|
||||
result.bit = bitOffset % 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(ivarList);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
FreedomBitfield freedomIvarBitOffset2(Class targetClass, uint32_t fieldName, uint32_t bitfieldName)
|
||||
{
|
||||
FreedomBitfield result = {.offset = -1, .bit = -1 };
|
||||
|
||||
unsigned int count = 0;
|
||||
Ivar *ivarList = class_copyIvarList(targetClass, &count);
|
||||
|
||||
if (ivarList != NULL)
|
||||
{
|
||||
for (unsigned int i = 0; i < count; i++)
|
||||
{
|
||||
const char *ivarName = ivar_getName(ivarList[i]);
|
||||
uint32_t hashName = (uint32_t)murMurHashBytes32((void *)ivarName, (int)strlen(ivarName));
|
||||
//NSLog(@"%s -> 0x%x", ivarName, hashName);
|
||||
if (hashName == fieldName)
|
||||
{
|
||||
ptrdiff_t ivarOffset = ivar_getOffset(ivarList[i]);
|
||||
int bitOffset = freedomBitfieldOffsetInBits(ivar_getTypeEncoding(ivarList[i]), bitfieldName);
|
||||
if (bitOffset >= 0)
|
||||
{
|
||||
result.offset = ivarOffset + bitOffset / 8;
|
||||
result.bit = bitOffset % 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(ivarList);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void freedomSetBitfield(void *object, FreedomBitfield bitfield, int value)
|
||||
{
|
||||
if (object == nil || bitfield.offset < 0 || bitfield.bit < 0)
|
||||
return;
|
||||
|
||||
uint8_t *bytePtr = (((uint8_t *)object) + bitfield.offset);
|
||||
|
||||
if (value != 0)
|
||||
*bytePtr = (*bytePtr) | ((uint8_t)(1 << (bitfield.bit)));
|
||||
else
|
||||
*bytePtr = (*bytePtr) & ((uint8_t)~(1 << (bitfield.bit)));
|
||||
}
|
||||
|
||||
int freedomGetBitfield(void *object, FreedomBitfield bitfield)
|
||||
{
|
||||
if (object == nil || bitfield.offset < 0 || bitfield.bit < 0)
|
||||
return 0;
|
||||
|
||||
uint8_t *bytePtr = (((uint8_t *)object) + bitfield.offset);
|
||||
|
||||
return ((*bytePtr) & ((uint8_t)(1 << (bitfield.bit)))) != 0;
|
||||
}
|
||||
|
||||
void freedomDumpBitfields(Class targetClass, void *object, uint32_t fieldName)
|
||||
{
|
||||
unsigned int count = 0;
|
||||
Ivar *ivarList = class_copyIvarList(targetClass, &count);
|
||||
|
||||
if (ivarList != NULL)
|
||||
{
|
||||
for (unsigned int i = 0; i < count; i++)
|
||||
{
|
||||
const char *ivarName = ivar_getName(ivarList[i]);
|
||||
uint32_t hashName = (uint32_t)murMurHashBytes32((void *)ivarName, (int)strlen(ivarName));
|
||||
if (hashName == fieldName)
|
||||
{
|
||||
ptrdiff_t ivarOffset = ivar_getOffset(ivarList[i]);
|
||||
freedomDumpBitfieldsByDescription(((uint8_t *)object) + ivarOffset, ivar_getTypeEncoding(ivarList[i]));
|
||||
}
|
||||
}
|
||||
|
||||
free(ivarList);
|
||||
}
|
||||
}
|
||||
|
||||
IMP freedomNativeImpl(Class targetClass, SEL selector)
|
||||
{
|
||||
return class_getMethodImplementation(class_getSuperclass(targetClass), selector);
|
||||
}
|
||||
|
||||
Class adjustDecoratedClass(Class targetClass)
|
||||
{
|
||||
__unsafe_unretained Class decoratedClass = Nil;
|
||||
__unsafe_unretained Class currentClass = targetClass;
|
||||
while (currentClass != Nil)
|
||||
{
|
||||
__unsafe_unretained Class currentDecoratedClass = objc_getAssociatedObject(currentClass, freedomDecoratedClass);
|
||||
if (currentDecoratedClass != Nil)
|
||||
{
|
||||
decoratedClass = currentDecoratedClass;
|
||||
|
||||
if (currentClass != targetClass)
|
||||
{
|
||||
const char *currentClassName = class_getName(targetClass);
|
||||
Class adjustedDecoratedClass = objc_allocateClassPair(targetClass, [[NSString alloc] initWithFormat:@"%s_Super%" PRIx32 "", class_getName(decoratedClass), murMurHashBytes32((void *)currentClassName, (int)strlen(currentClassName))].UTF8String, 0);
|
||||
|
||||
unsigned int methodCount = 0;
|
||||
Method *methodList = class_copyMethodList(decoratedClass, &methodCount);
|
||||
for (unsigned int i = 0; i < methodCount; i++)
|
||||
{
|
||||
class_addMethod(adjustedDecoratedClass, method_getName(methodList[i]), method_getImplementation(methodList[i]), method_getTypeEncoding(methodList[i]));
|
||||
}
|
||||
free(methodList);
|
||||
|
||||
objc_registerClassPair(adjustedDecoratedClass);
|
||||
|
||||
decoratedClass = adjustedDecoratedClass;
|
||||
objc_setAssociatedObject(targetClass, freedomDecoratedClass, adjustedDecoratedClass, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
currentClass = class_getSuperclass(currentClass);
|
||||
}
|
||||
|
||||
return decoratedClass;
|
||||
}
|
||||
|
||||
id freedomAllocImpl(id self, SEL _cmd)
|
||||
{
|
||||
static id (*nativeImp)(id, SEL) = NULL;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^
|
||||
{
|
||||
nativeImp = (id (*)(id, SEL))class_getMethodImplementation(object_getClass([NSObject class]), @selector(alloc));
|
||||
});
|
||||
|
||||
id result = nativeImp(self, _cmd);
|
||||
|
||||
Class decoratedClass = adjustDecoratedClass(self);
|
||||
if (decoratedClass != Nil)
|
||||
object_setClass(result, decoratedClass);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static SEL freedomFindMethodName(uint32_t name, Class className, Method *methodList, unsigned int methodCount, const char **methodTypeEncoding)
|
||||
{
|
||||
for (unsigned int j = 0; j < methodCount; j++)
|
||||
{
|
||||
SEL methodName = method_getName(methodList[j]);
|
||||
const char *selectorName = sel_getName(methodName);
|
||||
uint32_t methodHashName = (uint32_t)murMurHashBytes32((void *)selectorName, (int)strlen(selectorName));
|
||||
//NSLog(@"0x%" PRIx32 " -> %s", methodHashName, selectorName);
|
||||
if (methodHashName == name)
|
||||
{
|
||||
if (methodTypeEncoding != NULL)
|
||||
*methodTypeEncoding = method_getTypeEncoding(methodList[j]);
|
||||
return methodName;
|
||||
}
|
||||
}
|
||||
|
||||
Class superClass = class_getSuperclass(className);
|
||||
if (superClass != NULL)
|
||||
{
|
||||
unsigned int superClassMethodCount = 0;
|
||||
Method *superClassMethodList = class_copyMethodList(superClass, &superClassMethodCount);
|
||||
SEL result = freedomFindMethodName(name, superClass, superClassMethodList, superClassMethodCount, methodTypeEncoding);
|
||||
free(superClassMethodList);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void freedomClassAutoDecorate(uint32_t name, __unused FreedomDecoration *classDecorations, __unused int numClassDecorations, FreedomDecoration *instanceDecorations, int numInstanceDecorations)
|
||||
{
|
||||
__unsafe_unretained Class className = freedomClass(name);
|
||||
freedomClassAutoDecorateExplicit(className, name, classDecorations, numClassDecorations, instanceDecorations, numInstanceDecorations);
|
||||
}
|
||||
|
||||
void freedomClassAutoDecorateExplicit(Class className, uint32_t name, __unused FreedomDecoration *classDecorations, __unused int numClassDecorations, FreedomDecoration *instanceDecorations, int numInstanceDecorations)
|
||||
{
|
||||
if (className != nil)
|
||||
{
|
||||
if (name == 0)
|
||||
{
|
||||
const char *string = [NSStringFromClass(className) UTF8String];
|
||||
name = (uint32_t)murMurHashBytes32((void *)string, (int)strlen(string));
|
||||
}
|
||||
|
||||
Class decoratedClass = objc_allocateClassPair(className, [[NSString alloc] initWithFormat:@"Decorated%" PRIx32 "", name].UTF8String, 0);
|
||||
|
||||
unsigned int methodCount = 0;
|
||||
Method *methodList = class_copyMethodList(className, &methodCount);
|
||||
|
||||
for (int i = 0; i < numInstanceDecorations; i++)
|
||||
{
|
||||
if (instanceDecorations[i].name != 0)
|
||||
{
|
||||
const char *methodTypeEncoding = NULL;
|
||||
SEL methodName = freedomFindMethodName(instanceDecorations[i].name, className, methodList, methodCount, &methodTypeEncoding);
|
||||
if (methodName != NULL)
|
||||
class_addMethod(decoratedClass, methodName, instanceDecorations[i].imp, methodTypeEncoding);
|
||||
else
|
||||
{
|
||||
#ifdef DEBUG
|
||||
NSLog(@"[Freedom coulnd'n find method named 0x%" PRIx32 "]", instanceDecorations[i].name);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else if (instanceDecorations[i].newIdentifier.string != NULL && instanceDecorations[i].newEncoding.string != NULL)
|
||||
{
|
||||
char *identifier = copyFreedomIdentifierValue(instanceDecorations[i].newIdentifier);
|
||||
char *encoding = copyFreedomIdentifierValue(instanceDecorations[i].newEncoding);
|
||||
|
||||
SEL selector = sel_getUid(identifier);
|
||||
if (!sel_isMapped(selector))
|
||||
selector = sel_registerName(identifier);
|
||||
|
||||
class_addMethod(decoratedClass, selector, instanceDecorations[i].imp, encoding);
|
||||
|
||||
free(identifier);
|
||||
free(encoding);
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef DEBUG
|
||||
NSLog(@"[Freedom invalid decoration description]");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
free(methodList);
|
||||
|
||||
objc_registerClassPair(decoratedClass);
|
||||
|
||||
objc_setAssociatedObject(className, freedomDecoratedClass, decoratedClass, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
Class metaClass = object_getClass(className);
|
||||
class_addMethod(metaClass, @selector(alloc), (IMP)&freedomAllocImpl, "@:@");
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef DEBUG
|
||||
NSLog(@"[Freedom coulnd'n find class named 0x%" PRIx32 "]", name);
|
||||
assert(false);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
IMP freedomImpl(id target, uint32_t name, SEL *selector)
|
||||
{
|
||||
if (target == nil)
|
||||
return NULL;
|
||||
|
||||
Class targetClass = object_getClass(target);
|
||||
return freedomImplInstancesOfClass(targetClass, name, selector);
|
||||
}
|
||||
|
||||
IMP freedomImplInstancesOfClass(Class targetClass, uint32_t name, SEL *selector) {
|
||||
if (targetClass == NULL)
|
||||
return NULL;
|
||||
|
||||
unsigned int methodCount = 0;
|
||||
Method *methodList = class_copyMethodList(targetClass, &methodCount);
|
||||
SEL methodName = freedomFindMethodName(name, targetClass, methodList, methodCount, NULL);
|
||||
free(methodList);
|
||||
|
||||
if (methodName != NULL)
|
||||
{
|
||||
if (selector != NULL)
|
||||
*selector = methodName;
|
||||
IMP result = class_getMethodImplementation(targetClass, methodName);
|
||||
if (result == NULL)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
NSLog(@"[Freedom coulnd'n find method named 0x%" PRIx32 "]", name);
|
||||
#endif
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void freedomInit()
|
||||
{
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^
|
||||
{
|
||||
freedom_getClassList = &objc_getClassList;
|
||||
freedom_class_getName = &class_getName;
|
||||
});
|
||||
}
|
||||
|
||||
bool freedomInitialized()
|
||||
{
|
||||
return freedom_initialized;
|
||||
}
|
||||
27
submodules/LegacyComponents/LegacyComponents/FreedomUIKit.h
Normal file
27
submodules/LegacyComponents/LegacyComponents/FreedomUIKit.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#ifndef FreedomUIKit_h
|
||||
#define FreedomUIKit_h
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void freedomUIKitInit();
|
||||
|
||||
bool freedomUIKitTest3();
|
||||
bool freedomUIKitTest3_1();
|
||||
void freedomUIKitTest4(dispatch_block_t);
|
||||
void freedomUIKitTest4_1();
|
||||
|
||||
@interface FFNotificationCenter : NSNotificationCenter
|
||||
|
||||
+ (void)setShouldRotateBlock:(bool (^)())block;
|
||||
|
||||
@end
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
288
submodules/LegacyComponents/LegacyComponents/FreedomUIKit.m
Normal file
288
submodules/LegacyComponents/LegacyComponents/FreedomUIKit.m
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
#import "FreedomUIKit.h"
|
||||
|
||||
#import "LegacyComponentsInternal.h"
|
||||
#import "Freedom.h"
|
||||
|
||||
#import <objc/runtime.h>
|
||||
#import <objc/message.h>
|
||||
|
||||
#import "TGHacks.h"
|
||||
#import "TGNavigationController.h"
|
||||
|
||||
#define DEBUG_KEYBOARD_QUEUE false
|
||||
|
||||
void freedomUIKitInit2(); // iOS 6
|
||||
void freedomUIKitInit3(); // Notification Center Hook
|
||||
|
||||
#if defined(DEBUG) && DEBUG_KEYBOARD_QUEUE
|
||||
void freedomUIKitInit4(); // Keyboard Queue Debug
|
||||
#endif
|
||||
|
||||
void freedomUIKitInit()
|
||||
{
|
||||
freedomUIKitInit2();
|
||||
freedomUIKitInit3();
|
||||
|
||||
#if defined(DEBUG) && DEBUG_KEYBOARD_QUEUE
|
||||
freedomUIKitInit4();
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
static int freedomUIKit_decorated2(__unused id self, __unused SEL _cmd)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void freedomUIKitInit2()
|
||||
{
|
||||
if (iosMajorVersion() == 6)
|
||||
{
|
||||
FreedomDecoration instanceDecorations[] = {
|
||||
{ .name = 0,
|
||||
.imp = (IMP)&freedomUIKit_decorated2,
|
||||
.newIdentifier = (FreedomIdentifier){ .string = "3066a13b3e6b", .key = 0x52d50551U },
|
||||
.newEncoding = (FreedomIdentifier){ .string = "1a7291", .key = 0x3ab3273U }
|
||||
}
|
||||
};
|
||||
|
||||
freedomClassAutoDecorate(0x1468e61aU, NULL, 0, instanceDecorations, sizeof(instanceDecorations) / sizeof(instanceDecorations[0]));
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
static bool test3 = false;
|
||||
static bool test3_1 = false;
|
||||
|
||||
@implementation FFNotificationCenter
|
||||
|
||||
static bool (^shouldRotateBlock)() = nil;
|
||||
|
||||
+ (void)setShouldRotateBlock:(bool (^)())block
|
||||
{
|
||||
shouldRotateBlock = [block copy];
|
||||
}
|
||||
|
||||
- (void)postNotificationName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo
|
||||
{
|
||||
bool clearFlag = false;
|
||||
if ([aName isEqualToString:UIDeviceOrientationDidChangeNotification])
|
||||
{
|
||||
clearFlag = true;
|
||||
test3 = true;
|
||||
test3_1 = true;
|
||||
}
|
||||
|
||||
if ([aName isEqualToString:UIDeviceOrientationDidChangeNotification])
|
||||
{
|
||||
if (shouldRotateBlock == nil || shouldRotateBlock())
|
||||
[super postNotificationName:aName object:anObject userInfo:aUserInfo];
|
||||
}
|
||||
else
|
||||
[super postNotificationName:aName object:anObject userInfo:aUserInfo];
|
||||
|
||||
if (clearFlag)
|
||||
test3 = false;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
bool freedomUIKitTest3()
|
||||
{
|
||||
return test3;
|
||||
}
|
||||
|
||||
bool freedomUIKitTest3_1()
|
||||
{
|
||||
bool value = test3_1;
|
||||
test3_1 = false;
|
||||
return value;
|
||||
}
|
||||
|
||||
void freedomUIKitInit3()
|
||||
{
|
||||
object_setClass([NSNotificationCenter defaultCenter], [FFNotificationCenter class]);
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
static bool test4 = false;
|
||||
static bool test4_1 = false;
|
||||
|
||||
@interface TGHelperQueue : NSProxy
|
||||
{
|
||||
id _target;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TGHelperQueue
|
||||
|
||||
- (instancetype)initWithTargetQueue:(id)target
|
||||
{
|
||||
if (self != nil)
|
||||
{
|
||||
_target = target;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
|
||||
{
|
||||
return [_target methodSignatureForSelector:selector];
|
||||
}
|
||||
|
||||
- (void)forwardInvocation:(NSInvocation *)__unused invocation
|
||||
{
|
||||
}
|
||||
|
||||
- (id)forwardingTargetForSelector:(SEL)selector
|
||||
{
|
||||
static char *name = NULL;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^
|
||||
{
|
||||
name = copyFreedomIdentifierValue((FreedomIdentifier){ .string = "536772a971686fb4484777b1706768b6574769b8626f75b4576e7eb9", .key = 0xdd1b0624U });
|
||||
});
|
||||
|
||||
if (!strcmp(sel_getName(selector), name))
|
||||
{
|
||||
if (test4 || test4_1)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
return _target;
|
||||
}
|
||||
|
||||
- (void)doWork
|
||||
{
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static UIView *freedomUIKitFindView(UIView *view)
|
||||
{
|
||||
if (view == nil)
|
||||
return nil;
|
||||
|
||||
if (object_getClass(view) == freedomClass(0x9cb128e7U))
|
||||
return view;
|
||||
|
||||
for (UIView *subview in view.subviews)
|
||||
{
|
||||
UIView *result = freedomUIKitFindView(subview);
|
||||
if (result != nil)
|
||||
return result;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
void freedomUIKitTest4(dispatch_block_t block)
|
||||
{
|
||||
if (iosMajorVersion() < 7 || iosMajorVersion() > 7 || (iosMajorVersion() == 7 && iosMinorVersion() >= 1))
|
||||
{
|
||||
if (block != nil)
|
||||
block();
|
||||
}
|
||||
else
|
||||
{
|
||||
UIView *view = freedomUIKitFindView([TGHacks applicationKeyboardWindow]);
|
||||
if (view != nil)
|
||||
{
|
||||
static ptrdiff_t queueOffset = -1;
|
||||
static bool queueInitialized = false;
|
||||
if (!queueInitialized)
|
||||
{
|
||||
queueInitialized = true;
|
||||
queueOffset = freedomIvarOffset(object_getClass(view), 0xba913cbU);
|
||||
}
|
||||
|
||||
if (queueOffset >= 0)
|
||||
{
|
||||
__strong NSObject **queue = ((__strong NSObject **)(void *)(((uint8_t *)(__bridge void *)view) + queueOffset));
|
||||
if (*queue != nil)
|
||||
{
|
||||
if (object_getClass(*queue) != [TGHelperQueue class])
|
||||
{
|
||||
TGHelperQueue *helper = [[TGHelperQueue alloc] initWithTargetQueue:*queue];
|
||||
*queue = (NSObject *)helper;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool previousTest4 = test4;
|
||||
test4 = true;
|
||||
|
||||
if (block != nil)
|
||||
block();
|
||||
|
||||
test4 = previousTest4;
|
||||
}
|
||||
}
|
||||
|
||||
void freedomUIKitTest4_1()
|
||||
{
|
||||
test4_1 = true;
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^
|
||||
{
|
||||
test4_1 = false;
|
||||
});
|
||||
}
|
||||
|
||||
#if defined(DEBUG) && DEBUG_KEYBOARD_QUEUE
|
||||
void freedomUIKit_decorated4_1(id self, SEL _cmd, id arg1)
|
||||
{
|
||||
static void (*nativeImpl)(id, SEL, id) = NULL;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^
|
||||
{
|
||||
nativeImpl = (void *)freedomNativeImpl([self class], _cmd);
|
||||
});
|
||||
|
||||
if (nativeImpl != NULL)
|
||||
nativeImpl(self, _cmd, arg1);
|
||||
|
||||
TGLegacyLog(@"invoke %@", NSStringFromSelector(_cmd));
|
||||
}
|
||||
|
||||
|
||||
void freedomUIKit_decorated4_2(id self, SEL _cmd)
|
||||
{
|
||||
static void (*nativeImpl)(id, SEL) = NULL;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^
|
||||
{
|
||||
nativeImpl = (void *)freedomNativeImpl([self class], _cmd);
|
||||
});
|
||||
|
||||
if (nativeImpl != NULL)
|
||||
nativeImpl(self, _cmd);
|
||||
|
||||
TGLegacyLog(@"invoke %@", NSStringFromSelector(_cmd));
|
||||
}
|
||||
|
||||
void freedomUIKitInit4()
|
||||
{
|
||||
FreedomDecoration instanceDecorations[] = {
|
||||
{ .name = 0xbb6dbb9eU, //addTask:
|
||||
.imp = (IMP)&freedomUIKit_decorated4_1,
|
||||
.newIdentifier = FreedomIdentifierEmpty,
|
||||
.newEncoding = FreedomIdentifierEmpty
|
||||
},
|
||||
{ .name = 0x757d9b1cU, //waitUntilAllTasksAreFinished
|
||||
.imp = (IMP)&freedomUIKit_decorated4_2,
|
||||
.newIdentifier = FreedomIdentifierEmpty,
|
||||
.newEncoding = FreedomIdentifierEmpty
|
||||
}
|
||||
};
|
||||
|
||||
freedomClassAutoDecorate(0xfed2643dU, NULL, 0, instanceDecorations, sizeof(instanceDecorations) / sizeof(instanceDecorations[0]));
|
||||
}
|
||||
#endif
|
||||
42
submodules/LegacyComponents/LegacyComponents/GLProgram.h
Executable file
42
submodules/LegacyComponents/LegacyComponents/GLProgram.h
Executable file
|
|
@ -0,0 +1,42 @@
|
|||
// This is Jeff LaMarche's GLProgram OpenGL shader wrapper class from his OpenGL ES 2.0 book.
|
||||
// A description of this can be found at his page on the topic:
|
||||
// http://iphonedevelopment.blogspot.com/2010/11/opengl-es-20-for-ios-chapter-4.html
|
||||
// I've extended this to be able to take programs as NSStrings in addition to files, for baked-in shaders
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
|
||||
#import <OpenGLES/ES2/gl.h>
|
||||
#import <OpenGLES/ES2/glext.h>
|
||||
#else
|
||||
#import <OpenGL/OpenGL.h>
|
||||
#import <OpenGL/gl.h>
|
||||
#endif
|
||||
|
||||
@interface GLProgram : NSObject
|
||||
{
|
||||
NSMutableArray *attributes;
|
||||
NSMutableArray *uniforms;
|
||||
GLuint program,
|
||||
vertShader,
|
||||
fragShader;
|
||||
}
|
||||
|
||||
@property(readwrite, nonatomic) BOOL initialized;
|
||||
|
||||
- (id)initWithVertexShaderString:(NSString *)vShaderString
|
||||
fragmentShaderString:(NSString *)fShaderString;
|
||||
- (id)initWithVertexShaderString:(NSString *)vShaderString
|
||||
fragmentShaderFilename:(NSString *)fShaderFilename;
|
||||
- (id)initWithVertexShaderFilename:(NSString *)vShaderFilename
|
||||
fragmentShaderFilename:(NSString *)fShaderFilename;
|
||||
- (void)addAttribute:(NSString *)attributeName;
|
||||
- (GLuint)attributeIndex:(NSString *)attributeName;
|
||||
- (GLuint)uniformIndex:(NSString *)uniformName;
|
||||
- (BOOL)link;
|
||||
- (void)use;
|
||||
- (NSString *)vertexShaderLog;
|
||||
- (NSString *)fragmentShaderLog;
|
||||
- (NSString *)programLog;
|
||||
- (void)validate;
|
||||
@end
|
||||
274
submodules/LegacyComponents/LegacyComponents/GLProgram.m
Executable file
274
submodules/LegacyComponents/LegacyComponents/GLProgram.m
Executable file
|
|
@ -0,0 +1,274 @@
|
|||
// This is Jeff LaMarche's GLProgram OpenGL shader wrapper class from his OpenGL ES 2.0 book.
|
||||
// A description of this can be found at his page on the topic:
|
||||
// http://iphonedevelopment.blogspot.com/2010/11/opengl-es-20-for-ios-chapter-4.html
|
||||
|
||||
#import "LegacyComponentsInternal.h"
|
||||
|
||||
#import "GLProgram.h"
|
||||
// START:typedefs
|
||||
#pragma mark Function Pointer Definitions
|
||||
typedef void (*GLInfoFunction)(GLuint program,
|
||||
GLenum pname,
|
||||
GLint* params);
|
||||
typedef void (*GLLogFunction) (GLuint program,
|
||||
GLsizei bufsize,
|
||||
GLsizei* length,
|
||||
GLchar* infolog);
|
||||
// END:typedefs
|
||||
#pragma mark -
|
||||
#pragma mark Private Extension Method Declaration
|
||||
// START:extension
|
||||
@interface GLProgram()
|
||||
|
||||
- (BOOL)compileShader:(GLuint *)shader
|
||||
type:(GLenum)type
|
||||
string:(NSString *)shaderString;
|
||||
- (NSString *)logForOpenGLObject:(GLuint)object
|
||||
infoCallback:(GLInfoFunction)infoFunc
|
||||
logFunc:(GLLogFunction)logFunc;
|
||||
@end
|
||||
// END:extension
|
||||
#pragma mark -
|
||||
|
||||
@implementation GLProgram
|
||||
// START:init
|
||||
|
||||
@synthesize initialized = _initialized;
|
||||
|
||||
- (id)initWithVertexShaderString:(NSString *)vShaderString
|
||||
fragmentShaderString:(NSString *)fShaderString
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
_initialized = NO;
|
||||
|
||||
attributes = [[NSMutableArray alloc] init];
|
||||
uniforms = [[NSMutableArray alloc] init];
|
||||
program = glCreateProgram();
|
||||
|
||||
if (![self compileShader:&vertShader
|
||||
type:GL_VERTEX_SHADER
|
||||
string:vShaderString])
|
||||
NSLog(@"Failed to compile vertex shader");
|
||||
|
||||
// Create and compile fragment shader
|
||||
if (![self compileShader:&fragShader
|
||||
type:GL_FRAGMENT_SHADER
|
||||
string:fShaderString])
|
||||
NSLog(@"Failed to compile fragment shader");
|
||||
|
||||
glAttachShader(program, vertShader);
|
||||
glAttachShader(program, fragShader);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithVertexShaderString:(NSString *)vShaderString
|
||||
fragmentShaderFilename:(NSString *)fShaderFilename
|
||||
{
|
||||
NSString *fragShaderPathname = TGComponentsPathForResource(fShaderFilename, @"fsh");
|
||||
NSString *fragmentShaderString = [NSString stringWithContentsOfFile:fragShaderPathname encoding:NSUTF8StringEncoding error:nil];
|
||||
|
||||
if ((self = [self initWithVertexShaderString:vShaderString fragmentShaderString:fragmentShaderString]))
|
||||
{
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithVertexShaderFilename:(NSString *)vShaderFilename
|
||||
fragmentShaderFilename:(NSString *)fShaderFilename
|
||||
{
|
||||
NSString *vertShaderPathname = TGComponentsPathForResource(vShaderFilename, @"vsh");
|
||||
NSString *vertexShaderString = [NSString stringWithContentsOfFile:vertShaderPathname encoding:NSUTF8StringEncoding error:nil];
|
||||
|
||||
NSString *fragShaderPathname = TGComponentsPathForResource(fShaderFilename, @"fsh");
|
||||
NSString *fragmentShaderString = [NSString stringWithContentsOfFile:fragShaderPathname encoding:NSUTF8StringEncoding error:nil];
|
||||
|
||||
if ((self = [self initWithVertexShaderString:vertexShaderString fragmentShaderString:fragmentShaderString]))
|
||||
{
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
// END:init
|
||||
// START:compile
|
||||
- (BOOL)compileShader:(GLuint *)shader
|
||||
type:(GLenum)type
|
||||
string:(NSString *)shaderString
|
||||
{
|
||||
// CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
|
||||
|
||||
GLint status;
|
||||
const GLchar *source;
|
||||
|
||||
source =
|
||||
(GLchar *)[shaderString UTF8String];
|
||||
if (!source)
|
||||
{
|
||||
NSLog(@"Failed to load vertex shader");
|
||||
return NO;
|
||||
}
|
||||
|
||||
*shader = glCreateShader(type);
|
||||
glShaderSource(*shader, 1, &source, NULL);
|
||||
glCompileShader(*shader);
|
||||
|
||||
glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
|
||||
|
||||
if (status != GL_TRUE)
|
||||
{
|
||||
GLint logLength;
|
||||
glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength);
|
||||
if (logLength > 0)
|
||||
{
|
||||
GLchar *log = (GLchar *)malloc(logLength);
|
||||
glGetShaderInfoLog(*shader, logLength, &logLength, log);
|
||||
NSLog(@"Shader compile log:\n%s", log);
|
||||
free(log);
|
||||
}
|
||||
}
|
||||
|
||||
// CFAbsoluteTime linkTime = (CFAbsoluteTimeGetCurrent() - startTime);
|
||||
// NSLog(@"Compiled in %f ms", linkTime * 1000.0);
|
||||
|
||||
return status == GL_TRUE;
|
||||
}
|
||||
// END:compile
|
||||
#pragma mark -
|
||||
// START:addattribute
|
||||
- (void)addAttribute:(NSString *)attributeName
|
||||
{
|
||||
if (![attributes containsObject:attributeName])
|
||||
{
|
||||
[attributes addObject:attributeName];
|
||||
glBindAttribLocation(program,
|
||||
(GLuint)[attributes indexOfObject:attributeName],
|
||||
[attributeName UTF8String]);
|
||||
}
|
||||
}
|
||||
// END:addattribute
|
||||
// START:indexmethods
|
||||
- (GLuint)attributeIndex:(NSString *)attributeName
|
||||
{
|
||||
return (GLuint)[attributes indexOfObject:attributeName];
|
||||
}
|
||||
- (GLuint)uniformIndex:(NSString *)uniformName
|
||||
{
|
||||
return glGetUniformLocation(program, [uniformName UTF8String]);
|
||||
}
|
||||
// END:indexmethods
|
||||
#pragma mark -
|
||||
// START:link
|
||||
- (BOOL)link
|
||||
{
|
||||
// CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
|
||||
|
||||
GLint status;
|
||||
|
||||
glLinkProgram(program);
|
||||
|
||||
glGetProgramiv(program, GL_LINK_STATUS, &status);
|
||||
if (status == GL_FALSE)
|
||||
return NO;
|
||||
|
||||
if (vertShader)
|
||||
{
|
||||
glDeleteShader(vertShader);
|
||||
vertShader = 0;
|
||||
}
|
||||
if (fragShader)
|
||||
{
|
||||
glDeleteShader(fragShader);
|
||||
fragShader = 0;
|
||||
}
|
||||
|
||||
self.initialized = YES;
|
||||
|
||||
// CFAbsoluteTime linkTime = (CFAbsoluteTimeGetCurrent() - startTime);
|
||||
// NSLog(@"Linked in %f ms", linkTime * 1000.0);
|
||||
|
||||
return YES;
|
||||
}
|
||||
// END:link
|
||||
// START:use
|
||||
- (void)use
|
||||
{
|
||||
glUseProgram(program);
|
||||
}
|
||||
// END:use
|
||||
#pragma mark -
|
||||
// START:privatelog
|
||||
- (NSString *)logForOpenGLObject:(GLuint)object
|
||||
infoCallback:(GLInfoFunction)infoFunc
|
||||
logFunc:(GLLogFunction)logFunc
|
||||
{
|
||||
GLint logLength = 0, charsWritten = 0;
|
||||
|
||||
infoFunc(object, GL_INFO_LOG_LENGTH, &logLength);
|
||||
if (logLength < 1)
|
||||
return nil;
|
||||
|
||||
char *logBytes = malloc(logLength);
|
||||
logFunc(object, logLength, &charsWritten, logBytes);
|
||||
NSString *log = [[NSString alloc] initWithBytes:logBytes
|
||||
length:logLength
|
||||
encoding:NSUTF8StringEncoding];
|
||||
free(logBytes);
|
||||
return log;
|
||||
}
|
||||
// END:privatelog
|
||||
// START:log
|
||||
- (NSString *)vertexShaderLog
|
||||
{
|
||||
return [self logForOpenGLObject:vertShader
|
||||
infoCallback:(GLInfoFunction)&glGetProgramiv
|
||||
logFunc:(GLLogFunction)&glGetProgramInfoLog];
|
||||
|
||||
}
|
||||
- (NSString *)fragmentShaderLog
|
||||
{
|
||||
return [self logForOpenGLObject:fragShader
|
||||
infoCallback:(GLInfoFunction)&glGetProgramiv
|
||||
logFunc:(GLLogFunction)&glGetProgramInfoLog];
|
||||
}
|
||||
- (NSString *)programLog
|
||||
{
|
||||
return [self logForOpenGLObject:program
|
||||
infoCallback:(GLInfoFunction)&glGetProgramiv
|
||||
logFunc:(GLLogFunction)&glGetProgramInfoLog];
|
||||
}
|
||||
// END:log
|
||||
|
||||
- (void)validate
|
||||
{
|
||||
GLint logLength;
|
||||
|
||||
glValidateProgram(program);
|
||||
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);
|
||||
if (logLength > 0)
|
||||
{
|
||||
GLchar *log = (GLchar *)malloc(logLength);
|
||||
glGetProgramInfoLog(program, logLength, &logLength, log);
|
||||
NSLog(@"Program validate log:\n%s", log);
|
||||
free(log);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
// START:dealloc
|
||||
- (void)dealloc
|
||||
{
|
||||
if (vertShader)
|
||||
glDeleteShader(vertShader);
|
||||
|
||||
if (fragShader)
|
||||
glDeleteShader(fragShader);
|
||||
|
||||
if (program)
|
||||
glDeleteProgram(program);
|
||||
|
||||
}
|
||||
// END:dealloc
|
||||
@end
|
||||
20
submodules/LegacyComponents/LegacyComponents/GPUImage.h
Executable file
20
submodules/LegacyComponents/LegacyComponents/GPUImage.h
Executable file
|
|
@ -0,0 +1,20 @@
|
|||
#import "GLProgram.h"
|
||||
|
||||
// Base classes
|
||||
#import "GPUImageContext.h"
|
||||
#import "GPUImageOutput.h"
|
||||
#import "GPUImageView.h"
|
||||
#import "GPUImageVideoCamera.h"
|
||||
#import "GPUImageStillCamera.h"
|
||||
#import "GPUImagePicture.h"
|
||||
#import "GPUImageRawDataInput.h"
|
||||
#import "GPUImageRawDataOutput.h"
|
||||
#import "GPUImageTextureOutput.h"
|
||||
#import "GPUImageFilterGroup.h"
|
||||
#import "GPUImageFramebuffer.h"
|
||||
#import "GPUImageFramebufferCache.h"
|
||||
|
||||
// Filters
|
||||
#import "GPUImageFilter.h"
|
||||
#import "GPUImageTwoInputFilter.h"
|
||||
#import "GPUImageGaussianBlurFilter.h"
|
||||
55
submodules/LegacyComponents/LegacyComponents/GPUImageContext.h
Executable file
55
submodules/LegacyComponents/LegacyComponents/GPUImageContext.h
Executable file
|
|
@ -0,0 +1,55 @@
|
|||
#import "GLProgram.h"
|
||||
#import "GPUImageFramebuffer.h"
|
||||
#import "GPUImageFramebufferCache.h"
|
||||
|
||||
#define GPUImageRotationSwapsWidthAndHeight(rotation) ((rotation) == kGPUImageRotateLeft || (rotation) == kGPUImageRotateRight || (rotation) == kGPUImageRotateRightFlipVertical || (rotation) == kGPUImageRotateRightFlipHorizontal)
|
||||
|
||||
typedef enum { kGPUImageNoRotation, kGPUImageRotateLeft, kGPUImageRotateRight, kGPUImageFlipVertical, kGPUImageFlipHorizonal, kGPUImageRotateRightFlipVertical, kGPUImageRotateRightFlipHorizontal, kGPUImageRotate180 } GPUImageRotationMode;
|
||||
|
||||
@interface GPUImageContext : NSObject
|
||||
|
||||
@property(readonly, nonatomic) dispatch_queue_t contextQueue;
|
||||
@property(readwrite, retain, nonatomic) GLProgram *currentShaderProgram;
|
||||
@property(readonly, retain, nonatomic) EAGLContext *context;
|
||||
@property(readonly, nonatomic) CVOpenGLESTextureCacheRef coreVideoTextureCache;
|
||||
@property(readonly, nonatomic) GPUImageFramebufferCache *framebufferCache;
|
||||
|
||||
+ (void *)contextKey;
|
||||
+ (GPUImageContext *)sharedImageProcessingContext;
|
||||
+ (dispatch_queue_t)sharedContextQueue;
|
||||
+ (GPUImageFramebufferCache *)sharedFramebufferCache;
|
||||
+ (void)useImageProcessingContext;
|
||||
- (void)useAsCurrentContext;
|
||||
+ (void)setActiveShaderProgram:(GLProgram *)shaderProgram;
|
||||
- (void)setContextShaderProgram:(GLProgram *)shaderProgram;
|
||||
+ (GLint)maximumTextureSizeForThisDevice;
|
||||
+ (GLint)maximumTextureUnitsForThisDevice;
|
||||
+ (GLint)maximumVaryingVectorsForThisDevice;
|
||||
+ (BOOL)deviceSupportsOpenGLESExtension:(NSString *)extension;
|
||||
+ (BOOL)deviceSupportsRedTextures;
|
||||
+ (BOOL)deviceSupportsFramebufferReads;
|
||||
+ (CGSize)sizeThatFitsWithinATextureForSize:(CGSize)inputSize;
|
||||
|
||||
- (void)presentBufferForDisplay;
|
||||
- (GLProgram *)programForVertexShaderString:(NSString *)vertexShaderString fragmentShaderString:(NSString *)fragmentShaderString;
|
||||
|
||||
- (void)useSharegroup:(EAGLSharegroup *)sharegroup;
|
||||
|
||||
// Manage fast texture upload
|
||||
+ (BOOL)supportsFastTextureUpload;
|
||||
|
||||
@end
|
||||
|
||||
@protocol GPUImageInput <NSObject>
|
||||
- (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex;
|
||||
- (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)textureIndex;
|
||||
- (NSInteger)nextAvailableTextureIndex;
|
||||
- (void)setInputSize:(CGSize)newSize atIndex:(NSInteger)textureIndex;
|
||||
- (void)setInputRotation:(GPUImageRotationMode)newInputRotation atIndex:(NSInteger)textureIndex;
|
||||
- (CGSize)maximumOutputSize;
|
||||
- (void)endProcessing;
|
||||
- (BOOL)shouldIgnoreUpdatesToThisTarget;
|
||||
- (BOOL)enabled;
|
||||
- (BOOL)wantsMonochromeInput;
|
||||
- (void)setCurrentlyReceivingMonochromeInput:(BOOL)newValue;
|
||||
@end
|
||||
313
submodules/LegacyComponents/LegacyComponents/GPUImageContext.m
Executable file
313
submodules/LegacyComponents/LegacyComponents/GPUImageContext.m
Executable file
|
|
@ -0,0 +1,313 @@
|
|||
#import "GPUImageContext.h"
|
||||
#import <OpenGLES/EAGLDrawable.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
#define MAXSHADERPROGRAMSALLOWEDINCACHE 40
|
||||
|
||||
@interface GPUImageContext()
|
||||
{
|
||||
NSMutableDictionary *shaderProgramCache;
|
||||
NSMutableArray *shaderProgramUsageHistory;
|
||||
EAGLSharegroup *_sharegroup;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation GPUImageContext
|
||||
|
||||
@synthesize context = _context;
|
||||
@synthesize currentShaderProgram = _currentShaderProgram;
|
||||
@synthesize contextQueue = _contextQueue;
|
||||
@synthesize coreVideoTextureCache = _coreVideoTextureCache;
|
||||
@synthesize framebufferCache = _framebufferCache;
|
||||
|
||||
static void *openGLESContextQueueKey;
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if (!(self = [super init]))
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
openGLESContextQueueKey = &openGLESContextQueueKey;
|
||||
_contextQueue = dispatch_queue_create("com.sunsetlakesoftware.GPUImage.openGLESContextQueue", NULL);
|
||||
|
||||
#if OS_OBJECT_USE_OBJC
|
||||
dispatch_queue_set_specific(_contextQueue, openGLESContextQueueKey, (__bridge void *)self, NULL);
|
||||
#endif
|
||||
shaderProgramCache = [[NSMutableDictionary alloc] init];
|
||||
shaderProgramUsageHistory = [[NSMutableArray alloc] init];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (void *)contextKey {
|
||||
return openGLESContextQueueKey;
|
||||
}
|
||||
|
||||
// Based on Colin Wheeler's example here: http://cocoasamurai.blogspot.com/2011/04/singletons-your-doing-them-wrong.html
|
||||
+ (GPUImageContext *)sharedImageProcessingContext
|
||||
{
|
||||
static dispatch_once_t pred;
|
||||
static GPUImageContext *sharedImageProcessingContext = nil;
|
||||
|
||||
dispatch_once(&pred, ^{
|
||||
sharedImageProcessingContext = [[[self class] alloc] init];
|
||||
});
|
||||
return sharedImageProcessingContext;
|
||||
}
|
||||
|
||||
+ (dispatch_queue_t)sharedContextQueue
|
||||
{
|
||||
return [[self sharedImageProcessingContext] contextQueue];
|
||||
}
|
||||
|
||||
+ (GPUImageFramebufferCache *)sharedFramebufferCache
|
||||
{
|
||||
return [[self sharedImageProcessingContext] framebufferCache];
|
||||
}
|
||||
|
||||
+ (void)useImageProcessingContext
|
||||
{
|
||||
[[GPUImageContext sharedImageProcessingContext] useAsCurrentContext];
|
||||
}
|
||||
|
||||
- (void)useAsCurrentContext
|
||||
{
|
||||
EAGLContext *imageProcessingContext = [self context];
|
||||
if ([EAGLContext currentContext] != imageProcessingContext)
|
||||
{
|
||||
[EAGLContext setCurrentContext:imageProcessingContext];
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)setActiveShaderProgram:(GLProgram *)shaderProgram
|
||||
{
|
||||
GPUImageContext *sharedContext = [GPUImageContext sharedImageProcessingContext];
|
||||
[sharedContext setContextShaderProgram:shaderProgram];
|
||||
}
|
||||
|
||||
- (void)setContextShaderProgram:(GLProgram *)shaderProgram
|
||||
{
|
||||
EAGLContext *imageProcessingContext = [self context];
|
||||
if ([EAGLContext currentContext] != imageProcessingContext)
|
||||
{
|
||||
[EAGLContext setCurrentContext:imageProcessingContext];
|
||||
}
|
||||
|
||||
if (self.currentShaderProgram != shaderProgram)
|
||||
{
|
||||
self.currentShaderProgram = shaderProgram;
|
||||
[shaderProgram use];
|
||||
}
|
||||
}
|
||||
|
||||
+ (GLint)maximumTextureSizeForThisDevice
|
||||
{
|
||||
static dispatch_once_t pred;
|
||||
static GLint maxTextureSize = 0;
|
||||
|
||||
dispatch_once(&pred, ^{
|
||||
[self useImageProcessingContext];
|
||||
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
|
||||
});
|
||||
|
||||
return maxTextureSize;
|
||||
}
|
||||
|
||||
+ (GLint)maximumTextureUnitsForThisDevice
|
||||
{
|
||||
static dispatch_once_t pred;
|
||||
static GLint maxTextureUnits = 0;
|
||||
|
||||
dispatch_once(&pred, ^{
|
||||
[self useImageProcessingContext];
|
||||
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureUnits);
|
||||
});
|
||||
|
||||
return maxTextureUnits;
|
||||
}
|
||||
|
||||
+ (GLint)maximumVaryingVectorsForThisDevice
|
||||
{
|
||||
static dispatch_once_t pred;
|
||||
static GLint maxVaryingVectors = 0;
|
||||
|
||||
dispatch_once(&pred, ^{
|
||||
[self useImageProcessingContext];
|
||||
glGetIntegerv(GL_MAX_VARYING_VECTORS, &maxVaryingVectors);
|
||||
});
|
||||
|
||||
return maxVaryingVectors;
|
||||
}
|
||||
|
||||
+ (BOOL)deviceSupportsOpenGLESExtension:(NSString *)extension
|
||||
{
|
||||
static dispatch_once_t pred;
|
||||
static NSArray *extensionNames = nil;
|
||||
|
||||
// Cache extensions for later quick reference, since this won't change for a given device
|
||||
dispatch_once(&pred, ^{
|
||||
[GPUImageContext useImageProcessingContext];
|
||||
NSString *extensionsString = [NSString stringWithCString:(const char *)glGetString(GL_EXTENSIONS) encoding:NSASCIIStringEncoding];
|
||||
extensionNames = [extensionsString componentsSeparatedByString:@" "];
|
||||
});
|
||||
|
||||
return [extensionNames containsObject:extension];
|
||||
}
|
||||
|
||||
|
||||
// http://www.khronos.org/registry/gles/extensions/EXT/EXT_texture_rg.txt
|
||||
|
||||
+ (BOOL)deviceSupportsRedTextures
|
||||
{
|
||||
static dispatch_once_t pred;
|
||||
static BOOL supportsRedTextures = NO;
|
||||
|
||||
dispatch_once(&pred, ^{
|
||||
supportsRedTextures = [GPUImageContext deviceSupportsOpenGLESExtension:@"GL_EXT_texture_rg"];
|
||||
});
|
||||
|
||||
return supportsRedTextures;
|
||||
}
|
||||
|
||||
+ (BOOL)deviceSupportsFramebufferReads
|
||||
{
|
||||
static dispatch_once_t pred;
|
||||
static BOOL supportsFramebufferReads = NO;
|
||||
|
||||
dispatch_once(&pred, ^{
|
||||
supportsFramebufferReads = [GPUImageContext deviceSupportsOpenGLESExtension:@"GL_EXT_shader_framebuffer_fetch"];
|
||||
});
|
||||
|
||||
return supportsFramebufferReads;
|
||||
}
|
||||
|
||||
+ (CGSize)sizeThatFitsWithinATextureForSize:(CGSize)inputSize
|
||||
{
|
||||
GLint maxTextureSize = [self maximumTextureSizeForThisDevice];
|
||||
if ( (inputSize.width < maxTextureSize) && (inputSize.height < maxTextureSize) )
|
||||
{
|
||||
return inputSize;
|
||||
}
|
||||
|
||||
CGSize adjustedSize;
|
||||
if (inputSize.width > inputSize.height)
|
||||
{
|
||||
adjustedSize.width = (CGFloat)maxTextureSize;
|
||||
adjustedSize.height = ((CGFloat)maxTextureSize / inputSize.width) * inputSize.height;
|
||||
}
|
||||
else
|
||||
{
|
||||
adjustedSize.height = (CGFloat)maxTextureSize;
|
||||
adjustedSize.width = ((CGFloat)maxTextureSize / inputSize.height) * inputSize.width;
|
||||
}
|
||||
|
||||
return CGSizeMake(floor(adjustedSize.width), floor(adjustedSize.height));
|
||||
}
|
||||
|
||||
- (void)presentBufferForDisplay
|
||||
{
|
||||
[self.context presentRenderbuffer:GL_RENDERBUFFER];
|
||||
}
|
||||
|
||||
- (GLProgram *)programForVertexShaderString:(NSString *)vertexShaderString fragmentShaderString:(NSString *)fragmentShaderString
|
||||
{
|
||||
NSString *lookupKeyForShaderProgram = [NSString stringWithFormat:@"V: %@ - F: %@", vertexShaderString, fragmentShaderString];
|
||||
GLProgram *programFromCache = [shaderProgramCache objectForKey:lookupKeyForShaderProgram];
|
||||
|
||||
if (programFromCache == nil)
|
||||
{
|
||||
programFromCache = [[GLProgram alloc] initWithVertexShaderString:vertexShaderString fragmentShaderString:fragmentShaderString];
|
||||
[shaderProgramCache setObject:programFromCache forKey:lookupKeyForShaderProgram];
|
||||
// [shaderProgramUsageHistory addObject:lookupKeyForShaderProgram];
|
||||
// if ([shaderProgramUsageHistory count] >= MAXSHADERPROGRAMSALLOWEDINCACHE)
|
||||
// {
|
||||
// for (NSUInteger currentShaderProgramRemovedFromCache = 0; currentShaderProgramRemovedFromCache < 10; currentShaderProgramRemovedFromCache++)
|
||||
// {
|
||||
// NSString *shaderProgramToRemoveFromCache = [shaderProgramUsageHistory objectAtIndex:0];
|
||||
// [shaderProgramUsageHistory removeObjectAtIndex:0];
|
||||
// [shaderProgramCache removeObjectForKey:shaderProgramToRemoveFromCache];
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
return programFromCache;
|
||||
}
|
||||
|
||||
- (void)useSharegroup:(EAGLSharegroup *)sharegroup
|
||||
{
|
||||
NSAssert(_context == nil, @"Unable to use a share group when the context has already been created. Call this method before you use the context for the first time.");
|
||||
|
||||
_sharegroup = sharegroup;
|
||||
}
|
||||
|
||||
- (EAGLContext *)createContext
|
||||
{
|
||||
EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2 sharegroup:_sharegroup];
|
||||
NSAssert(context != nil, @"Unable to create an OpenGL ES 2.0 context. The GPUImage framework requires OpenGL ES 2.0 support to work.");
|
||||
return context;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Manage fast texture upload
|
||||
|
||||
+ (BOOL)supportsFastTextureUpload
|
||||
{
|
||||
#if TARGET_IPHONE_SIMULATOR
|
||||
return NO;
|
||||
#else
|
||||
return (CVOpenGLESTextureCacheCreate != NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Accessors
|
||||
|
||||
- (EAGLContext *)context
|
||||
{
|
||||
if (_context == nil)
|
||||
{
|
||||
_context = [self createContext];
|
||||
[EAGLContext setCurrentContext:_context];
|
||||
|
||||
// Set up a few global settings for the image processing pipeline
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
}
|
||||
|
||||
return _context;
|
||||
}
|
||||
|
||||
- (CVOpenGLESTextureCacheRef)coreVideoTextureCache
|
||||
{
|
||||
if (_coreVideoTextureCache == NULL)
|
||||
{
|
||||
#if defined(__IPHONE_6_0)
|
||||
CVReturn err = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, NULL, [self context], NULL, &_coreVideoTextureCache);
|
||||
#else
|
||||
CVReturn err = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, NULL, (__bridge void *)[self context], NULL, &_coreVideoTextureCache);
|
||||
#endif
|
||||
|
||||
if (err)
|
||||
{
|
||||
NSAssert(NO, @"Error at CVOpenGLESTextureCacheCreate %d", err);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return _coreVideoTextureCache;
|
||||
}
|
||||
|
||||
- (GPUImageFramebufferCache *)framebufferCache
|
||||
{
|
||||
if (_framebufferCache == nil)
|
||||
{
|
||||
_framebufferCache = [[GPUImageFramebufferCache alloc] init];
|
||||
}
|
||||
|
||||
return _framebufferCache;
|
||||
}
|
||||
|
||||
@end
|
||||
139
submodules/LegacyComponents/LegacyComponents/GPUImageFilter.h
Executable file
139
submodules/LegacyComponents/LegacyComponents/GPUImageFilter.h
Executable file
|
|
@ -0,0 +1,139 @@
|
|||
#import "GPUImageOutput.h"
|
||||
|
||||
#define STRINGIZE(x) #x
|
||||
#define STRINGIZE2(x) STRINGIZE(x)
|
||||
#define SHADER_STRING(text) @ STRINGIZE2(text)
|
||||
|
||||
#define GPUImageHashIdentifier #
|
||||
#define GPUImageWrappedLabel(x) x
|
||||
#define GPUImageEscapedHashIdentifier(a) GPUImageWrappedLabel(GPUImageHashIdentifier)a
|
||||
|
||||
extern NSString *const kGPUImageVertexShaderString;
|
||||
extern NSString *const kGPUImagePassthroughFragmentShaderString;
|
||||
|
||||
struct GPUVector4 {
|
||||
GLfloat one;
|
||||
GLfloat two;
|
||||
GLfloat three;
|
||||
GLfloat four;
|
||||
};
|
||||
typedef struct GPUVector4 GPUVector4;
|
||||
|
||||
struct GPUVector3 {
|
||||
GLfloat one;
|
||||
GLfloat two;
|
||||
GLfloat three;
|
||||
};
|
||||
typedef struct GPUVector3 GPUVector3;
|
||||
|
||||
struct GPUMatrix4x4 {
|
||||
GPUVector4 one;
|
||||
GPUVector4 two;
|
||||
GPUVector4 three;
|
||||
GPUVector4 four;
|
||||
};
|
||||
typedef struct GPUMatrix4x4 GPUMatrix4x4;
|
||||
|
||||
struct GPUMatrix3x3 {
|
||||
GPUVector3 one;
|
||||
GPUVector3 two;
|
||||
GPUVector3 three;
|
||||
};
|
||||
typedef struct GPUMatrix3x3 GPUMatrix3x3;
|
||||
|
||||
/** GPUImage's base filter class
|
||||
|
||||
Filters and other subsequent elements in the chain conform to the GPUImageInput protocol, which lets them take in the supplied or processed texture from the previous link in the chain and do something with it. Objects one step further down the chain are considered targets, and processing can be branched by adding multiple targets to a single output or filter.
|
||||
*/
|
||||
@interface GPUImageFilter : GPUImageOutput <GPUImageInput>
|
||||
{
|
||||
GPUImageFramebuffer *firstInputFramebuffer;
|
||||
|
||||
@public
|
||||
GLProgram *filterProgram;
|
||||
@protected
|
||||
GLint filterPositionAttribute, filterTextureCoordinateAttribute;
|
||||
GLint filterInputTextureUniform;
|
||||
GLfloat backgroundColorRed, backgroundColorGreen, backgroundColorBlue, backgroundColorAlpha;
|
||||
|
||||
BOOL isEndProcessing;
|
||||
|
||||
CGSize currentFilterSize;
|
||||
GPUImageRotationMode inputRotation;
|
||||
|
||||
BOOL currentlyReceivingMonochromeInput;
|
||||
|
||||
NSMutableDictionary *uniformStateRestorationBlocks;
|
||||
dispatch_semaphore_t imageCaptureSemaphore;
|
||||
}
|
||||
|
||||
@property (readonly, nonatomic) GLProgram *program;
|
||||
@property (nonatomic, readonly) CVPixelBufferRef renderTarget;
|
||||
@property (readwrite, nonatomic) BOOL preventRendering;
|
||||
@property (readwrite, nonatomic) BOOL currentlyReceivingMonochromeInput;
|
||||
|
||||
/// @name Initialization and teardown
|
||||
|
||||
/**
|
||||
Initialize with vertex and fragment shaders
|
||||
|
||||
You make take advantage of the SHADER_STRING macro to write your shaders in-line.
|
||||
@param vertexShaderString Source code of the vertex shader to use
|
||||
@param fragmentShaderString Source code of the fragment shader to use
|
||||
*/
|
||||
- (id)initWithVertexShaderFromString:(NSString *)vertexShaderString fragmentShaderFromString:(NSString *)fragmentShaderString;
|
||||
|
||||
/**
|
||||
Initialize with a fragment shader
|
||||
|
||||
You may take advantage of the SHADER_STRING macro to write your shader in-line.
|
||||
@param fragmentShaderString Source code of fragment shader to use
|
||||
*/
|
||||
- (id)initWithFragmentShaderFromString:(NSString *)fragmentShaderString;
|
||||
/**
|
||||
Initialize with a fragment shader
|
||||
@param fragmentShaderFilename Filename of fragment shader to load
|
||||
*/
|
||||
- (id)initWithFragmentShaderFromFile:(NSString *)fragmentShaderFilename;
|
||||
- (void)initializeAttributes;
|
||||
- (void)setupFilterForSize:(CGSize)filterFrameSize;
|
||||
- (CGSize)rotatedSize:(CGSize)sizeToRotate forIndex:(NSInteger)textureIndex;
|
||||
- (CGPoint)rotatedPoint:(CGPoint)pointToRotate forRotation:(GPUImageRotationMode)rotation;
|
||||
|
||||
/// @name Managing the display FBOs
|
||||
/** Size of the frame buffer object
|
||||
*/
|
||||
- (CGSize)sizeOfFBO;
|
||||
|
||||
/// @name Rendering
|
||||
+ (const GLfloat *)textureCoordinatesForRotation:(GPUImageRotationMode)rotationMode;
|
||||
- (void)renderToTextureWithVertices:(const GLfloat *)vertices textureCoordinates:(const GLfloat *)textureCoordinates;
|
||||
- (void)informTargetsAboutNewFrameAtTime:(CMTime)frameTime;
|
||||
- (CGSize)outputFrameSize;
|
||||
|
||||
/// @name Input parameters
|
||||
- (void)setBackgroundColorRed:(GLfloat)redComponent green:(GLfloat)greenComponent blue:(GLfloat)blueComponent alpha:(GLfloat)alphaComponent;
|
||||
- (void)setInteger:(GLint)newInteger forUniformName:(NSString *)uniformName;
|
||||
- (void)setFloat:(GLfloat)newFloat forUniformName:(NSString *)uniformName;
|
||||
- (void)setSize:(CGSize)newSize forUniformName:(NSString *)uniformName;
|
||||
- (void)setPoint:(CGPoint)newPoint forUniformName:(NSString *)uniformName;
|
||||
- (void)setFloatVec3:(GPUVector3)newVec3 forUniformName:(NSString *)uniformName;
|
||||
- (void)setFloatVec4:(GPUVector4)newVec4 forUniform:(NSString *)uniformName;
|
||||
- (void)setFloatArray:(GLfloat *)array length:(GLsizei)count forUniform:(NSString*)uniformName;
|
||||
|
||||
- (void)setMatrix3f:(GPUMatrix3x3)matrix forUniform:(GLint)uniform program:(GLProgram *)shaderProgram;
|
||||
- (void)setMatrix4f:(GPUMatrix4x4)matrix forUniform:(GLint)uniform program:(GLProgram *)shaderProgram;
|
||||
- (void)setFloat:(GLfloat)floatValue forUniform:(GLint)uniform program:(GLProgram *)shaderProgram;
|
||||
- (void)setPoint:(CGPoint)pointValue forUniform:(GLint)uniform program:(GLProgram *)shaderProgram;
|
||||
- (void)setSize:(CGSize)sizeValue forUniform:(GLint)uniform program:(GLProgram *)shaderProgram;
|
||||
- (void)setVec3:(GPUVector3)vectorValue forUniform:(GLint)uniform program:(GLProgram *)shaderProgram;
|
||||
- (void)setVec4:(GPUVector4)vectorValue forUniform:(GLint)uniform program:(GLProgram *)shaderProgram;
|
||||
- (void)setFloatArray:(GLfloat *)arrayValue length:(GLsizei)arrayLength forUniform:(GLint)uniform program:(GLProgram *)shaderProgram;
|
||||
- (void)setInteger:(GLint)intValue forUniform:(GLint)uniform program:(GLProgram *)shaderProgram;
|
||||
|
||||
- (void)setAndExecuteUniformStateCallbackAtIndex:(GLint)uniform forProgram:(GLProgram *)shaderProgram toBlock:(dispatch_block_t)uniformStateBlock;
|
||||
- (void)setUniformsForProgramAtIndex:(NSUInteger)programIndex;
|
||||
|
||||
- (GLint)uniformIndexForName:(NSString *)name;
|
||||
|
||||
@end
|
||||
764
submodules/LegacyComponents/LegacyComponents/GPUImageFilter.m
Executable file
764
submodules/LegacyComponents/LegacyComponents/GPUImageFilter.m
Executable file
|
|
@ -0,0 +1,764 @@
|
|||
#import "GPUImageFilter.h"
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
#import "LegacyComponentsInternal.h"
|
||||
|
||||
// Hardcode the vertex shader for standard filters, but this can be overridden
|
||||
NSString *const kGPUImageVertexShaderString = SHADER_STRING
|
||||
(
|
||||
attribute vec4 position;
|
||||
attribute vec4 inputTexCoord;
|
||||
|
||||
varying vec2 texCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = position;
|
||||
texCoord = inputTexCoord.xy;
|
||||
}
|
||||
);
|
||||
|
||||
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
|
||||
|
||||
NSString *const kGPUImagePassthroughFragmentShaderString = SHADER_STRING
|
||||
(
|
||||
varying highp vec2 texCoord;
|
||||
|
||||
uniform sampler2D sourceImage;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_FragColor = texture2D(sourceImage, texCoord);
|
||||
}
|
||||
);
|
||||
|
||||
#else
|
||||
|
||||
NSString *const kGPUImagePassthroughFragmentShaderString = SHADER_STRING
|
||||
(
|
||||
varying vec2 texCoord;
|
||||
|
||||
uniform sampler2D sourceImage;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_FragColor = texture2D(sourceImage, texCoord);
|
||||
}
|
||||
);
|
||||
#endif
|
||||
|
||||
|
||||
@implementation GPUImageFilter
|
||||
|
||||
@synthesize preventRendering = _preventRendering;
|
||||
@synthesize currentlyReceivingMonochromeInput;
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Initialization and teardown
|
||||
|
||||
- (id)initWithVertexShaderFromString:(NSString *)vertexShaderString fragmentShaderFromString:(NSString *)fragmentShaderString
|
||||
{
|
||||
if (!(self = [super init]))
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
uniformStateRestorationBlocks = [NSMutableDictionary dictionaryWithCapacity:10];
|
||||
_preventRendering = NO;
|
||||
currentlyReceivingMonochromeInput = NO;
|
||||
inputRotation = kGPUImageNoRotation;
|
||||
backgroundColorRed = 0.0;
|
||||
backgroundColorGreen = 0.0;
|
||||
backgroundColorBlue = 0.0;
|
||||
backgroundColorAlpha = 0.0;
|
||||
imageCaptureSemaphore = dispatch_semaphore_create(0);
|
||||
dispatch_semaphore_signal(imageCaptureSemaphore);
|
||||
|
||||
runSynchronouslyOnVideoProcessingQueue(^{
|
||||
[GPUImageContext useImageProcessingContext];
|
||||
|
||||
filterProgram = [[GPUImageContext sharedImageProcessingContext] programForVertexShaderString:vertexShaderString fragmentShaderString:fragmentShaderString];
|
||||
|
||||
if (!filterProgram.initialized)
|
||||
{
|
||||
[self initializeAttributes];
|
||||
|
||||
if (![filterProgram link])
|
||||
{
|
||||
NSString *progLog = [filterProgram programLog];
|
||||
NSLog(@"Program link log: %@", progLog);
|
||||
NSString *fragLog = [filterProgram fragmentShaderLog];
|
||||
NSLog(@"Fragment shader compile log: %@", fragLog);
|
||||
NSString *vertLog = [filterProgram vertexShaderLog];
|
||||
NSLog(@"Vertex shader compile log: %@", vertLog);
|
||||
filterProgram = nil;
|
||||
NSAssert(NO, @"Filter shader link failed");
|
||||
}
|
||||
}
|
||||
|
||||
filterPositionAttribute = [filterProgram attributeIndex:@"position"];
|
||||
filterTextureCoordinateAttribute = [filterProgram attributeIndex:@"inputTexCoord"];
|
||||
filterInputTextureUniform = [filterProgram uniformIndex:@"sourceImage"];
|
||||
|
||||
[GPUImageContext setActiveShaderProgram:filterProgram];
|
||||
|
||||
glEnableVertexAttribArray(filterPositionAttribute);
|
||||
glEnableVertexAttribArray(filterTextureCoordinateAttribute);
|
||||
});
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithFragmentShaderFromString:(NSString *)fragmentShaderString
|
||||
{
|
||||
if (!(self = [self initWithVertexShaderFromString:kGPUImageVertexShaderString fragmentShaderFromString:fragmentShaderString]))
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithFragmentShaderFromFile:(NSString *)fragmentShaderFilename
|
||||
{
|
||||
NSString *fragmentShaderPathname = TGComponentsPathForResource(fragmentShaderFilename, @"fsh");
|
||||
NSString *fragmentShaderString = [NSString stringWithContentsOfFile:fragmentShaderPathname encoding:NSUTF8StringEncoding error:nil];
|
||||
|
||||
if (!(self = [self initWithFragmentShaderFromString:fragmentShaderString]))
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if (!(self = [self initWithFragmentShaderFromString:kGPUImagePassthroughFragmentShaderString]))
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)initializeAttributes
|
||||
{
|
||||
[filterProgram addAttribute:@"position"];
|
||||
[filterProgram addAttribute:@"inputTexCoord"];
|
||||
|
||||
// Override this, calling back to this super method, in order to add new attributes to your vertex shader
|
||||
}
|
||||
|
||||
- (void)setupFilterForSize:(CGSize)__unused filterFrameSize
|
||||
{
|
||||
// This is where you can override to provide some custom setup, if your filter has a size-dependent element
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
if (imageCaptureSemaphore != NULL)
|
||||
{
|
||||
dispatch_release(imageCaptureSemaphore);
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Still image processing
|
||||
|
||||
- (void)useNextFrameForImageCapture
|
||||
{
|
||||
usingNextFrameForImageCapture = YES;
|
||||
|
||||
// Set the semaphore high, if it isn't already
|
||||
if (dispatch_semaphore_wait(imageCaptureSemaphore, DISPATCH_TIME_NOW) != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
- (CGImageRef)newCGImageFromCurrentlyProcessedOutput
|
||||
{
|
||||
// Give it three seconds to process, then abort if they forgot to set up the image capture properly
|
||||
double timeoutForImageCapture = 3.0;
|
||||
dispatch_time_t convertedTimeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeoutForImageCapture * NSEC_PER_SEC));
|
||||
|
||||
if (dispatch_semaphore_wait(imageCaptureSemaphore, convertedTimeout) != 0)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GPUImageFramebuffer* framebuffer = [self framebufferForOutput];
|
||||
|
||||
usingNextFrameForImageCapture = NO;
|
||||
dispatch_semaphore_signal(imageCaptureSemaphore);
|
||||
|
||||
CGImageRef image = [framebuffer newCGImageFromFramebufferContents];
|
||||
return image;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Managing the display FBOs
|
||||
|
||||
- (CGSize)sizeOfFBO
|
||||
{
|
||||
CGSize outputSize = [self maximumOutputSize];
|
||||
if ( (CGSizeEqualToSize(outputSize, CGSizeZero)) || (inputTextureSize.width < outputSize.width) )
|
||||
{
|
||||
return inputTextureSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
return outputSize;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Rendering
|
||||
|
||||
+ (const GLfloat *)textureCoordinatesForRotation:(GPUImageRotationMode)rotationMode
|
||||
{
|
||||
static const GLfloat noRotationTextureCoordinates[] = {
|
||||
0.0f, 0.0f,
|
||||
1.0f, 0.0f,
|
||||
0.0f, 1.0f,
|
||||
1.0f, 1.0f,
|
||||
};
|
||||
|
||||
static const GLfloat rotateLeftTextureCoordinates[] = {
|
||||
1.0f, 0.0f,
|
||||
1.0f, 1.0f,
|
||||
0.0f, 0.0f,
|
||||
0.0f, 1.0f,
|
||||
};
|
||||
|
||||
static const GLfloat rotateRightTextureCoordinates[] = {
|
||||
0.0f, 1.0f,
|
||||
0.0f, 0.0f,
|
||||
1.0f, 1.0f,
|
||||
1.0f, 0.0f,
|
||||
};
|
||||
|
||||
static const GLfloat verticalFlipTextureCoordinates[] = {
|
||||
0.0f, 1.0f,
|
||||
1.0f, 1.0f,
|
||||
0.0f, 0.0f,
|
||||
1.0f, 0.0f,
|
||||
};
|
||||
|
||||
static const GLfloat horizontalFlipTextureCoordinates[] = {
|
||||
1.0f, 0.0f,
|
||||
0.0f, 0.0f,
|
||||
1.0f, 1.0f,
|
||||
0.0f, 1.0f,
|
||||
};
|
||||
|
||||
static const GLfloat rotateRightVerticalFlipTextureCoordinates[] = {
|
||||
0.0f, 0.0f,
|
||||
0.0f, 1.0f,
|
||||
1.0f, 0.0f,
|
||||
1.0f, 1.0f,
|
||||
};
|
||||
|
||||
static const GLfloat rotateRightHorizontalFlipTextureCoordinates[] = {
|
||||
1.0f, 1.0f,
|
||||
1.0f, 0.0f,
|
||||
0.0f, 1.0f,
|
||||
0.0f, 0.0f,
|
||||
};
|
||||
|
||||
static const GLfloat rotate180TextureCoordinates[] = {
|
||||
1.0f, 1.0f,
|
||||
0.0f, 1.0f,
|
||||
1.0f, 0.0f,
|
||||
0.0f, 0.0f,
|
||||
};
|
||||
|
||||
switch(rotationMode)
|
||||
{
|
||||
case kGPUImageNoRotation: return noRotationTextureCoordinates;
|
||||
case kGPUImageRotateLeft: return rotateLeftTextureCoordinates;
|
||||
case kGPUImageRotateRight: return rotateRightTextureCoordinates;
|
||||
case kGPUImageFlipVertical: return verticalFlipTextureCoordinates;
|
||||
case kGPUImageFlipHorizonal: return horizontalFlipTextureCoordinates;
|
||||
case kGPUImageRotateRightFlipVertical: return rotateRightVerticalFlipTextureCoordinates;
|
||||
case kGPUImageRotateRightFlipHorizontal: return rotateRightHorizontalFlipTextureCoordinates;
|
||||
case kGPUImageRotate180: return rotate180TextureCoordinates;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)renderToTextureWithVertices:(const GLfloat *)vertices textureCoordinates:(const GLfloat *)textureCoordinates
|
||||
{
|
||||
if (self.preventRendering)
|
||||
{
|
||||
[firstInputFramebuffer unlock];
|
||||
return;
|
||||
}
|
||||
|
||||
[GPUImageContext setActiveShaderProgram:filterProgram];
|
||||
|
||||
outputFramebuffer = [[GPUImageContext sharedFramebufferCache] fetchFramebufferForSize:[self sizeOfFBO] textureOptions:self.outputTextureOptions onlyTexture:NO];
|
||||
[outputFramebuffer activateFramebuffer];
|
||||
if (usingNextFrameForImageCapture)
|
||||
{
|
||||
[outputFramebuffer lock];
|
||||
}
|
||||
|
||||
[self setUniformsForProgramAtIndex:0];
|
||||
|
||||
glClearColor(backgroundColorRed, backgroundColorGreen, backgroundColorBlue, backgroundColorAlpha);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
glActiveTexture(GL_TEXTURE2);
|
||||
glBindTexture(GL_TEXTURE_2D, [firstInputFramebuffer texture]);
|
||||
|
||||
glUniform1i(filterInputTextureUniform, 2);
|
||||
|
||||
glVertexAttribPointer(filterPositionAttribute, 2, GL_FLOAT, 0, 0, vertices);
|
||||
glVertexAttribPointer(filterTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, textureCoordinates);
|
||||
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
|
||||
[firstInputFramebuffer unlock];
|
||||
|
||||
if (usingNextFrameForImageCapture)
|
||||
{
|
||||
dispatch_semaphore_signal(imageCaptureSemaphore);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)informTargetsAboutNewFrameAtTime:(CMTime)frameTime
|
||||
{
|
||||
if (self.frameProcessingCompletionBlock != NULL)
|
||||
{
|
||||
self.frameProcessingCompletionBlock(self, frameTime);
|
||||
}
|
||||
|
||||
// Get all targets the framebuffer so they can grab a lock on it
|
||||
for (id<GPUImageInput> currentTarget in targets)
|
||||
{
|
||||
if (currentTarget != self.targetToIgnoreForUpdates)
|
||||
{
|
||||
NSInteger indexOfObject = [targets indexOfObject:currentTarget];
|
||||
NSInteger textureIndex = [[targetTextureIndices objectAtIndex:indexOfObject] integerValue];
|
||||
|
||||
[self setInputFramebufferForTarget:currentTarget atIndex:textureIndex];
|
||||
[currentTarget setInputSize:[self outputFrameSize] atIndex:textureIndex];
|
||||
}
|
||||
}
|
||||
|
||||
// Release our hold so it can return to the cache immediately upon processing
|
||||
[[self framebufferForOutput] unlock];
|
||||
|
||||
if (usingNextFrameForImageCapture)
|
||||
{
|
||||
// usingNextFrameForImageCapture = NO;
|
||||
}
|
||||
else
|
||||
{
|
||||
[self removeOutputFramebuffer];
|
||||
}
|
||||
|
||||
// Trigger processing last, so that our unlock comes first in serial execution, avoiding the need for a callback
|
||||
for (id<GPUImageInput> currentTarget in targets)
|
||||
{
|
||||
if (currentTarget != self.targetToIgnoreForUpdates)
|
||||
{
|
||||
NSInteger indexOfObject = [targets indexOfObject:currentTarget];
|
||||
NSInteger textureIndex = [[targetTextureIndices objectAtIndex:indexOfObject] integerValue];
|
||||
[currentTarget newFrameReadyAtTime:frameTime atIndex:textureIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (CGSize)outputFrameSize
|
||||
{
|
||||
return inputTextureSize;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Input parameters
|
||||
|
||||
- (void)setBackgroundColorRed:(GLfloat)redComponent green:(GLfloat)greenComponent blue:(GLfloat)blueComponent alpha:(GLfloat)alphaComponent
|
||||
{
|
||||
backgroundColorRed = redComponent;
|
||||
backgroundColorGreen = greenComponent;
|
||||
backgroundColorBlue = blueComponent;
|
||||
backgroundColorAlpha = alphaComponent;
|
||||
}
|
||||
|
||||
- (void)setInteger:(GLint)newInteger forUniformName:(NSString *)uniformName
|
||||
{
|
||||
GLint uniformIndex = [filterProgram uniformIndex:uniformName];
|
||||
[self setInteger:newInteger forUniform:uniformIndex program:filterProgram];
|
||||
}
|
||||
|
||||
- (void)setFloat:(GLfloat)newFloat forUniformName:(NSString *)uniformName
|
||||
{
|
||||
GLint uniformIndex = [filterProgram uniformIndex:uniformName];
|
||||
[self setFloat:newFloat forUniform:uniformIndex program:filterProgram];
|
||||
}
|
||||
|
||||
- (void)setSize:(CGSize)newSize forUniformName:(NSString *)uniformName
|
||||
{
|
||||
GLint uniformIndex = [filterProgram uniformIndex:uniformName];
|
||||
[self setSize:newSize forUniform:uniformIndex program:filterProgram];
|
||||
}
|
||||
|
||||
- (void)setPoint:(CGPoint)newPoint forUniformName:(NSString *)uniformName
|
||||
{
|
||||
GLint uniformIndex = [filterProgram uniformIndex:uniformName];
|
||||
[self setPoint:newPoint forUniform:uniformIndex program:filterProgram];
|
||||
}
|
||||
|
||||
- (void)setFloatVec3:(GPUVector3)newVec3 forUniformName:(NSString *)uniformName
|
||||
{
|
||||
GLint uniformIndex = [filterProgram uniformIndex:uniformName];
|
||||
[self setVec3:newVec3 forUniform:uniformIndex program:filterProgram];
|
||||
}
|
||||
|
||||
- (void)setFloatVec4:(GPUVector4)newVec4 forUniform:(NSString *)uniformName
|
||||
{
|
||||
GLint uniformIndex = [filterProgram uniformIndex:uniformName];
|
||||
[self setVec4:newVec4 forUniform:uniformIndex program:filterProgram];
|
||||
}
|
||||
|
||||
- (void)setFloatArray:(GLfloat *)array length:(GLsizei)count forUniform:(NSString*)uniformName
|
||||
{
|
||||
GLint uniformIndex = [filterProgram uniformIndex:uniformName];
|
||||
|
||||
[self setFloatArray:array length:count forUniform:uniformIndex program:filterProgram];
|
||||
}
|
||||
|
||||
- (void)setMatrix3f:(GPUMatrix3x3)matrix forUniform:(GLint)uniform program:(GLProgram *)shaderProgram
|
||||
{
|
||||
runAsynchronouslyOnVideoProcessingQueue(^{
|
||||
[GPUImageContext setActiveShaderProgram:shaderProgram];
|
||||
[self setAndExecuteUniformStateCallbackAtIndex:uniform forProgram:shaderProgram toBlock:^{
|
||||
glUniformMatrix3fv(uniform, 1, GL_FALSE, (GLfloat *)&matrix);
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)setMatrix4f:(GPUMatrix4x4)matrix forUniform:(GLint)uniform program:(GLProgram *)shaderProgram
|
||||
{
|
||||
runAsynchronouslyOnVideoProcessingQueue(^{
|
||||
[GPUImageContext setActiveShaderProgram:shaderProgram];
|
||||
[self setAndExecuteUniformStateCallbackAtIndex:uniform forProgram:shaderProgram toBlock:^{
|
||||
glUniformMatrix4fv(uniform, 1, GL_FALSE, (GLfloat *)&matrix);
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)setFloat:(GLfloat)floatValue forUniform:(GLint)uniform program:(GLProgram *)shaderProgram
|
||||
{
|
||||
runAsynchronouslyOnVideoProcessingQueue(^{
|
||||
[GPUImageContext setActiveShaderProgram:shaderProgram];
|
||||
[self setAndExecuteUniformStateCallbackAtIndex:uniform forProgram:shaderProgram toBlock:^{
|
||||
glUniform1f(uniform, floatValue);
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)setPoint:(CGPoint)pointValue forUniform:(GLint)uniform program:(GLProgram *)shaderProgram
|
||||
{
|
||||
runAsynchronouslyOnVideoProcessingQueue(^{
|
||||
[GPUImageContext setActiveShaderProgram:shaderProgram];
|
||||
[self setAndExecuteUniformStateCallbackAtIndex:uniform forProgram:shaderProgram toBlock:^{
|
||||
GLfloat positionArray[2];
|
||||
positionArray[0] = (GLfloat)pointValue.x;
|
||||
positionArray[1] = (GLfloat)pointValue.y;
|
||||
|
||||
glUniform2fv(uniform, 1, positionArray);
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)setSize:(CGSize)sizeValue forUniform:(GLint)uniform program:(GLProgram *)shaderProgram
|
||||
{
|
||||
runAsynchronouslyOnVideoProcessingQueue(^{
|
||||
[GPUImageContext setActiveShaderProgram:shaderProgram];
|
||||
|
||||
[self setAndExecuteUniformStateCallbackAtIndex:uniform forProgram:shaderProgram toBlock:^{
|
||||
GLfloat sizeArray[2];
|
||||
sizeArray[0] = (GLfloat)sizeValue.width;
|
||||
sizeArray[1] = (GLfloat)sizeValue.height;
|
||||
|
||||
glUniform2fv(uniform, 1, sizeArray);
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)setVec3:(GPUVector3)vectorValue forUniform:(GLint)uniform program:(GLProgram *)shaderProgram
|
||||
{
|
||||
runAsynchronouslyOnVideoProcessingQueue(^{
|
||||
[GPUImageContext setActiveShaderProgram:shaderProgram];
|
||||
|
||||
[self setAndExecuteUniformStateCallbackAtIndex:uniform forProgram:shaderProgram toBlock:^{
|
||||
glUniform3fv(uniform, 1, (GLfloat *)&vectorValue);
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)setVec4:(GPUVector4)vectorValue forUniform:(GLint)uniform program:(GLProgram *)shaderProgram
|
||||
{
|
||||
runAsynchronouslyOnVideoProcessingQueue(^{
|
||||
[GPUImageContext setActiveShaderProgram:shaderProgram];
|
||||
|
||||
[self setAndExecuteUniformStateCallbackAtIndex:uniform forProgram:shaderProgram toBlock:^{
|
||||
glUniform4fv(uniform, 1, (GLfloat *)&vectorValue);
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)setFloatArray:(GLfloat *)arrayValue length:(GLsizei)arrayLength forUniform:(GLint)uniform program:(GLProgram *)shaderProgram
|
||||
{
|
||||
// Make a copy of the data, so it doesn't get overwritten before async call executes
|
||||
NSData* arrayData = [NSData dataWithBytes:arrayValue length:arrayLength * sizeof(arrayValue[0])];
|
||||
|
||||
runAsynchronouslyOnVideoProcessingQueue(^{
|
||||
[GPUImageContext setActiveShaderProgram:shaderProgram];
|
||||
|
||||
[self setAndExecuteUniformStateCallbackAtIndex:uniform forProgram:shaderProgram toBlock:^{
|
||||
glUniform1fv(uniform, arrayLength, [arrayData bytes]);
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)setInteger:(GLint)intValue forUniform:(GLint)uniform program:(GLProgram *)shaderProgram
|
||||
{
|
||||
runAsynchronouslyOnVideoProcessingQueue(^{
|
||||
[GPUImageContext setActiveShaderProgram:shaderProgram];
|
||||
|
||||
[self setAndExecuteUniformStateCallbackAtIndex:uniform forProgram:shaderProgram toBlock:^{
|
||||
glUniform1i(uniform, intValue);
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)setAndExecuteUniformStateCallbackAtIndex:(GLint)uniform forProgram:(GLProgram *)__unused shaderProgram toBlock:(dispatch_block_t)uniformStateBlock
|
||||
{
|
||||
[uniformStateRestorationBlocks setObject:[uniformStateBlock copy] forKey:[NSNumber numberWithInt:uniform]];
|
||||
uniformStateBlock();
|
||||
}
|
||||
|
||||
- (void)setUniformsForProgramAtIndex:(NSUInteger)__unused programIndex
|
||||
{
|
||||
[uniformStateRestorationBlocks enumerateKeysAndObjectsUsingBlock:^(__unused id key, id obj, __unused BOOL *stop){
|
||||
dispatch_block_t currentBlock = obj;
|
||||
currentBlock();
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark GPUImageInput
|
||||
|
||||
- (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)__unused textureIndex
|
||||
{
|
||||
static const GLfloat imageVertices[] = {
|
||||
-1.0f, -1.0f,
|
||||
1.0f, -1.0f,
|
||||
-1.0f, 1.0f,
|
||||
1.0f, 1.0f,
|
||||
};
|
||||
|
||||
[self renderToTextureWithVertices:imageVertices textureCoordinates:[[self class] textureCoordinatesForRotation:inputRotation]];
|
||||
|
||||
[self informTargetsAboutNewFrameAtTime:frameTime];
|
||||
}
|
||||
|
||||
- (NSInteger)nextAvailableTextureIndex
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)__unused textureIndex
|
||||
{
|
||||
firstInputFramebuffer = newInputFramebuffer;
|
||||
[firstInputFramebuffer lock];
|
||||
}
|
||||
|
||||
- (CGSize)rotatedSize:(CGSize)sizeToRotate forIndex:(NSInteger)__unused textureIndex
|
||||
{
|
||||
CGSize rotatedSize = sizeToRotate;
|
||||
|
||||
if (GPUImageRotationSwapsWidthAndHeight(inputRotation))
|
||||
{
|
||||
rotatedSize.width = sizeToRotate.height;
|
||||
rotatedSize.height = sizeToRotate.width;
|
||||
}
|
||||
|
||||
return rotatedSize;
|
||||
}
|
||||
|
||||
- (CGPoint)rotatedPoint:(CGPoint)pointToRotate forRotation:(GPUImageRotationMode)rotation
|
||||
{
|
||||
CGPoint rotatedPoint;
|
||||
switch(rotation)
|
||||
{
|
||||
case kGPUImageNoRotation: return pointToRotate; break;
|
||||
case kGPUImageFlipHorizonal:
|
||||
{
|
||||
rotatedPoint.x = 1.0f - (GLfloat)pointToRotate.x;
|
||||
rotatedPoint.y = (GLfloat)pointToRotate.y;
|
||||
}; break;
|
||||
case kGPUImageFlipVertical:
|
||||
{
|
||||
rotatedPoint.x = pointToRotate.x;
|
||||
rotatedPoint.y = 1.0f - pointToRotate.y;
|
||||
}; break;
|
||||
case kGPUImageRotateLeft:
|
||||
{
|
||||
rotatedPoint.x = 1.0f - pointToRotate.y;
|
||||
rotatedPoint.y = pointToRotate.x;
|
||||
}; break;
|
||||
case kGPUImageRotateRight:
|
||||
{
|
||||
rotatedPoint.x = pointToRotate.y;
|
||||
rotatedPoint.y = 1.0f - pointToRotate.x;
|
||||
}; break;
|
||||
case kGPUImageRotateRightFlipVertical:
|
||||
{
|
||||
rotatedPoint.x = pointToRotate.y;
|
||||
rotatedPoint.y = pointToRotate.x;
|
||||
}; break;
|
||||
case kGPUImageRotateRightFlipHorizontal:
|
||||
{
|
||||
rotatedPoint.x = 1.0f - pointToRotate.y;
|
||||
rotatedPoint.y = 1.0f - pointToRotate.x;
|
||||
}; break;
|
||||
case kGPUImageRotate180:
|
||||
{
|
||||
rotatedPoint.x = 1.0f - pointToRotate.x;
|
||||
rotatedPoint.y = 1.0f - pointToRotate.y;
|
||||
}; break;
|
||||
}
|
||||
|
||||
return rotatedPoint;
|
||||
}
|
||||
|
||||
- (void)setInputSize:(CGSize)newSize atIndex:(NSInteger)textureIndex
|
||||
{
|
||||
if (self.preventRendering)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (overrideInputSize)
|
||||
{
|
||||
if (CGSizeEqualToSize(forcedMaximumSize, CGSizeZero))
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
CGRect insetRect = AVMakeRectWithAspectRatioInsideRect(newSize, CGRectMake(0.0, 0.0, forcedMaximumSize.width, forcedMaximumSize.height));
|
||||
inputTextureSize = insetRect.size;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CGSize rotatedSize = [self rotatedSize:newSize forIndex:textureIndex];
|
||||
|
||||
if (CGSizeEqualToSize(rotatedSize, CGSizeZero))
|
||||
{
|
||||
inputTextureSize = rotatedSize;
|
||||
}
|
||||
else if (!CGSizeEqualToSize(inputTextureSize, rotatedSize))
|
||||
{
|
||||
inputTextureSize = rotatedSize;
|
||||
}
|
||||
}
|
||||
|
||||
[self setupFilterForSize:[self sizeOfFBO]];
|
||||
}
|
||||
|
||||
- (void)setInputRotation:(GPUImageRotationMode)newInputRotation atIndex:(NSInteger)__unused textureIndex
|
||||
{
|
||||
inputRotation = newInputRotation;
|
||||
}
|
||||
|
||||
- (void)forceProcessingAtSize:(CGSize)frameSize
|
||||
{
|
||||
if (CGSizeEqualToSize(frameSize, CGSizeZero))
|
||||
{
|
||||
overrideInputSize = NO;
|
||||
}
|
||||
else
|
||||
{
|
||||
overrideInputSize = YES;
|
||||
inputTextureSize = frameSize;
|
||||
forcedMaximumSize = CGSizeZero;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)forceProcessingAtSizeRespectingAspectRatio:(CGSize)frameSize
|
||||
{
|
||||
if (CGSizeEqualToSize(frameSize, CGSizeZero))
|
||||
{
|
||||
overrideInputSize = NO;
|
||||
inputTextureSize = CGSizeZero;
|
||||
forcedMaximumSize = CGSizeZero;
|
||||
}
|
||||
else
|
||||
{
|
||||
overrideInputSize = YES;
|
||||
forcedMaximumSize = frameSize;
|
||||
}
|
||||
}
|
||||
|
||||
- (CGSize)maximumOutputSize
|
||||
{
|
||||
// I'm temporarily disabling adjustments for smaller output sizes until I figure out how to make this work better
|
||||
return CGSizeZero;
|
||||
|
||||
/*
|
||||
if (CGSizeEqualToSize(cachedMaximumOutputSize, CGSizeZero))
|
||||
{
|
||||
for (id<GPUImageInput> currentTarget in targets)
|
||||
{
|
||||
if ([currentTarget maximumOutputSize].width > cachedMaximumOutputSize.width)
|
||||
{
|
||||
cachedMaximumOutputSize = [currentTarget maximumOutputSize];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cachedMaximumOutputSize;
|
||||
*/
|
||||
}
|
||||
|
||||
- (void)endProcessing
|
||||
{
|
||||
if (!isEndProcessing)
|
||||
{
|
||||
isEndProcessing = YES;
|
||||
|
||||
for (id<GPUImageInput> currentTarget in targets)
|
||||
{
|
||||
[currentTarget endProcessing];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)wantsMonochromeInput
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (GLint)uniformIndexForName:(NSString *)name
|
||||
{
|
||||
return [filterProgram uniformIndex:name];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Accessors
|
||||
|
||||
- (GLProgram *)program
|
||||
{
|
||||
return filterProgram;
|
||||
}
|
||||
|
||||
@end
|
||||
58
submodules/LegacyComponents/LegacyComponents/GPUImageFramebuffer.h
Executable file
58
submodules/LegacyComponents/LegacyComponents/GPUImageFramebuffer.h
Executable file
|
|
@ -0,0 +1,58 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
|
||||
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
|
||||
#import <OpenGLES/EAGL.h>
|
||||
#import <OpenGLES/ES2/gl.h>
|
||||
#import <OpenGLES/ES2/glext.h>
|
||||
#else
|
||||
#import <OpenGL/OpenGL.h>
|
||||
#import <OpenGL/gl.h>
|
||||
#endif
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import <CoreMedia/CoreMedia.h>
|
||||
|
||||
|
||||
typedef struct GPUTextureOptions {
|
||||
GLenum minFilter;
|
||||
GLenum magFilter;
|
||||
GLenum wrapS;
|
||||
GLenum wrapT;
|
||||
GLenum internalFormat;
|
||||
GLenum format;
|
||||
GLenum type;
|
||||
} GPUTextureOptions;
|
||||
|
||||
@interface GPUImageFramebuffer : NSObject
|
||||
|
||||
@property (nonatomic, readonly) CGSize size;
|
||||
@property (nonatomic, readonly) GPUTextureOptions textureOptions;
|
||||
@property (nonatomic, readonly) GLuint texture;
|
||||
@property (nonatomic, readonly) BOOL missingFramebuffer;
|
||||
|
||||
// Initialization and teardown
|
||||
- (id)initWithSize:(CGSize)framebufferSize;
|
||||
- (id)initWithSize:(CGSize)framebufferSize textureOptions:(GPUTextureOptions)fboTextureOptions onlyTexture:(BOOL)onlyGenerateTexture;
|
||||
- (id)initWithSize:(CGSize)framebufferSize overriddenTexture:(GLuint)inputTexture;
|
||||
|
||||
// Usage
|
||||
- (void)activateFramebuffer;
|
||||
|
||||
// Reference counting
|
||||
- (void)lock;
|
||||
- (void)unlock;
|
||||
- (void)clearAllLocks;
|
||||
- (void)disableReferenceCounting;
|
||||
- (void)enableReferenceCounting;
|
||||
|
||||
// Image capture
|
||||
- (CGImageRef)newCGImageFromFramebufferContents;
|
||||
- (void)restoreRenderTarget;
|
||||
|
||||
// Raw data bytes
|
||||
- (void)lockForReading;
|
||||
- (void)unlockAfterReading;
|
||||
- (NSUInteger)bytesPerRow;
|
||||
- (GLubyte *)byteBuffer;
|
||||
|
||||
@end
|
||||
437
submodules/LegacyComponents/LegacyComponents/GPUImageFramebuffer.m
Executable file
437
submodules/LegacyComponents/LegacyComponents/GPUImageFramebuffer.m
Executable file
|
|
@ -0,0 +1,437 @@
|
|||
#import "GPUImageFramebuffer.h"
|
||||
#import "GPUImageOutput.h"
|
||||
|
||||
@interface GPUImageFramebuffer()
|
||||
{
|
||||
GLuint framebuffer;
|
||||
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
|
||||
CVPixelBufferRef renderTarget;
|
||||
CVOpenGLESTextureRef renderTexture;
|
||||
NSUInteger readLockCount;
|
||||
#else
|
||||
#endif
|
||||
NSUInteger framebufferReferenceCount;
|
||||
BOOL referenceCountingDisabled;
|
||||
}
|
||||
|
||||
- (void)generateFramebuffer;
|
||||
- (void)generateTexture;
|
||||
- (void)destroyFramebuffer;
|
||||
|
||||
@end
|
||||
|
||||
void dataProviderReleaseCallback (void *info, const void *data, size_t size);
|
||||
void dataProviderUnlockCallback (void *info, const void *data, size_t size);
|
||||
|
||||
@implementation GPUImageFramebuffer
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Initialization and teardown
|
||||
|
||||
- (id)initWithSize:(CGSize)framebufferSize textureOptions:(GPUTextureOptions)fboTextureOptions onlyTexture:(BOOL)onlyGenerateTexture
|
||||
{
|
||||
if (!(self = [super init]))
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
_textureOptions = fboTextureOptions;
|
||||
_size = framebufferSize;
|
||||
framebufferReferenceCount = 0;
|
||||
referenceCountingDisabled = NO;
|
||||
_missingFramebuffer = onlyGenerateTexture;
|
||||
|
||||
if (_missingFramebuffer)
|
||||
{
|
||||
runSynchronouslyOnVideoProcessingQueue(^{
|
||||
[GPUImageContext useImageProcessingContext];
|
||||
[self generateTexture];
|
||||
framebuffer = 0;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
[self generateFramebuffer];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithSize:(CGSize)framebufferSize overriddenTexture:(GLuint)inputTexture
|
||||
{
|
||||
if (!(self = [super init]))
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
GPUTextureOptions defaultTextureOptions;
|
||||
defaultTextureOptions.minFilter = GL_LINEAR;
|
||||
defaultTextureOptions.magFilter = GL_LINEAR;
|
||||
defaultTextureOptions.wrapS = GL_CLAMP_TO_EDGE;
|
||||
defaultTextureOptions.wrapT = GL_CLAMP_TO_EDGE;
|
||||
defaultTextureOptions.internalFormat = GL_RGBA;
|
||||
defaultTextureOptions.format = GL_BGRA;
|
||||
defaultTextureOptions.type = GL_UNSIGNED_BYTE;
|
||||
|
||||
_textureOptions = defaultTextureOptions;
|
||||
_size = framebufferSize;
|
||||
framebufferReferenceCount = 0;
|
||||
referenceCountingDisabled = YES;
|
||||
|
||||
_texture = inputTexture;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithSize:(CGSize)framebufferSize
|
||||
{
|
||||
GPUTextureOptions defaultTextureOptions;
|
||||
defaultTextureOptions.minFilter = GL_LINEAR;
|
||||
defaultTextureOptions.magFilter = GL_LINEAR;
|
||||
defaultTextureOptions.wrapS = GL_CLAMP_TO_EDGE;
|
||||
defaultTextureOptions.wrapT = GL_CLAMP_TO_EDGE;
|
||||
defaultTextureOptions.internalFormat = GL_RGBA;
|
||||
defaultTextureOptions.format = GL_BGRA;
|
||||
defaultTextureOptions.type = GL_UNSIGNED_BYTE;
|
||||
|
||||
if (!(self = [self initWithSize:framebufferSize textureOptions:defaultTextureOptions onlyTexture:NO]))
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self destroyFramebuffer];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Internal
|
||||
|
||||
- (void)generateTexture
|
||||
{
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
glGenTextures(1, &_texture);
|
||||
glBindTexture(GL_TEXTURE_2D, _texture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _textureOptions.minFilter);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, _textureOptions.magFilter);
|
||||
// This is necessary for non-power-of-two textures
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, _textureOptions.wrapS);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, _textureOptions.wrapT);
|
||||
|
||||
// TODO: Handle mipmaps
|
||||
}
|
||||
|
||||
- (void)generateFramebuffer
|
||||
{
|
||||
runSynchronouslyOnVideoProcessingQueue(^{
|
||||
[GPUImageContext useImageProcessingContext];
|
||||
|
||||
glGenFramebuffers(1, &framebuffer);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
|
||||
|
||||
// By default, all framebuffers on iOS 5.0+ devices are backed by texture caches, using one shared cache
|
||||
if ([GPUImageContext supportsFastTextureUpload])
|
||||
{
|
||||
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
|
||||
CVOpenGLESTextureCacheRef coreVideoTextureCache = [[GPUImageContext sharedImageProcessingContext] coreVideoTextureCache];
|
||||
// Code originally sourced from http://allmybrain.com/2011/12/08/rendering-to-a-texture-with-ios-5-texture-cache-api/
|
||||
|
||||
CFDictionaryRef empty; // empty value for attr value.
|
||||
CFMutableDictionaryRef attrs;
|
||||
empty = CFDictionaryCreate(kCFAllocatorDefault, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); // our empty IOSurface properties dictionary
|
||||
attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
||||
CFDictionarySetValue(attrs, kCVPixelBufferIOSurfacePropertiesKey, empty);
|
||||
|
||||
CVReturn err = CVPixelBufferCreate(kCFAllocatorDefault, (int)_size.width, (int)_size.height, kCVPixelFormatType_32BGRA, attrs, &renderTarget);
|
||||
if (err)
|
||||
{
|
||||
NSLog(@"FBO size: %f, %f", _size.width, _size.height);
|
||||
NSAssert(NO, @"Error at CVPixelBufferCreate %d", err);
|
||||
}
|
||||
|
||||
err = CVOpenGLESTextureCacheCreateTextureFromImage (kCFAllocatorDefault, coreVideoTextureCache, renderTarget,
|
||||
NULL, // texture attributes
|
||||
GL_TEXTURE_2D,
|
||||
_textureOptions.internalFormat, // opengl format
|
||||
(int)_size.width,
|
||||
(int)_size.height,
|
||||
_textureOptions.format, // native iOS format
|
||||
_textureOptions.type,
|
||||
0,
|
||||
&renderTexture);
|
||||
if (err)
|
||||
{
|
||||
NSAssert(NO, @"Error at CVOpenGLESTextureCacheCreateTextureFromImage %d", err);
|
||||
}
|
||||
|
||||
CFRelease(attrs);
|
||||
CFRelease(empty);
|
||||
|
||||
glBindTexture(CVOpenGLESTextureGetTarget(renderTexture), CVOpenGLESTextureGetName(renderTexture));
|
||||
_texture = CVOpenGLESTextureGetName(renderTexture);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, _textureOptions.wrapS);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, _textureOptions.wrapT);
|
||||
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, CVOpenGLESTextureGetName(renderTexture), 0);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
[self generateTexture];
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, _texture);
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, _textureOptions.internalFormat, (int)_size.width, (int)_size.height, 0, _textureOptions.format, _textureOptions.type, 0);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0);
|
||||
}
|
||||
|
||||
#ifndef NS_BLOCK_ASSERTIONS
|
||||
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
NSAssert(status == GL_FRAMEBUFFER_COMPLETE, @"Incomplete filter FBO: %d", status);
|
||||
#endif
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
});
|
||||
}
|
||||
|
||||
- (void)destroyFramebuffer
|
||||
{
|
||||
runSynchronouslyOnVideoProcessingQueue(^{
|
||||
[GPUImageContext useImageProcessingContext];
|
||||
|
||||
if (framebuffer)
|
||||
{
|
||||
glDeleteFramebuffers(1, &framebuffer);
|
||||
framebuffer = 0;
|
||||
}
|
||||
|
||||
|
||||
if ([GPUImageContext supportsFastTextureUpload] && (!_missingFramebuffer))
|
||||
{
|
||||
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
|
||||
if (renderTarget)
|
||||
{
|
||||
CFRelease(renderTarget);
|
||||
renderTarget = NULL;
|
||||
}
|
||||
|
||||
if (renderTexture)
|
||||
{
|
||||
CFRelease(renderTexture);
|
||||
renderTexture = NULL;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
glDeleteTextures(1, &_texture);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Usage
|
||||
|
||||
- (void)activateFramebuffer
|
||||
{
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
|
||||
glViewport(0, 0, (int)_size.width, (int)_size.height);
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Reference counting
|
||||
|
||||
- (void)lock
|
||||
{
|
||||
if (referenceCountingDisabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
framebufferReferenceCount++;
|
||||
}
|
||||
|
||||
- (void)unlock
|
||||
{
|
||||
if (referenceCountingDisabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NSAssert(framebufferReferenceCount > 0, @"Tried to overrelease a framebuffer, did you forget to call -useNextFrameForImageCapture before using -imageFromCurrentFramebuffer?");
|
||||
framebufferReferenceCount--;
|
||||
if (framebufferReferenceCount < 1)
|
||||
{
|
||||
[[GPUImageContext sharedFramebufferCache] returnFramebufferToCache:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)clearAllLocks
|
||||
{
|
||||
framebufferReferenceCount = 0;
|
||||
}
|
||||
|
||||
- (void)disableReferenceCounting
|
||||
{
|
||||
referenceCountingDisabled = YES;
|
||||
}
|
||||
|
||||
- (void)enableReferenceCounting
|
||||
{
|
||||
referenceCountingDisabled = NO;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Image capture
|
||||
|
||||
void dataProviderReleaseCallback (__unused void *info, const void *data, __unused size_t size)
|
||||
{
|
||||
free((void *)data);
|
||||
}
|
||||
|
||||
void dataProviderUnlockCallback (void *info, __unused const void *data, __unused size_t size)
|
||||
{
|
||||
GPUImageFramebuffer *framebuffer = (__bridge_transfer GPUImageFramebuffer*)info;
|
||||
|
||||
[framebuffer restoreRenderTarget];
|
||||
[framebuffer unlock];
|
||||
[[GPUImageContext sharedFramebufferCache] removeFramebufferFromActiveImageCaptureList:framebuffer];
|
||||
}
|
||||
|
||||
- (CGImageRef)newCGImageFromFramebufferContents
|
||||
{
|
||||
// a CGImage can only be created from a 'normal' color texture
|
||||
NSAssert(self.textureOptions.internalFormat == GL_RGBA, @"For conversion to a CGImage the output texture format for this filter must be GL_RGBA.");
|
||||
NSAssert(self.textureOptions.type == GL_UNSIGNED_BYTE, @"For conversion to a CGImage the type of the output texture of this filter must be GL_UNSIGNED_BYTE.");
|
||||
|
||||
__block CGImageRef cgImageFromBytes;
|
||||
|
||||
runSynchronouslyOnVideoProcessingQueue(^{
|
||||
[GPUImageContext useImageProcessingContext];
|
||||
|
||||
NSUInteger totalBytesForImage = (int)_size.width * (int)_size.height * 4;
|
||||
// It appears that the width of a texture must be padded out to be a multiple of 8 (32 bytes) if reading from it using a texture cache
|
||||
|
||||
GLubyte *rawImagePixels;
|
||||
|
||||
CGDataProviderRef dataProvider = NULL;
|
||||
if ([GPUImageContext supportsFastTextureUpload])
|
||||
{
|
||||
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
|
||||
NSUInteger paddedWidthOfImage = (NSUInteger)(CVPixelBufferGetBytesPerRow(renderTarget) / 4.0);
|
||||
NSUInteger paddedBytesForImage = paddedWidthOfImage * (int)_size.height * 4;
|
||||
|
||||
glFinish();
|
||||
CFRetain(renderTarget); // I need to retain the pixel buffer here and release in the data source callback to prevent its bytes from being prematurely deallocated during a photo write operation
|
||||
[self lockForReading];
|
||||
rawImagePixels = (GLubyte *)CVPixelBufferGetBaseAddress(renderTarget);
|
||||
dataProvider = CGDataProviderCreateWithData((__bridge_retained void*)self, rawImagePixels, paddedBytesForImage, dataProviderUnlockCallback);
|
||||
[[GPUImageContext sharedFramebufferCache] addFramebufferToActiveImageCaptureList:self]; // In case the framebuffer is swapped out on the filter, need to have a strong reference to it somewhere for it to hang on while the image is in existence
|
||||
#else
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
[self activateFramebuffer];
|
||||
rawImagePixels = (GLubyte *)malloc(totalBytesForImage);
|
||||
glReadPixels(0, 0, (int)_size.width, (int)_size.height, GL_RGBA, GL_UNSIGNED_BYTE, rawImagePixels);
|
||||
dataProvider = CGDataProviderCreateWithData(NULL, rawImagePixels, totalBytesForImage, dataProviderReleaseCallback);
|
||||
[self unlock]; // Don't need to keep this around anymore
|
||||
}
|
||||
|
||||
CGColorSpaceRef defaultRGBColorSpace = CGColorSpaceCreateDeviceRGB();
|
||||
|
||||
if ([GPUImageContext supportsFastTextureUpload])
|
||||
{
|
||||
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
|
||||
cgImageFromBytes = CGImageCreate((int)_size.width, (int)_size.height, 8, 32, CVPixelBufferGetBytesPerRow(renderTarget), defaultRGBColorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst, dataProvider, NULL, NO, kCGRenderingIntentDefault);
|
||||
#else
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
cgImageFromBytes = CGImageCreate((int)_size.width, (int)_size.height, 8, 32, 4 * (int)_size.width, defaultRGBColorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast, dataProvider, NULL, NO, kCGRenderingIntentDefault);
|
||||
}
|
||||
|
||||
// Capture image with current device orientation
|
||||
CGDataProviderRelease(dataProvider);
|
||||
CGColorSpaceRelease(defaultRGBColorSpace);
|
||||
|
||||
});
|
||||
|
||||
return cgImageFromBytes;
|
||||
}
|
||||
|
||||
- (void)restoreRenderTarget
|
||||
{
|
||||
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
|
||||
[self unlockAfterReading];
|
||||
CFRelease(renderTarget);
|
||||
#else
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Raw data bytes
|
||||
|
||||
- (void)lockForReading
|
||||
{
|
||||
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
|
||||
if ([GPUImageContext supportsFastTextureUpload])
|
||||
{
|
||||
if (readLockCount == 0)
|
||||
{
|
||||
CVPixelBufferLockBaseAddress(renderTarget, 0);
|
||||
}
|
||||
readLockCount++;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)unlockAfterReading
|
||||
{
|
||||
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
|
||||
if ([GPUImageContext supportsFastTextureUpload])
|
||||
{
|
||||
NSAssert(readLockCount > 0, @"Unbalanced call to -[GPUImageFramebuffer unlockAfterReading]");
|
||||
readLockCount--;
|
||||
if (readLockCount == 0)
|
||||
{
|
||||
CVPixelBufferUnlockBaseAddress(renderTarget, 0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
- (NSUInteger)bytesPerRow
|
||||
{
|
||||
if ([GPUImageContext supportsFastTextureUpload])
|
||||
{
|
||||
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
|
||||
return CVPixelBufferGetBytesPerRow(renderTarget);
|
||||
#else
|
||||
return _size.width * 4; // TODO: do more with this on the non-texture-cache side
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
return (NSUInteger)_size.width * 4;
|
||||
}
|
||||
}
|
||||
|
||||
- (GLubyte *)byteBuffer
|
||||
{
|
||||
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
|
||||
[self lockForReading];
|
||||
GLubyte * bufferBytes = CVPixelBufferGetBaseAddress(renderTarget);
|
||||
[self unlockAfterReading];
|
||||
return bufferBytes;
|
||||
#else
|
||||
return NULL; // TODO: do more with this on the non-texture-cache side
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
||||
15
submodules/LegacyComponents/LegacyComponents/GPUImageFramebufferCache.h
Executable file
15
submodules/LegacyComponents/LegacyComponents/GPUImageFramebufferCache.h
Executable file
|
|
@ -0,0 +1,15 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import "GPUImageFramebuffer.h"
|
||||
|
||||
@interface GPUImageFramebufferCache : NSObject
|
||||
|
||||
// Framebuffer management
|
||||
- (GPUImageFramebuffer *)fetchFramebufferForSize:(CGSize)framebufferSize textureOptions:(GPUTextureOptions)textureOptions onlyTexture:(BOOL)onlyTexture;
|
||||
- (GPUImageFramebuffer *)fetchFramebufferForSize:(CGSize)framebufferSize onlyTexture:(BOOL)onlyTexture;
|
||||
- (void)returnFramebufferToCache:(GPUImageFramebuffer *)framebuffer;
|
||||
- (void)purgeAllUnassignedFramebuffers;
|
||||
- (void)addFramebufferToActiveImageCaptureList:(GPUImageFramebuffer *)framebuffer;
|
||||
- (void)removeFramebufferFromActiveImageCaptureList:(GPUImageFramebuffer *)framebuffer;
|
||||
|
||||
@end
|
||||
189
submodules/LegacyComponents/LegacyComponents/GPUImageFramebufferCache.m
Executable file
189
submodules/LegacyComponents/LegacyComponents/GPUImageFramebufferCache.m
Executable file
|
|
@ -0,0 +1,189 @@
|
|||
#import "GPUImageFramebufferCache.h"
|
||||
#import "GPUImageContext.h"
|
||||
#import "GPUImageOutput.h"
|
||||
|
||||
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
|
||||
#import <UIKit/UIKit.h>
|
||||
#else
|
||||
#endif
|
||||
|
||||
@interface GPUImageFramebufferCache()
|
||||
{
|
||||
// NSCache *framebufferCache;
|
||||
NSMutableDictionary *framebufferCache;
|
||||
NSMutableDictionary *framebufferTypeCounts;
|
||||
NSMutableArray *activeImageCaptureList; // Where framebuffers that may be lost by a filter, but which are still needed for a UIImage, etc., are stored
|
||||
id memoryWarningObserver;
|
||||
|
||||
dispatch_queue_t framebufferCacheQueue;
|
||||
}
|
||||
|
||||
- (NSString *)hashForSize:(CGSize)size textureOptions:(GPUTextureOptions)textureOptions onlyTexture:(BOOL)onlyTexture;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation GPUImageFramebufferCache
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Initialization and teardown
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if (!(self = [super init]))
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
|
||||
__weak GPUImageFramebufferCache *weakSelf = self;
|
||||
memoryWarningObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidReceiveMemoryWarningNotification object:nil queue:nil usingBlock:^(__unused NSNotification *note)
|
||||
{
|
||||
__strong GPUImageFramebufferCache *strongSelf = weakSelf;
|
||||
if (strongSelf != nil)
|
||||
[strongSelf purgeAllUnassignedFramebuffers];
|
||||
}];
|
||||
#else
|
||||
#endif
|
||||
|
||||
// framebufferCache = [[NSCache alloc] init];
|
||||
framebufferCache = [[NSMutableDictionary alloc] init];
|
||||
framebufferTypeCounts = [[NSMutableDictionary alloc] init];
|
||||
activeImageCaptureList = [[NSMutableArray alloc] init];
|
||||
framebufferCacheQueue = dispatch_queue_create("com.sunsetlakesoftware.GPUImage.framebufferCacheQueue", NULL);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Framebuffer management
|
||||
|
||||
- (NSString *)hashForSize:(CGSize)size textureOptions:(GPUTextureOptions)textureOptions onlyTexture:(BOOL)onlyTexture
|
||||
{
|
||||
if (onlyTexture)
|
||||
{
|
||||
return [NSString stringWithFormat:@"%.1fx%.1f-%d:%d:%d:%d:%d:%d:%d-NOFB", size.width, size.height, textureOptions.minFilter, textureOptions.magFilter, textureOptions.wrapS, textureOptions.wrapT, textureOptions.internalFormat, textureOptions.format, textureOptions.type];
|
||||
}
|
||||
else
|
||||
{
|
||||
return [NSString stringWithFormat:@"%.1fx%.1f-%d:%d:%d:%d:%d:%d:%d", size.width, size.height, textureOptions.minFilter, textureOptions.magFilter, textureOptions.wrapS, textureOptions.wrapT, textureOptions.internalFormat, textureOptions.format, textureOptions.type];
|
||||
}
|
||||
}
|
||||
|
||||
- (GPUImageFramebuffer *)fetchFramebufferForSize:(CGSize)framebufferSize textureOptions:(GPUTextureOptions)textureOptions onlyTexture:(BOOL)onlyTexture
|
||||
{
|
||||
__block GPUImageFramebuffer *framebufferFromCache = nil;
|
||||
// dispatch_sync(framebufferCacheQueue, ^{
|
||||
runSynchronouslyOnVideoProcessingQueue(^{
|
||||
NSString *lookupHash = [self hashForSize:framebufferSize textureOptions:textureOptions onlyTexture:onlyTexture];
|
||||
NSNumber *numberOfMatchingTexturesInCache = [framebufferTypeCounts objectForKey:lookupHash];
|
||||
NSInteger numberOfMatchingTextures = [numberOfMatchingTexturesInCache integerValue];
|
||||
|
||||
if ([numberOfMatchingTexturesInCache integerValue] < 1)
|
||||
{
|
||||
// Nothing in the cache, create a new framebuffer to use
|
||||
framebufferFromCache = [[GPUImageFramebuffer alloc] initWithSize:framebufferSize textureOptions:textureOptions onlyTexture:onlyTexture];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Something found, pull the old framebuffer and decrement the count
|
||||
NSInteger currentTextureID = (numberOfMatchingTextures - 1);
|
||||
while ((framebufferFromCache == nil) && (currentTextureID >= 0))
|
||||
{
|
||||
NSString *textureHash = [NSString stringWithFormat:@"%@-%ld", lookupHash, (long)currentTextureID];
|
||||
framebufferFromCache = [framebufferCache objectForKey:textureHash];
|
||||
// Test the values in the cache first, to see if they got invalidated behind our back
|
||||
if (framebufferFromCache != nil)
|
||||
{
|
||||
// Withdraw this from the cache while it's in use
|
||||
[framebufferCache removeObjectForKey:textureHash];
|
||||
}
|
||||
currentTextureID--;
|
||||
}
|
||||
|
||||
currentTextureID++;
|
||||
|
||||
[framebufferTypeCounts setObject:[NSNumber numberWithInteger:currentTextureID] forKey:lookupHash];
|
||||
|
||||
if (framebufferFromCache == nil)
|
||||
{
|
||||
framebufferFromCache = [[GPUImageFramebuffer alloc] initWithSize:framebufferSize textureOptions:textureOptions onlyTexture:onlyTexture];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
[framebufferFromCache lock];
|
||||
return framebufferFromCache;
|
||||
}
|
||||
|
||||
- (GPUImageFramebuffer *)fetchFramebufferForSize:(CGSize)framebufferSize onlyTexture:(BOOL)onlyTexture
|
||||
{
|
||||
GPUTextureOptions defaultTextureOptions;
|
||||
defaultTextureOptions.minFilter = GL_LINEAR;
|
||||
defaultTextureOptions.magFilter = GL_LINEAR;
|
||||
defaultTextureOptions.wrapS = GL_CLAMP_TO_EDGE;
|
||||
defaultTextureOptions.wrapT = GL_CLAMP_TO_EDGE;
|
||||
defaultTextureOptions.internalFormat = GL_RGBA;
|
||||
defaultTextureOptions.format = GL_BGRA;
|
||||
defaultTextureOptions.type = GL_UNSIGNED_BYTE;
|
||||
|
||||
return [self fetchFramebufferForSize:framebufferSize textureOptions:defaultTextureOptions onlyTexture:onlyTexture];
|
||||
}
|
||||
|
||||
- (void)returnFramebufferToCache:(GPUImageFramebuffer *)framebuffer
|
||||
{
|
||||
[framebuffer clearAllLocks];
|
||||
|
||||
// dispatch_async(framebufferCacheQueue, ^{
|
||||
runAsynchronouslyOnVideoProcessingQueue(^{
|
||||
CGSize framebufferSize = framebuffer.size;
|
||||
GPUTextureOptions framebufferTextureOptions = framebuffer.textureOptions;
|
||||
NSString *lookupHash = [self hashForSize:framebufferSize textureOptions:framebufferTextureOptions onlyTexture:framebuffer.missingFramebuffer];
|
||||
NSNumber *numberOfMatchingTexturesInCache = [framebufferTypeCounts objectForKey:lookupHash];
|
||||
NSInteger numberOfMatchingTextures = [numberOfMatchingTexturesInCache integerValue];
|
||||
|
||||
NSString *textureHash = [NSString stringWithFormat:@"%@-%ld", lookupHash, (long)numberOfMatchingTextures];
|
||||
|
||||
// [framebufferCache setObject:framebuffer forKey:textureHash cost:round(framebufferSize.width * framebufferSize.height * 4.0)];
|
||||
[framebufferCache setObject:framebuffer forKey:textureHash];
|
||||
[framebufferTypeCounts setObject:[NSNumber numberWithInteger:(numberOfMatchingTextures + 1)] forKey:lookupHash];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)purgeAllUnassignedFramebuffers
|
||||
{
|
||||
runAsynchronouslyOnVideoProcessingQueue(^{
|
||||
// dispatch_async(framebufferCacheQueue, ^{
|
||||
[framebufferCache removeAllObjects];
|
||||
[framebufferTypeCounts removeAllObjects];
|
||||
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
|
||||
CVOpenGLESTextureCacheFlush([[GPUImageContext sharedImageProcessingContext] coreVideoTextureCache], 0);
|
||||
#else
|
||||
#endif
|
||||
});
|
||||
}
|
||||
|
||||
- (void)addFramebufferToActiveImageCaptureList:(GPUImageFramebuffer *)framebuffer
|
||||
{
|
||||
runAsynchronouslyOnVideoProcessingQueue(^{
|
||||
// dispatch_async(framebufferCacheQueue, ^{
|
||||
[activeImageCaptureList addObject:framebuffer];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)removeFramebufferFromActiveImageCaptureList:(GPUImageFramebuffer *)framebuffer
|
||||
{
|
||||
runAsynchronouslyOnVideoProcessingQueue(^{
|
||||
// dispatch_async(framebufferCacheQueue, ^{
|
||||
[activeImageCaptureList removeObject:framebuffer];
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
121
submodules/LegacyComponents/LegacyComponents/GPUImageOutput.h
Executable file
121
submodules/LegacyComponents/LegacyComponents/GPUImageOutput.h
Executable file
|
|
@ -0,0 +1,121 @@
|
|||
#import "GPUImageContext.h"
|
||||
#import "GPUImageFramebuffer.h"
|
||||
|
||||
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
|
||||
#import <UIKit/UIKit.h>
|
||||
#else
|
||||
// For now, just redefine this on the Mac
|
||||
typedef NS_ENUM(NSInteger, UIImageOrientation) {
|
||||
UIImageOrientationUp, // default orientation
|
||||
UIImageOrientationDown, // 180 deg rotation
|
||||
UIImageOrientationLeft, // 90 deg CCW
|
||||
UIImageOrientationRight, // 90 deg CW
|
||||
UIImageOrientationUpMirrored, // as above but image mirrored along other axis. horizontal flip
|
||||
UIImageOrientationDownMirrored, // horizontal flip
|
||||
UIImageOrientationLeftMirrored, // vertical flip
|
||||
UIImageOrientationRightMirrored, // vertical flip
|
||||
};
|
||||
#endif
|
||||
|
||||
void runOnMainQueueWithoutDeadlocking(void (^block)(void));
|
||||
void runSynchronouslyOnVideoProcessingQueue(void (^block)(void));
|
||||
void runAsynchronouslyOnVideoProcessingQueue(void (^block)(void));
|
||||
void runSynchronouslyOnContextQueue(GPUImageContext *context, void (^block)(void));
|
||||
void runAsynchronouslyOnContextQueue(GPUImageContext *context, void (^block)(void));
|
||||
void reportAvailableMemoryForGPUImage(NSString *tag);
|
||||
|
||||
/** GPUImage's base source object
|
||||
|
||||
Images or frames of video are uploaded from source objects, which are subclasses of GPUImageOutput. These include:
|
||||
|
||||
- GPUImageVideoCamera (for live video from an iOS camera)
|
||||
- GPUImageStillCamera (for taking photos with the camera)
|
||||
- GPUImagePicture (for still images)
|
||||
- GPUImageMovie (for movies)
|
||||
|
||||
Source objects upload still image frames to OpenGL ES as textures, then hand those textures off to the next objects in the processing chain.
|
||||
*/
|
||||
@interface GPUImageOutput : NSObject
|
||||
{
|
||||
GPUImageFramebuffer *outputFramebuffer;
|
||||
|
||||
NSMutableArray *targets, *targetTextureIndices;
|
||||
|
||||
CGSize inputTextureSize, cachedMaximumOutputSize, forcedMaximumSize;
|
||||
|
||||
BOOL overrideInputSize;
|
||||
|
||||
BOOL allTargetsWantMonochromeData;
|
||||
BOOL usingNextFrameForImageCapture;
|
||||
}
|
||||
|
||||
@property(readwrite, nonatomic) BOOL shouldSmoothlyScaleOutput;
|
||||
@property(readwrite, nonatomic) BOOL shouldIgnoreUpdatesToThisTarget;
|
||||
@property(readwrite, nonatomic, unsafe_unretained) id<GPUImageInput> targetToIgnoreForUpdates;
|
||||
@property(nonatomic, copy) void(^frameProcessingCompletionBlock)(GPUImageOutput*, CMTime);
|
||||
@property(nonatomic) BOOL enabled;
|
||||
@property(readwrite, nonatomic) GPUTextureOptions outputTextureOptions;
|
||||
|
||||
/// @name Managing targets
|
||||
- (void)setInputFramebufferForTarget:(id<GPUImageInput>)target atIndex:(NSInteger)inputTextureIndex;
|
||||
- (GPUImageFramebuffer *)framebufferForOutput;
|
||||
- (void)removeOutputFramebuffer;
|
||||
- (void)notifyTargetsAboutNewOutputTexture;
|
||||
|
||||
- (CGSize)inputTextureSize;
|
||||
|
||||
/** Returns an array of the current targets.
|
||||
*/
|
||||
- (NSArray*)targets;
|
||||
|
||||
/** Adds a target to receive notifications when new frames are available.
|
||||
|
||||
The target will be asked for its next available texture.
|
||||
|
||||
See [GPUImageInput newFrameReadyAtTime:]
|
||||
|
||||
@param newTarget Target to be added
|
||||
*/
|
||||
- (void)addTarget:(id<GPUImageInput>)newTarget;
|
||||
|
||||
/** Adds a target to receive notifications when new frames are available.
|
||||
|
||||
See [GPUImageInput newFrameReadyAtTime:]
|
||||
|
||||
@param newTarget Target to be added
|
||||
*/
|
||||
- (void)addTarget:(id<GPUImageInput>)newTarget atTextureLocation:(NSInteger)textureLocation;
|
||||
|
||||
/** Removes a target. The target will no longer receive notifications when new frames are available.
|
||||
|
||||
@param targetToRemove Target to be removed
|
||||
*/
|
||||
- (void)removeTarget:(id<GPUImageInput>)targetToRemove;
|
||||
|
||||
/** Removes all targets.
|
||||
*/
|
||||
- (void)removeAllTargets;
|
||||
|
||||
/// @name Manage the output texture
|
||||
|
||||
- (void)forceProcessingAtSize:(CGSize)frameSize;
|
||||
- (void)forceProcessingAtSizeRespectingAspectRatio:(CGSize)frameSize;
|
||||
|
||||
/// @name Still image processing
|
||||
|
||||
- (void)useNextFrameForImageCapture;
|
||||
- (CGImageRef)newCGImageFromCurrentlyProcessedOutput;
|
||||
|
||||
// Platform-specific image output methods
|
||||
// If you're trying to use these methods, remember that you need to set -useNextFrameForImageCapture before running -processImage or running video and calling any of these methods, or you will get a nil image
|
||||
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
|
||||
- (UIImage *)imageFromCurrentFramebuffer;
|
||||
- (UIImage *)imageFromCurrentFramebufferWithOrientation:(UIImageOrientation)imageOrientation;
|
||||
#else
|
||||
- (NSImage *)imageFromCurrentFramebuffer;
|
||||
- (NSImage *)imageFromCurrentFramebufferWithOrientation:(UIImageOrientation)imageOrientation;
|
||||
#endif
|
||||
|
||||
- (BOOL)providesMonochromeOutput;
|
||||
|
||||
@end
|
||||
362
submodules/LegacyComponents/LegacyComponents/GPUImageOutput.m
Executable file
362
submodules/LegacyComponents/LegacyComponents/GPUImageOutput.m
Executable file
|
|
@ -0,0 +1,362 @@
|
|||
#import "GPUImageOutput.h"
|
||||
//#import "GPUImagePicture.h"
|
||||
#import <mach/mach.h>
|
||||
|
||||
void runOnMainQueueWithoutDeadlocking(void (^block)(void))
|
||||
{
|
||||
if ([NSThread isMainThread])
|
||||
{
|
||||
block();
|
||||
}
|
||||
else
|
||||
{
|
||||
dispatch_sync(dispatch_get_main_queue(), block);
|
||||
}
|
||||
}
|
||||
|
||||
void runSynchronouslyOnVideoProcessingQueue(void (^block)(void))
|
||||
{
|
||||
dispatch_queue_t videoProcessingQueue = [GPUImageContext sharedContextQueue];
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
if (dispatch_get_current_queue() == videoProcessingQueue)
|
||||
#else
|
||||
if (dispatch_get_specific([GPUImageContext contextKey]))
|
||||
#endif
|
||||
{
|
||||
block();
|
||||
}else
|
||||
{
|
||||
dispatch_sync(videoProcessingQueue, block);
|
||||
}
|
||||
}
|
||||
|
||||
void runAsynchronouslyOnVideoProcessingQueue(void (^block)(void))
|
||||
{
|
||||
dispatch_queue_t videoProcessingQueue = [GPUImageContext sharedContextQueue];
|
||||
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
if (dispatch_get_current_queue() == videoProcessingQueue)
|
||||
#else
|
||||
if (dispatch_get_specific([GPUImageContext contextKey]))
|
||||
#endif
|
||||
{
|
||||
block();
|
||||
}else
|
||||
{
|
||||
dispatch_async(videoProcessingQueue, block);
|
||||
}
|
||||
}
|
||||
|
||||
void runSynchronouslyOnContextQueue(GPUImageContext *context, void (^block)(void))
|
||||
{
|
||||
dispatch_queue_t videoProcessingQueue = [context contextQueue];
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
if (dispatch_get_current_queue() == videoProcessingQueue)
|
||||
#else
|
||||
if (dispatch_get_specific([GPUImageContext contextKey]))
|
||||
#endif
|
||||
{
|
||||
block();
|
||||
}else
|
||||
{
|
||||
dispatch_sync(videoProcessingQueue, block);
|
||||
}
|
||||
}
|
||||
|
||||
void runAsynchronouslyOnContextQueue(GPUImageContext *context, void (^block)(void))
|
||||
{
|
||||
dispatch_queue_t videoProcessingQueue = [context contextQueue];
|
||||
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
if (dispatch_get_current_queue() == videoProcessingQueue)
|
||||
#else
|
||||
if (dispatch_get_specific([GPUImageContext contextKey]))
|
||||
#endif
|
||||
{
|
||||
block();
|
||||
}else
|
||||
{
|
||||
dispatch_async(videoProcessingQueue, block);
|
||||
}
|
||||
}
|
||||
|
||||
void reportAvailableMemoryForGPUImage(NSString *tag)
|
||||
{
|
||||
if (!tag)
|
||||
tag = @"Default";
|
||||
|
||||
struct task_basic_info info;
|
||||
|
||||
mach_msg_type_number_t size = sizeof(info);
|
||||
|
||||
kern_return_t kerr = task_info(mach_task_self(),
|
||||
|
||||
TASK_BASIC_INFO,
|
||||
|
||||
(task_info_t)&info,
|
||||
|
||||
&size);
|
||||
if( kerr == KERN_SUCCESS ) {
|
||||
NSLog(@"%@ - Memory used: %u", tag, (unsigned int)info.resident_size); //in bytes
|
||||
} else {
|
||||
NSLog(@"%@ - Error: %s", tag, mach_error_string(kerr));
|
||||
}
|
||||
}
|
||||
|
||||
@implementation GPUImageOutput
|
||||
|
||||
@synthesize shouldSmoothlyScaleOutput = _shouldSmoothlyScaleOutput;
|
||||
@synthesize shouldIgnoreUpdatesToThisTarget = _shouldIgnoreUpdatesToThisTarget;
|
||||
@synthesize targetToIgnoreForUpdates = _targetToIgnoreForUpdates;
|
||||
@synthesize frameProcessingCompletionBlock = _frameProcessingCompletionBlock;
|
||||
@synthesize enabled = _enabled;
|
||||
@synthesize outputTextureOptions = _outputTextureOptions;
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Initialization and teardown
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if (!(self = [super init]))
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
targets = [[NSMutableArray alloc] init];
|
||||
targetTextureIndices = [[NSMutableArray alloc] init];
|
||||
_enabled = YES;
|
||||
allTargetsWantMonochromeData = YES;
|
||||
usingNextFrameForImageCapture = NO;
|
||||
|
||||
// set default texture options
|
||||
_outputTextureOptions.minFilter = GL_LINEAR;
|
||||
_outputTextureOptions.magFilter = GL_LINEAR;
|
||||
_outputTextureOptions.wrapS = GL_CLAMP_TO_EDGE;
|
||||
_outputTextureOptions.wrapT = GL_CLAMP_TO_EDGE;
|
||||
_outputTextureOptions.internalFormat = GL_RGBA;
|
||||
_outputTextureOptions.format = GL_BGRA;
|
||||
_outputTextureOptions.type = GL_UNSIGNED_BYTE;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self removeAllTargets];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Managing targets
|
||||
|
||||
- (void)setInputFramebufferForTarget:(id<GPUImageInput>)target atIndex:(NSInteger)inputTextureIndex
|
||||
{
|
||||
[target setInputFramebuffer:[self framebufferForOutput] atIndex:inputTextureIndex];
|
||||
}
|
||||
|
||||
- (GPUImageFramebuffer *)framebufferForOutput
|
||||
{
|
||||
return outputFramebuffer;
|
||||
}
|
||||
|
||||
- (void)removeOutputFramebuffer
|
||||
{
|
||||
outputFramebuffer = nil;
|
||||
}
|
||||
|
||||
- (void)notifyTargetsAboutNewOutputTexture
|
||||
{
|
||||
for (id<GPUImageInput> currentTarget in targets)
|
||||
{
|
||||
NSInteger indexOfObject = [targets indexOfObject:currentTarget];
|
||||
NSInteger textureIndex = [[targetTextureIndices objectAtIndex:indexOfObject] integerValue];
|
||||
|
||||
[self setInputFramebufferForTarget:currentTarget atIndex:textureIndex];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray*)targets
|
||||
{
|
||||
return [NSArray arrayWithArray:targets];
|
||||
}
|
||||
|
||||
- (void)addTarget:(id<GPUImageInput>)newTarget
|
||||
{
|
||||
NSInteger nextAvailableTextureIndex = [newTarget nextAvailableTextureIndex];
|
||||
[self addTarget:newTarget atTextureLocation:nextAvailableTextureIndex];
|
||||
|
||||
if ([newTarget shouldIgnoreUpdatesToThisTarget])
|
||||
{
|
||||
_targetToIgnoreForUpdates = newTarget;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)addTarget:(id<GPUImageInput>)newTarget atTextureLocation:(NSInteger)textureLocation
|
||||
{
|
||||
if (newTarget == nil || [targets containsObject:newTarget])
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cachedMaximumOutputSize = CGSizeZero;
|
||||
runSynchronouslyOnVideoProcessingQueue(^{
|
||||
[self setInputFramebufferForTarget:newTarget atIndex:textureLocation];
|
||||
[targets addObject:newTarget];
|
||||
[targetTextureIndices addObject:[NSNumber numberWithInteger:textureLocation]];
|
||||
|
||||
allTargetsWantMonochromeData = allTargetsWantMonochromeData && [newTarget wantsMonochromeInput];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)removeTarget:(id<GPUImageInput>)targetToRemove
|
||||
{
|
||||
if(![targets containsObject:targetToRemove])
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_targetToIgnoreForUpdates == targetToRemove)
|
||||
{
|
||||
_targetToIgnoreForUpdates = nil;
|
||||
}
|
||||
|
||||
cachedMaximumOutputSize = CGSizeZero;
|
||||
|
||||
NSInteger indexOfObject = [targets indexOfObject:targetToRemove];
|
||||
NSInteger textureIndexOfTarget = [[targetTextureIndices objectAtIndex:indexOfObject] integerValue];
|
||||
|
||||
runSynchronouslyOnVideoProcessingQueue(^{
|
||||
[targetToRemove setInputSize:CGSizeZero atIndex:textureIndexOfTarget];
|
||||
[targetToRemove setInputRotation:kGPUImageNoRotation atIndex:textureIndexOfTarget];
|
||||
|
||||
[targetTextureIndices removeObjectAtIndex:indexOfObject];
|
||||
[targets removeObject:targetToRemove];
|
||||
[targetToRemove endProcessing];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)removeAllTargets
|
||||
{
|
||||
cachedMaximumOutputSize = CGSizeZero;
|
||||
runSynchronouslyOnVideoProcessingQueue(^{
|
||||
for (id<GPUImageInput> targetToRemove in targets)
|
||||
{
|
||||
NSInteger indexOfObject = [targets indexOfObject:targetToRemove];
|
||||
NSInteger textureIndexOfTarget = [[targetTextureIndices objectAtIndex:indexOfObject] integerValue];
|
||||
|
||||
[targetToRemove setInputSize:CGSizeZero atIndex:textureIndexOfTarget];
|
||||
[targetToRemove setInputRotation:kGPUImageNoRotation atIndex:textureIndexOfTarget];
|
||||
}
|
||||
[targets removeAllObjects];
|
||||
[targetTextureIndices removeAllObjects];
|
||||
|
||||
allTargetsWantMonochromeData = YES;
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Manage the output texture
|
||||
|
||||
- (void)forceProcessingAtSize:(CGSize)__unused frameSize
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
- (void)forceProcessingAtSizeRespectingAspectRatio:(CGSize)__unused frameSize
|
||||
{
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Still image processing
|
||||
|
||||
- (void)useNextFrameForImageCapture
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
- (CGImageRef)newCGImageFromCurrentlyProcessedOutput
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)providesMonochromeOutput
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Platform-specific image output methods
|
||||
|
||||
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
|
||||
|
||||
- (UIImage *)imageFromCurrentFramebuffer
|
||||
{
|
||||
UIDeviceOrientation deviceOrientation = [[UIDevice currentDevice] orientation];
|
||||
UIImageOrientation imageOrientation = UIImageOrientationLeft;
|
||||
switch (deviceOrientation)
|
||||
{
|
||||
case UIDeviceOrientationPortrait:
|
||||
imageOrientation = UIImageOrientationUp;
|
||||
break;
|
||||
case UIDeviceOrientationPortraitUpsideDown:
|
||||
imageOrientation = UIImageOrientationDown;
|
||||
break;
|
||||
case UIDeviceOrientationLandscapeLeft:
|
||||
imageOrientation = UIImageOrientationLeft;
|
||||
break;
|
||||
case UIDeviceOrientationLandscapeRight:
|
||||
imageOrientation = UIImageOrientationRight;
|
||||
break;
|
||||
default:
|
||||
imageOrientation = UIImageOrientationUp;
|
||||
break;
|
||||
}
|
||||
|
||||
return [self imageFromCurrentFramebufferWithOrientation:imageOrientation];
|
||||
}
|
||||
|
||||
- (UIImage *)imageFromCurrentFramebufferWithOrientation:(UIImageOrientation)imageOrientation
|
||||
{
|
||||
CGImageRef cgImageFromBytes = [self newCGImageFromCurrentlyProcessedOutput];
|
||||
UIImage *finalImage = [UIImage imageWithCGImage:cgImageFromBytes scale:1.0 orientation:imageOrientation];
|
||||
CGImageRelease(cgImageFromBytes);
|
||||
|
||||
return finalImage;
|
||||
}
|
||||
|
||||
- (CGSize)inputTextureSize
|
||||
{
|
||||
return inputTextureSize;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
- (NSImage *)imageFromCurrentFramebuffer;
|
||||
{
|
||||
return [self imageFromCurrentFramebufferWithOrientation:UIImageOrientationLeft];
|
||||
}
|
||||
|
||||
- (NSImage *)imageFromCurrentFramebufferWithOrientation:(UIImageOrientation)imageOrientation;
|
||||
{
|
||||
CGImageRef cgImageFromBytes = [self newCGImageFromCurrentlyProcessedOutput];
|
||||
NSImage *finalImage = [[NSImage alloc] initWithCGImage:cgImageFromBytes size:NSZeroSize];
|
||||
CGImageRelease(cgImageFromBytes);
|
||||
|
||||
return finalImage;
|
||||
}
|
||||
|
||||
- (NSImage *)imageByFilteringImage:(NSImage *)imageToFilter;
|
||||
{
|
||||
CGImageRef image = [self newCGImageByFilteringCGImage:[imageToFilter CGImageForProposedRect:NULL context:[NSGraphicsContext currentContext] hints:nil]];
|
||||
NSImage *processedImage = [[NSImage alloc] initWithCGImage:image size:NSZeroSize];
|
||||
CGImageRelease(image);
|
||||
return processedImage;
|
||||
}
|
||||
|
||||
- (CGImageRef)newCGImageByFilteringImage:(NSImage *)imageToFilter
|
||||
{
|
||||
return [self newCGImageByFilteringCGImage:[imageToFilter CGImageForProposedRect:NULL context:[NSGraphicsContext currentContext] hints:nil]];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@end
|
||||
21
submodules/LegacyComponents/LegacyComponents/GPUImageTwoInputFilter.h
Executable file
21
submodules/LegacyComponents/LegacyComponents/GPUImageTwoInputFilter.h
Executable file
|
|
@ -0,0 +1,21 @@
|
|||
#import "GPUImageFilter.h"
|
||||
|
||||
extern NSString *const kGPUImageTwoInputTextureVertexShaderString;
|
||||
|
||||
@interface GPUImageTwoInputFilter : GPUImageFilter
|
||||
{
|
||||
GPUImageFramebuffer *secondInputFramebuffer;
|
||||
|
||||
GLint filterSecondTextureCoordinateAttribute;
|
||||
GLint filterInputTextureUniform2;
|
||||
GPUImageRotationMode inputRotation2;
|
||||
CMTime firstFrameTime, secondFrameTime;
|
||||
|
||||
BOOL hasSetFirstTexture, hasReceivedFirstFrame, hasReceivedSecondFrame, firstFrameWasVideo, secondFrameWasVideo;
|
||||
BOOL firstFrameCheckDisabled, secondFrameCheckDisabled;
|
||||
}
|
||||
|
||||
- (void)disableFirstFrameCheck;
|
||||
- (void)disableSecondFrameCheck;
|
||||
|
||||
@end
|
||||
264
submodules/LegacyComponents/LegacyComponents/GPUImageTwoInputFilter.m
Executable file
264
submodules/LegacyComponents/LegacyComponents/GPUImageTwoInputFilter.m
Executable file
|
|
@ -0,0 +1,264 @@
|
|||
#import "GPUImageTwoInputFilter.h"
|
||||
|
||||
NSString *const kGPUImageTwoInputTextureVertexShaderString = SHADER_STRING
|
||||
(
|
||||
attribute vec4 position;
|
||||
attribute vec4 inputTexCoord;
|
||||
attribute vec4 inputTexCoord2;
|
||||
|
||||
varying vec2 texCoord;
|
||||
varying vec2 texCoord2;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = position;
|
||||
texCoord = inputTexCoord.xy;
|
||||
texCoord2 = inputTexCoord2.xy;
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@implementation GPUImageTwoInputFilter
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Initialization and teardown
|
||||
|
||||
- (id)initWithFragmentShaderFromString:(NSString *)fragmentShaderString
|
||||
{
|
||||
if (!(self = [self initWithVertexShaderFromString:kGPUImageTwoInputTextureVertexShaderString fragmentShaderFromString:fragmentShaderString]))
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithVertexShaderFromString:(NSString *)vertexShaderString fragmentShaderFromString:(NSString *)fragmentShaderString
|
||||
{
|
||||
if (!(self = [super initWithVertexShaderFromString:vertexShaderString fragmentShaderFromString:fragmentShaderString]))
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
inputRotation2 = kGPUImageNoRotation;
|
||||
|
||||
hasSetFirstTexture = NO;
|
||||
|
||||
hasReceivedFirstFrame = NO;
|
||||
hasReceivedSecondFrame = NO;
|
||||
firstFrameWasVideo = NO;
|
||||
secondFrameWasVideo = NO;
|
||||
firstFrameCheckDisabled = NO;
|
||||
secondFrameCheckDisabled = NO;
|
||||
|
||||
firstFrameTime = kCMTimeInvalid;
|
||||
secondFrameTime = kCMTimeInvalid;
|
||||
|
||||
runSynchronouslyOnVideoProcessingQueue(^{
|
||||
[GPUImageContext useImageProcessingContext];
|
||||
filterSecondTextureCoordinateAttribute = [filterProgram attributeIndex:@"inputTexCoord2"];
|
||||
|
||||
filterInputTextureUniform2 = [filterProgram uniformIndex:@"inputImageTexture2"];
|
||||
glEnableVertexAttribArray(filterSecondTextureCoordinateAttribute);
|
||||
});
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)initializeAttributes
|
||||
{
|
||||
[super initializeAttributes];
|
||||
[filterProgram addAttribute:@"inputTexCoord2"];
|
||||
}
|
||||
|
||||
- (void)disableFirstFrameCheck
|
||||
{
|
||||
firstFrameCheckDisabled = YES;
|
||||
}
|
||||
|
||||
- (void)disableSecondFrameCheck
|
||||
{
|
||||
secondFrameCheckDisabled = YES;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Rendering
|
||||
|
||||
- (void)renderToTextureWithVertices:(const GLfloat *)vertices textureCoordinates:(const GLfloat *)textureCoordinates
|
||||
{
|
||||
if (self.preventRendering)
|
||||
{
|
||||
[firstInputFramebuffer unlock];
|
||||
[secondInputFramebuffer unlock];
|
||||
return;
|
||||
}
|
||||
|
||||
[GPUImageContext setActiveShaderProgram:filterProgram];
|
||||
outputFramebuffer = [[GPUImageContext sharedFramebufferCache] fetchFramebufferForSize:[self sizeOfFBO] textureOptions:self.outputTextureOptions onlyTexture:NO];
|
||||
[outputFramebuffer activateFramebuffer];
|
||||
if (usingNextFrameForImageCapture)
|
||||
{
|
||||
[outputFramebuffer lock];
|
||||
}
|
||||
|
||||
[self setUniformsForProgramAtIndex:0];
|
||||
|
||||
glClearColor(backgroundColorRed, backgroundColorGreen, backgroundColorBlue, backgroundColorAlpha);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
glActiveTexture(GL_TEXTURE2);
|
||||
glBindTexture(GL_TEXTURE_2D, [firstInputFramebuffer texture]);
|
||||
glUniform1i(filterInputTextureUniform, 2);
|
||||
|
||||
glActiveTexture(GL_TEXTURE3);
|
||||
glBindTexture(GL_TEXTURE_2D, [secondInputFramebuffer texture]);
|
||||
glUniform1i(filterInputTextureUniform2, 3);
|
||||
|
||||
glVertexAttribPointer(filterPositionAttribute, 2, GL_FLOAT, 0, 0, vertices);
|
||||
glVertexAttribPointer(filterTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, textureCoordinates);
|
||||
glVertexAttribPointer(filterSecondTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, [[self class] textureCoordinatesForRotation:inputRotation2]);
|
||||
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
|
||||
[firstInputFramebuffer unlock];
|
||||
[secondInputFramebuffer unlock];
|
||||
if (usingNextFrameForImageCapture)
|
||||
{
|
||||
dispatch_semaphore_signal(imageCaptureSemaphore);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark GPUImageInput
|
||||
|
||||
- (NSInteger)nextAvailableTextureIndex
|
||||
{
|
||||
if (hasSetFirstTexture)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)textureIndex
|
||||
{
|
||||
if (textureIndex == 0)
|
||||
{
|
||||
firstInputFramebuffer = newInputFramebuffer;
|
||||
hasSetFirstTexture = YES;
|
||||
[firstInputFramebuffer lock];
|
||||
}
|
||||
else
|
||||
{
|
||||
secondInputFramebuffer = newInputFramebuffer;
|
||||
[secondInputFramebuffer lock];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setInputSize:(CGSize)newSize atIndex:(NSInteger)textureIndex
|
||||
{
|
||||
if (textureIndex == 0)
|
||||
{
|
||||
[super setInputSize:newSize atIndex:textureIndex];
|
||||
|
||||
if (CGSizeEqualToSize(newSize, CGSizeZero))
|
||||
{
|
||||
hasSetFirstTexture = NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setInputRotation:(GPUImageRotationMode)newInputRotation atIndex:(NSInteger)textureIndex
|
||||
{
|
||||
if (textureIndex == 0)
|
||||
{
|
||||
inputRotation = newInputRotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
inputRotation2 = newInputRotation;
|
||||
}
|
||||
}
|
||||
|
||||
- (CGSize)rotatedSize:(CGSize)sizeToRotate forIndex:(NSInteger)textureIndex
|
||||
{
|
||||
CGSize rotatedSize = sizeToRotate;
|
||||
|
||||
GPUImageRotationMode rotationToCheck;
|
||||
if (textureIndex == 0)
|
||||
{
|
||||
rotationToCheck = inputRotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
rotationToCheck = inputRotation2;
|
||||
}
|
||||
|
||||
if (GPUImageRotationSwapsWidthAndHeight(rotationToCheck))
|
||||
{
|
||||
rotatedSize.width = sizeToRotate.height;
|
||||
rotatedSize.height = sizeToRotate.width;
|
||||
}
|
||||
|
||||
return rotatedSize;
|
||||
}
|
||||
|
||||
- (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex
|
||||
{
|
||||
// You can set up infinite update loops, so this helps to short circuit them
|
||||
if (hasReceivedFirstFrame && hasReceivedSecondFrame)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BOOL updatedMovieFrameOppositeStillImage = NO;
|
||||
|
||||
if (textureIndex == 0)
|
||||
{
|
||||
hasReceivedFirstFrame = YES;
|
||||
firstFrameTime = frameTime;
|
||||
if (secondFrameCheckDisabled)
|
||||
{
|
||||
hasReceivedSecondFrame = YES;
|
||||
}
|
||||
|
||||
if (!CMTIME_IS_INDEFINITE(frameTime))
|
||||
{
|
||||
if CMTIME_IS_INDEFINITE(secondFrameTime)
|
||||
{
|
||||
updatedMovieFrameOppositeStillImage = YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
hasReceivedSecondFrame = YES;
|
||||
secondFrameTime = frameTime;
|
||||
if (firstFrameCheckDisabled)
|
||||
{
|
||||
hasReceivedFirstFrame = YES;
|
||||
}
|
||||
|
||||
if (!CMTIME_IS_INDEFINITE(frameTime))
|
||||
{
|
||||
if CMTIME_IS_INDEFINITE(firstFrameTime)
|
||||
{
|
||||
updatedMovieFrameOppositeStillImage = YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// || (hasReceivedFirstFrame && secondFrameCheckDisabled) || (hasReceivedSecondFrame && firstFrameCheckDisabled)
|
||||
if ((hasReceivedFirstFrame && hasReceivedSecondFrame))
|
||||
{
|
||||
CMTime passOnFrameTime = (!CMTIME_IS_INDEFINITE(firstFrameTime)) ? firstFrameTime : secondFrameTime;
|
||||
[super newFrameReadyAtTime:passOnFrameTime atIndex:0]; // Bugfix when trying to record: always use time from first input (unless indefinite, in which case use the second input)
|
||||
hasReceivedFirstFrame = NO;
|
||||
hasReceivedSecondFrame = NO;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
116
submodules/LegacyComponents/LegacyComponents/HPGrowingTextView.h
Normal file
116
submodules/LegacyComponents/LegacyComponents/HPGrowingTextView.h
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
|
||||
|
||||
//
|
||||
// HPTextView.h
|
||||
//
|
||||
// Created by Hans Pinckaers on 29-06-10.
|
||||
//
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2011 Hans Pinckaers
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class HPGrowingTextView;
|
||||
@class HPTextViewInternal;
|
||||
|
||||
extern NSString *TGMentionUidAttributeName;
|
||||
extern NSString *TGMentionBoldAttributeName;
|
||||
@class TGMessageEntity;
|
||||
|
||||
@class TGKeyCommandController;
|
||||
|
||||
@protocol HPGrowingTextViewDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
|
||||
- (BOOL)growingTextViewShouldBeginEditing:(HPGrowingTextView *)growingTextView;
|
||||
- (void)growingTextViewDidBeginEditing:(HPGrowingTextView *)growingTextView;
|
||||
- (void)growingTextViewDidEndEditing:(HPGrowingTextView *)growingTextView;
|
||||
- (BOOL)growingTextViewEnabled:(HPGrowingTextView *)growingTextView;
|
||||
|
||||
- (BOOL)growingTextView:(HPGrowingTextView *)growingTextView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text;
|
||||
- (void)growingTextViewDidChange:(HPGrowingTextView *)growingTextView afterSetText:(bool)afterSetText afterPastingText:(bool)afterPastingText;
|
||||
|
||||
- (void)growingTextView:(HPGrowingTextView *)growingTextView willChangeHeight:(CGFloat)height duration:(NSTimeInterval)duration animationCurve:(int)animationCurve;
|
||||
|
||||
- (void)growingTextViewDidChangeSelection:(HPGrowingTextView *)growingTextView;
|
||||
- (BOOL)growingTextViewShouldReturn:(HPGrowingTextView *)growingTextView;
|
||||
|
||||
- (void)growingTextView:(HPGrowingTextView *)growingTextView didPasteImages:(NSArray *)images andText:(NSString *)text;
|
||||
- (void)growingTextView:(HPGrowingTextView *)growingTextView didPasteData:(NSData *)data;
|
||||
|
||||
- (void)growingTextView:(HPGrowingTextView *)growingTextView receivedReturnKeyCommandWithModifierFlags:(UIKeyModifierFlags)flags;
|
||||
|
||||
@end
|
||||
|
||||
@interface TGAttributedTextRange : NSObject
|
||||
|
||||
@property (nonatomic, strong, readonly) id attachment;
|
||||
|
||||
- (instancetype)initWithAttachment:(id)attachment;
|
||||
|
||||
@end
|
||||
|
||||
@interface HPGrowingTextView : UIView <UITextViewDelegate>
|
||||
|
||||
@property (nonatomic, strong) UIView *placeholderView;
|
||||
@property (nonatomic, assign) bool showPlaceholderWhenFocussed;
|
||||
|
||||
@property (nonatomic) int minNumberOfLines;
|
||||
@property (nonatomic) int maxNumberOfLines;
|
||||
@property (nonatomic) CGFloat maxHeight;
|
||||
@property (nonatomic) CGFloat minHeight;
|
||||
@property (nonatomic) BOOL animateHeightChange;
|
||||
@property (nonatomic) NSTimeInterval animationDuration;
|
||||
@property (nonatomic, strong) HPTextViewInternal *internalTextView;
|
||||
@property (nonatomic, assign) bool disableFormatting;
|
||||
|
||||
@property (nonatomic) bool oneTimeLongAnimation;
|
||||
|
||||
@property (nonatomic, weak) id<HPGrowingTextViewDelegate> delegate;
|
||||
@property (nonatomic, strong) NSString *text;
|
||||
@property (nonatomic, strong) NSAttributedString *attributedText;
|
||||
@property (nonatomic, strong) UIFont *font;
|
||||
@property (nonatomic, strong) UIColor *textColor;
|
||||
@property (nonatomic, strong) UIColor *accentColor;
|
||||
@property (nonatomic) NSTextAlignment textAlignment;
|
||||
|
||||
@property (nonatomic, readonly) bool ignoreChangeNotification;
|
||||
|
||||
@property (nonatomic, assign) bool receiveKeyCommands;
|
||||
|
||||
- (instancetype)initWithKeyCommandController:(TGKeyCommandController *)keyCommandController;
|
||||
|
||||
- (void)refreshHeight:(bool)textChanged;
|
||||
- (void)notifyHeight;
|
||||
|
||||
- (void)setText:(NSString *)newText animated:(bool)animated;
|
||||
- (void)setAttributedText:(NSAttributedString *)newText animated:(bool)animated;
|
||||
- (void)setAttributedText:(NSAttributedString *)newText keepFormatting:(bool)keepFormatting animated:(bool)animated;
|
||||
- (void)selectRange:(NSRange)range force:(bool)force;
|
||||
|
||||
- (NSString *)textWithEntities:(__autoreleasing NSArray<TGMessageEntity *> **)entities;
|
||||
|
||||
+ (void)replaceMention:(NSString *)mention inputField:(HPGrowingTextView *)inputField username:(bool)username userId:(int32_t)userId;
|
||||
+ (void)replaceHashtag:(NSString *)hashtag inputField:(HPGrowingTextView *)inputField;
|
||||
|
||||
@end
|
||||
918
submodules/LegacyComponents/LegacyComponents/HPGrowingTextView.m
Normal file
918
submodules/LegacyComponents/LegacyComponents/HPGrowingTextView.m
Normal file
|
|
@ -0,0 +1,918 @@
|
|||
#import "HPGrowingTextView.h"
|
||||
|
||||
#import "LegacyComponentsInternal.h"
|
||||
#import "TGFont.h"
|
||||
|
||||
#import "HPTextViewInternal.h"
|
||||
|
||||
#import "TGInputTextTag.h"
|
||||
|
||||
#import "TGMessage.h"
|
||||
|
||||
#import "TGColor.h"
|
||||
|
||||
NSString *TGMentionUidAttributeName = @"TGMentionUidAttributeName";
|
||||
NSString *TGMentionBoldAttributeName = @"TGMentionBoldAttributeName";
|
||||
|
||||
@implementation TGAttributedTextRange
|
||||
|
||||
- (instancetype)initWithAttachment:(id)attachment {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_attachment = attachment;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface HPGrowingTextView ()
|
||||
{
|
||||
UIColor *_intrinsicTextColor;
|
||||
UIFont *_intrinsicTextFont;
|
||||
|
||||
__weak TGKeyCommandController *_keyCommandController;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation HPGrowingTextView
|
||||
|
||||
- (instancetype)initWithKeyCommandController:(TGKeyCommandController *)keyCommandController
|
||||
{
|
||||
self = [super initWithFrame:CGRectZero];
|
||||
if (self != nil)
|
||||
{
|
||||
_keyCommandController = keyCommandController;
|
||||
|
||||
[self commonInitialiser];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSDictionary *)defaultAttributes {
|
||||
if (_intrinsicTextFont == nil) {
|
||||
return @{NSFontAttributeName: TGSystemFontOfSize(17)};
|
||||
} else {
|
||||
if (_intrinsicTextColor)
|
||||
return @{NSFontAttributeName: _intrinsicTextFont, NSForegroundColorAttributeName: _intrinsicTextColor};
|
||||
else
|
||||
return @{NSFontAttributeName: _intrinsicTextFont};
|
||||
}
|
||||
}
|
||||
|
||||
- (void)commonInitialiser
|
||||
{
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^
|
||||
{
|
||||
[HPTextViewInternal addTextViewMethods];
|
||||
});
|
||||
|
||||
CGRect frame = self.frame;
|
||||
frame.origin = CGPointZero;
|
||||
_internalTextView = [[HPTextViewInternal alloc] initWithKeyCommandController:_keyCommandController];
|
||||
_internalTextView.frame = frame;
|
||||
_internalTextView.delegate = self;
|
||||
_internalTextView.contentInset = UIEdgeInsetsZero;
|
||||
_internalTextView.showsHorizontalScrollIndicator = NO;
|
||||
_internalTextView.attributedText = [[NSAttributedString alloc] initWithString:@"-" attributes:[self defaultAttributes]];
|
||||
_internalTextView.scrollsToTop = false;
|
||||
if (iosMajorVersion() >= 7) {
|
||||
_internalTextView.textContainer.layoutManager.allowsNonContiguousLayout = true;
|
||||
_internalTextView.allowsEditingTextAttributes = true;
|
||||
}
|
||||
[self addSubview:_internalTextView];
|
||||
|
||||
_minHeight = _internalTextView.frame.size.height;
|
||||
_minNumberOfLines = 1;
|
||||
|
||||
_animateHeightChange = true;
|
||||
_animationDuration = 0.1f;
|
||||
|
||||
_internalTextView.attributedText = [[NSAttributedString alloc] initWithString:@"" attributes:[self defaultAttributes]];
|
||||
}
|
||||
|
||||
- (void)setDisableFormatting:(bool)disableFormatting
|
||||
{
|
||||
_disableFormatting = disableFormatting;
|
||||
if (iosMajorVersion() >= 7)
|
||||
_internalTextView.allowsEditingTextAttributes = !disableFormatting;
|
||||
}
|
||||
|
||||
- (void)setFrame:(CGRect)frame
|
||||
{
|
||||
[super setFrame:frame];
|
||||
|
||||
frame.origin = CGPointZero;
|
||||
_internalTextView.frame = frame;
|
||||
}
|
||||
|
||||
- (CGSize)sizeThatFits:(CGSize)size
|
||||
{
|
||||
if (self.attributedText.length == 0)
|
||||
size.height = _minHeight;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
- (void)setMaxNumberOfLines:(int)maxNumberOfLines
|
||||
{
|
||||
if (maxNumberOfLines == 0 && _maxHeight > 0) // the user specified a maxHeight themselves.
|
||||
return;
|
||||
|
||||
// Use internalTextView for height calculations, thanks to Gwynne <http://blog.darkrainfall.org/>
|
||||
NSAttributedString *saveText = _internalTextView.attributedText;
|
||||
NSMutableAttributedString *newText = [[NSMutableAttributedString alloc] initWithString:@"-" attributes:[self defaultAttributes]];
|
||||
|
||||
_internalTextView.delegate = nil;
|
||||
_internalTextView.hidden = YES;
|
||||
|
||||
for (int i = 1; i < maxNumberOfLines; ++i) {
|
||||
[newText appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n|W|"]];
|
||||
}
|
||||
|
||||
_internalTextView.attributedText = newText;
|
||||
|
||||
_maxHeight = [self measureHeight];
|
||||
|
||||
_internalTextView.attributedText = saveText;
|
||||
_internalTextView.hidden = NO;
|
||||
_internalTextView.delegate = self;
|
||||
|
||||
[self sizeToFit];
|
||||
|
||||
_maxNumberOfLines = maxNumberOfLines;
|
||||
}
|
||||
|
||||
- (void)setMaxHeight:(CGFloat)maxHeight
|
||||
{
|
||||
_maxHeight = maxHeight;
|
||||
_maxNumberOfLines = 0;
|
||||
}
|
||||
|
||||
- (void)setMinNumberOfLines:(int)minNumberOfLines
|
||||
{
|
||||
if (minNumberOfLines == 0 && _minHeight > 0) // the user specified a minHeight themselves.
|
||||
return;
|
||||
|
||||
// Use internalTextView for height calculations, thanks to Gwynne <http://blog.darkrainfall.org/>
|
||||
NSAttributedString *saveText = _internalTextView.attributedText;
|
||||
NSMutableAttributedString *newText = [[NSMutableAttributedString alloc] initWithString:@"-" attributes:[self defaultAttributes]];
|
||||
|
||||
_internalTextView.delegate = nil;
|
||||
_internalTextView.hidden = YES;
|
||||
|
||||
for (int i = 1; i < minNumberOfLines; ++i) {
|
||||
[newText appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n|W|"]];
|
||||
}
|
||||
|
||||
_internalTextView.attributedText = newText;
|
||||
|
||||
_minHeight = [self measureHeight];
|
||||
|
||||
_internalTextView.attributedText = saveText;
|
||||
_internalTextView.hidden = NO;
|
||||
_internalTextView.delegate = self;
|
||||
|
||||
[self sizeToFit];
|
||||
|
||||
_minNumberOfLines = minNumberOfLines;
|
||||
}
|
||||
|
||||
- (void)setMinHeight:(CGFloat)minHeight
|
||||
{
|
||||
_minHeight = minHeight;
|
||||
_minNumberOfLines = 0;
|
||||
}
|
||||
|
||||
- (void)textViewDidChange:(UITextView *)__unused textView
|
||||
{
|
||||
[self refreshAttributes];
|
||||
|
||||
[self refreshHeight:true];
|
||||
if (self.showPlaceholderWhenFocussed)
|
||||
_placeholderView.hidden = [_internalTextView hasText];
|
||||
}
|
||||
|
||||
- (void)textViewDidChangeSelection:(UITextView *)__unused textView
|
||||
{
|
||||
id<HPGrowingTextViewDelegate> delegate = _delegate;
|
||||
|
||||
if ([delegate respondsToSelector:@selector(growingTextViewDidChangeSelection:)])
|
||||
[delegate growingTextViewDidChangeSelection:self];
|
||||
|
||||
//_internalTextView.typingAttributes = [self defaultAttributes];
|
||||
|
||||
if ([_internalTextView selectedRange].length == 0) {
|
||||
//[self refreshAttributes];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)refreshHeight:(bool)textChanged
|
||||
{
|
||||
CGFloat newSizeH = [self measureHeight]; //size of content, so we can set the frame of self
|
||||
|
||||
if(newSizeH < _minHeight || !_internalTextView.hasText)
|
||||
newSizeH = _minHeight; //not smalles than minHeight
|
||||
|
||||
if (_internalTextView.frame.size.height > _maxHeight)
|
||||
newSizeH = _maxHeight; // not taller than maxHeight
|
||||
|
||||
id<HPGrowingTextViewDelegate> delegate = _delegate;
|
||||
|
||||
if (ABS(_internalTextView.frame.size.height - newSizeH) > FLT_EPSILON || _oneTimeLongAnimation)
|
||||
{
|
||||
// [fixed] Pasting too much text into the view failed to fire the height change,
|
||||
// thanks to Gwynne <http://blog.darkrainfall.org/>
|
||||
|
||||
if (newSizeH > _maxHeight && _internalTextView.frame.size.height <= _maxHeight)
|
||||
newSizeH = _maxHeight;
|
||||
|
||||
if (newSizeH <= _maxHeight)
|
||||
{
|
||||
if (_animateHeightChange && !_internalTextView.isPasting)
|
||||
{
|
||||
NSTimeInterval currentAnimationDuration = 0.12;
|
||||
if (_oneTimeLongAnimation)
|
||||
{
|
||||
_oneTimeLongAnimation = false;
|
||||
currentAnimationDuration = 0.3;
|
||||
if (iosMajorVersion() < 7)
|
||||
currentAnimationDuration *= 0.7;
|
||||
}
|
||||
|
||||
[UIView animateWithDuration:currentAnimationDuration delay:0 options:(UIViewAnimationOptionAllowUserInteraction| UIViewAnimationOptionBeginFromCurrentState) animations:^
|
||||
{
|
||||
[self resizeTextView:newSizeH];
|
||||
} completion:nil];
|
||||
|
||||
if ([delegate respondsToSelector:@selector(growingTextView:willChangeHeight:duration:animationCurve:)])
|
||||
[delegate growingTextView:self willChangeHeight:newSizeH duration:currentAnimationDuration animationCurve:0];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self resizeTextView:newSizeH];
|
||||
|
||||
if ([delegate respondsToSelector:@selector(growingTextView:willChangeHeight:duration:animationCurve:)])
|
||||
[delegate growingTextView:self willChangeHeight:newSizeH duration:0.0 animationCurve:0];
|
||||
}
|
||||
}
|
||||
|
||||
// scroll to caret (needed on iOS7)
|
||||
if (iosMajorVersion() >= 7)
|
||||
{
|
||||
/*NSRange range = _internalTextView.selectedRange;
|
||||
[_internalTextView _scrollRangeToVisible:range animated:false];
|
||||
|
||||
CGRect r = [_internalTextView caretRectForPosition:_internalTextView.selectedTextRange.end];
|
||||
CGFloat frameHeight = _internalTextView.frame.size.height;
|
||||
CGFloat caretY = MAX(r.origin.y - frameHeight + r.size.height + 8, 0);
|
||||
if (r.origin.y != INFINITY)
|
||||
{
|
||||
CGPoint contentOffset = _internalTextView.contentOffset;
|
||||
contentOffset.y = caretY;
|
||||
_internalTextView.contentOffset = contentOffset;
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
if (textChanged && [delegate respondsToSelector:@selector(growingTextViewDidChange:afterSetText:afterPastingText:)]) {
|
||||
[delegate growingTextViewDidChange:self afterSetText:_ignoreChangeNotification afterPastingText:_internalTextView.isPasting];
|
||||
}
|
||||
|
||||
_oneTimeLongAnimation = false;
|
||||
}
|
||||
|
||||
- (void)notifyHeight {
|
||||
id<HPGrowingTextViewDelegate> delegate = _delegate;
|
||||
if ([delegate respondsToSelector:@selector(growingTextView:willChangeHeight:duration:animationCurve:)])
|
||||
[delegate growingTextView:self willChangeHeight:_internalTextView.frame.size.height duration:0.0 animationCurve:0];
|
||||
}
|
||||
|
||||
// Code from apple developer forum - @Steve Krulewitz, @Mark Marszal, @Eric Silverberg
|
||||
- (CGFloat)measureHeight
|
||||
{
|
||||
if (iosMajorVersion() >= 7)
|
||||
{
|
||||
CGRect frame = _internalTextView.bounds;
|
||||
CGSize fudgeFactor = CGSizeMake(10.0, 17.0);
|
||||
|
||||
frame.size.height -= fudgeFactor.height;
|
||||
frame.size.width -= fudgeFactor.width;
|
||||
|
||||
frame.size.width -= _internalTextView.textContainerInset.right;
|
||||
|
||||
NSMutableAttributedString *textToMeasure = [[NSMutableAttributedString alloc] initWithAttributedString:_internalTextView.attributedText];
|
||||
if ([textToMeasure.string hasSuffix:@"\n"])
|
||||
{
|
||||
[textToMeasure appendAttributedString:[[NSAttributedString alloc] initWithString:@"-"]];
|
||||
}
|
||||
[textToMeasure removeAttribute:NSFontAttributeName range:NSMakeRange(0, textToMeasure.length)];
|
||||
if (_intrinsicTextFont != nil) {
|
||||
[textToMeasure addAttribute:NSFontAttributeName value:_intrinsicTextFont range:NSMakeRange(0, textToMeasure.length)];
|
||||
}
|
||||
|
||||
// NSString class method: boundingRectWithSize:options:attributes:context is
|
||||
// available only on ios7.0 sdk.
|
||||
CGRect size = [textToMeasure boundingRectWithSize:CGSizeMake(CGRectGetWidth(frame), MAXFLOAT)
|
||||
options:NSStringDrawingUsesLineFragmentOrigin
|
||||
//attributes:attributes
|
||||
context:nil];
|
||||
|
||||
return CGFloor(CGRectGetHeight(size) + fudgeFactor.height);
|
||||
}
|
||||
else
|
||||
{
|
||||
return CGFloor(self.internalTextView.contentSize.height);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)resizeTextView:(CGFloat)newSizeH
|
||||
{
|
||||
CGRect internalTextViewFrame = self.frame;
|
||||
internalTextViewFrame.size.height = CGFloor(newSizeH);
|
||||
self.frame = internalTextViewFrame;
|
||||
|
||||
internalTextViewFrame.origin = CGPointZero;
|
||||
if(!CGRectEqualToRect(_internalTextView.frame, internalTextViewFrame))
|
||||
_internalTextView.frame = internalTextViewFrame;
|
||||
|
||||
//[_internalTextView textViewEnsureSelectionVisible];
|
||||
}
|
||||
|
||||
- (BOOL)becomeFirstResponder
|
||||
{
|
||||
return [_internalTextView becomeFirstResponder];
|
||||
}
|
||||
|
||||
- (BOOL)resignFirstResponder
|
||||
{
|
||||
return [_internalTextView resignFirstResponder];
|
||||
}
|
||||
|
||||
- (BOOL)isFirstResponder
|
||||
{
|
||||
return [_internalTextView isFirstResponder];
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeFirstResponder
|
||||
{
|
||||
return [_internalTextView canBecomeFirstResponder];
|
||||
}
|
||||
|
||||
- (void)setText:(NSString *)newText
|
||||
{
|
||||
[self setText:newText animated:true];
|
||||
}
|
||||
|
||||
- (void)setAttributedText:(NSAttributedString *)attributedText {
|
||||
[self setAttributedText:attributedText animated:true];
|
||||
}
|
||||
|
||||
- (NSAttributedString *)attributedText {
|
||||
return _internalTextView.attributedText;
|
||||
}
|
||||
|
||||
- (void)setText:(NSString *)newText animated:(bool)animated {
|
||||
[self setAttributedText:[[NSAttributedString alloc] initWithString:newText == nil ? @"" : newText attributes:[self defaultAttributes]] animated:animated];
|
||||
}
|
||||
|
||||
- (void)setAttributedText:(NSAttributedString *)newText animated:(bool)animated {
|
||||
[self setAttributedText:newText keepFormatting:false animated:animated];
|
||||
}
|
||||
|
||||
- (void)setAttributedText:(NSAttributedString *)newText keepFormatting:(bool)keepFormatting animated:(bool)animated {
|
||||
NSMutableAttributedString *fixedFontString = [[NSMutableAttributedString alloc] initWithAttributedString:newText];
|
||||
if (!keepFormatting)
|
||||
{
|
||||
[fixedFontString removeAttribute:NSFontAttributeName range:NSMakeRange(0, fixedFontString.length)];
|
||||
[fixedFontString addAttribute:NSFontAttributeName value:_intrinsicTextFont == nil ? [UIFont systemFontOfSize:17.0f] : _intrinsicTextFont range:NSMakeRange(0, fixedFontString.length)];
|
||||
}
|
||||
|
||||
_internalTextView.attributedText = fixedFontString;
|
||||
_internalTextView.typingAttributes = [self defaultAttributes];
|
||||
[self refreshAttributes];
|
||||
|
||||
_placeholderView.hidden = fixedFontString.length != 0 || [_internalTextView isFirstResponder];
|
||||
|
||||
// include this line to analyze the height of the textview.
|
||||
// fix from Ankit Thakur
|
||||
|
||||
bool previousAnimateHeightChange = _animateHeightChange;
|
||||
_animateHeightChange = animated;
|
||||
_ignoreChangeNotification = true;
|
||||
[self performSelector:@selector(textViewDidChange:) withObject:_internalTextView];
|
||||
_ignoreChangeNotification = false;
|
||||
_animateHeightChange = previousAnimateHeightChange;
|
||||
}
|
||||
|
||||
- (void)selectRange:(NSRange)range force:(bool)force {
|
||||
if (range.length != 0 || force) {
|
||||
UITextPosition *startPosition = [_internalTextView positionFromPosition:_internalTextView.beginningOfDocument offset:range.location];
|
||||
UITextPosition *endPosition = [_internalTextView positionFromPosition:_internalTextView.beginningOfDocument offset:range.location + range.length];
|
||||
UITextRange *selection = [_internalTextView textRangeFromPosition:startPosition toPosition:endPosition];
|
||||
_internalTextView.selectedTextRange = selection;
|
||||
}
|
||||
}
|
||||
|
||||
-(NSString *)text
|
||||
{
|
||||
return _internalTextView.text;
|
||||
}
|
||||
|
||||
- (void)setFont:(UIFont *)afont
|
||||
{
|
||||
_internalTextView.font = afont;
|
||||
_intrinsicTextFont = afont;
|
||||
|
||||
[self setMaxNumberOfLines:_maxNumberOfLines];
|
||||
[self setMinNumberOfLines:_minNumberOfLines];
|
||||
}
|
||||
|
||||
- (UIFont *)font
|
||||
{
|
||||
return _intrinsicTextFont;
|
||||
}
|
||||
|
||||
- (void)setTextColor:(UIColor *)color
|
||||
{
|
||||
_internalTextView.textColor = color;
|
||||
_intrinsicTextColor = color;
|
||||
}
|
||||
|
||||
- (UIColor *)textColor
|
||||
{
|
||||
return _internalTextView.textColor;
|
||||
}
|
||||
|
||||
- (UIColor *)accentColor
|
||||
{
|
||||
if (_accentColor != nil)
|
||||
return _accentColor;
|
||||
|
||||
return TGAccentColor();
|
||||
}
|
||||
|
||||
- (void)setTextAlignment:(NSTextAlignment)aligment
|
||||
{
|
||||
_internalTextView.textAlignment = aligment;
|
||||
}
|
||||
|
||||
- (NSTextAlignment)textAlignment
|
||||
{
|
||||
return _internalTextView.textAlignment;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (BOOL)textViewShouldBeginEditing:(UITextView *)__unused textView
|
||||
{
|
||||
id<HPGrowingTextViewDelegate> delegate = _delegate;
|
||||
|
||||
if ([delegate respondsToSelector:@selector(growingTextViewShouldBeginEditing:)])
|
||||
return [delegate growingTextViewShouldBeginEditing:self];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
- (void)textViewDidBeginEditing:(UITextView *)__unused textView
|
||||
{
|
||||
id<HPGrowingTextViewDelegate> delegate = _delegate;
|
||||
|
||||
if ([delegate respondsToSelector:@selector(growingTextViewDidBeginEditing:)])
|
||||
[delegate growingTextViewDidBeginEditing:self];
|
||||
|
||||
if (!self.showPlaceholderWhenFocussed && ![_internalTextView hasText])
|
||||
_placeholderView.hidden = true;
|
||||
}
|
||||
|
||||
- (void)textViewDidEndEditing:(UITextView *)__unused textView
|
||||
{
|
||||
id<HPGrowingTextViewDelegate> delegate = _delegate;
|
||||
|
||||
if ([delegate respondsToSelector:@selector(growingTextViewDidEndEditing:)])
|
||||
[delegate growingTextViewDidEndEditing:self];
|
||||
|
||||
if (!self.showPlaceholderWhenFocussed)
|
||||
_placeholderView.hidden = [_internalTextView hasText];
|
||||
}
|
||||
|
||||
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)atext
|
||||
{
|
||||
id<HPGrowingTextViewDelegate> delegate = _delegate;
|
||||
|
||||
if ([delegate respondsToSelector:@selector(growingTextViewEnabled:)]) {
|
||||
if (![delegate growingTextViewEnabled:self]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (![textView hasText] && [atext isEqualToString:@""])
|
||||
return NO;
|
||||
|
||||
if ([atext isEqualToString:@"\n"])
|
||||
{
|
||||
id<HPGrowingTextViewDelegate> delegate = _delegate;
|
||||
|
||||
if ([delegate respondsToSelector:@selector(growingTextViewShouldReturn:)])
|
||||
return (BOOL)[delegate performSelector:@selector(growingTextViewShouldReturn:) withObject:self];
|
||||
}
|
||||
|
||||
if (atext.length == 0) {
|
||||
if (range.location != 0 && range.length == 1) {
|
||||
NSAttributedString *string = self.attributedText;
|
||||
if ([string attributesAtIndex:range.location effectiveRange:nil][NSAttachmentAttributeName] != nil) {
|
||||
NSMutableAttributedString *mutableString = [[NSMutableAttributedString alloc] initWithAttributedString:string];
|
||||
[mutableString replaceCharactersInRange:NSMakeRange(range.location - 1, 1) withString:@""];
|
||||
NSRange badRange = [mutableString.string rangeOfString:@"\uFFFC"];
|
||||
if (badRange.location != NSNotFound)
|
||||
[mutableString replaceCharactersInRange:badRange withString:@""];
|
||||
self.attributedText = mutableString;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (range.length == 0) {
|
||||
NSAttributedString *string = self.attributedText;
|
||||
if (range.location != 0 && [string attributesAtIndex:range.location - 1 effectiveRange:nil][NSAttachmentAttributeName] != nil) {
|
||||
NSMutableAttributedString *mutableString = [[NSMutableAttributedString alloc] initWithAttributedString:string];
|
||||
[mutableString replaceCharactersInRange:NSMakeRange(range.location - 1, 0) withString:atext];
|
||||
NSRange badRange = [mutableString.string rangeOfString:@"\uFFFC"];
|
||||
if (badRange.location != NSNotFound)
|
||||
[mutableString replaceCharactersInRange:badRange withString:@""];
|
||||
self.attributedText = mutableString;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
- (void)keyCommandPressed:(UIKeyCommand *)keyCommand
|
||||
{
|
||||
id<HPGrowingTextViewDelegate> delegate = _delegate;
|
||||
|
||||
if ([delegate respondsToSelector:@selector(growingTextView:receivedReturnKeyCommandWithModifierFlags:)])
|
||||
[delegate growingTextView:self receivedReturnKeyCommandWithModifierFlags:keyCommand.modifierFlags];
|
||||
}
|
||||
|
||||
- (NSArray *)keyCommands
|
||||
{
|
||||
if (!self.receiveKeyCommands)
|
||||
return nil;
|
||||
|
||||
return @
|
||||
[
|
||||
[UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:0 action:@selector(keyCommandPressed:)],
|
||||
[UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:UIKeyModifierAlternate action:@selector(keyCommandPressed:)]
|
||||
];
|
||||
}
|
||||
|
||||
- (void)refreshAttributes {
|
||||
if (iosMajorVersion() < 7) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.internalTextView.typingAttributes = [self defaultAttributes];
|
||||
|
||||
NSAttributedString *string = self.attributedText;
|
||||
if (string.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
CGPoint contentOffset = _internalTextView.contentOffset;
|
||||
[_internalTextView setScrollEnabled:false];
|
||||
_internalTextView.disableContentOffsetAnimation = true;
|
||||
_internalTextView.freezeContentOffset = true;
|
||||
|
||||
//NSMutableAttributedString *mutableString = [[NSMutableAttributedString alloc] initWithAttributedString:string];
|
||||
//[mutableString removeAttribute:NSForegroundColorAttributeName range:NSMakeRange(0, string.length)];
|
||||
[_internalTextView.textStorage removeAttribute:NSForegroundColorAttributeName range:NSMakeRange(0, string.length)];
|
||||
|
||||
if (_intrinsicTextColor != nil) {
|
||||
//[mutableString addAttribute:NSForegroundColorAttributeName value:_intrinsicTextColor == nil ? [UIColor blackColor] : _intrinsicTextColor range:NSMakeRange(0, string.length)];
|
||||
[_internalTextView.textStorage addAttribute:NSForegroundColorAttributeName value:_intrinsicTextColor == nil ? [UIColor blackColor] : _intrinsicTextColor range:NSMakeRange(0, string.length)];
|
||||
}
|
||||
|
||||
__block NSMutableArray<TGInputTextTagAndRange *> *inputTextTags = [[NSMutableArray alloc] init];
|
||||
[string enumerateAttribute:TGMentionUidAttributeName inRange:NSMakeRange(0, string.length) options:0 usingBlock:^(__unused id value, NSRange range, __unused BOOL *stop) {
|
||||
if ([value isKindOfClass:[TGInputTextTag class]]) {
|
||||
[inputTextTags addObject:[[TGInputTextTagAndRange alloc] initWithTag:value range:range]];
|
||||
}
|
||||
}];
|
||||
|
||||
[string enumerateAttribute:TGMentionBoldAttributeName inRange:NSMakeRange(0, string.length) options:0 usingBlock:^(__unused id value, NSRange range, __unused BOOL *stop) {
|
||||
if ([value isKindOfClass:[TGInputTextTag class]]) {
|
||||
[inputTextTags addObject:[[TGInputTextTagAndRange alloc] initWithTag:value range:range]];
|
||||
}
|
||||
}];
|
||||
|
||||
if (inputTextTags != nil) {
|
||||
/*if (mutableString == nil) {
|
||||
mutableString = [[NSMutableAttributedString alloc] initWithAttributedString:string];
|
||||
}*/
|
||||
|
||||
static NSCharacterSet *alphanumericSet = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
alphanumericSet = [NSCharacterSet alphanumericCharacterSet];
|
||||
});
|
||||
|
||||
NSMutableSet<NSNumber *> *removeTags = [[NSMutableSet alloc] init];
|
||||
for (NSInteger i = 0; i < ((NSInteger)inputTextTags.count); i++) {
|
||||
TGInputTextTagAndRange *tagAndRange = inputTextTags[i];
|
||||
if ([removeTags containsObject:@(tagAndRange.tag.uniqueId)]) {
|
||||
[inputTextTags removeObjectAtIndex:i];
|
||||
//[mutableString removeAttribute:TGMentionUidAttributeName range:tagAndRange.range];
|
||||
[_internalTextView.textStorage removeAttribute:TGMentionUidAttributeName range:tagAndRange.range];
|
||||
|
||||
i--;
|
||||
} else {
|
||||
NSInteger j = tagAndRange.range.location;
|
||||
while (j < (NSInteger)(tagAndRange.range.location + tagAndRange.range.length)) {
|
||||
unichar c = [string.string characterAtIndex:j];
|
||||
if (c != ' ') {
|
||||
break;
|
||||
}
|
||||
j++;
|
||||
}
|
||||
|
||||
if (j != (NSInteger)tagAndRange.range.location) {
|
||||
NSRange updatedRange = NSMakeRange(j, tagAndRange.range.location + tagAndRange.range.length - j);
|
||||
//[mutableString removeAttribute:TGMentionUidAttributeName range:tagAndRange.range];
|
||||
[_internalTextView.textStorage removeAttribute:TGMentionUidAttributeName range:tagAndRange.range];
|
||||
|
||||
//[mutableString addAttribute:TGMentionUidAttributeName value:tagAndRange.tag range:updatedRange];
|
||||
[_internalTextView.textStorage addAttribute:TGMentionUidAttributeName value:tagAndRange.tag range:updatedRange];
|
||||
|
||||
inputTextTags[i] = [[TGInputTextTagAndRange alloc] initWithTag:tagAndRange.tag range:updatedRange];
|
||||
|
||||
i--;
|
||||
} else {
|
||||
NSInteger j = tagAndRange.range.location;
|
||||
while (j >= 0) {
|
||||
unichar c = [string.string characterAtIndex:j];
|
||||
if (![alphanumericSet characterIsMember:c]) {
|
||||
break;
|
||||
}
|
||||
j--;
|
||||
}
|
||||
j++;
|
||||
|
||||
if (j < ((NSInteger)tagAndRange.range.location)) {
|
||||
NSRange updatedRange = NSMakeRange(j, tagAndRange.range.location + tagAndRange.range.length - j);
|
||||
//[mutableString removeAttribute:TGMentionUidAttributeName range:tagAndRange.range];
|
||||
[_internalTextView.textStorage removeAttribute:TGMentionUidAttributeName range:tagAndRange.range];
|
||||
|
||||
//[mutableString addAttribute:TGMentionUidAttributeName value:tagAndRange.tag range:updatedRange];
|
||||
[_internalTextView.textStorage addAttribute:TGMentionUidAttributeName value:tagAndRange.tag range:updatedRange];
|
||||
|
||||
inputTextTags[i] = [[TGInputTextTagAndRange alloc] initWithTag:tagAndRange.tag range:updatedRange];
|
||||
|
||||
i--;
|
||||
} else {
|
||||
TGInputTextTagAndRange *nextTagAndRange = nil;
|
||||
if (i != ((NSInteger)inputTextTags.count) - 1) {
|
||||
nextTagAndRange = inputTextTags[i + 1];
|
||||
}
|
||||
|
||||
if (nextTagAndRange == nil || nextTagAndRange.tag.uniqueId != tagAndRange.tag.uniqueId) {
|
||||
NSInteger candidateStart = tagAndRange.range.location + tagAndRange.range.length;
|
||||
NSInteger candidateEnd = nextTagAndRange == nil ? string.length : nextTagAndRange.range.location;
|
||||
NSInteger j = candidateStart;
|
||||
while (j < candidateEnd) {
|
||||
unichar c = [string.string characterAtIndex:j];
|
||||
static NSCharacterSet *alphanumericSet = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
alphanumericSet = [NSCharacterSet alphanumericCharacterSet];
|
||||
});
|
||||
if (![alphanumericSet characterIsMember:c]) {
|
||||
break;
|
||||
}
|
||||
j++;
|
||||
}
|
||||
|
||||
if (j == candidateStart) {
|
||||
[removeTags addObject:@(tagAndRange.tag.uniqueId)];
|
||||
//[mutableString addAttribute:NSForegroundColorAttributeName value:TGAccentColor() range:tagAndRange.range];
|
||||
[_internalTextView.textStorage addAttribute:NSForegroundColorAttributeName value:self.accentColor range:tagAndRange.range];
|
||||
} else {
|
||||
//[mutableString removeAttribute:TGMentionUidAttributeName range:tagAndRange.range];
|
||||
[_internalTextView.textStorage removeAttribute:TGMentionUidAttributeName range:tagAndRange.range];
|
||||
|
||||
NSRange updatedRange = NSMakeRange(tagAndRange.range.location, j - tagAndRange.range.location);
|
||||
//[mutableString addAttribute:TGMentionUidAttributeName value:tagAndRange.tag range:updatedRange];
|
||||
[_internalTextView.textStorage addAttribute:TGMentionUidAttributeName value:tagAndRange.tag range:updatedRange];
|
||||
inputTextTags[i] = [[TGInputTextTagAndRange alloc] initWithTag:tagAndRange.tag range:updatedRange];
|
||||
|
||||
i--;
|
||||
}
|
||||
} else {
|
||||
NSInteger candidateStart = tagAndRange.range.location + tagAndRange.range.length;
|
||||
NSInteger candidateEnd = nextTagAndRange.range.location;
|
||||
NSInteger j = candidateStart;
|
||||
while (j < candidateEnd) {
|
||||
unichar c = [string.string characterAtIndex:j];
|
||||
if (![alphanumericSet characterIsMember:c] && c != ' ') {
|
||||
break;
|
||||
}
|
||||
j++;
|
||||
}
|
||||
|
||||
if (j == candidateEnd) {
|
||||
//[mutableString removeAttribute:TGMentionUidAttributeName range:tagAndRange.range];
|
||||
[_internalTextView.textStorage removeAttribute:TGMentionUidAttributeName range:tagAndRange.range];
|
||||
|
||||
//[mutableString removeAttribute:TGMentionUidAttributeName range:nextTagAndRange.range];
|
||||
[_internalTextView.textStorage removeAttribute:TGMentionUidAttributeName range:nextTagAndRange.range];
|
||||
|
||||
NSRange updatedRange = NSMakeRange(tagAndRange.range.location, nextTagAndRange.range.location + nextTagAndRange.range.length - tagAndRange.range.location);
|
||||
|
||||
//[mutableString addAttribute:TGMentionUidAttributeName value:tagAndRange.tag range:updatedRange];
|
||||
[_internalTextView.textStorage addAttribute:TGMentionUidAttributeName value:tagAndRange.tag range:updatedRange];
|
||||
|
||||
inputTextTags[i] = [[TGInputTextTagAndRange alloc] initWithTag:tagAndRange.tag range:updatedRange];
|
||||
[inputTextTags removeObjectAtIndex:i + 1];
|
||||
|
||||
i--;
|
||||
} else if (j != candidateStart) {
|
||||
//[mutableString removeAttribute:TGMentionUidAttributeName range:tagAndRange.range];
|
||||
[_internalTextView.textStorage removeAttribute:TGMentionUidAttributeName range:tagAndRange.range];
|
||||
|
||||
NSRange updatedRange = NSMakeRange(tagAndRange.range.location, j - tagAndRange.range.location);
|
||||
//[mutableString addAttribute:TGMentionUidAttributeName value:tagAndRange.tag range:updatedRange];
|
||||
[_internalTextView.textStorage addAttribute:TGMentionUidAttributeName value:tagAndRange.tag range:updatedRange];
|
||||
|
||||
inputTextTags[i] = [[TGInputTextTagAndRange alloc] initWithTag:tagAndRange.tag range:updatedRange];
|
||||
|
||||
i--;
|
||||
} else {
|
||||
[removeTags addObject:@(tagAndRange.tag.uniqueId)];
|
||||
//[mutableString addAttribute:NSForegroundColorAttributeName value:TGAccentColor() range:tagAndRange.range];
|
||||
[_internalTextView.textStorage addAttribute:NSForegroundColorAttributeName value:self.accentColor range:tagAndRange.range];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_internalTextView.freezeContentOffset = false;
|
||||
[_internalTextView setContentOffset:contentOffset];
|
||||
_internalTextView.disableContentOffsetAnimation = false;
|
||||
[_internalTextView setScrollEnabled:true];
|
||||
|
||||
/*if (mutableString != nil && ![mutableString isEqualToAttributedString:_internalTextView.attributedText])*/ {
|
||||
/*[_internalTextView.textStorage removeAttribute:NSForegroundColorAttributeName range:NSMakeRange(0, string.length)];
|
||||
[_internalTextView.textStorage addAttribute:NSForegroundColorAttributeName value:TGAccentColor() range:NSMakeRange(string.length - 1, 1)];
|
||||
|
||||
return;
|
||||
|
||||
UITextRange *previousRange = [_internalTextView selectedTextRange];
|
||||
UITextPosition *selStartPos = previousRange.start;
|
||||
NSInteger previousIdx = [_internalTextView offsetFromPosition:_internalTextView.beginningOfDocument toPosition:selStartPos];
|
||||
|
||||
_internalTextView.attributedText = mutableString;
|
||||
|
||||
UITextPosition *textPosition = [_internalTextView positionFromPosition:_internalTextView.beginningOfDocument offset:MIN(previousIdx, (NSInteger)mutableString.length)];
|
||||
[_internalTextView setSelectedTextRange:[_internalTextView textRangeFromPosition:textPosition toPosition:textPosition]];*/
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)textWithEntities:(__autoreleasing NSArray<TGMessageEntity *> ** _Nullable)entities {
|
||||
NSAttributedString *string = self.attributedText;
|
||||
|
||||
NSMutableArray *result = [[NSMutableArray alloc] init];
|
||||
[string enumerateAttribute:TGMentionUidAttributeName inRange:NSMakeRange(0, string.length) options:0 usingBlock:^(__unused id value, NSRange range, __unused BOOL *stop) {
|
||||
if ([value isKindOfClass:[TGInputTextTag class]]) {
|
||||
[result addObject:[[TGMessageEntityMentionName alloc] initWithRange:range userId:[((TGInputTextTag *)value).attachment intValue]]];
|
||||
}
|
||||
}];
|
||||
|
||||
if (iosMajorVersion() >= 7) {
|
||||
[string enumerateAttribute:NSFontAttributeName inRange:NSMakeRange(0, string.length) options:0 usingBlock:^(UIFont *font, NSRange range, __unused BOOL *stop) {
|
||||
NSString *fontDescription = font.description;
|
||||
if ([fontDescription rangeOfString:@"font-weight: bold"].location != NSNotFound) {
|
||||
[result addObject:[[TGMessageEntityBold alloc] initWithRange:range]];
|
||||
} else if ([fontDescription rangeOfString:@"font-style: italic"].location != NSNotFound) {
|
||||
[result addObject:[[TGMessageEntityItalic alloc] initWithRange:range]];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
if (entities) {
|
||||
*entities = result;
|
||||
}
|
||||
|
||||
return string.string;
|
||||
}
|
||||
|
||||
+ (void)replaceMention:(NSString *)mention inputField:(HPGrowingTextView *)inputField username:(bool)username userId:(int32_t)userId
|
||||
{
|
||||
NSString *replacementText = [mention stringByAppendingString:@" "];
|
||||
|
||||
NSMutableAttributedString *text = inputField.internalTextView.attributedText == nil ? [[NSMutableAttributedString alloc] init] : [[NSMutableAttributedString alloc] initWithAttributedString:inputField.internalTextView.attributedText];
|
||||
|
||||
UITextRange *selRange = inputField.internalTextView.selectedTextRange;
|
||||
UITextPosition *selStartPos = selRange.start;
|
||||
NSInteger idx = [inputField.internalTextView offsetFromPosition:inputField.internalTextView.beginningOfDocument toPosition:selStartPos];
|
||||
idx--;
|
||||
NSRange candidateMentionRange = NSMakeRange(NSNotFound, 0);
|
||||
|
||||
if (idx >= 0 && idx < (int)text.length)
|
||||
{
|
||||
for (NSInteger i = idx; i >= 0; i--)
|
||||
{
|
||||
unichar c = [text.string characterAtIndex:i];
|
||||
if (c == '@')
|
||||
{
|
||||
if (i == idx)
|
||||
candidateMentionRange = NSMakeRange(i + 1, 0);
|
||||
else
|
||||
candidateMentionRange = NSMakeRange(i + 1, idx - i);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (candidateMentionRange.location != NSNotFound)
|
||||
{
|
||||
if (!username) {
|
||||
candidateMentionRange.location -= 1;
|
||||
candidateMentionRange.length += 1;
|
||||
|
||||
[text replaceCharactersInRange:candidateMentionRange withString:replacementText];
|
||||
|
||||
static int64_t nextId = 0;
|
||||
nextId++;
|
||||
[text addAttributes:@{TGMentionUidAttributeName: [[TGInputTextTag alloc] initWithUniqueId:nextId left:true attachment:@(userId)]} range:NSMakeRange(candidateMentionRange.location, replacementText.length - 1)];
|
||||
} else {
|
||||
[text replaceCharactersInRange:candidateMentionRange withString:replacementText];
|
||||
}
|
||||
|
||||
[inputField setAttributedText:text];
|
||||
UITextPosition *textPosition = [inputField.internalTextView positionFromPosition:inputField.internalTextView.beginningOfDocument offset:candidateMentionRange.location + replacementText.length];
|
||||
[inputField.internalTextView setSelectedTextRange:[inputField.internalTextView textRangeFromPosition:textPosition toPosition:textPosition]];
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)replaceHashtag:(NSString *)hashtag inputField:(HPGrowingTextView *)inputField
|
||||
{
|
||||
if (inputField.attributedText == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
static NSCharacterSet *characterSet = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^
|
||||
{
|
||||
characterSet = [NSCharacterSet alphanumericCharacterSet];
|
||||
});
|
||||
|
||||
NSString *replacementText = [hashtag stringByAppendingString:@" "];
|
||||
|
||||
NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithAttributedString:inputField.attributedText];
|
||||
|
||||
UITextRange *selRange = inputField.internalTextView.selectedTextRange;
|
||||
UITextPosition *selStartPos = selRange.start;
|
||||
NSInteger idx = [inputField.internalTextView offsetFromPosition:inputField.internalTextView.beginningOfDocument toPosition:selStartPos];
|
||||
idx--;
|
||||
NSRange candidateHashtagRange = NSMakeRange(NSNotFound, 0);
|
||||
NSString *string = text.string;
|
||||
|
||||
if (idx >= 0 && idx < (int)text.length)
|
||||
{
|
||||
for (NSInteger i = idx; i >= 0; i--)
|
||||
{
|
||||
unichar c = [string characterAtIndex:i];
|
||||
if (c == '#')
|
||||
{
|
||||
if (i == idx)
|
||||
candidateHashtagRange = NSMakeRange(i + 1, 0);
|
||||
else
|
||||
candidateHashtagRange = NSMakeRange(i + 1, idx - i);
|
||||
break;
|
||||
}
|
||||
|
||||
if (c == ' ' || (![characterSet characterIsMember:c] && c != '_'))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (candidateHashtagRange.location != NSNotFound)
|
||||
{
|
||||
[text replaceCharactersInRange:candidateHashtagRange withString:replacementText];
|
||||
[inputField setAttributedText:text];
|
||||
UITextPosition *textPosition = [inputField.internalTextView positionFromPosition:inputField.internalTextView.beginningOfDocument offset:candidateHashtagRange.location + replacementText.length];
|
||||
[inputField.internalTextView setSelectedTextRange:[inputField.internalTextView textRangeFromPosition:textPosition toPosition:textPosition]];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
|
||||
|
||||
//
|
||||
// HPTextViewInternal.h
|
||||
//
|
||||
// Created by Hans Pinckaers on 29-06-10.
|
||||
//
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2011 Hans Pinckaers
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <LegacyComponents/TGWeakDelegate.h>
|
||||
|
||||
@class TGKeyCommandController;
|
||||
|
||||
@interface HPTextViewInternal : UITextView
|
||||
|
||||
@property (nonatomic) bool isPasting;
|
||||
|
||||
@property (nonatomic) bool freezeContentOffset;
|
||||
@property (nonatomic) bool disableContentOffsetAnimation;
|
||||
|
||||
@property (nonatomic, strong) TGWeakDelegate *responderStateDelegate;
|
||||
|
||||
@property (nonatomic) bool enableFirstResponder;
|
||||
|
||||
- (instancetype)initWithKeyCommandController:(TGKeyCommandController *)keyCommandController;
|
||||
|
||||
+ (void)addTextViewMethods;
|
||||
|
||||
- (void)textViewEnsureSelectionVisible;
|
||||
|
||||
@end
|
||||
|
||||
@protocol HPTextViewInternalDelegate <NSObject>
|
||||
|
||||
@required
|
||||
|
||||
- (void)hpTextViewChangedResponderState:(bool)firstResponder;
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,318 @@
|
|||
#import "HPTextViewInternal.h"
|
||||
|
||||
#import "LegacyComponentsInternal.h"
|
||||
#import "TGHacks.h"
|
||||
#import "FreedomUIKit.h"
|
||||
|
||||
#import "HPGrowingTextView.h"
|
||||
|
||||
#import <objc/runtime.h>
|
||||
#import <objc/message.h>
|
||||
|
||||
#import <MobileCoreServices/MobileCoreServices.h>
|
||||
|
||||
#import "TGKeyCommandController.h"
|
||||
|
||||
@interface HPTextViewInternal () {
|
||||
__weak TGKeyCommandController *_keyCommandController;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation HPTextViewInternal
|
||||
|
||||
- (instancetype)initWithKeyCommandController:(TGKeyCommandController *)keyCommandController {
|
||||
self = [super initWithFrame:CGRectZero];
|
||||
if (self != nil) {
|
||||
_keyCommandController = keyCommandController;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (void)addTextViewMethods
|
||||
{
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^
|
||||
{
|
||||
InjectInstanceMethodFromAnotherClass([HPTextViewInternal class], [HPTextViewInternal class], @selector(textViewAdjustScrollRange:animated:), NSSelectorFromString(TGEncodeText(@"`tdspmmSbohfUpWjtjcmf;bojnbufe;", -1)));
|
||||
});
|
||||
}
|
||||
|
||||
- (void)setText:(NSString *)text
|
||||
{
|
||||
BOOL originalValue = self.scrollEnabled;
|
||||
//If one of GrowingTextView's superviews is a scrollView, and self.scrollEnabled == NO,
|
||||
//setting the text programatically will cause UIKit to search upwards until it finds a scrollView with scrollEnabled==yes
|
||||
//then scroll it erratically. Setting scrollEnabled temporarily to YES prevents this.
|
||||
[self setScrollEnabled:YES];
|
||||
[super setText:text];
|
||||
[self setScrollEnabled:originalValue];
|
||||
}
|
||||
|
||||
- (void)setAttributedText:(NSAttributedString *)attributedText {
|
||||
BOOL originalValue = self.scrollEnabled;
|
||||
//If one of GrowingTextView's superviews is a scrollView, and self.scrollEnabled == NO,
|
||||
//setting the text programatically will cause UIKit to search upwards until it finds a scrollView with scrollEnabled==yes
|
||||
//then scroll it erratically. Setting scrollEnabled temporarily to YES prevents this.
|
||||
[self setScrollEnabled:YES];
|
||||
[super setAttributedText:attributedText];
|
||||
[self setScrollEnabled:originalValue];
|
||||
}
|
||||
|
||||
- (void)setScrollable:(BOOL)isScrollable
|
||||
{
|
||||
[super setScrollEnabled:isScrollable];
|
||||
}
|
||||
|
||||
- (void)textViewAdjustScrollRange:(NSRange)range animated:(BOOL)animated
|
||||
{
|
||||
static SEL selector = NULL;
|
||||
static void (*impl)(id, SEL, NSRange, BOOL) = NULL;
|
||||
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^
|
||||
{
|
||||
Method method = class_getInstanceMethod([UITextView class], selector);
|
||||
if (method != NULL)
|
||||
impl = (void (*)(id, SEL, NSRange, BOOL))method_getImplementation(method);
|
||||
});
|
||||
|
||||
animated = false;
|
||||
|
||||
if (impl != NULL)
|
||||
impl(self, selector, range, animated);
|
||||
}
|
||||
|
||||
- (void)scrollRectToVisible:(CGRect)__unused rect animated:(BOOL)__unused animated
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated
|
||||
{
|
||||
if (_freezeContentOffset)
|
||||
return;
|
||||
|
||||
[super setContentOffset:contentOffset animated:_disableContentOffsetAnimation ? false : animated];
|
||||
}
|
||||
|
||||
- (void)setFrame:(CGRect)frame
|
||||
{
|
||||
[super setFrame:frame];
|
||||
}
|
||||
|
||||
-(void)setContentOffset:(CGPoint)s
|
||||
{
|
||||
if (_freezeContentOffset)
|
||||
return;
|
||||
|
||||
[super setContentOffset:s];
|
||||
}
|
||||
|
||||
- (void)textViewEnsureSelectionVisible
|
||||
{
|
||||
if (iosMajorVersion() >= 9) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
CGRect caretFrame = [self caretRectForPosition:self.selectedTextRange.end];
|
||||
if (caretFrame.origin.x < CGFLOAT_MAX && caretFrame.origin.y < CGFLOAT_MAX && !CGRectIsInfinite(caretFrame))
|
||||
{
|
||||
UIEdgeInsets implicitInset = UIEdgeInsetsMake(8, 0, 8, 0);
|
||||
|
||||
caretFrame.origin.y -= implicitInset.top;
|
||||
caretFrame.size.height += implicitInset.top + implicitInset.bottom;
|
||||
caretFrame.origin.y = CGFloor(caretFrame.origin.y * 2.0f) / 2.0f;
|
||||
caretFrame.size.height = CGFloor(caretFrame.size.height * 2.0f) / 2.0f;
|
||||
|
||||
CGFloat frameHeight = self.frame.size.height;
|
||||
CGPoint contentOffset = self.contentOffset;
|
||||
|
||||
if (caretFrame.origin.y < contentOffset.y)
|
||||
contentOffset.y = caretFrame.origin.y;
|
||||
if (caretFrame.origin.y + caretFrame.size.height > contentOffset.y + frameHeight)
|
||||
contentOffset.y = caretFrame.origin.y + caretFrame.size.height - frameHeight;
|
||||
contentOffset.y = MAX(0, contentOffset.y);
|
||||
|
||||
if (!CGPointEqualToPoint(contentOffset, self.contentOffset))
|
||||
self.contentOffset = contentOffset;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
CGRect caretFrame = [self caretRectForPosition:self.selectedTextRange.end];
|
||||
if (caretFrame.origin.x < CGFLOAT_MAX && caretFrame.origin.y < CGFLOAT_MAX && !CGRectIsInfinite(caretFrame))
|
||||
{
|
||||
UIEdgeInsets implicitInset = UIEdgeInsetsMake(8, 0, 8, 0);
|
||||
|
||||
caretFrame.origin.y -= implicitInset.top;
|
||||
caretFrame.size.height += implicitInset.top + implicitInset.bottom;
|
||||
caretFrame.origin.y = CGFloor(caretFrame.origin.y * 2.0f) / 2.0f;
|
||||
caretFrame.size.height = CGFloor(caretFrame.size.height * 2.0f) / 2.0f;
|
||||
|
||||
CGFloat frameHeight = self.frame.size.height;
|
||||
CGPoint contentOffset = self.contentOffset;
|
||||
|
||||
if (caretFrame.origin.y < contentOffset.y)
|
||||
contentOffset.y = caretFrame.origin.y;
|
||||
if (caretFrame.origin.y + caretFrame.size.height > contentOffset.y + frameHeight)
|
||||
contentOffset.y = caretFrame.origin.y + caretFrame.size.height - frameHeight;
|
||||
contentOffset.y = MAX(0, contentOffset.y);
|
||||
|
||||
if (!CGPointEqualToPoint(contentOffset, self.contentOffset))
|
||||
self.contentOffset = contentOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setContentSize:(CGSize)contentSize
|
||||
{
|
||||
[super setContentSize:contentSize];
|
||||
|
||||
[self textViewEnsureSelectionVisible];
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeFirstResponder
|
||||
{
|
||||
if (!_enableFirstResponder)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
- (BOOL)becomeFirstResponder
|
||||
{
|
||||
if (!_enableFirstResponder)
|
||||
return false;
|
||||
|
||||
__block BOOL result = false;
|
||||
freedomUIKitTest4(^
|
||||
{
|
||||
if ([self.delegate respondsToSelector:@selector(textViewShouldBeginEditing:)] && ![self.delegate textViewShouldBeginEditing:self])
|
||||
result = false;
|
||||
else
|
||||
result = [super becomeFirstResponder];
|
||||
});
|
||||
|
||||
if (result)
|
||||
{
|
||||
id delegate = _responderStateDelegate.object;
|
||||
if (delegate != nil && [delegate conformsToProtocol:@protocol(HPTextViewInternalDelegate)])
|
||||
{
|
||||
[(id<HPTextViewInternalDelegate>)delegate hpTextViewChangedResponderState:true];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (BOOL)resignFirstResponder
|
||||
{
|
||||
__block BOOL result = false;
|
||||
freedomUIKitTest4(^
|
||||
{
|
||||
result = [super resignFirstResponder];
|
||||
});
|
||||
|
||||
if (result)
|
||||
{
|
||||
id delegate = _responderStateDelegate.object;
|
||||
if (delegate != nil && [delegate conformsToProtocol:@protocol(HPTextViewInternalDelegate)])
|
||||
{
|
||||
[(id<HPTextViewInternalDelegate>)delegate hpTextViewChangedResponderState:false];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
|
||||
{
|
||||
if (action == @selector(paste:))
|
||||
return true;
|
||||
|
||||
if (action == @selector(toggleUnderline:)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wundeclared-selector"
|
||||
static SEL promptForReplaceSelector;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
promptForReplaceSelector = NSSelectorFromString(@"_promptForReplace:");
|
||||
});
|
||||
if (action == promptForReplaceSelector) {
|
||||
return false;
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
return [super canPerformAction:action withSender:sender];
|
||||
}
|
||||
|
||||
- (id)targetForAction:(SEL)action withSender:(id)__unused sender
|
||||
{
|
||||
if (action == @selector(processKeyCommand:)) {
|
||||
TGKeyCommandController *keyCommandController = _keyCommandController;
|
||||
return [keyCommandController targetForAction:action withSender:sender];
|
||||
}
|
||||
|
||||
return [super targetForAction:action withSender:sender];
|
||||
}
|
||||
|
||||
- (void)paste:(id)sender
|
||||
{
|
||||
UIPasteboard *pasteBoard = [UIPasteboard generalPasteboard];
|
||||
|
||||
NSData *gifData = [pasteBoard dataForPasteboardType:@"com.compuserve.gif"];
|
||||
if (gifData != nil)
|
||||
{
|
||||
id delegate = self.delegate;
|
||||
if ([delegate isKindOfClass:[HPGrowingTextView class]])
|
||||
{
|
||||
HPGrowingTextView *textView = delegate;
|
||||
NSObject<HPGrowingTextViewDelegate> *textViewDelegate = (NSObject<HPGrowingTextViewDelegate> *)textView.delegate;
|
||||
if ([textViewDelegate respondsToSelector:@selector(growingTextView:didPasteData:)])
|
||||
[textViewDelegate growingTextView:textView didPasteData:gifData];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NSMutableArray *images = [NSMutableArray arrayWithCapacity:1];
|
||||
NSString *text = nil;
|
||||
|
||||
for (NSDictionary *item in pasteBoard.items) {
|
||||
if (item[(__bridge NSString *)kUTTypeJPEG] != nil) {
|
||||
[images addObject:item[(__bridge NSString *)kUTTypeJPEG]];
|
||||
} else if (item[(__bridge NSString *)kUTTypePNG] != nil) {
|
||||
[images addObject:item[(__bridge NSString *)kUTTypePNG]];
|
||||
} else if (item[(__bridge NSString *)kUTTypeGIF] != nil) {
|
||||
[images addObject:item[(__bridge NSString *)kUTTypeGIF]];
|
||||
} else if (item[(__bridge NSString *)kUTTypeURL] != nil) {
|
||||
id url = item[(__bridge NSString *)kUTTypeURL];
|
||||
if ([url respondsToSelector:@selector(characterAtIndex:)]) {
|
||||
text = url;
|
||||
} else if ([url isKindOfClass:[NSURL class]]) {
|
||||
text = ((NSURL *)url).absoluteString;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (images.count != 0)
|
||||
{
|
||||
id delegate = self.delegate;
|
||||
if ([delegate isKindOfClass:[HPGrowingTextView class]])
|
||||
{
|
||||
HPGrowingTextView *textView = delegate;
|
||||
NSObject<HPGrowingTextViewDelegate> *textViewDelegate = (NSObject<HPGrowingTextViewDelegate> *)textView.delegate;
|
||||
if ([textViewDelegate respondsToSelector:@selector(growingTextView:didPasteImages:andText:)])
|
||||
[textViewDelegate growingTextView:textView didPasteImages:images andText:text];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_isPasting = true;
|
||||
bool previousAllowsEditingTextAttributes = self.allowsEditingTextAttributes;
|
||||
self.allowsEditingTextAttributes = false;
|
||||
[super paste:sender];
|
||||
self.allowsEditingTextAttributes = previousAllowsEditingTextAttributes;
|
||||
_isPasting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
24
submodules/LegacyComponents/LegacyComponents/Info.plist
Normal file
24
submodules/LegacyComponents/LegacyComponents/Info.plist
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
Copyright (c) 2013, Jonathan Willing. All rights reserved.
|
||||
Licensed under the MIT license <http://opensource.org/licenses/MIT>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
// Adds the ability to add spring to your animations.
|
||||
//
|
||||
// Duration cannot be set directly. Instead, duration is
|
||||
// a side effect of changing the various properties below.
|
||||
//
|
||||
// Although JNWSpringAnimation is a subclass of CAKeyframeAnimation,
|
||||
// it should be treated as if it were a subclass of CABasicAnimation.
|
||||
@interface JNWSpringAnimation : CAKeyframeAnimation
|
||||
|
||||
// The dedicated initializer for the animation.
|
||||
//
|
||||
// Not all layer properties can be animated. The following are compatible:
|
||||
// - position
|
||||
// - position.{x, y}
|
||||
// - cornerRadius
|
||||
// - shadowRadius
|
||||
// - bounds
|
||||
// - bounds.size
|
||||
// - transform.translation.{x, y, z}
|
||||
// - transform.rotation.{x, y, z}
|
||||
// - transform.scale.{x, y, z}
|
||||
// - transform.translation
|
||||
// - transform (** experimental, only performs linear interpolation on all components **)
|
||||
+ (instancetype)animationWithKeyPath:(NSString *)path;
|
||||
|
||||
// A damped spring can be modeled with the following equation:
|
||||
// F = - kx - bv
|
||||
// where k is the spring constant, x is the distance from equilibrium,
|
||||
// and b is the coefficient of damping.
|
||||
//
|
||||
// Under the hood, a damped harmonic oscillation equation is used to
|
||||
// provide the same results as the data obtained from Hooke's law.
|
||||
|
||||
// The spring constant.
|
||||
//
|
||||
// Defaults to 300.
|
||||
@property (nonatomic, assign) CGFloat stiffness;
|
||||
@property (nonatomic, assign) CGFloat durationFactor;
|
||||
|
||||
// The coefficient of damping.
|
||||
//
|
||||
// Defaults to 30.
|
||||
@property (nonatomic, assign) CGFloat damping;
|
||||
|
||||
// The mass of the object.
|
||||
//
|
||||
// Defaults to 5.
|
||||
@property (nonatomic, assign) CGFloat mass;
|
||||
|
||||
// Equivalent to CABasicAnimation's counterparts.
|
||||
//
|
||||
// Both must be non-nil.
|
||||
@property (nonatomic, strong) id fromValue;
|
||||
@property (nonatomic, strong) id toValue;
|
||||
@property (nonatomic, strong) id initialVelocity;
|
||||
|
||||
// The duration, which is derived from the stiffness, damping, mass, and values.
|
||||
//
|
||||
// Note that this property will only return a non-zero value if both the `fromValue`
|
||||
// and the `toValue` properties have both been set.
|
||||
//
|
||||
// Defaults to 0 if no from or to values have been set.
|
||||
@property (nonatomic, assign, readonly) CFTimeInterval duration;
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,295 @@
|
|||
#import "JNWSpringAnimation.h"
|
||||
#import "NSValue+JNWAdditions.h"
|
||||
|
||||
#import "TGHacks.h"
|
||||
|
||||
static const CGFloat JNWSpringAnimationDefaultMass = 5.f;
|
||||
static const CGFloat JNWSpringAnimationDefaultDamping = 30.f;
|
||||
static const CGFloat JNWSpringAnimationDefaultStiffness = 300.f;
|
||||
static const CGFloat JNWSpringAnimationKeyframeStep = 0.001f;
|
||||
static const CGFloat JNWSpringAnimationMinimumThreshold = 0.0001f;
|
||||
|
||||
@interface JNWSpringAnimation()
|
||||
@property (nonatomic, copy) NSArray *interpolatedValues;
|
||||
@property (nonatomic, assign) BOOL needsRecalculation;
|
||||
@end
|
||||
|
||||
@implementation JNWSpringAnimation
|
||||
|
||||
#pragma mark Initialization
|
||||
|
||||
+ (instancetype)animationWithKeyPath:(NSString *)path {
|
||||
return [super animationWithKeyPath:path];
|
||||
}
|
||||
|
||||
- (id)init {
|
||||
self = [super init];
|
||||
_mass = JNWSpringAnimationDefaultMass;
|
||||
_damping = JNWSpringAnimationDefaultDamping;
|
||||
_stiffness = JNWSpringAnimationDefaultStiffness;
|
||||
_durationFactor = 1.0f;
|
||||
_needsRecalculation = NO;
|
||||
return self;
|
||||
}
|
||||
|
||||
// Since animations are copied before they are added onto the layer, we
|
||||
// take this opportunity to override the copy method and actually
|
||||
// calculate the key frames, and move those over to the new animation.
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
JNWSpringAnimation *copy = [super copyWithZone:zone];
|
||||
|
||||
copy.interpolatedValues = self.interpolatedValues;
|
||||
copy.duration = self.interpolatedValues.count * JNWSpringAnimationKeyframeStep * TGAnimationSpeedFactor() * _durationFactor;
|
||||
copy.fromValue = self.fromValue;
|
||||
copy.stiffness = self.stiffness;
|
||||
copy.toValue = self.toValue;
|
||||
copy.damping = self.damping;
|
||||
copy.mass = self.mass;
|
||||
|
||||
copy.needsRecalculation = NO;
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
#pragma mark API
|
||||
|
||||
- (void)setToValue:(id)toValue {
|
||||
_toValue = toValue;
|
||||
self.needsRecalculation = YES;
|
||||
}
|
||||
|
||||
- (void)setFromValue:(id)fromValue {
|
||||
_fromValue = fromValue;
|
||||
self.needsRecalculation = YES;
|
||||
}
|
||||
|
||||
- (void)setStiffness:(CGFloat)stiffness {
|
||||
_stiffness = stiffness;
|
||||
self.needsRecalculation = YES;
|
||||
}
|
||||
|
||||
- (void)setMass:(CGFloat)mass {
|
||||
_mass = mass;
|
||||
self.needsRecalculation = YES;
|
||||
}
|
||||
|
||||
- (NSArray *)values {
|
||||
return self.interpolatedValues;
|
||||
}
|
||||
|
||||
- (void)setDamping:(CGFloat)damping {
|
||||
if (damping <= 0) {
|
||||
NSLog(@"[%@] LOGIC ERROR. `damping` should be > 0.0f to avoid an infinite spring calculation", NSStringFromClass([self class]));
|
||||
damping = 1.0f;
|
||||
}
|
||||
_damping = damping;
|
||||
self.needsRecalculation = YES;
|
||||
}
|
||||
|
||||
- (CFTimeInterval)duration {
|
||||
if (self.fromValue != nil && self.toValue != nil) {
|
||||
return self.interpolatedValues.count * JNWSpringAnimationKeyframeStep * TGAnimationSpeedFactor() * _durationFactor;
|
||||
}
|
||||
|
||||
return 0.f;
|
||||
}
|
||||
|
||||
- (NSArray *)interpolatedValues {
|
||||
if (self.needsRecalculation || _interpolatedValues == nil) {
|
||||
[self calculateInterpolatedValues];
|
||||
}
|
||||
|
||||
return _interpolatedValues;
|
||||
}
|
||||
|
||||
#pragma mark Interpolation
|
||||
|
||||
- (void)calculateInterpolatedValues {
|
||||
NSAssert(self.fromValue != nil && self.toValue != nil, @"fromValue and or toValue must not be nil.");
|
||||
|
||||
JNWValueType fromType = [self.fromValue jnw_type];
|
||||
JNWValueType toType = [self.toValue jnw_type];
|
||||
NSAssert(fromType == toType, @"fromValue and toValue must be of the same type.");
|
||||
NSAssert(fromType != JNWValueTypeUnknown, @"Type of value could not be determined. Please ensure the value types are supported.");
|
||||
|
||||
NSArray *values = nil;
|
||||
|
||||
if (fromType == JNWValueTypeNumber) {
|
||||
values = [self valuesFromNumbers:@[self.fromValue] toNumbers:@[self.toValue] map:^id(CGFloat *values, NSUInteger __unused count) {
|
||||
return @(values[0]);
|
||||
}];
|
||||
} else if (fromType == JNWValueTypePoint) {
|
||||
CGPoint f = [self.fromValue jnw_pointValue];
|
||||
CGPoint t = [self.toValue jnw_pointValue];
|
||||
values = [self valuesFromNumbers:@[@(f.x), @(f.y)]
|
||||
toNumbers:@[@(t.x), @(t.y)]
|
||||
map:^id(CGFloat *values, __unused NSUInteger count) {
|
||||
return [NSValue jnw_valueWithPoint:CGPointMake(values[0], values[1])];
|
||||
}];
|
||||
} else if (fromType == JNWValueTypeSize) {
|
||||
CGSize f = [self.fromValue jnw_sizeValue];
|
||||
CGSize t = [self.toValue jnw_sizeValue];
|
||||
values = [self valuesFromNumbers:@[@(f.width), @(f.height)]
|
||||
toNumbers:@[@(t.width), @(t.height)]
|
||||
map:^id(CGFloat *values, __unused NSUInteger count) {
|
||||
return [NSValue jnw_valueWithSize:CGSizeMake(values[0], values[1])];
|
||||
}];
|
||||
} else if (fromType == JNWValueTypeRect) { // note that CA will not animate the `frame` property
|
||||
CGRect f = [self.fromValue jnw_rectValue];
|
||||
CGRect t = [self.toValue jnw_rectValue];
|
||||
values = [self valuesFromNumbers:@[@(f.origin.x), @(f.origin.y), @(f.size.width), @(f.size.height)]
|
||||
toNumbers:@[@(t.origin.x), @(t.origin.y), @(t.size.width), @(t.size.height)]
|
||||
map:^id(CGFloat *values, __unused NSUInteger count) {
|
||||
return [NSValue jnw_valueWithRect:CGRectMake(values[0], values[1], values[2], values[3])];
|
||||
}];
|
||||
} else if (fromType == JNWValueTypeAffineTransform) {
|
||||
CGAffineTransform f = [self.fromValue jnw_affineTransformValue];
|
||||
CGAffineTransform t = [self.toValue jnw_affineTransformValue];
|
||||
|
||||
values = [self valuesFromNumbers:@[@(f.a), @(f.b), @(f.c), @(f.d), @(f.tx), @(f.ty)]
|
||||
toNumbers:@[@(t.a), @(t.b), @(t.c), @(t.d), @(t.tx), @(t.ty)]
|
||||
map:^id(CGFloat *values, __unused NSUInteger count) {
|
||||
CGAffineTransform transform;
|
||||
transform.a = values[0];
|
||||
transform.b = values[1];
|
||||
transform.c = values[2];
|
||||
transform.d = values[3];
|
||||
transform.tx = values[4];
|
||||
transform.ty = values[5];
|
||||
|
||||
return [NSValue jnw_valueWithAffineTransform:transform];
|
||||
}];
|
||||
} else if (fromType == JNWValueTypeTransform3D) {
|
||||
CATransform3D f = [self.fromValue CATransform3DValue];
|
||||
CATransform3D t = [self.toValue CATransform3DValue];
|
||||
|
||||
values = [self valuesFromNumbers:@[@(f.m11), @(f.m12), @(f.m13), @(f.m14), @(f.m21), @(f.m22), @(f.m23), @(f.m24), @(f.m31), @(f.m32), @(f.m33), @(f.m34), @(f.m41), @(f.m42), @(f.m43), @(f.m44) ]
|
||||
toNumbers:@[@(t.m11), @(t.m12), @(t.m13), @(t.m14), @(t.m21), @(t.m22), @(t.m23), @(t.m24), @(t.m31), @(t.m32), @(t.m33), @(t.m34), @(t.m41), @(t.m42), @(t.m43), @(t.m44) ]
|
||||
map:^id(CGFloat *values, __unused NSUInteger count) {
|
||||
CATransform3D transform = CATransform3DIdentity;
|
||||
transform.m11 = values[0];
|
||||
transform.m12 = values[1];
|
||||
transform.m13 = values[2];
|
||||
transform.m14 = values[3];
|
||||
transform.m21 = values[4];
|
||||
transform.m22 = values[5];
|
||||
transform.m23 = values[6];
|
||||
transform.m24 = values[7];
|
||||
transform.m31 = values[8];
|
||||
transform.m32 = values[9];
|
||||
transform.m33 = values[10];
|
||||
transform.m34 = values[11];
|
||||
transform.m41 = values[12];
|
||||
transform.m42 = values[13];
|
||||
transform.m43 = values[14];
|
||||
transform.m44 = values[15];
|
||||
|
||||
return [NSValue valueWithCATransform3D:transform];
|
||||
}];
|
||||
}
|
||||
|
||||
self.interpolatedValues = values;
|
||||
self.needsRecalculation = NO;
|
||||
}
|
||||
|
||||
- (NSArray *)valuesFromNumbers:(NSArray *)fromNumbers toNumbers:(NSArray *)toNumbers map:(id (^)(CGFloat *values, NSUInteger count))map {
|
||||
NSAssert(fromNumbers.count == toNumbers.count, @"count of from and to numbers must be equal");
|
||||
|
||||
NSUInteger count = fromNumbers.count;
|
||||
|
||||
// This will never happen, but this is peformed in order to shush the analyzer.
|
||||
if (count < 1) {
|
||||
return [NSArray array];
|
||||
}
|
||||
|
||||
CGFloat *distances = calloc(count, sizeof(CGFloat));
|
||||
CGFloat *thresholds = calloc(count, sizeof(CGFloat));
|
||||
for (NSUInteger i = 0; i < count; i++) {
|
||||
distances[i] = [toNumbers[i] floatValue] - [fromNumbers[i] floatValue];
|
||||
thresholds[i] = JNWSpringAnimationThreshold(ABS(distances[i]));
|
||||
}
|
||||
|
||||
CFTimeInterval step = JNWSpringAnimationKeyframeStep;
|
||||
CFTimeInterval elapsed = 0;
|
||||
|
||||
CGFloat *stepValues = calloc(count, sizeof(CGFloat));
|
||||
CGFloat *stepProposedValues = calloc(count, sizeof(CGFloat));
|
||||
|
||||
NSMutableArray *valuesMapped = [NSMutableArray array];
|
||||
while (YES) {
|
||||
BOOL thresholdReached = YES;
|
||||
|
||||
for (NSUInteger i = 0; i < count; i++) {
|
||||
stepProposedValues[i] = JNWAbsolutePosition(distances[i], (CGFloat)elapsed, 0, self.damping, self.mass, self.stiffness, [fromNumbers[i] floatValue]);
|
||||
|
||||
if (thresholdReached)
|
||||
thresholdReached = JNWThresholdReached(stepValues[i], stepProposedValues[i], [toNumbers[i] floatValue], thresholds[i]);
|
||||
}
|
||||
|
||||
if (thresholdReached)
|
||||
break;
|
||||
|
||||
for (NSUInteger i = 0; i < count; i++) {
|
||||
stepValues[i] = stepProposedValues[i];
|
||||
}
|
||||
|
||||
[valuesMapped addObject:map(stepValues, count)];
|
||||
elapsed += step;
|
||||
}
|
||||
|
||||
free(distances);
|
||||
free(thresholds);
|
||||
free(stepValues);
|
||||
free(stepProposedValues);
|
||||
|
||||
return valuesMapped;
|
||||
}
|
||||
|
||||
BOOL JNWThresholdReached(CGFloat previousValue, CGFloat proposedValue, CGFloat finalValue, CGFloat threshold) {
|
||||
CGFloat previousDifference = ABS(proposedValue - previousValue);
|
||||
CGFloat finalDifference = ABS(previousValue - finalValue);
|
||||
if (previousDifference <= threshold && finalDifference <= threshold) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
BOOL JNWCalculationsAreComplete(CGFloat value1, CGFloat proposedValue1, CGFloat to1, CGFloat value2, CGFloat proposedValue2, CGFloat to2, CGFloat value3, CGFloat proposedValue3, CGFloat to3) {
|
||||
return ((fabs(proposedValue1 - value1) < JNWSpringAnimationKeyframeStep) && (fabs(value1 - to1) < JNWSpringAnimationKeyframeStep)
|
||||
&& (fabs(proposedValue2 - value2) < JNWSpringAnimationKeyframeStep) && (fabs(value2 - to2) < JNWSpringAnimationKeyframeStep)
|
||||
&& (fabs(proposedValue3 - value3) < JNWSpringAnimationKeyframeStep) && (fabs(value3 - to3) < JNWSpringAnimationKeyframeStep));
|
||||
}
|
||||
|
||||
#pragma mark Damped Harmonic Oscillation
|
||||
|
||||
|
||||
CGFloat JNWAngularFrequency(CGFloat k, CGFloat m, CGFloat b) {
|
||||
CGFloat w0 = (CGFloat)sqrt(k / m);
|
||||
CGFloat frequency = (CGFloat)sqrt(pow(w0, 2) - (pow(b, 2) / (4*pow(m, 2))));
|
||||
if (isnan(frequency)) frequency = 0;
|
||||
return frequency;
|
||||
}
|
||||
|
||||
CGFloat JNWRelativePosition(CGFloat A, CGFloat t, CGFloat phi, CGFloat b, CGFloat m, CGFloat k) {
|
||||
if (A == 0) return A;
|
||||
CGFloat ex = (-b / (2 * m) * t);
|
||||
CGFloat freq = JNWAngularFrequency(k, m, b);
|
||||
return (CGFloat)(A * exp(ex) * cos(freq * t + phi));
|
||||
}
|
||||
|
||||
CGFloat JNWAbsolutePosition(CGFloat A, CGFloat t, CGFloat phi, CGFloat b, CGFloat m, CGFloat k, CGFloat from) {
|
||||
return from + A - JNWRelativePosition(A, t, phi, b, m, k);
|
||||
}
|
||||
|
||||
// This feels a bit hacky. I'm sure there's a better way to accomplish this.
|
||||
CGFloat JNWSpringAnimationThreshold(CGFloat magnitude) {
|
||||
return JNWSpringAnimationMinimumThreshold * magnitude;
|
||||
}
|
||||
|
||||
#pragma mark Description
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"<%@: %p> mass: %f, damping: %f, stiffness: %f, keyPath: %@, toValue: %@, fromValue %@", self.class, self, self.mass, self.damping, self.stiffness, self.keyPath, self.toValue, self.fromValue];
|
||||
}
|
||||
|
||||
@end
|
||||
341
submodules/LegacyComponents/LegacyComponents/LegacyComponents.h
Normal file
341
submodules/LegacyComponents/LegacyComponents/LegacyComponents.h
Normal file
|
|
@ -0,0 +1,341 @@
|
|||
#import <UIKit/UIKit.h>
|
||||
|
||||
//! Project version number for LegacyComponents.
|
||||
FOUNDATION_EXPORT double LegacyComponentsVersionNumber;
|
||||
|
||||
//! Project version string for LegacyComponents.
|
||||
FOUNDATION_EXPORT const unsigned char LegacyComponentsVersionString[];
|
||||
|
||||
|
||||
#import <LegacyComponents/LegacyComponentsGlobals.h>
|
||||
#import <LegacyComponents/LegacyComponentsContext.h>
|
||||
#import <LegacyComponents/TGLocalization.h>
|
||||
#import <LegacyComponents/TGPluralization.h>
|
||||
#import <LegacyComponents/TGStringUtils.h>
|
||||
#import <LegacyComponents/TGPhoneUtils.h>
|
||||
#import <LegacyComponents/NSObject+TGLock.h>
|
||||
#import <LegacyComponents/RMPhoneFormat.h>
|
||||
#import <LegacyComponents/NSInputStream+TL.h>
|
||||
#import <LegacyComponents/TGFont.h>
|
||||
#import <LegacyComponents/TGImageUtils.h>
|
||||
#import <LegacyComponents/TGDateUtils.h>
|
||||
#import <LegacyComponents/Freedom.h>
|
||||
#import <LegacyComponents/FreedomUIKit.h>
|
||||
#import <LegacyComponents/TGHacks.h>
|
||||
#import <LegacyComponents/TGImageBlur.h>
|
||||
#import <LegacyComponents/UIDevice+PlatformInfo.h>
|
||||
#import <LegacyComponents/TGObserverProxy.h>
|
||||
#import <LegacyComponents/TGModernCache.h>
|
||||
#import <LegacyComponents/TGMemoryImageCache.h>
|
||||
#import <LegacyComponents/LegacyComponentsAccessChecker.h>
|
||||
#import <LegacyComponents/TGTimerTarget.h>
|
||||
#import <LegacyComponents/TGKeyCommand.h>
|
||||
#import <LegacyComponents/TGKeyCommandController.h>
|
||||
#import <LegacyComponents/TGWeakDelegate.h>
|
||||
#import <LegacyComponents/TGCache.h>
|
||||
#import <LegacyComponents/TGLiveUploadInterface.h>
|
||||
|
||||
#import <LegacyComponents/JNWSpringAnimation.h>
|
||||
#import <LegacyComponents/POPAnimationEvent.h>
|
||||
#import <LegacyComponents/POPAnimationTracer.h>
|
||||
#import <LegacyComponents/POPAnimation.h>
|
||||
#import <LegacyComponents/POPBasicAnimation.h>
|
||||
#import <LegacyComponents/POPCustomAnimation.h>
|
||||
#import <LegacyComponents/POPDecayAnimation.h>
|
||||
#import <LegacyComponents/POPPropertyAnimation.h>
|
||||
#import <LegacyComponents/POPSpringAnimation.h>
|
||||
|
||||
#import <LegacyComponents/lmdb.h>
|
||||
#import <LegacyComponents/PSLMDBTable.h>
|
||||
#import <LegacyComponents/PSLMDBKeyValueStore.h>
|
||||
#import <LegacyComponents/PSLMDBKeyValueReaderWriter.h>
|
||||
#import <LegacyComponents/PSLMDBKeyValueCursor.h>
|
||||
|
||||
#import <LegacyComponents/PSCoding.h>
|
||||
#import <LegacyComponents/PSData.h>
|
||||
#import <LegacyComponents/PSKeyValueCoder.h>
|
||||
#import <LegacyComponents/PSKeyValueDecoder.h>
|
||||
#import <LegacyComponents/PSKeyValueEncoder.h>
|
||||
#import <LegacyComponents/PSKeyValueReader.h>
|
||||
#import <LegacyComponents/PSKeyValueStore.h>
|
||||
#import <LegacyComponents/PSKeyValueWriter.h>
|
||||
|
||||
#import <LegacyComponents/TGPeerIdAdapter.h>
|
||||
#import <LegacyComponents/TGUser.h>
|
||||
#import <LegacyComponents/TGBotInfo.h>
|
||||
#import <LegacyComponents/TGBotComandInfo.h>
|
||||
#import <LegacyComponents/TGConversation.h>
|
||||
|
||||
#import <LegacyComponents/TGModernConversationAssociatedInputPanel.h>
|
||||
#import <LegacyComponents/TGModernConversationHashtagsAssociatedPanel.h>
|
||||
#import <LegacyComponents/TGModernConversationMentionsAssociatedPanel.h>
|
||||
#import <LegacyComponents/TGModernConversationAlphacodeAssociatedPanel.h>
|
||||
#import <LegacyComponents/TGSuggestionContext.h>
|
||||
#import <LegacyComponents/TGAlphacode.h>
|
||||
|
||||
#import <LegacyComponents/TGTextCheckingResult.h>
|
||||
#import <LegacyComponents/TGChannelBannedRights.h>
|
||||
#import <LegacyComponents/TGChannelAdminRights.h>
|
||||
#import <LegacyComponents/TGDatabaseMessageDraft.h>
|
||||
#import <LegacyComponents/TGMessageGroup.h>
|
||||
#import <LegacyComponents/TGMessageHole.h>
|
||||
#import <LegacyComponents/TGMessageViewCountContentProperty.h>
|
||||
#import <LegacyComponents/TGAuthorSignatureMediaAttachment.h>
|
||||
#import <LegacyComponents/TGWebDocument.h>
|
||||
#import <LegacyComponents/TGInvoiceMediaAttachment.h>
|
||||
#import <LegacyComponents/TGGameMediaAttachment.h>
|
||||
#import <LegacyComponents/TGViaUserAttachment.h>
|
||||
#import <LegacyComponents/TGBotContextResultAttachment.h>
|
||||
#import <LegacyComponents/TGMessageEntity.h>
|
||||
#import <LegacyComponents/TGMessageEntityBold.h>
|
||||
#import <LegacyComponents/TGMessageEntityBotCommand.h>
|
||||
#import <LegacyComponents/TGMessageEntityCode.h>
|
||||
#import <LegacyComponents/TGMessageEntityEmail.h>
|
||||
#import <LegacyComponents/TGMessageEntityHashtag.h>
|
||||
#import <LegacyComponents/TGMessageEntityItalic.h>
|
||||
#import <LegacyComponents/TGMessageEntityMention.h>
|
||||
#import <LegacyComponents/TGMessageEntityMentionName.h>
|
||||
#import <LegacyComponents/TGMessageEntityPre.h>
|
||||
#import <LegacyComponents/TGMessageEntityTextUrl.h>
|
||||
#import <LegacyComponents/TGMessageEntityUrl.h>
|
||||
#import <LegacyComponents/TGMessageEntitiesAttachment.h>
|
||||
#import <LegacyComponents/TGBotReplyMarkup.h>
|
||||
#import <LegacyComponents/TGBotReplyMarkupButton.h>
|
||||
#import <LegacyComponents/TGBotReplyMarkupRow.h>
|
||||
#import <LegacyComponents/TGReplyMarkupAttachment.h>
|
||||
#import <LegacyComponents/TGInstantPage.h>
|
||||
#import <LegacyComponents/TGWebPageMediaAttachment.h>
|
||||
#import <LegacyComponents/TGAudioMediaAttachment.h>
|
||||
#import <LegacyComponents/TGAudioWaveform.h>
|
||||
#import <LegacyComponents/TGStickerPackReference.h>
|
||||
#import <LegacyComponents/TGDocumentAttributeFilename.h>
|
||||
#import <LegacyComponents/TGDocumentAttributeImageSize.h>
|
||||
#import <LegacyComponents/TGDocumentAttributeSticker.h>
|
||||
#import <LegacyComponents/TGDocumentAttributeVideo.h>
|
||||
#import <LegacyComponents/TGDocumentAttributeAnimated.h>
|
||||
#import <LegacyComponents/TGDocumentAttributeAudio.h>
|
||||
#import <LegacyComponents/TGDocumentMediaAttachment.h>
|
||||
#import <LegacyComponents/TGUnsupportedMediaAttachment.h>
|
||||
#import <LegacyComponents/TGForwardedMessageMediaAttachment.h>
|
||||
#import <LegacyComponents/TGContactMediaAttachment.h>
|
||||
#import <LegacyComponents/TGVideoInfo.h>
|
||||
#import <LegacyComponents/TGVideoMediaAttachment.h>
|
||||
#import <LegacyComponents/TGLocalMessageMetaMediaAttachment.h>
|
||||
#import <LegacyComponents/TGLocationMediaAttachment.h>
|
||||
#import <LegacyComponents/TGImageMediaAttachment.h>
|
||||
#import <LegacyComponents/TGMediaAttachment.h>
|
||||
#import <LegacyComponents/TGImageInfo.h>
|
||||
#import <LegacyComponents/TGMediaOriginInfo.h>
|
||||
#import <LegacyComponents/TGMessage.h>
|
||||
#import <LegacyComponents/TGStickerPack.h>
|
||||
#import <LegacyComponents/TGStickerAssociation.h>
|
||||
#import <LegacyComponents/TGPhotoMaskPosition.h>
|
||||
|
||||
#import <LegacyComponents/ActionStage.h>
|
||||
#import <LegacyComponents/ASActor.h>
|
||||
#import <LegacyComponents/ASHandle.h>
|
||||
#import <LegacyComponents/ASQueue.h>
|
||||
#import <LegacyComponents/ASWatcher.h>
|
||||
#import <LegacyComponents/SGraphListNode.h>
|
||||
#import <LegacyComponents/SGraphNode.h>
|
||||
#import <LegacyComponents/SGraphObjectNode.h>
|
||||
|
||||
#import <LegacyComponents/TGLabel.h>
|
||||
#import <LegacyComponents/TGToolbarButton.h>
|
||||
#import <LegacyComponents/UIScrollView+TGHacks.h>
|
||||
#import <LegacyComponents/TGAnimationBlockDelegate.h>
|
||||
#import <LegacyComponents/TGBackdropView.h>
|
||||
#import <LegacyComponents/UIImage+TG.h>
|
||||
#import <LegacyComponents/TGStaticBackdropAreaData.h>
|
||||
#import <LegacyComponents/TGStaticBackdropImageData.h>
|
||||
#import <LegacyComponents/TGImageLuminanceMap.h>
|
||||
#import <LegacyComponents/TGFullscreenContainerView.h>
|
||||
#import <LegacyComponents/TGDoubleTapGestureRecognizer.h>
|
||||
#import <LegacyComponents/TGModernButton.h>
|
||||
#import <LegacyComponents/TGModernToolbarButton.h>
|
||||
#import <LegacyComponents/TGModernBackToolbarButton.h>
|
||||
#import <LegacyComponents/UIControl+HitTestEdgeInsets.h>
|
||||
#import <LegacyComponents/TGMenuView.h>
|
||||
#import <LegacyComponents/TGImageView.h>
|
||||
#import <LegacyComponents/UICollectionView+Utils.h>
|
||||
#import <LegacyComponents/TGMessageImageViewOverlayView.h>
|
||||
#import <LegacyComponents/TGLetteredAvatarView.h>
|
||||
#import <LegacyComponents/TGGradientLabel.h>
|
||||
#import <LegacyComponents/TGRemoteImageView.h>
|
||||
|
||||
#import <LegacyComponents/TGProgressSpinnerView.h>
|
||||
#import <LegacyComponents/TGProgressWindow.h>
|
||||
|
||||
#import <LegacyComponents/TGMenuSheetController.h>
|
||||
#import <LegacyComponents/TGMenuSheetButtonItemView.h>
|
||||
#import <LegacyComponents/TGMenuSheetCollectionView.h>
|
||||
#import <LegacyComponents/TGMenuSheetItemView.h>
|
||||
#import <LegacyComponents/TGMenuSheetTitleItemView.h>
|
||||
#import <LegacyComponents/TGMenuSheetView.h>
|
||||
|
||||
#import <LegacyComponents/HPGrowingTextView.h>
|
||||
#import <LegacyComponents/HPTextViewInternal.h>
|
||||
#import <LegacyComponents/TGInputTextTag.h>
|
||||
|
||||
#import <LegacyComponents/TGStickerKeyboardTabPanel.h>
|
||||
|
||||
#import <LegacyComponents/TGItemPreviewController.h>
|
||||
#import <LegacyComponents/TGItemPreviewView.h>
|
||||
#import <LegacyComponents/TGItemMenuSheetPreviewView.h>
|
||||
|
||||
#import <LegacyComponents/TGImageManager.h>
|
||||
#import <LegacyComponents/TGDataResource.h>
|
||||
#import <LegacyComponents/TGImageDataSource.h>
|
||||
#import <LegacyComponents/TGImageManagerTask.h>
|
||||
|
||||
#import <LegacyComponents/TGRTLScreenEdgePanGestureRecognizer.h>
|
||||
#import <LegacyComponents/TGPopoverController.h>
|
||||
#import <LegacyComponents/TGNavigationController.h>
|
||||
#import <LegacyComponents/TGNavigationBar.h>
|
||||
#import <LegacyComponents/TGViewController.h>
|
||||
#import <LegacyComponents/TGViewController+TGRecursiveEnumeration.h>
|
||||
#import <LegacyComponents/TGOverlayController.h>
|
||||
#import <LegacyComponents/TGOverlayControllerWindow.h>
|
||||
|
||||
#import <LegacyComponents/TGMediaAssetsLibrary.h>
|
||||
#import <LegacyComponents/TGMediaAssetsModernLibrary.h>
|
||||
#import <LegacyComponents/TGMediaAsset.h>
|
||||
#import <LegacyComponents/TGMediaAssetFetchResult.h>
|
||||
#import <LegacyComponents/TGMediaAssetFetchResultChange.h>
|
||||
#import <LegacyComponents/TGMediaAssetGroup.h>
|
||||
#import <LegacyComponents/TGMediaAssetMoment.h>
|
||||
#import <LegacyComponents/TGMediaAssetMomentList.h>
|
||||
#import <LegacyComponents/TGMediaAssetImageSignals.h>
|
||||
#import <LegacyComponents/TGMediaSelectionContext.h>
|
||||
#import <LegacyComponents/TGMediaEditingContext.h>
|
||||
|
||||
#import <LegacyComponents/TGModernGalleryZoomableItemViewContent.h>
|
||||
#import <LegacyComponents/TGModernGalleryZoomableScrollView.h>
|
||||
#import <LegacyComponents/TGModernGalleryZoomableScrollViewSwipeGestureRecognizer.h>
|
||||
#import <LegacyComponents/TGModernGalleryVideoView.h>
|
||||
#import <LegacyComponents/TGModernGalleryScrollView.h>
|
||||
#import <LegacyComponents/TGModernGalleryItem.h>
|
||||
#import <LegacyComponents/TGModernGalleryItemView.h>
|
||||
#import <LegacyComponents/TGModernGalleryImageItem.h>
|
||||
#import <LegacyComponents/TGModernGalleryImageItemView.h>
|
||||
#import <LegacyComponents/TGModernGalleryImageItemImageView.h>
|
||||
#import <LegacyComponents/TGModernGalleryEditableItemView.h>
|
||||
#import <LegacyComponents/TGModernGallerySelectableItem.h>
|
||||
#import <LegacyComponents/TGModernGalleryDefaultFooterAccessoryView.h>
|
||||
#import <LegacyComponents/TGModernGalleryDefaultFooterView.h>
|
||||
#import <LegacyComponents/TGModernGalleryDefaultHeaderView.h>
|
||||
#import <LegacyComponents/TGModernGalleryDefaultInterfaceView.h>
|
||||
#import <LegacyComponents/TGModernGalleryInterfaceView.h>
|
||||
#import <LegacyComponents/TGModernGalleryImageItemContainerView.h>
|
||||
#import <LegacyComponents/TGModernGalleryZoomableItemView.h>
|
||||
#import <LegacyComponents/TGModernGalleryModel.h>
|
||||
#import <LegacyComponents/TGModernGalleryTransitionView.h>
|
||||
#import <LegacyComponents/TGModernGalleryView.h>
|
||||
#import <LegacyComponents/TGModernGalleryContainerView.h>
|
||||
#import <LegacyComponents/TGModernGalleryEmbeddedStickersHeaderView.h>
|
||||
#import <LegacyComponents/TGModernGalleryController.h>
|
||||
|
||||
#import <LegacyComponents/TGPhotoToolbarView.h>
|
||||
#import <LegacyComponents/TGMediaPickerGalleryModel.h>
|
||||
#import <LegacyComponents/TGMediaPickerGalleryInterfaceView.h>
|
||||
#import <LegacyComponents/TGPhotoEditorController.h>
|
||||
#import <LegacyComponents/TGMediaAvatarEditorTransition.h>
|
||||
|
||||
#import <LegacyComponents/TGPhotoEditorUtils.h>
|
||||
#import <LegacyComponents/PGPhotoEditorValues.h>
|
||||
#import <LegacyComponents/TGVideoEditAdjustments.h>
|
||||
#import <LegacyComponents/AVURLAsset+TGMediaItem.h>
|
||||
#import <LegacyComponents/UIImage+TGMediaEditableItem.h>
|
||||
#import <LegacyComponents/TGMediaVideoConverter.h>
|
||||
#import <LegacyComponents/TGGifConverter.h>
|
||||
|
||||
#import <LegacyComponents/TGPhotoEditorAnimation.h>
|
||||
#import <LegacyComponents/TGPhotoToolbarView.h>
|
||||
|
||||
#import <LegacyComponents/TGPaintingData.h>
|
||||
#import <LegacyComponents/TGPaintUtils.h>
|
||||
#import <LegacyComponents/TGPhotoPaintEntity.h>
|
||||
#import <LegacyComponents/TGPhotoPaintStickerEntity.h>
|
||||
#import <LegacyComponents/TGPaintUndoManager.h>
|
||||
|
||||
#import <LegacyComponents/PGCamera.h>
|
||||
#import <LegacyComponents/PGCameraCaptureSession.h>
|
||||
#import <LegacyComponents/PGCameraDeviceAngleSampler.h>
|
||||
#import <LegacyComponents/PGCameraMomentSegment.h>
|
||||
#import <LegacyComponents/PGCameraMomentSession.h>
|
||||
#import <LegacyComponents/PGCameraMovieWriter.h>
|
||||
#import <LegacyComponents/PGCameraShotMetadata.h>
|
||||
#import <LegacyComponents/PGCameraVolumeButtonHandler.h>
|
||||
#import <LegacyComponents/TGCameraPreviewView.h>
|
||||
#import <LegacyComponents/TGCameraMainPhoneView.h>
|
||||
#import <LegacyComponents/TGCameraMainTabletView.h>
|
||||
#import <LegacyComponents/TGCameraMainView.h>
|
||||
#import <LegacyComponents/TGCameraFlashActiveView.h>
|
||||
#import <LegacyComponents/TGCameraFlashControl.h>
|
||||
#import <LegacyComponents/TGCameraFlipButton.h>
|
||||
#import <LegacyComponents/TGCameraInterfaceAssets.h>
|
||||
#import <LegacyComponents/TGCameraModeControl.h>
|
||||
#import <LegacyComponents/TGCameraSegmentsView.h>
|
||||
#import <LegacyComponents/TGCameraShutterButton.h>
|
||||
#import <LegacyComponents/TGCameraTimeCodeView.h>
|
||||
#import <LegacyComponents/TGCameraZoomView.h>
|
||||
#import <LegacyComponents/TGCameraPhotoPreviewController.h>
|
||||
#import <LegacyComponents/TGCameraController.h>
|
||||
#import <LegacyComponents/TGCameraCapturedPhoto.h>
|
||||
#import <LegacyComponents/TGCameraCapturedVideo.h>
|
||||
#import <LegacyComponents/TGPhotoVideoEditor.h>
|
||||
|
||||
#import <LegacyComponents/TGModernConversationTitleActivityIndicator.h>
|
||||
#import <LegacyComponents/TGEmbedPIPButton.h>
|
||||
#import <LegacyComponents/TGEmbedPIPPullArrowView.h>
|
||||
#import <LegacyComponents/TGEmbedPlayerState.h>
|
||||
#import <LegacyComponents/TGAttachmentCameraView.h>
|
||||
#import <LegacyComponents/TGMediaAvatarMenuMixin.h>
|
||||
#import <LegacyComponents/TGPassportAttachMenu.h>
|
||||
#import <LegacyComponents/TGPassportScanController.h>
|
||||
#import <LegacyComponents/TGPassportOCR.h>
|
||||
#import <LegacyComponents/TGPassportMRZ.h>
|
||||
#import <LegacyComponents/TGPasscodeEntryController.h>
|
||||
#import <LegacyComponents/TGPassportICloud.h>
|
||||
#import <LegacyComponents/TGEmbedPlayerView.h>
|
||||
#import <LegacyComponents/TGWallpaperInfo.h>
|
||||
#import <LegacyComponents/TGMemoryImageCache.h>
|
||||
#import <LegacyComponents/LegacyHTTPRequestOperation.h>
|
||||
#import <LegacyComponents/LegacyComponentsAccessChecker.h>
|
||||
|
||||
#import <LegacyComponents/TGAttachmentCarouselItemView.h>
|
||||
#import <LegacyComponents/TGMediaAssetsController.h>
|
||||
|
||||
#import <LegacyComponents/TGLocationVenue.h>
|
||||
#import <LegacyComponents/TGLocationMapViewController.h>
|
||||
#import <LegacyComponents/TGLocationPickerController.h>
|
||||
#import <LegacyComponents/TGLocationViewController.h>
|
||||
#import <LegacyComponents/TGListsTableView.h>
|
||||
#import <LegacyComponents/TGSearchBar.h>
|
||||
#import <LegacyComponents/TGSearchDisplayMixin.h>
|
||||
|
||||
#import <LegacyComponents/TGPhotoEditorSliderView.h>
|
||||
|
||||
#import <LegacyComponents/TGClipboardGalleryMixin.h>
|
||||
#import <LegacyComponents/TGClipboardGalleryPhotoItem.h>
|
||||
#import <LegacyComponents/TGVideoMessageCaptureController.h>
|
||||
#import <LegacyComponents/TGModernConversationInputMicButton.h>
|
||||
|
||||
#import <LegacyComponents/TGLocationPulseView.h>
|
||||
#import <LegacyComponents/TGLocationWavesView.h>
|
||||
#import <LegacyComponents/TGLocationLiveElapsedView.h>
|
||||
#import <LegacyComponents/TGLocationLiveSessionItemView.h>
|
||||
|
||||
#import <LegacyComponents/TGTooltipView.h>
|
||||
|
||||
#import <LegacyComponents/TGCheckButtonView.h>
|
||||
#import <LegacyComponents/TGBuiltinWallpaperInfo.h>
|
||||
#import <LegacyComponents/TGRemoteWallpaperInfo.h>
|
||||
#import <LegacyComponents/TGColorWallpaperInfo.h>
|
||||
#import <LegacyComponents/TGCustomImageWallpaperInfo.h>
|
||||
#import <LegacyComponents/TGClipboardMenu.h>
|
||||
#import <LegacyComponents/TGImagePickerController.h>
|
||||
#import <LegacyComponents/TGLegacyCameraController.h>
|
||||
#import <LegacyComponents/TGWallpaperController.h>
|
||||
#import <LegacyComponents/TGProxyWindow.h>
|
||||
#import <LegacyComponents/TGIconSwitchView.h>
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef enum {
|
||||
TGPhotoAccessIntentRead,
|
||||
TGPhotoAccessIntentSave,
|
||||
TGPhotoAccessIntentCustomWallpaper
|
||||
} TGPhotoAccessIntent;
|
||||
|
||||
typedef enum {
|
||||
TGMicrophoneAccessIntentVoice,
|
||||
TGMicrophoneAccessIntentVideo,
|
||||
TGMicrophoneAccessIntentCall,
|
||||
TGMicrophoneAccessIntentVideoMessage
|
||||
} TGMicrophoneAccessIntent;
|
||||
|
||||
typedef enum {
|
||||
TGCameraAccessIntentDefault,
|
||||
TGCameraAccessIntentVideoMessage
|
||||
} TGCameraAccessIntent;
|
||||
|
||||
typedef enum {
|
||||
TGLocationAccessIntentSend,
|
||||
TGLocationAccessIntentTracking,
|
||||
TGLocationAccessIntentLiveLocation
|
||||
} TGLocationAccessIntent;
|
||||
|
||||
@protocol LegacyComponentsAccessChecker <NSObject>
|
||||
|
||||
- (bool)checkAddressBookAuthorizationStatusWithAlertDismissComlpetion:(void (^)(void))alertDismissCompletion;
|
||||
|
||||
- (bool)checkPhotoAuthorizationStatusForIntent:(TGPhotoAccessIntent)intent alertDismissCompletion:(void (^)(void))alertDismissCompletion;
|
||||
|
||||
- (bool)checkMicrophoneAuthorizationStatusForIntent:(TGMicrophoneAccessIntent)intent alertDismissCompletion:(void (^)(void))alertDismissCompletion;
|
||||
|
||||
- (bool)checkCameraAuthorizationStatusForIntent:(TGCameraAccessIntent)intent alertDismissCompletion:(void (^)(void))alertDismissCompletion;
|
||||
|
||||
- (bool)checkLocationAuthorizationStatusForIntent:(TGLocationAccessIntent)intent alertDismissComlpetion:(void (^)(void))alertDismissCompletion;
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class TGKeyCommandController;
|
||||
@class SSignal;
|
||||
@class TGOverlayControllerWindow;
|
||||
|
||||
typedef enum {
|
||||
LegacyComponentsActionSheetActionTypeGeneric,
|
||||
LegacyComponentsActionSheetActionTypeDestructive,
|
||||
LegacyComponentsActionSheetActionTypeCancel
|
||||
} LegacyComponentsActionSheetActionType;
|
||||
|
||||
@interface LegacyComponentsActionSheetAction : NSObject
|
||||
|
||||
@property (nonatomic, strong, readonly) NSString *title;
|
||||
@property (nonatomic, strong, readonly) NSString *action;
|
||||
@property (nonatomic, readonly) LegacyComponentsActionSheetActionType type;
|
||||
|
||||
- (instancetype)initWithTitle:(NSString *)title action:(NSString *)action;
|
||||
- (instancetype)initWithTitle:(NSString *)title action:(NSString *)action type:(LegacyComponentsActionSheetActionType)type;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@protocol LegacyComponentsContext;
|
||||
|
||||
@protocol LegacyComponentsOverlayWindowManager <NSObject>
|
||||
|
||||
- (id<LegacyComponentsContext>)context;
|
||||
- (void)bindController:(UIViewController *)controller;
|
||||
- (bool)managesWindow;
|
||||
- (void)setHidden:(bool)hidden window:(UIWindow *)window;
|
||||
|
||||
@end
|
||||
|
||||
@protocol LegacyComponentsContext <NSObject>
|
||||
|
||||
- (UIEdgeInsets)safeAreaInset;
|
||||
- (CGRect)fullscreenBounds;
|
||||
- (TGKeyCommandController *)keyCommandController;
|
||||
- (CGRect)statusBarFrame;
|
||||
- (bool)isStatusBarHidden;
|
||||
- (void)setStatusBarHidden:(BOOL)hidden withAnimation:(UIStatusBarAnimation)animation;
|
||||
- (void)forceSetStatusBarHidden:(BOOL)hidden withAnimation:(UIStatusBarAnimation)animation;
|
||||
- (UIStatusBarStyle)statusBarStyle;
|
||||
- (void)setStatusBarStyle:(UIStatusBarStyle)statusBarStyle animated:(BOOL)animated;
|
||||
- (void)forceStatusBarAppearanceUpdate;
|
||||
- (bool)prefersLightStatusBar;
|
||||
|
||||
@required
|
||||
|
||||
- (CGFloat)applicationStatusBarAlpha;
|
||||
- (void)setApplicationStatusBarAlpha:(CGFloat)alpha;
|
||||
|
||||
- (void)animateApplicationStatusBarAppearance:(int)statusBarAnimation delay:(NSTimeInterval)delay duration:(NSTimeInterval)duration completion:(void (^)())completion;
|
||||
- (void)animateApplicationStatusBarAppearance:(int)statusBarAnimation duration:(NSTimeInterval)duration completion:(void (^)())completion;
|
||||
|
||||
- (void)animateApplicationStatusBarStyleTransitionWithDuration:(NSTimeInterval)duration;
|
||||
|
||||
- (bool)rootCallStatusBarHidden;
|
||||
|
||||
- (bool)currentlyInSplitView;
|
||||
|
||||
- (UIUserInterfaceSizeClass)currentSizeClass;
|
||||
- (UIUserInterfaceSizeClass)currentHorizontalSizeClass;
|
||||
- (UIUserInterfaceSizeClass)currentVerticalSizeClass;
|
||||
- (SSignal *)sizeClassSignal;
|
||||
|
||||
- (bool)canOpenURL:(NSURL *)url;
|
||||
- (void)openURL:(NSURL *)url;
|
||||
|
||||
- (NSDictionary *)serverMediaDataForAssetUrl:(NSString *)url;
|
||||
|
||||
- (void)presentActionSheet:(NSArray<LegacyComponentsActionSheetAction *> *)actions view:(UIView *)view completion:(void (^)(LegacyComponentsActionSheetAction *))completion;
|
||||
- (void)presentActionSheet:(NSArray<LegacyComponentsActionSheetAction *> *)actions view:(UIView *)view sourceRect:(CGRect (^)(void))sourceRect completion:(void (^)(LegacyComponentsActionSheetAction *))completion ;
|
||||
|
||||
- (id<LegacyComponentsOverlayWindowManager>)makeOverlayWindowManager;
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
#import "LegacyComponentsContext.h"
|
||||
|
||||
@implementation LegacyComponentsActionSheetAction
|
||||
|
||||
- (instancetype)initWithTitle:(NSString *)title action:(NSString *)action {
|
||||
return [self initWithTitle:title action:action type:LegacyComponentsActionSheetActionTypeGeneric];
|
||||
}
|
||||
|
||||
- (instancetype)initWithTitle:(NSString *)title action:(NSString *)action type:(LegacyComponentsActionSheetActionType)type {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_title = title;
|
||||
_action = action;
|
||||
_type = type;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <LegacyComponents/LegacyComponentsAccessChecker.h>
|
||||
#import <LegacyComponents/LegacyHTTPRequestOperation.h>
|
||||
|
||||
@class SSignal;
|
||||
@class SThreadPool;
|
||||
@protocol SDisposable;
|
||||
@class TGLocalization;
|
||||
@class UIViewController;
|
||||
@class TGWallpaperInfo;
|
||||
@class TGMemoryImageCache;
|
||||
@class TGImageMediaAttachment;
|
||||
@class TGNavigationBarPallete;
|
||||
@class TGMenuSheetPallete;
|
||||
@class TGMediaAssetsPallete;
|
||||
@class TGImageBorderPallete;
|
||||
@class TGCheckButtonPallete;
|
||||
|
||||
typedef enum {
|
||||
TGAudioSessionTypePlayVoice,
|
||||
TGAudioSessionTypePlayMusic,
|
||||
TGAudioSessionTypePlayVideo,
|
||||
TGAudioSessionTypePlayEmbedVideo,
|
||||
TGAudioSessionTypePlayAndRecord,
|
||||
TGAudioSessionTypePlayAndRecordHeadphones,
|
||||
TGAudioSessionTypeCall
|
||||
} TGAudioSessionType;
|
||||
|
||||
@protocol LegacyComponentsGlobalsProvider <NSObject>
|
||||
|
||||
- (TGLocalization *)effectiveLocalization;
|
||||
- (void)log:(NSString *)string;
|
||||
- (NSArray<UIWindow *> *)applicationWindows;
|
||||
- (UIWindow *)applicationStatusBarWindow;
|
||||
- (UIWindow *)applicationKeyboardWindow;
|
||||
- (UIApplication *)applicationInstance;
|
||||
|
||||
- (UIInterfaceOrientation)applicationStatusBarOrientation;
|
||||
- (CGRect)statusBarFrame;
|
||||
- (bool)isStatusBarHidden;
|
||||
- (void)setStatusBarHidden:(BOOL)hidden withAnimation:(UIStatusBarAnimation)animation;
|
||||
- (UIStatusBarStyle)statusBarStyle;
|
||||
- (void)setStatusBarStyle:(UIStatusBarStyle)statusBarStyle animated:(BOOL)animated;
|
||||
- (void)forceStatusBarAppearanceUpdate;
|
||||
|
||||
- (bool)canOpenURL:(NSURL *)url;
|
||||
- (void)openURL:(NSURL *)url;
|
||||
- (void)openURLNative:(NSURL *)url;
|
||||
|
||||
- (void)disableUserInteractionFor:(NSTimeInterval)timeInterval;
|
||||
- (void)setIdleTimerDisabled:(bool)value;
|
||||
|
||||
- (void)pauseMusicPlayback;
|
||||
|
||||
- (NSString *)dataStoragePath;
|
||||
- (NSString *)dataCachePath;
|
||||
|
||||
- (id<LegacyComponentsAccessChecker>)accessChecker;
|
||||
|
||||
- (SSignal *)stickerPacksSignal;
|
||||
- (SSignal *)maskStickerPacksSignal;
|
||||
- (SSignal *)recentStickerMasksSignal;
|
||||
|
||||
- (id<SDisposable>)requestAudioSession:(TGAudioSessionType)type interrupted:(void (^)())interrupted;
|
||||
|
||||
- (TGWallpaperInfo *)currentWallpaperInfo;
|
||||
- (UIImage *)currentWallpaperImage;
|
||||
|
||||
- (SThreadPool *)sharedMediaImageProcessingThreadPool;
|
||||
- (TGMemoryImageCache *)sharedMediaMemoryImageCache;
|
||||
- (SSignal *)squarePhotoThumbnail:(TGImageMediaAttachment *)imageAttachment ofSize:(CGSize)size threadPool:(SThreadPool *)threadPool memoryCache:(TGMemoryImageCache *)memoryCache pixelProcessingBlock:(void (^)(void *, int, int, int))pixelProcessingBlock downloadLargeImage:(bool)downloadLargeImage placeholder:(SSignal *)placeholder;
|
||||
|
||||
- (NSString *)localDocumentDirectoryForLocalDocumentId:(int64_t)localDocumentId version:(int32_t)version;
|
||||
- (NSString *)localDocumentDirectoryForDocumentId:(int64_t)documentId version:(int32_t)version;
|
||||
|
||||
- (SSignal *)jsonForHttpLocation:(NSString *)httpLocation;
|
||||
- (SSignal *)dataForHttpLocation:(NSString *)httpLocation;
|
||||
|
||||
- (NSOperation<LegacyHTTPRequestOperation> *)makeHTTPRequestOperationWithRequest:(NSURLRequest *)request;
|
||||
|
||||
- (void)pausePictureInPicturePlayback;
|
||||
- (void)resumePictureInPicturePlayback;
|
||||
- (void)maybeReleaseVolumeOverlay;
|
||||
|
||||
- (TGNavigationBarPallete *)navigationBarPallete;
|
||||
- (TGMenuSheetPallete *)menuSheetPallete;
|
||||
- (TGMediaAssetsPallete *)mediaAssetsPallete;
|
||||
- (TGCheckButtonPallete *)checkButtonPallete;
|
||||
|
||||
@optional
|
||||
- (TGImageBorderPallete *)imageBorderPallete;
|
||||
|
||||
@end
|
||||
|
||||
@interface LegacyComponentsGlobals : NSObject
|
||||
|
||||
+ (void)setProvider:(id<LegacyComponentsGlobalsProvider>)provider;
|
||||
+ (id<LegacyComponentsGlobalsProvider>)provider;
|
||||
|
||||
@end
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
UIImage *TGComponentsImageNamed(NSString *name);
|
||||
NSString *TGComponentsPathForResource(NSString *name, NSString *type);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "LegacyComponentsInternal.h"
|
||||
|
||||
#import "TGLocalization.h"
|
||||
|
||||
static id<LegacyComponentsGlobalsProvider> _provider;
|
||||
|
||||
@implementation LegacyComponentsGlobals
|
||||
|
||||
+ (void)setProvider:(id<LegacyComponentsGlobalsProvider>)provider {
|
||||
_provider = provider;
|
||||
}
|
||||
|
||||
+ (id<LegacyComponentsGlobalsProvider>)provider {
|
||||
return _provider;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
#import "LegacyComponentsGlobals.h"
|
||||
|
||||
@class TGLocalization;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
TGLocalization *legacyEffectiveLocalization();
|
||||
NSString *TGLocalized(NSString *s);
|
||||
bool TGObjectCompare(id obj1, id obj2);
|
||||
bool TGStringCompare(NSString *s1, NSString *s2);
|
||||
void TGLegacyLog(NSString *format, ...);
|
||||
int iosMajorVersion();
|
||||
int iosMinorVersion();
|
||||
|
||||
NSString *TGEncodeText(NSString *string, int key);
|
||||
|
||||
void TGDispatchOnMainThread(dispatch_block_t block);
|
||||
void TGDispatchAfter(double delay, dispatch_queue_t queue, dispatch_block_t block);
|
||||
|
||||
int deviceMemorySize();
|
||||
int cpuCoreCount();
|
||||
|
||||
#define UIColorRGB(rgb) ([[UIColor alloc] initWithRed:(((rgb >> 16) & 0xff) / 255.0f) green:(((rgb >> 8) & 0xff) / 255.0f) blue:(((rgb) & 0xff) / 255.0f) alpha:1.0f])
|
||||
#define UIColorRGBA(rgb,a) ([[UIColor alloc] initWithRed:(((rgb >> 16) & 0xff) / 255.0f) green:(((rgb >> 8) & 0xff) / 255.0f) blue:(((rgb) & 0xff) / 255.0f) alpha:a])
|
||||
|
||||
#define TGRestrictedToMainThread {if(![[NSThread currentThread] isMainThread]) TGLegacyLog(@"***** Warning: main thread-bound operation is running in background! *****");}
|
||||
|
||||
#define TG_TIMESTAMP_DEFINE(s) CFAbsoluteTime tg_timestamp_##s = CFAbsoluteTimeGetCurrent(); int tg_timestamp_line_##s = __LINE__;
|
||||
#define TG_TIMESTAMP_MEASURE(s) { CFAbsoluteTime tg_timestamp_current_time = CFAbsoluteTimeGetCurrent(); TGLegacyLog(@"%s %d-%d: %f ms", #s, tg_timestamp_line_##s, __LINE__, (tg_timestamp_current_time - tg_timestamp_##s) * 1000.0); tg_timestamp_##s = tg_timestamp_current_time; tg_timestamp_line_##s = __LINE__; }
|
||||
|
||||
#ifdef __LP64__
|
||||
# define CGFloor floor
|
||||
#else
|
||||
# define CGFloor floorf
|
||||
#endif
|
||||
|
||||
#ifdef __LP64__
|
||||
# define CGRound round
|
||||
# define CGCeil ceil
|
||||
# define CGPow pow
|
||||
# define CGSin sin
|
||||
# define CGCos cos
|
||||
# define CGSqrt sqrt
|
||||
#else
|
||||
# define CGRound roundf
|
||||
# define CGCeil ceilf
|
||||
# define CGPow powf
|
||||
# define CGSin sinf
|
||||
# define CGCos cosf
|
||||
# define CGSqrt sqrtf
|
||||
#endif
|
||||
|
||||
#define CGEven(x) ((((int)x) & 1) ? (x + 1) : x)
|
||||
#define CGOdd(x) ((((int)x) & 1) ? x : (x + 1))
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
#import "LegacyComponentsInternal.h"
|
||||
|
||||
#import "TGLocalization.h"
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <sys/sysctl.h>
|
||||
|
||||
TGLocalization *legacyEffectiveLocalization() {
|
||||
return [[LegacyComponentsGlobals provider] effectiveLocalization];
|
||||
}
|
||||
|
||||
NSString *TGLocalized(NSString *s) {
|
||||
return [legacyEffectiveLocalization() get:s];
|
||||
}
|
||||
|
||||
bool TGObjectCompare(id obj1, id obj2) {
|
||||
if (obj1 == nil && obj2 == nil)
|
||||
return true;
|
||||
|
||||
return [obj1 isEqual:obj2];
|
||||
}
|
||||
|
||||
bool TGStringCompare(NSString *s1, NSString *s2) {
|
||||
if (s1.length == 0 && s2.length == 0)
|
||||
return true;
|
||||
|
||||
if ((s1 == nil) != (s2 == nil))
|
||||
return false;
|
||||
|
||||
return s1 == nil || [s1 isEqualToString:s2];
|
||||
}
|
||||
|
||||
void TGLegacyLog(NSString *format, ...)
|
||||
{
|
||||
va_list L;
|
||||
va_start(L, format);
|
||||
NSString *string = [[NSString alloc] initWithFormat:format arguments:L];
|
||||
va_end(L);
|
||||
[[LegacyComponentsGlobals provider] log:string];
|
||||
}
|
||||
|
||||
int iosMajorVersion()
|
||||
{
|
||||
static bool initialized = false;
|
||||
static int version = 7;
|
||||
if (!initialized)
|
||||
{
|
||||
switch ([[[UIDevice currentDevice] systemVersion] intValue])
|
||||
{
|
||||
case 4:
|
||||
version = 4;
|
||||
break;
|
||||
case 5:
|
||||
version = 5;
|
||||
break;
|
||||
case 6:
|
||||
version = 6;
|
||||
break;
|
||||
case 7:
|
||||
version = 7;
|
||||
break;
|
||||
case 8:
|
||||
version = 8;
|
||||
break;
|
||||
case 9:
|
||||
version = 9;
|
||||
break;
|
||||
case 10:
|
||||
version = 10;
|
||||
break;
|
||||
case 11:
|
||||
version = 11;
|
||||
break;
|
||||
case 12:
|
||||
version = 12;
|
||||
break;
|
||||
default:
|
||||
version = 9;
|
||||
break;
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
int iosMinorVersion()
|
||||
{
|
||||
static bool initialized = false;
|
||||
static int version = 0;
|
||||
if (!initialized)
|
||||
{
|
||||
NSString *versionString = [[UIDevice currentDevice] systemVersion];
|
||||
NSRange range = [versionString rangeOfString:@"."];
|
||||
if (range.location != NSNotFound)
|
||||
version = [[versionString substringFromIndex:range.location + 1] intValue];
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
NSString *TGEncodeText(NSString *string, int key)
|
||||
{
|
||||
NSMutableString *result = [[NSMutableString alloc] init];
|
||||
|
||||
for (int i = 0; i < (int)[string length]; i++)
|
||||
{
|
||||
unichar c = [string characterAtIndex:i];
|
||||
c += key;
|
||||
[result appendString:[NSString stringWithCharacters:&c length:1]];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int deviceMemorySize()
|
||||
{
|
||||
static int memorySize = 0;
|
||||
if (memorySize == 0)
|
||||
{
|
||||
size_t len;
|
||||
__int64_t nmem;
|
||||
|
||||
len = sizeof(nmem);
|
||||
sysctlbyname("hw.memsize", &nmem, &len, NULL, 0);
|
||||
memorySize = (int)(nmem / (1024 * 1024));
|
||||
}
|
||||
return memorySize;
|
||||
}
|
||||
|
||||
int cpuCoreCount()
|
||||
{
|
||||
static int count = 0;
|
||||
if (count == 0)
|
||||
{
|
||||
size_t len;
|
||||
unsigned int ncpu;
|
||||
|
||||
len = sizeof(ncpu);
|
||||
sysctlbyname("hw.ncpu", &ncpu, &len, NULL, 0);
|
||||
count = ncpu;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
void TGDispatchOnMainThread(dispatch_block_t block)
|
||||
{
|
||||
if ([NSThread isMainThread])
|
||||
block();
|
||||
else
|
||||
dispatch_async(dispatch_get_main_queue(), block);
|
||||
}
|
||||
|
||||
void TGDispatchAfter(double delay, dispatch_queue_t queue, dispatch_block_t block)
|
||||
{
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((delay) * NSEC_PER_SEC)), queue, block);
|
||||
}
|
||||
|
||||
static NSBundle *frameworkBundle() {
|
||||
static NSBundle *currentBundle = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
currentBundle = [NSBundle bundleForClass:[LegacyComponentsGlobals class]];
|
||||
NSString *updatedPath = [[currentBundle bundlePath] stringByAppendingPathComponent:@"LegacyComponentsResources.bundle"];
|
||||
currentBundle = [NSBundle bundleWithPath:updatedPath];
|
||||
});
|
||||
return currentBundle;
|
||||
}
|
||||
|
||||
UIImage *TGComponentsImageNamed(NSString *name) {
|
||||
if (iosMajorVersion() < 8)
|
||||
return [UIImage imageNamed:[NSString stringWithFormat:@"LegacyComponentsResources.bundle/%@", name]];
|
||||
|
||||
UIImage *image = [UIImage imageNamed:name inBundle:frameworkBundle() compatibleWithTraitCollection:nil];
|
||||
if (image == nil) {
|
||||
assert(true);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
NSString *TGComponentsPathForResource(NSString *name, NSString *type) {
|
||||
return [frameworkBundle() pathForResource:name ofType:type];
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
|
||||
@protocol LegacyHTTPRequestOperation <NSObject>
|
||||
|
||||
@property (nonatomic, retain) NSOutputStream *outputStream;
|
||||
|
||||
- (void)setCompletionBlockWithSuccess:(void (^)(NSOperation *operation, id responseObject))success
|
||||
failure:(void (^)(NSOperation *operation, NSError *error))failure;
|
||||
- (void)setDownloadProgressBlock:(void (^)(NSInteger bytesRead, NSInteger totalBytesRead, NSInteger totalBytesExpectedToRead))block;
|
||||
|
||||
@end
|
||||
16
submodules/LegacyComponents/LegacyComponents/NSDictionary+CBExtensions.h
Executable file
16
submodules/LegacyComponents/LegacyComponents/NSDictionary+CBExtensions.h
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
//
|
||||
// NSDictionary+Extensions.h
|
||||
// Coub
|
||||
//
|
||||
// Created by Konstantin Anoshkin on 8.10.13.
|
||||
// Copyright 2013 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
|
||||
@interface NSDictionary (CBDictionaryExtensions)
|
||||
|
||||
- (NSString *) coubURIFromVersionTemplateWithPreferredSubstitutions: (NSArray *) preferredVersions;
|
||||
|
||||
@end
|
||||
38
submodules/LegacyComponents/LegacyComponents/NSDictionary+CBExtensions.m
Executable file
38
submodules/LegacyComponents/LegacyComponents/NSDictionary+CBExtensions.m
Executable file
|
|
@ -0,0 +1,38 @@
|
|||
//
|
||||
// NSDictionary+Extensions.m
|
||||
// Coub
|
||||
//
|
||||
// Created by Konstantin Anoshkin on 8.10.13.
|
||||
// Copyright 2013 Coub. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSDictionary+CBExtensions.h"
|
||||
|
||||
|
||||
@implementation NSDictionary (CBDictionaryExtensions)
|
||||
|
||||
|
||||
- (NSString *) coubURIFromVersionTemplateWithPreferredSubstitutions: (NSArray *) preferredVersions
|
||||
{
|
||||
NSString *urlTemplate = self[@"template"];
|
||||
if (urlTemplate) {
|
||||
NSArray *availableVersions = self[@"versions"];
|
||||
__block NSString *bestVersion = nil;
|
||||
[preferredVersions enumerateObjectsUsingBlock: ^(NSString *version, __unused NSUInteger idx, BOOL *stop) {
|
||||
if ([availableVersions containsObject: version]) {
|
||||
bestVersion = version;
|
||||
*stop = YES;
|
||||
}
|
||||
}];
|
||||
if (bestVersion) {
|
||||
//KALog(@"%@", [urlTemplate stringByReplacingOccurrencesOfString: @"%{version}" withString: bestVersion]);
|
||||
return [urlTemplate stringByReplacingOccurrencesOfString: @"%{version}" withString: bestVersion];
|
||||
} else {
|
||||
//KAObjectLogError(@"Could not find appropriate URI version: {%@}", [preferredVersions componentsJoinedByString: @", "]);
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface NSInputStream (TL)
|
||||
|
||||
- (int32_t)readInt32;
|
||||
- (int32_t)readInt32:(bool *)failed __attribute__((nonnull(1)));
|
||||
- (int64_t)readInt64;
|
||||
- (int64_t)readInt64:(bool *)failed __attribute__((nonnull(1)));
|
||||
- (double)readDouble;
|
||||
- (double)readDouble:(bool *)failed __attribute__((nonnull(1)));
|
||||
- (NSData *)readData:(int)length;
|
||||
- (NSData *)readData:(int)length failed:(bool *)failed __attribute__((nonnull(2)));
|
||||
- (NSMutableData *)readMutableData:(int)length;
|
||||
- (NSMutableData *)readMutableData:(int)length failed:(bool *)failed __attribute__((nonnull(2)));
|
||||
- (NSString *)readString;
|
||||
- (NSString *)readString:(bool *)failed __attribute__((nonnull(1)));
|
||||
- (NSData *)readBytes;
|
||||
- (NSData *)readBytes:(bool *)failed __attribute__((nonnull(1)));
|
||||
|
||||
@end
|
||||
390
submodules/LegacyComponents/LegacyComponents/NSInputStream+TL.m
Normal file
390
submodules/LegacyComponents/LegacyComponents/NSInputStream+TL.m
Normal file
|
|
@ -0,0 +1,390 @@
|
|||
#import "NSInputStream+TL.h"
|
||||
|
||||
#import "LegacyComponentsInternal.h"
|
||||
|
||||
#import <endian.h>
|
||||
|
||||
static inline int roundUpInput(int numToRound, int multiple)
|
||||
{
|
||||
if (multiple == 0)
|
||||
{
|
||||
return numToRound;
|
||||
}
|
||||
|
||||
int remainder = numToRound % multiple;
|
||||
if (remainder == 0)
|
||||
{
|
||||
return numToRound;
|
||||
}
|
||||
|
||||
return numToRound + multiple - remainder;
|
||||
}
|
||||
|
||||
@implementation NSInputStream (TL)
|
||||
|
||||
- (int32_t)readInt32
|
||||
{
|
||||
int32_t value = 0;
|
||||
|
||||
if ([self read:(uint8_t *)&value maxLength:4] != 4)
|
||||
{
|
||||
TGLegacyLog(@"***** Couldn't read int32");
|
||||
|
||||
@throw [[NSException alloc] initWithName:@"NSInputStream+TLException" reason:@"readInt32 end of stream" userInfo:@{}];
|
||||
}
|
||||
|
||||
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
#elif __BYTE_ORDER == __BIG_ENDIAN
|
||||
# error "Big endian is not implemented"
|
||||
#else
|
||||
# error "Unknown byte order"
|
||||
#endif
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
- (int32_t)readInt32:(bool *)failed
|
||||
{
|
||||
int32_t value = 0;
|
||||
|
||||
if ([self read:(uint8_t *)&value maxLength:4] != 4)
|
||||
{
|
||||
*failed = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
#elif __BYTE_ORDER == __BIG_ENDIAN
|
||||
# error "Big endian is not implemented"
|
||||
#else
|
||||
# error "Unknown byte order"
|
||||
#endif
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
- (int64_t)readInt64
|
||||
{
|
||||
int64_t value = 0;
|
||||
|
||||
if ([self read:(uint8_t *)&value maxLength:8] != 8)
|
||||
{
|
||||
TGLegacyLog(@"***** Couldn't read int64");
|
||||
}
|
||||
|
||||
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
#elif __BYTE_ORDER == __BIG_ENDIAN
|
||||
# error "Big endian is not implemented"
|
||||
#else
|
||||
# error "Unknown byte order"
|
||||
#endif
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
- (int64_t)readInt64:(bool *)failed
|
||||
{
|
||||
int64_t value = 0;
|
||||
|
||||
if ([self read:(uint8_t *)&value maxLength:8] != 8)
|
||||
{
|
||||
*failed = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
#elif __BYTE_ORDER == __BIG_ENDIAN
|
||||
# error "Big endian is not implemented"
|
||||
#else
|
||||
# error "Unknown byte order"
|
||||
#endif
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
- (double)readDouble
|
||||
{
|
||||
double value = 0.0;
|
||||
|
||||
if ([self read:(uint8_t *)&value maxLength:8] != 8)
|
||||
{
|
||||
TGLegacyLog(@"***** Couldn't read double");
|
||||
}
|
||||
|
||||
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
#elif __BYTE_ORDER == __BIG_ENDIAN
|
||||
# error "Big endian is not implemented"
|
||||
#else
|
||||
# error "Unknown byte order"
|
||||
#endif
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
- (double)readDouble:(bool *)failed
|
||||
{
|
||||
double value = 0.0;
|
||||
|
||||
if ([self read:(uint8_t *)&value maxLength:8] != 8)
|
||||
{
|
||||
*failed = true;
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
#elif __BYTE_ORDER == __BIG_ENDIAN
|
||||
# error "Big endian is not implemented"
|
||||
#else
|
||||
# error "Unknown byte order"
|
||||
#endif
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
- (NSData *)readData:(int)length
|
||||
{
|
||||
uint8_t *bytes = (uint8_t *)malloc(length);
|
||||
int readLen = (int)[self read:bytes maxLength:length];
|
||||
if (readLen != length)
|
||||
{
|
||||
TGLegacyLog(@"***** Couldn't read %d bytes", length);
|
||||
}
|
||||
NSData *data = [[NSData alloc] initWithBytesNoCopy:bytes length:length freeWhenDone:true];
|
||||
return data;
|
||||
}
|
||||
|
||||
- (NSData *)readData:(int)length failed:(bool *)failed
|
||||
{
|
||||
uint8_t *bytes = (uint8_t *)malloc(length);
|
||||
int readLen = (int)[self read:bytes maxLength:length];
|
||||
if (readLen != length)
|
||||
{
|
||||
free(bytes);
|
||||
*failed = true;
|
||||
return nil;
|
||||
}
|
||||
NSData *data = [[NSData alloc] initWithBytesNoCopy:bytes length:length freeWhenDone:true];
|
||||
return data;
|
||||
}
|
||||
|
||||
- (NSMutableData *)readMutableData:(int)length
|
||||
{
|
||||
uint8_t *bytes = (uint8_t *)malloc(length);
|
||||
int readLen = (int)[self read:bytes maxLength:length];
|
||||
if (readLen != length)
|
||||
{
|
||||
TGLegacyLog(@"***** Couldn't read %d bytes", length);
|
||||
}
|
||||
NSMutableData *data = [[NSMutableData alloc] initWithBytesNoCopy:bytes length:length freeWhenDone:true];
|
||||
return data;
|
||||
}
|
||||
|
||||
- (NSMutableData *)readMutableData:(int)length failed:(bool *)failed
|
||||
{
|
||||
uint8_t *bytes = (uint8_t *)malloc(length);
|
||||
int readLen = (int)[self read:bytes maxLength:length];
|
||||
if (readLen != length)
|
||||
{
|
||||
free(bytes);
|
||||
*failed = true;
|
||||
return nil;
|
||||
}
|
||||
NSMutableData *data = [[NSMutableData alloc] initWithBytesNoCopy:bytes length:length freeWhenDone:true];
|
||||
return data;
|
||||
}
|
||||
|
||||
- (NSString *)readString
|
||||
{
|
||||
uint8_t tmp = 0;
|
||||
[self read:&tmp maxLength:1];
|
||||
|
||||
int paddingBytes = 0;
|
||||
|
||||
int32_t length = tmp;
|
||||
if (length == 254)
|
||||
{
|
||||
length = 0;
|
||||
[self read:((uint8_t *)&length) + 1 maxLength:3];
|
||||
length >>= 8;
|
||||
|
||||
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
#elif __BYTE_ORDER == __BIG_ENDIAN
|
||||
# error "Big endian is not implemented"
|
||||
#else
|
||||
# error "Unknown byte order"
|
||||
#endif
|
||||
|
||||
paddingBytes = roundUpInput(length, 4) - length;
|
||||
}
|
||||
else
|
||||
{
|
||||
paddingBytes = roundUpInput(length + 1, 4) - (length + 1);
|
||||
}
|
||||
|
||||
NSString *string = nil;
|
||||
|
||||
if (length > 0)
|
||||
{
|
||||
uint8_t *bytes = (uint8_t *)malloc(length);
|
||||
int readLen = (int)[self read:bytes maxLength:length];
|
||||
if (readLen != length)
|
||||
{
|
||||
TGLegacyLog(@"***** Couldn't read %d bytes", length);
|
||||
}
|
||||
|
||||
string = [[NSString alloc] initWithBytesNoCopy:bytes length:length encoding:NSUTF8StringEncoding freeWhenDone:true];
|
||||
}
|
||||
else
|
||||
{
|
||||
string = @"";
|
||||
}
|
||||
|
||||
for (int i = 0; i < paddingBytes; i++)
|
||||
[self read:&tmp maxLength:1];
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
- (NSString *)readString:(bool *)failed
|
||||
{
|
||||
uint8_t tmp = 0;
|
||||
[self read:&tmp maxLength:1];
|
||||
|
||||
int paddingBytes = 0;
|
||||
|
||||
int32_t length = tmp;
|
||||
if (length == 254)
|
||||
{
|
||||
length = 0;
|
||||
[self read:((uint8_t *)&length) + 1 maxLength:3];
|
||||
length >>= 8;
|
||||
|
||||
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
#elif __BYTE_ORDER == __BIG_ENDIAN
|
||||
# error "Big endian is not implemented"
|
||||
#else
|
||||
# error "Unknown byte order"
|
||||
#endif
|
||||
|
||||
paddingBytes = roundUpInput(length, 4) - length;
|
||||
}
|
||||
else
|
||||
{
|
||||
paddingBytes = roundUpInput(length + 1, 4) - (length + 1);
|
||||
}
|
||||
|
||||
NSString *string = nil;
|
||||
|
||||
if (length > 0)
|
||||
{
|
||||
uint8_t *bytes = (uint8_t *)malloc(length);
|
||||
int readLen = (int)[self read:bytes maxLength:length];
|
||||
if (readLen != length)
|
||||
{
|
||||
free(bytes);
|
||||
*failed = true;
|
||||
return nil;
|
||||
}
|
||||
|
||||
string = [[NSString alloc] initWithBytesNoCopy:bytes length:length encoding:NSUTF8StringEncoding freeWhenDone:true];
|
||||
}
|
||||
else
|
||||
{
|
||||
string = @"";
|
||||
}
|
||||
|
||||
for (int i = 0; i < paddingBytes; i++)
|
||||
[self read:&tmp maxLength:1];
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
- (NSData *)readBytes
|
||||
{
|
||||
uint8_t tmp = 0;
|
||||
[self read:&tmp maxLength:1];
|
||||
|
||||
int paddingBytes = 0;
|
||||
|
||||
int32_t length = tmp;
|
||||
if (length == 254)
|
||||
{
|
||||
length = 0;
|
||||
[self read:((uint8_t *)&length) + 1 maxLength:3];
|
||||
length >>= 8;
|
||||
|
||||
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
#elif __BYTE_ORDER == __BIG_ENDIAN
|
||||
# error "Big endian is not implemented"
|
||||
#else
|
||||
# error "Unknown byte order"
|
||||
#endif
|
||||
|
||||
paddingBytes = roundUpInput(length, 4) - length;
|
||||
}
|
||||
else
|
||||
{
|
||||
paddingBytes = roundUpInput(length + 1, 4) - (length + 1);
|
||||
}
|
||||
|
||||
uint8_t *bytes = (uint8_t *)malloc(length);
|
||||
int readLen = (int)[self read:bytes maxLength:length];
|
||||
if (readLen != length)
|
||||
{
|
||||
TGLegacyLog(@"***** Couldn't read %d bytes", length);
|
||||
}
|
||||
|
||||
NSData *result = [NSData dataWithBytesNoCopy:bytes length:length freeWhenDone:true];
|
||||
|
||||
for (int i = 0; i < paddingBytes; i++)
|
||||
[self read:&tmp maxLength:1];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSData *)readBytes:(bool *)failed
|
||||
{
|
||||
uint8_t tmp = 0;
|
||||
[self read:&tmp maxLength:1];
|
||||
|
||||
int paddingBytes = 0;
|
||||
|
||||
int32_t length = tmp;
|
||||
if (length == 254)
|
||||
{
|
||||
length = 0;
|
||||
[self read:((uint8_t *)&length) + 1 maxLength:3];
|
||||
length >>= 8;
|
||||
|
||||
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
#elif __BYTE_ORDER == __BIG_ENDIAN
|
||||
# error "Big endian is not implemented"
|
||||
#else
|
||||
# error "Unknown byte order"
|
||||
#endif
|
||||
|
||||
paddingBytes = roundUpInput(length, 4) - length;
|
||||
}
|
||||
else
|
||||
{
|
||||
paddingBytes = roundUpInput(length + 1, 4) - (length + 1);
|
||||
}
|
||||
|
||||
uint8_t *bytes = (uint8_t *)malloc(length);
|
||||
int readLen = (int)[self read:bytes maxLength:length];
|
||||
if (readLen != length)
|
||||
{
|
||||
free(bytes);
|
||||
*failed = true;
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSData *result = [NSData dataWithBytesNoCopy:bytes length:length freeWhenDone:true];
|
||||
|
||||
for (int i = 0; i < paddingBytes; i++)
|
||||
[self read:&tmp maxLength:1];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
17
submodules/LegacyComponents/LegacyComponents/NSMutableArray+STKAudioPlayer.h
Executable file
17
submodules/LegacyComponents/LegacyComponents/NSMutableArray+STKAudioPlayer.h
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
//
|
||||
// NSMutableArray+STKAudioPlayer.h
|
||||
// StreamingKit
|
||||
//
|
||||
// Created by Thong Nguyen on 30/01/2014.
|
||||
// Copyright (c) 2014 Thong Nguyen. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface NSMutableArray (STKAudioPlayer)
|
||||
-(void) enqueue:(id)obj;
|
||||
-(void) skipQueue:(id)obj;
|
||||
-(void) skipQueueWithQueue:(NSMutableArray*)queue;
|
||||
-(id) dequeue;
|
||||
-(id) peek;
|
||||
@end
|
||||
50
submodules/LegacyComponents/LegacyComponents/NSMutableArray+STKAudioPlayer.m
Executable file
50
submodules/LegacyComponents/LegacyComponents/NSMutableArray+STKAudioPlayer.m
Executable file
|
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// NSMutableArray+STKAudioPlayer.m
|
||||
// StreamingKit
|
||||
//
|
||||
// Created by Thong Nguyen on 30/01/2014.
|
||||
// Copyright (c) 2014 Thong Nguyen. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSMutableArray+STKAudioPlayer.h"
|
||||
|
||||
@implementation NSMutableArray (STKAudioPlayer)
|
||||
|
||||
-(void) enqueue:(id)obj
|
||||
{
|
||||
[self insertObject:obj atIndex:0];
|
||||
}
|
||||
|
||||
-(void) skipQueue:(id)obj
|
||||
{
|
||||
[self addObject:obj];
|
||||
}
|
||||
|
||||
-(void) skipQueueWithQueue:(NSMutableArray*)queue
|
||||
{
|
||||
for (id item in queue)
|
||||
{
|
||||
[self addObject:item];
|
||||
}
|
||||
}
|
||||
|
||||
-(id) dequeue
|
||||
{
|
||||
if ([self count] == 0)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
id retval = [self lastObject];
|
||||
|
||||
[self removeLastObject];
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
-(id) peek
|
||||
{
|
||||
return [self lastObject];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import <pthread.h>
|
||||
|
||||
#define TG_SYNCHRONIZED_DEFINE(lock) pthread_mutex_t _TG_SYNCHRONIZED_##lock
|
||||
#define TG_SYNCHRONIZED_INIT(lock) pthread_mutex_init(&_TG_SYNCHRONIZED_##lock, NULL)
|
||||
#define TG_SYNCHRONIZED_BEGIN(lock) pthread_mutex_lock(&_TG_SYNCHRONIZED_##lock);
|
||||
#define TG_SYNCHRONIZED_END(lock) pthread_mutex_unlock(&_TG_SYNCHRONIZED_##lock);
|
||||
|
||||
@interface NSObject (TGLock)
|
||||
|
||||
- (void)tgLockObject;
|
||||
- (void)tgUnlockObject;
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
#import "NSObject+TGLock.h"
|
||||
|
||||
#import <objc/runtime.h>
|
||||
|
||||
static const char *lockPropertyKey = "TGObjectLock::lock";
|
||||
|
||||
@interface TGObjectLockImpl : NSObject
|
||||
{
|
||||
TG_SYNCHRONIZED_DEFINE(objectLock);
|
||||
}
|
||||
|
||||
- (void)tgTakeLock;
|
||||
- (void)tgFreeLock;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TGObjectLockImpl
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil)
|
||||
{
|
||||
TG_SYNCHRONIZED_INIT(objectLock);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)tgTakeLock
|
||||
{
|
||||
TG_SYNCHRONIZED_BEGIN(objectLock);
|
||||
}
|
||||
|
||||
- (void)tgFreeLock
|
||||
{
|
||||
TG_SYNCHRONIZED_END(objectLock);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation NSObject (TGLock)
|
||||
|
||||
- (void)tgLockObject
|
||||
{
|
||||
TGObjectLockImpl *lock = (TGObjectLockImpl *)objc_getAssociatedObject(self, lockPropertyKey);
|
||||
if (lock == nil)
|
||||
{
|
||||
@synchronized(self)
|
||||
{
|
||||
lock = [[TGObjectLockImpl alloc] init];
|
||||
objc_setAssociatedObject(self, lockPropertyKey, lock, OBJC_ASSOCIATION_RETAIN);
|
||||
}
|
||||
}
|
||||
|
||||
[lock tgTakeLock];
|
||||
}
|
||||
|
||||
- (void)tgUnlockObject
|
||||
{
|
||||
TGObjectLockImpl *lock = (TGObjectLockImpl *)objc_getAssociatedObject(self, lockPropertyKey);
|
||||
if (lock == nil)
|
||||
{
|
||||
@synchronized(self)
|
||||
{
|
||||
lock = [[TGObjectLockImpl alloc] init];
|
||||
objc_setAssociatedObject(self, lockPropertyKey, lock, OBJC_ASSOCIATION_RETAIN);
|
||||
}
|
||||
}
|
||||
|
||||
[lock tgFreeLock];
|
||||
}
|
||||
|
||||
@end
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue