diff --git a/Classes/IDMPhoto.h b/Classes/IDMPhoto.h index f87bd178..26181e1a 100644 --- a/Classes/IDMPhoto.h +++ b/Classes/IDMPhoto.h @@ -22,6 +22,7 @@ typedef void (^IDMProgressUpdateBlock)(CGFloat progress); // Properties @property (nonatomic, strong) NSString *caption; @property (nonatomic, strong) NSURL *photoURL; +@property (nonatomic, strong) NSURL *videoURL; @property (nonatomic, strong) IDMProgressUpdateBlock progressUpdateBlock; @property (nonatomic, strong) UIImage *placeholderImage; @@ -36,6 +37,7 @@ typedef void (^IDMProgressUpdateBlock)(CGFloat progress); // Init - (id)initWithImage:(UIImage *)image; +- (id)initWithVideo:(NSURL *)videoURL; - (id)initWithFilePath:(NSString *)path; - (id)initWithURL:(NSURL *)url; diff --git a/Classes/IDMPhoto.m b/Classes/IDMPhoto.m index a2959d5c..1bcac63f 100644 --- a/Classes/IDMPhoto.m +++ b/Classes/IDMPhoto.m @@ -36,6 +36,7 @@ @implementation IDMPhoto // Properties @synthesize underlyingImage = _underlyingImage, photoURL = _photoURL, +videoURL = _videoURL, caption = _caption; #pragma mark Class Methods @@ -104,6 +105,13 @@ - (id)initWithImage:(UIImage *)image { return self; } +- (id)initWithVideo:(NSURL *)videoURL { + if ((self = [super init])) { + self.videoURL = videoURL; + } + return self; +} + - (id)initWithFilePath:(NSString *)path { if ((self = [super init])) { _photoPath = [path copy]; diff --git a/Classes/IDMPhotoBrowser.m b/Classes/IDMPhotoBrowser.m index 9cd39f32..cb7104b2 100644 --- a/Classes/IDMPhotoBrowser.m +++ b/Classes/IDMPhotoBrowser.m @@ -714,11 +714,15 @@ - (void)viewWillAppear:(BOOL)animated { // Update [self reloadData]; - if ([_delegate respondsToSelector:@selector(willAppearPhotoBrowser:)]) { [_delegate willAppearPhotoBrowser:self]; } + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(playerDidFinishPlaying:) + name:AVPlayerItemDidPlayToEndTimeNotification + object:NULL]; + // Super [super viewWillAppear:animated]; @@ -734,6 +738,15 @@ - (void)viewDidAppear:(BOOL)animated { _viewIsActive = YES; } +- (void)viewWillDisappear:(BOOL)animated { + [[NSNotificationCenter defaultCenter] removeObserver:self + name:AVPlayerItemDidPlayToEndTimeNotification + object:NULL]; + + // Super + [super viewWillDisappear:animated]; +} + // Release any retained subviews of the main view. - (void)viewDidUnload { _currentPageIndex = 0; @@ -748,6 +761,13 @@ - (void)viewDidUnload { [super viewDidUnload]; } +#pragma mark - Video Loop + +- (void)playerDidFinishPlaying:(NSNotification *)notification { + [[self pageDisplayedAtIndex:_currentPageIndex].videoPlayerLayer.player seekToTime:kCMTimeZero]; + [[self pageDisplayedAtIndex:_currentPageIndex].videoPlayerLayer.player play]; +} + #pragma mark - Status Bar - (UIStatusBarStyle)preferredStatusBarStyle { diff --git a/Classes/IDMPhotoProtocol.h b/Classes/IDMPhotoProtocol.h index 19b273dc..1d32f4aa 100644 --- a/Classes/IDMPhotoProtocol.h +++ b/Classes/IDMPhotoProtocol.h @@ -33,6 +33,8 @@ // methods returns nil. - (UIImage *)underlyingImage; +- (NSURL *)videoURL; + // Called when the browser has determined the underlying images is not // already loaded into memory but needs it. // You must load the image asyncronously (and decompress it for better performance). @@ -61,4 +63,4 @@ // Return nil if there is no placeholder - (UIImage *)placeholderImage; -@end \ No newline at end of file +@end diff --git a/Classes/IDMZoomingScrollView.h b/Classes/IDMZoomingScrollView.h index 9b820be2..46756118 100644 --- a/Classes/IDMZoomingScrollView.h +++ b/Classes/IDMZoomingScrollView.h @@ -7,6 +7,8 @@ // #import +#import +#import #import "IDMPhotoProtocol.h" #import "IDMTapDetectingImageView.h" #import "IDMTapDetectingView.h" @@ -29,6 +31,8 @@ } @property (nonatomic, strong) IDMTapDetectingImageView *photoImageView; +@property (nonatomic, strong) IDMTapDetectingView *videoPlayerView; +@property (nonatomic, strong) AVPlayerLayer *videoPlayerLayer; @property (nonatomic, strong) IDMCaptionView *captionView; @property (nonatomic, strong) id photo; @property (nonatomic) CGFloat maximumDoubleTapZoomScale; diff --git a/Classes/IDMZoomingScrollView.m b/Classes/IDMZoomingScrollView.m index 754070ed..11bc218b 100644 --- a/Classes/IDMZoomingScrollView.m +++ b/Classes/IDMZoomingScrollView.m @@ -28,7 +28,7 @@ - (void)handleDoubleTap:(CGPoint)touchPoint; @implementation IDMZoomingScrollView -@synthesize photoImageView = _photoImageView, photoBrowser = _photoBrowser, photo = _photo, captionView = _captionView; +@synthesize photoImageView = _photoImageView, videoPlayerView = _videoPlayerView, videoPlayerLayer = _videoPlayerLayer, photoBrowser = _photoBrowser, photo = _photo, captionView = _captionView; - (id)initWithPhotoBrowser:(IDMPhotoBrowser *)browser { if ((self = [super init])) { @@ -41,7 +41,20 @@ - (id)initWithPhotoBrowser:(IDMPhotoBrowser *)browser { _tapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; _tapView.backgroundColor = [UIColor clearColor]; [self addSubview:_tapView]; - + + // Video + _videoPlayerView = [[IDMTapDetectingView alloc] initWithFrame:self.bounds]; + _videoPlayerView.tapDelegate = self; + _videoPlayerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + _videoPlayerView.backgroundColor = [UIColor clearColor]; + [self addSubview:_videoPlayerView]; + + _videoPlayerLayer = [[AVPlayerLayer alloc] init]; + _videoPlayerLayer.backgroundColor = [UIColor clearColor].CGColor; + [_videoPlayerLayer setFrame:_videoPlayerView.bounds]; + [_videoPlayerLayer setVideoGravity:AVLayerVideoGravityResizeAspect]; + [_videoPlayerView.layer addSublayer:_videoPlayerLayer]; + // Image view _photoImageView = [[IDMTapDetectingImageView alloc] initWithFrame:CGRectZero]; _photoImageView.tapDelegate = self; @@ -57,6 +70,7 @@ - (id)initWithPhotoBrowser:(IDMPhotoBrowser *)browser { if (@available(iOS 11.0, *)) { UIDragInteraction *drag = [[UIDragInteraction alloc] initWithDelegate: self]; [_photoImageView addInteraction:drag]; + [_videoPlayerView addInteraction:drag]; } CGRect screenBound = [[UIScreen mainScreen] bounds]; @@ -99,6 +113,11 @@ - (void)setPhoto:(id)photo { } - (void)prepareForReuse { + [_progressView setProgress:0 animated:NO]; + [_progressView setIndeterminate:NO]; + [_videoPlayerLayer.player removeObserver:self forKeyPath:@"status" context:nil]; + [_videoPlayerLayer.player pause]; + _videoPlayerLayer.player = NULL; self.photo = nil; [_captionView removeFromSuperview]; self.captionView = nil; @@ -125,6 +144,9 @@ - (void)displayImage { // Get image from browser as it handles ordering of fetching UIImage *img = [self.photoBrowser imageForPhoto:_photo]; if (img) { + // Hide video + _videoPlayerView.hidden = YES; + // Hide ProgressView //_progressView.alpha = 0.0f; [_progressView removeFromSuperview]; @@ -143,17 +165,46 @@ - (void)displayImage { // Set zoom to minimum zoom [self setMaxMinZoomScalesForCurrentBounds]; + } else if (_photo.videoURL != NULL) { + // Hide ProgressView + //_progressView.alpha = 0.0f; + [_progressView setProgress:0.3 animated:YES]; + [_progressView setIndeterminateDuration:0.7f]; + [_progressView setIndeterminate:YES]; + + _photoImageView.hidden = YES; + _videoPlayerView.hidden = NO; + + [_videoPlayerLayer.player pause]; + _videoPlayerLayer.player = NULL; + AVPlayer *player = [AVPlayer playerWithURL:_photo.videoURL]; + [_videoPlayerLayer setPlayer:player]; + + [player seekToTime:kCMTimeZero]; + [player play]; + + [player addObserver:self forKeyPath:@"status" options:0 context:nil]; } else { // Hide image view _photoImageView.hidden = YES; _progressView.alpha = 1.0f; } - + [self setNeedsLayout]; } } +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if (object == _videoPlayerLayer.player && [keyPath isEqualToString:@"status"]) { + if (_videoPlayerLayer.player.status == AVPlayerStatusReadyToPlay) { + [_progressView removeFromSuperview]; + } + } else { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + } +} + - (void)setProgress:(CGFloat)progress forPhoto:(IDMPhoto*)photo { IDMPhoto *p = (IDMPhoto*)self.photo; @@ -241,7 +292,10 @@ - (void)setMaxMinZoomScalesForCurrentBounds { - (void)layoutSubviews { // Update tap view frame _tapView.frame = self.bounds; - + + _videoPlayerView.frame = self.bounds; + _videoPlayerLayer.frame = _videoPlayerView.bounds; + // Super [super layoutSubviews]; diff --git a/Demo/PhotoBrowserDemo.xcodeproj/project.pbxproj b/Demo/PhotoBrowserDemo.xcodeproj/project.pbxproj index 5d9c8df1..f9b04e4f 100644 --- a/Demo/PhotoBrowserDemo.xcodeproj/project.pbxproj +++ b/Demo/PhotoBrowserDemo.xcodeproj/project.pbxproj @@ -92,7 +92,6 @@ 7D2B79BF1FD25B7600F2094F /* PhotoBrowserDemoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PhotoBrowserDemoTests.m; sourceTree = ""; }; 7D2B79C11FD25B7600F2094F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7D2B79C71FD25BF300F2094F /* IDMUtilsTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = IDMUtilsTest.m; sourceTree = ""; }; - 7D7D66691E09D6D400410A67 /* IDMBrowserDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IDMBrowserDelegate.h; sourceTree = ""; }; 7D7D666A1E09D6DA00410A67 /* IDMPhotoDataSource.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IDMPhotoDataSource.h; sourceTree = ""; }; D00FA320EEACA8C835D9D626 /* Pods-PhotoBrowserDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PhotoBrowserDemo.debug.xcconfig"; path = "Pods/Target Support Files/Pods-PhotoBrowserDemo/Pods-PhotoBrowserDemo.debug.xcconfig"; sourceTree = ""; }; D11464B61802FFC4005B7BC3 /* IDMPBConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IDMPBConstants.h; sourceTree = ""; }; @@ -327,8 +326,6 @@ 4C6F978914AF734800F8389A /* Sources */, 4C6F978A14AF734800F8389A /* Frameworks */, 4C6F978B14AF734800F8389A /* Resources */, - 64D1E44DFBE2976FB9C75718 /* [CP] Embed Pods Frameworks */, - F88445D630091F1AA9A2F89A /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -438,49 +435,22 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 64D1E44DFBE2976FB9C75718 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-PhotoBrowserDemo/Pods-PhotoBrowserDemo-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; 9FEC6B6CD1E66BC894CF44FA /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-PhotoBrowserDemo-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; - showEnvVarsInLog = 0; - }; - F88445D630091F1AA9A2F89A /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-PhotoBrowserDemo/Pods-PhotoBrowserDemo-resources.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ diff --git a/Demo/PhotoBrowserDemo/MenuViewController.swift b/Demo/PhotoBrowserDemo/MenuViewController.swift index b14abd26..d8dd6242 100644 --- a/Demo/PhotoBrowserDemo/MenuViewController.swift +++ b/Demo/PhotoBrowserDemo/MenuViewController.swift @@ -123,7 +123,7 @@ extension MenuViewController { case 0: return 1 case 1: - return 3 + return 4 case 2: return 0 default: @@ -161,8 +161,10 @@ extension MenuViewController { cell?.textLabel?.text = "Local photos" case 1: cell?.textLabel?.text = "Photos from Flickr" - case 2: - cell?.textLabel?.text = "Photos from Flickr - Custom" + case 2: + cell?.textLabel?.text = "Photos from Flickr - Custom" + case 3: + cell?.textLabel?.text = "Video" default: break } @@ -191,7 +193,7 @@ extension MenuViewController { } else if indexPath.section == 1 { // Multiple photos if indexPath.row == 0 { // Local Photos - + let path_photo1l = [Bundle.main.path(forResource: "photo1l", ofType: "jpg")] photo = IDMPhoto.photos(withFilePaths:path_photo1l).first as! IDMPhoto photo.caption = "Grotto of the Madonna" @@ -211,15 +213,32 @@ extension MenuViewController { photo = IDMPhoto.photos(withFilePaths:path_photo4l).first as! IDMPhoto photo.caption = "Campervan"; photos.append(photo) - } else if indexPath.row == 1 || indexPath.row == 2 { // Photos from Flickr or Flickr - Custom - let photosWithURLArray = [NSURL.init(string: "http://farm4.static.flickr.com/3567/3523321514_371d9ac42f_b.jpg"), - NSURL.init(string: "http://farm4.static.flickr.com/3629/3339128908_7aecabc34b_b.jpg"), - NSURL.init(string: "http://farm4.static.flickr.com/3364/3338617424_7ff836d55f_b.jpg"), - NSURL.init(string: "http://farm4.static.flickr.com/3590/3329114220_5fbc5bc92b_b.jpg")] - let photosWithURL: [IDMPhoto] = IDMPhoto.photos(withURLs: photosWithURLArray) as! [IDMPhoto] - - photos = photosWithURL - } + } else if indexPath.row == 1 || indexPath.row == 2 { // Photos from Flickr or Flickr - Custom + let photosWithURLArray = [NSURL.init(string: "http://farm4.static.flickr.com/3567/3523321514_371d9ac42f_b.jpg"), + NSURL.init(string: "http://farm4.static.flickr.com/3629/3339128908_7aecabc34b_b.jpg"), + NSURL.init(string: "http://farm4.static.flickr.com/3364/3338617424_7ff836d55f_b.jpg"), + NSURL.init(string: "http://farm4.static.flickr.com/3590/3329114220_5fbc5bc92b_b.jpg")] + let photosWithURL: [IDMPhoto] = IDMPhoto.photos(withURLs: photosWithURLArray) as! [IDMPhoto] + + photos = photosWithURL + } else if indexPath.row == 3 { // Videos + let video1 = IDMPhoto(video: URL(string: "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")!)! + video1.caption = "Big Buck Bunny — by THE PEACH OPEN MOVIE PROJECT" + + let photo1 = IDMPhoto(url: URL(string: "http://farm4.static.flickr.com/3590/3329114220_5fbc5bc92b_b.jpg")!)! + photo1.caption = "A standard picture separating two videos" + + let video2 = IDMPhoto(video: URL(string: "https://staging.coverr.co/s3/mp4/Playful.mp4")!)! + video2.caption = "A cover coming straight from coverr.co for an example" + + let videos: [IDMPhoto] = [ + video1, + photo1, + video2 + ] + + photos = videos + } } // Create and setup browser @@ -227,7 +246,7 @@ extension MenuViewController { browser?.delegate = self if indexPath.section == 1 { // Multiple photos - if indexPath.row == 1 { // Photos from Flickr + if indexPath.row == 1 || indexPath.row == 3 { // Photos from Flickr browser?.displayCounterLabel = true browser?.displayActionButton = false } else if indexPath.row == 2 { // Photos from Flickr - Custom diff --git a/Demo/PhotoBrowserDemo/PhotoBrowserDemo-Bridging-Header.h b/Demo/PhotoBrowserDemo/PhotoBrowserDemo-Bridging-Header.h index 6d4b5549..93c742a0 100644 --- a/Demo/PhotoBrowserDemo/PhotoBrowserDemo-Bridging-Header.h +++ b/Demo/PhotoBrowserDemo/PhotoBrowserDemo-Bridging-Header.h @@ -2,5 +2,4 @@ // Use this file to import your target's public headers that you would like to expose to Swift. // -#import "Menu.h" #import "IDMPhotoBrowser.h" diff --git a/Demo/Podfile b/Demo/Podfile index cb335507..2341bfae 100644 --- a/Demo/Podfile +++ b/Demo/Podfile @@ -5,7 +5,7 @@ inhibit_all_warnings! target "PhotoBrowserDemo" do -pod 'SDWebImage' +pod 'SDWebImage', '4.3.0' pod 'DACircularProgress' pod 'pop' diff --git a/Demo/Podfile.lock b/Demo/Podfile.lock index 08ad9620..83ca6e81 100644 --- a/Demo/Podfile.lock +++ b/Demo/Podfile.lock @@ -1,20 +1,26 @@ PODS: - DACircularProgress (2.3.1) - pop (1.0.9) - - SDWebImage (4.0.0): - - SDWebImage/Core (= 4.0.0) - - SDWebImage/Core (4.0.0) + - SDWebImage (4.3.0): + - SDWebImage/Core (= 4.3.0) + - SDWebImage/Core (4.3.0) DEPENDENCIES: - DACircularProgress - pop - - SDWebImage + - SDWebImage (= 4.3.0) + +SPEC REPOS: + https://github.com/cocoapods/specs.git: + - DACircularProgress + - pop + - SDWebImage SPEC CHECKSUMS: DACircularProgress: 4dd437c0fc3da5161cb289e07ac449493d41db71 pop: f667631a5108a2e60d9e8797c9b32ddaf2080bce - SDWebImage: 76a6348bdc74eb5a55dd08a091ef298e56b55e41 + SDWebImage: 6da6c8bca115addc4de8613362e1b15f66333825 -PODFILE CHECKSUM: 7c9b8bb160246eb04b56feab0ec4a8fb149d5fa3 +PODFILE CHECKSUM: 1003e1b6f3d8af0dcbaf40d0441e56a86817bc6b -COCOAPODS: 1.2.0 +COCOAPODS: 1.5.3