首次提交

This commit is contained in:
启星
2025-09-22 18:48:29 +08:00
parent 28ae935e93
commit ae9be0b58e
8941 changed files with 999209 additions and 2 deletions

View File

@@ -0,0 +1,44 @@
//
// YBIBVideoActionBar.h
// YBImageBrowserDemo
//
// Created by 波儿菜 on 2019/7/11.
// Copyright © 2019 杨波. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@class YBIBVideoActionBar;
@protocol YBIBVideoActionBarDelegate <NSObject>
@required
- (void)yb_videoActionBar:(YBIBVideoActionBar *)actionBar clickPlayButton:(UIButton *)playButton;
- (void)yb_videoActionBar:(YBIBVideoActionBar *)actionBar clickPauseButton:(UIButton *)pauseButton;
- (void)yb_videoActionBar:(YBIBVideoActionBar *)actionBar changeValue:(float)value;
@end
@interface YBIBVideoActionBar : UIView
@property (nonatomic, weak) id<YBIBVideoActionBarDelegate> delegate;
- (void)setMaxValue:(float)value;
- (void)setCurrentValue:(float)value;
- (void)pause;
- (void)play;
+ (CGFloat)defaultHeight;
@property (nonatomic, assign, readonly) BOOL isTouchInside;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,191 @@
//
// YBIBVideoActionBar.m
// YBImageBrowserDemo
//
// Created by on 2019/7/11.
// Copyright © 2019 . All rights reserved.
//
#import "YBIBVideoActionBar.h"
#import "YBIBIconManager.h"
@interface YBVideoBrowseActionSlider : UISlider
@end
@implementation YBVideoBrowseActionSlider
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setThumbImage:YBIBIconManager.sharedManager.videoDragCircleImage() forState:UIControlStateNormal];
self.minimumTrackTintColor = UIColor.whiteColor;
self.maximumTrackTintColor = [UIColor.whiteColor colorWithAlphaComponent:0.5];
self.layer.shadowColor = UIColor.darkGrayColor.CGColor;
self.layer.shadowOffset = CGSizeMake(0, 1);
self.layer.shadowOpacity = 1;
self.layer.shadowRadius = 4;
}
return self;
}
- (CGRect)trackRectForBounds:(CGRect)bounds {
CGRect frame = [super trackRectForBounds:bounds];
return CGRectMake(frame.origin.x, frame.origin.y, frame.size.width, 2);
}
- (CGRect)thumbRectForBounds:(CGRect)bounds trackRect:(CGRect)rect value:(float)value {
CGRect frame = [super thumbRectForBounds:bounds trackRect:rect value:value];
return CGRectMake(frame.origin.x - 10, frame.origin.y - 10, frame.size.width + 20, frame.size.height + 20);
}
@end
@interface YBIBVideoActionBar ()
@property (nonatomic, strong) UIButton *playButton;
@property (nonatomic, strong) UILabel *preTimeLabel;
@property (nonatomic, strong) UILabel *sufTimeLabel;
@property (nonatomic, strong) YBVideoBrowseActionSlider *slider;
@end
@implementation YBIBVideoActionBar {
BOOL _dragging;
}
#pragma mark - life cycle
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
_dragging = NO;
[self addSubview:self.playButton];
[self addSubview:self.preTimeLabel];
[self addSubview:self.sufTimeLabel];
[self addSubview:self.slider];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
CGFloat width = self.bounds.size.width, height = self.bounds.size.height, labelWidth = 55, buttonWidth = 44, labelOffset = 10;
CGFloat imageWidth = YBIBIconManager.sharedManager.videoPlayImage().size.width;
CGFloat offset = (buttonWidth - imageWidth) * 0.5;
self.playButton.frame = CGRectMake(10, 0, buttonWidth, height);
self.preTimeLabel.frame = CGRectMake(CGRectGetMaxX(self.playButton.frame) + labelOffset - offset, 0, labelWidth, height);
self.sufTimeLabel.frame = CGRectMake(width - labelWidth - labelOffset, 0, labelWidth, height);
self.slider.frame = CGRectMake(CGRectGetMaxX(self.preTimeLabel.frame), 0, CGRectGetMinX(self.sufTimeLabel.frame) - CGRectGetMaxX(self.preTimeLabel.frame), height);
}
#pragma mark - public
+ (CGFloat)defaultHeight {
return 44;
}
- (void)setMaxValue:(float)value {
self.slider.maximumValue = value;
self.sufTimeLabel.attributedText = [self.class timeformatFromSeconds:value];
}
- (void)setCurrentValue:(float)value {
if (!_dragging) {
[self.slider setValue:value animated:YES];
}
self.preTimeLabel.attributedText = [self.class timeformatFromSeconds:value];
}
- (void)pause {
self.playButton.selected = NO;
}
- (void)play {
_dragging = NO;
self.playButton.selected = YES;
self.slider.userInteractionEnabled = YES;
}
#pragma mark - private
+ (NSAttributedString *)timeformatFromSeconds:(NSInteger)seconds {
NSInteger hour = seconds / 3600, min = (seconds % 3600) / 60, sec = seconds % 60;
NSString *text = seconds > 3600 ? [NSString stringWithFormat:@"%02ld:%02ld:%02ld", (long)hour, (long)min, (long)sec] : [NSString stringWithFormat:@"%02ld:%02ld", (long)min, (long)sec];
NSShadow *shadow = [NSShadow new];
shadow.shadowBlurRadius = 4;
shadow.shadowOffset = CGSizeMake(0, 1);
shadow.shadowColor = UIColor.darkGrayColor;
NSAttributedString *attr = [[NSMutableAttributedString alloc] initWithString:text attributes:@{NSShadowAttributeName:shadow, NSFontAttributeName:[UIFont boldSystemFontOfSize:11]}];
return attr;
}
#pragma mark - touch event
- (void)clickPlayButton:(UIButton *)button {
button.userInteractionEnabled = NO;
if (button.selected) {
[self.delegate yb_videoActionBar:self clickPauseButton:button];
} else {
[self.delegate yb_videoActionBar:self clickPlayButton:button];
}
button.userInteractionEnabled = YES;
}
- (void)respondsToSliderTouchFinished:(UISlider *)slider {
[self.delegate yb_videoActionBar:self changeValue:slider.value];
}
- (void)respondsToSliderTouchDown:(UISlider *)slider {
_dragging = YES;
slider.userInteractionEnabled = NO;
}
#pragma mark - getters
- (UIButton *)playButton {
if (!_playButton) {
_playButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_playButton setImage:YBIBIconManager.sharedManager.videoPlayImage() forState:UIControlStateNormal];
[_playButton setImage:YBIBIconManager.sharedManager.videoPauseImage() forState:UIControlStateSelected];
[_playButton addTarget:self action:@selector(clickPlayButton:) forControlEvents:UIControlEventTouchUpInside];
_playButton.layer.shadowColor = UIColor.darkGrayColor.CGColor;
_playButton.layer.shadowOffset = CGSizeMake(0, 1);
_playButton.layer.shadowOpacity = 1;
_playButton.layer.shadowRadius = 4;
}
return _playButton;
}
- (UILabel *)preTimeLabel {
if (!_preTimeLabel) {
_preTimeLabel = [UILabel new];
_preTimeLabel.attributedText = [self.class timeformatFromSeconds:0];
_preTimeLabel.adjustsFontSizeToFitWidth = YES;
_preTimeLabel.textAlignment = NSTextAlignmentCenter;
_preTimeLabel.textColor = [UIColor.whiteColor colorWithAlphaComponent:0.9];
}
return _preTimeLabel;
}
- (UILabel *)sufTimeLabel {
if (!_sufTimeLabel) {
_sufTimeLabel = [UILabel new];
_sufTimeLabel.attributedText = [self.class timeformatFromSeconds:0];
_sufTimeLabel.adjustsFontSizeToFitWidth = YES;
_sufTimeLabel.textAlignment = NSTextAlignmentCenter;
_sufTimeLabel.textColor = [UIColor.whiteColor colorWithAlphaComponent:0.9];
}
return _sufTimeLabel;
}
- (YBVideoBrowseActionSlider *)slider {
if (!_slider) {
_slider = [YBVideoBrowseActionSlider new];
[_slider addTarget:self action:@selector(respondsToSliderTouchFinished:) forControlEvents:UIControlEventTouchUpInside|UIControlEventTouchCancel|UIControlEventTouchUpOutside];
[_slider addTarget:self action:@selector(respondsToSliderTouchDown:) forControlEvents:UIControlEventTouchDown];
}
return _slider;
}
- (BOOL)isTouchInside {
return self.slider.isTouchInside;
}
@end

View File

@@ -0,0 +1,20 @@
//
// YBIBVideoCell+Internal.h
// YBImageBrowserDemo
//
// Created by 波儿菜 on 2019/12/23.
// Copyright © 2019 杨波. All rights reserved.
//
#import "YBIBVideoCell.h"
#import "YBIBVideoView.h"
NS_ASSUME_NONNULL_BEGIN
@interface YBIBVideoCell ()
@property (nonatomic, strong) YBIBVideoView *videoView;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,17 @@
//
// YBIBVideoCell.h
// YBImageBrowserDemo
//
// Created by 波儿菜 on 2019/7/10.
// Copyright © 2019 杨波. All rights reserved.
//
#import "YBIBCellProtocol.h"
NS_ASSUME_NONNULL_BEGIN
@interface YBIBVideoCell : UICollectionViewCell <YBIBCellProtocol>
@end
NS_ASSUME_NONNULL_END

448
Pods/YBImageBrowser/Video/YBIBVideoCell.m generated Normal file
View File

@@ -0,0 +1,448 @@
//
// YBIBVideoCell.m
// YBImageBrowserDemo
//
// Created by on 2019/7/10.
// Copyright © 2019 . All rights reserved.
//
#import "YBIBVideoCell.h"
#import "YBIBVideoData.h"
#import "YBIBVideoData+Internal.h"
#import "YBIBCopywriter.h"
#import "YBIBIconManager.h"
#import <objc/runtime.h>
#import "YBIBVideoCell+Internal.h"
@interface NSObject (YBIBVideoPlayingRecord)
- (void)ybib_videoPlayingAdd:(NSObject *)obj;
- (void)ybib_videoPlayingRemove:(NSObject *)obj;
- (BOOL)ybib_noVideoPlaying;
@end
@implementation NSObject (YBIBVideoPlayingRecord)
- (NSMutableSet *)ybib_videoPlayingSet {
static void *kRecordKey = &kRecordKey;
NSMutableSet *set = objc_getAssociatedObject(self, kRecordKey);
if (!set) {
set = [NSMutableSet set];
objc_setAssociatedObject(self, kRecordKey, set, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return set;
}
- (void)ybib_videoPlayingAdd:(NSObject *)obj {
[[self ybib_videoPlayingSet] addObject:[NSString stringWithFormat:@"%p", obj]];
}
- (void)ybib_videoPlayingRemove:(NSObject *)obj {
[[self ybib_videoPlayingSet] removeObject:[NSString stringWithFormat:@"%p", obj]];
}
- (BOOL)ybib_noVideoPlaying {
return [self ybib_videoPlayingSet].count == 0;
}
@end
@interface YBIBVideoCell () <YBIBVideoDataDelegate, YBIBVideoViewDelegate, UIGestureRecognizerDelegate>
@end
@implementation YBIBVideoCell {
CGPoint _interactStartPoint;
BOOL _interacting;
}
#pragma mark - life cycle
- (void)dealloc {
[self.yb_backView ybib_videoPlayingRemove:self];
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self initValue];
[self.contentView addSubview:self.videoView];
[self addGesture];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
self.videoView.frame = self.bounds;
}
- (void)initValue {
_interactStartPoint = CGPointZero;
_interacting = NO;
}
- (void)prepareForReuse {
((YBIBVideoData *)self.yb_cellData).delegate = nil;
self.videoView.thumbImageView.image = nil;
[self hideAuxiliaryView];
[self.videoView reset];
self.videoView.asset = nil;
[super prepareForReuse];
}
#pragma mark - private
- (void)hideAuxiliaryView {
[self.yb_auxiliaryViewHandler() yb_hideLoadingWithContainer:self];
[self.yb_auxiliaryViewHandler() yb_hideToastWithContainer:self];
}
- (void)updateImageLayoutWithOrientation:(UIDeviceOrientation)orientation previousImageSize:(CGSize)previousImageSize {
YBIBVideoData *data = self.yb_cellData;
UIImage *image = self.videoView.thumbImageView.image;
CGSize imageSize = image.size;
CGRect imageViewFrame = [data yb_imageViewFrameWithContainerSize:self.yb_containerSize(orientation) imageSize:imageSize orientation:orientation];
CGFloat scale;
if (previousImageSize.width > 0 && previousImageSize.height > 0) {
scale = imageSize.width / imageSize.height - previousImageSize.width / previousImageSize.height;
} else {
scale = 0;
}
// '0.001' is admissible error.
if (ABS(scale) <= 0.001) {
self.videoView.thumbImageView.frame = imageViewFrame;
} else {
[UIView animateWithDuration:0.25 animations:^{
self.videoView.thumbImageView.frame = imageViewFrame;
}];
}
}
- (void)hideBrowser {
((YBIBVideoData *)self.yb_cellData).delegate = nil;
self.videoView.thumbImageView.hidden = NO;
self.videoView.autoPlayCount = 0;
[self.videoView reset];
[self.videoView hideToolBar:YES];
[self.videoView hidePlayButton];
self.yb_hideBrowser();
_interacting = NO;
}
- (void)hideToolViews:(BOOL)hide {
if (hide) {
self.yb_hideToolViews(YES);
} else {
if ([self.yb_backView ybib_noVideoPlaying]) {
self.yb_hideToolViews(NO);
}
}
}
#pragma mark - <YBIBCellProtocol>
@synthesize yb_currentOrientation = _yb_currentOrientation;
@synthesize yb_containerSize = _yb_containerSize;
@synthesize yb_backView = _yb_backView;
@synthesize yb_collectionView = _yb_collectionView;
@synthesize yb_isTransitioning = _yb_isTransitioning;
@synthesize yb_auxiliaryViewHandler = _yb_auxiliaryViewHandler;
@synthesize yb_hideStatusBar = _yb_hideStatusBar;
@synthesize yb_hideBrowser = _yb_hideBrowser;
@synthesize yb_hideToolViews = _yb_hideToolViews;
@synthesize yb_cellData = _yb_cellData;
@synthesize yb_currentPage = _yb_currentPage;
@synthesize yb_selfPage = _yb_selfPage;
@synthesize yb_cellIsInCenter = _yb_cellIsInCenter;
@synthesize yb_isRotating = _yb_isRotating;
- (void)setYb_cellData:(id<YBIBDataProtocol>)yb_cellData {
_yb_cellData = yb_cellData;
YBIBVideoData *data = (YBIBVideoData *)yb_cellData;
data.delegate = self;
UIDeviceOrientation orientation = self.yb_currentOrientation();
CGSize containerSize = self.yb_containerSize(orientation);
[self.videoView updateLayoutWithExpectOrientation:orientation containerSize:containerSize];
self.videoView.autoPlayCount = data.autoPlayCount;
self.videoView.topBar.cancelButton.hidden = data.shouldHideForkButton;
}
- (void)yb_orientationWillChangeWithExpectOrientation:(UIDeviceOrientation)orientation {
if (_interacting) [self restoreGestureInteractionWithDuration:0];
}
- (void)yb_orientationChangeAnimationWithExpectOrientation:(UIDeviceOrientation)orientation {
[self updateImageLayoutWithOrientation:orientation previousImageSize:self.videoView.thumbImageView.image.size];
CGSize containerSize = self.yb_containerSize(orientation);
[self.videoView updateLayoutWithExpectOrientation:orientation containerSize:containerSize];
}
- (UIView *)yb_foregroundView {
return self.videoView.thumbImageView;
}
- (void)yb_pageChanged {
if (self.yb_currentPage() != self.yb_selfPage()) {
[self.videoView reset];
[self hideToolViews:NO];
[self.yb_auxiliaryViewHandler() yb_hideLoadingWithContainer:self];
if (_interacting) [self restoreGestureInteractionWithDuration:0];
self.videoView.needAutoPlay = NO;
} else {
self.videoView.needAutoPlay = YES;
}
}
#pragma mark - <YBIBVideoDataDelegate>
- (void)yb_startLoadingAVAssetFromPHAssetForData:(YBIBVideoData *)data {}
- (void)yb_finishLoadingAVAssetFromPHAssetForData:(YBIBVideoData *)data {}
- (void)yb_startLoadingFirstFrameForData:(YBIBVideoData *)data {
if (!self.videoView.thumbImageView.image) {
[self.yb_auxiliaryViewHandler() yb_showLoadingWithContainer:self];
}
}
- (void)yb_finishLoadingFirstFrameForData:(YBIBVideoData *)data {
[self.yb_auxiliaryViewHandler() yb_hideLoadingWithContainer:self];
}
- (void)yb_videoData:(YBIBVideoData *)data downloadingWithProgress:(CGFloat)progress {
[self.yb_auxiliaryViewHandler() yb_showLoadingWithContainer:self progress:progress];
}
- (void)yb_finishDownloadingForData:(YBIBVideoData *)data {
[self.yb_auxiliaryViewHandler() yb_hideLoadingWithContainer:self];
}
- (void)yb_videoData:(YBIBVideoData *)data readyForAVAsset:(AVAsset *)asset {
self.videoView.asset = asset;
}
- (void)yb_videoData:(YBIBVideoData *)data readyForThumbImage:(UIImage *)image {
if (!self.videoView.isPlaying) {
self.videoView.thumbImageView.hidden = NO;
}
if (!self.videoView.thumbImageView.image) {
CGSize previousSize = self.videoView.thumbImageView.image.size;
self.videoView.thumbImageView.image = image;
[self updateImageLayoutWithOrientation:self.yb_currentOrientation() previousImageSize:previousSize];
}
}
- (void)yb_videoIsInvalidForData:(YBIBVideoData *)data {
[self.yb_auxiliaryViewHandler() yb_hideLoadingWithContainer:self];
NSString *imageIsInvalid = [YBIBCopywriter sharedCopywriter].videoIsInvalid;
if (self.videoView.thumbImageView.image) {
[self.yb_auxiliaryViewHandler() yb_showIncorrectToastWithContainer:self text:imageIsInvalid];
} else {
[self.yb_auxiliaryViewHandler() yb_showLoadingWithContainer:self text:imageIsInvalid];
}
}
#pragma mark - <YBIBVideoViewDelegate>
- (BOOL)yb_isFreezingForVideoView:(YBIBVideoView *)view {
return self.yb_isTransitioning();
}
- (void)yb_preparePlayForVideoView:(YBIBVideoView *)view {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (!view.isPlaying && !view.isPlayFailed && self.yb_selfPage() == self.yb_currentPage()) {
[self.yb_auxiliaryViewHandler() yb_showLoadingWithContainer:self];
}
});
}
- (void)yb_startPlayForVideoView:(YBIBVideoView *)view {
self.videoView.thumbImageView.hidden = YES;
[self.yb_backView ybib_videoPlayingAdd:self];
[self.yb_auxiliaryViewHandler() yb_hideLoadingWithContainer:self];
[self hideToolViews:YES];
}
- (void)yb_didPlayToEndTimeForVideoView:(YBIBVideoView *)view {
YBIBVideoData *data = (YBIBVideoData *)self.yb_cellData;
if (data.repeatPlayCount == NSUIntegerMax) {
[view preparPlay];
} else if (data.repeatPlayCount > 0) {
--data.repeatPlayCount;
[view preparPlay];
} else {
[self hideToolViews:NO];
}
}
- (void)yb_finishPlayForVideoView:(YBIBVideoView *)view {
[self.yb_backView ybib_videoPlayingRemove:self];
[self hideToolViews:NO];
}
- (void)yb_playFailedForVideoView:(YBIBVideoView *)view {
[self.yb_auxiliaryViewHandler() yb_hideLoadingWithContainer:self];
[self.yb_auxiliaryViewHandler() yb_showIncorrectToastWithContainer:self text:YBIBCopywriter.sharedCopywriter.videoError];
}
- (void)yb_respondsToTapGestureForVideoView:(YBIBVideoView *)view {
if (self.yb_isRotating()) return;
YBIBVideoData *data = self.yb_cellData;
if (data.singleTouchBlock) {
data.singleTouchBlock(data);
} else {
[self hideBrowser];
}
}
- (void)yb_cancelledForVideoView:(YBIBVideoView *)view {
if (self.yb_isRotating()) return;
[self hideBrowser];
}
- (CGSize)yb_containerSizeForVideoView:(YBIBVideoView *)view {
return self.yb_containerSize(self.yb_currentOrientation());
}
- (void)yb_autoPlayCountChanged:(NSUInteger)count {
YBIBVideoData *data = (YBIBVideoData *)self.yb_cellData;
data.autoPlayCount = count;
}
#pragma mark - <UIGestureRecognizerDelegate>
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
#pragma mark - gesture
- (void)addGesture {
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(respondsToPanGesture:)];
panGesture.cancelsTouchesInView = NO;
panGesture.delegate = self;
[self.videoView.tapGesture requireGestureRecognizerToFail:panGesture];
[self.videoView addGestureRecognizer:panGesture];
}
- (void)respondsToPanGesture:(UIPanGestureRecognizer *)pan {
if (self.yb_isRotating()) return;
if ((!self.videoView.thumbImageView.image && !self.videoView.isPlaying)) return;
YBIBInteractionProfile *profile = ((YBIBVideoData *)self.yb_cellData).interactionProfile;
if (profile.disable) return;
CGPoint point = [pan locationInView:self];
CGSize containerSize = self.yb_containerSize(self.yb_currentOrientation());
if (pan.state == UIGestureRecognizerStateBegan) {
_interactStartPoint = point;
} else if (pan.state == UIGestureRecognizerStateCancelled || pan.state == UIGestureRecognizerStateEnded || pan.state == UIGestureRecognizerStateRecognized || pan.state == UIGestureRecognizerStateFailed) {
// End
if (_interacting) {
CGPoint velocity = [pan velocityInView:self.videoView];
BOOL velocityArrive = ABS(velocity.y) > profile.dismissVelocityY;
BOOL distanceArrive = ABS(point.y - _interactStartPoint.y) > containerSize.height * profile.dismissScale;
BOOL shouldDismiss = distanceArrive || velocityArrive;
if (shouldDismiss) {
[self hideBrowser];
} else {
[self restoreGestureInteractionWithDuration:profile.restoreDuration];
}
}
} else if (pan.state == UIGestureRecognizerStateChanged) {
if (_interacting) {
// Change
self.videoView.center = point;
CGFloat scale = 1 - ABS(point.y - _interactStartPoint.y) / (containerSize.height * 1.2);
if (scale > 1) scale = 1;
if (scale < 0.35) scale = 0.35;
self.videoView.transform = CGAffineTransformMakeScale(scale, scale);
CGFloat alpha = 1 - ABS(point.y - _interactStartPoint.y) / (containerSize.height * 0.7);
if (alpha > 1) alpha = 1;
if (alpha < 0) alpha = 0;
self.yb_backView.backgroundColor = [self.yb_backView.backgroundColor colorWithAlphaComponent:alpha];
} else {
// Start
if (CGPointEqualToPoint(_interactStartPoint, CGPointZero) || self.yb_currentPage() != self.yb_selfPage() || !self.yb_cellIsInCenter() || self.videoView.actionBar.isTouchInside) return;
CGPoint velocityPoint = [pan velocityInView:self.videoView];
CGFloat triggerDistance = profile.triggerDistance;
BOOL distanceArrive = ABS(point.y - _interactStartPoint.y) > triggerDistance && (ABS(point.x - _interactStartPoint.x) < triggerDistance && ABS(velocityPoint.x) < 500);
BOOL shouldStart = distanceArrive;
if (!shouldStart) return;
[self.videoView hideToolBar:YES];
_interactStartPoint = point;
CGRect startFrame = self.videoView.bounds;
CGFloat anchorX = (point.x - startFrame.origin.x) / startFrame.size.width,
anchorY = (point.y - startFrame.origin.y) / startFrame.size.height;
self.videoView.layer.anchorPoint = CGPointMake(anchorX, anchorY);
self.videoView.userInteractionEnabled = NO;
self.videoView.center = point;
[self hideToolViews:YES];
self.yb_hideStatusBar(NO);
self.yb_collectionView().scrollEnabled = NO;
_interacting = YES;
}
}
}
- (void)restoreGestureInteractionWithDuration:(NSTimeInterval)duration {
[self.videoView hideToolBar:NO];
CGSize containerSize = self.yb_containerSize(self.yb_currentOrientation());
void (^animations)(void) = ^{
self.yb_backView.backgroundColor = [self.yb_backView.backgroundColor colorWithAlphaComponent:1];
CGPoint anchorPoint = self.videoView.layer.anchorPoint;
self.videoView.center = CGPointMake(containerSize.width * anchorPoint.x, containerSize.height * anchorPoint.y);
self.videoView.transform = CGAffineTransformIdentity;
};
void (^completion)(BOOL finished) = ^(BOOL finished){
self.videoView.layer.anchorPoint = CGPointMake(0.5, 0.5);
self.videoView.center = CGPointMake(containerSize.width * 0.5, containerSize.height * 0.5);
self.videoView.userInteractionEnabled = YES;
self.yb_hideStatusBar(YES);
self.yb_collectionView().scrollEnabled = YES;
if (!self.videoView.isPlaying) [self hideToolViews:NO];;
self->_interactStartPoint = CGPointZero;
self->_interacting = NO;
};
if (duration <= 0) {
animations();
completion(NO);
} else {
[UIView animateWithDuration:duration animations:animations completion:completion];
}
}
#pragma mark - getters & setters
- (YBIBVideoView *)videoView {
if (!_videoView) {
_videoView = [YBIBVideoView new];
_videoView.delegate = self;
}
return _videoView;
}
@end

View File

@@ -0,0 +1,50 @@
//
// YBIBVideoData+Internal.h
// YBImageBrowserDemo
//
// Created by 波儿菜 on 2019/7/11.
// Copyright © 2019 杨波. All rights reserved.
//
#import "YBIBVideoData.h"
NS_ASSUME_NONNULL_BEGIN
@class YBIBVideoData;
@protocol YBIBVideoDataDelegate <NSObject>
@required
- (void)yb_startLoadingAVAssetFromPHAssetForData:(YBIBVideoData *)data;
- (void)yb_finishLoadingAVAssetFromPHAssetForData:(YBIBVideoData *)data;
- (void)yb_startLoadingFirstFrameForData:(YBIBVideoData *)data;
- (void)yb_finishLoadingFirstFrameForData:(YBIBVideoData *)data;
- (void)yb_videoData:(YBIBVideoData *)data downloadingWithProgress:(CGFloat)progress;
- (void)yb_finishDownloadingForData:(YBIBVideoData *)data;
- (void)yb_videoData:(YBIBVideoData *)data readyForThumbImage:(UIImage *)image;
- (void)yb_videoData:(YBIBVideoData *)data readyForAVAsset:(AVAsset *)asset;
- (void)yb_videoIsInvalidForData:(YBIBVideoData *)data;
@end
@interface YBIBVideoData ()
@property (nonatomic, assign, getter=isLoadingAVAssetFromPHAsset) BOOL loadingAVAssetFromPHAsset;
@property (nonatomic, assign, getter=isLoadingFirstFrame) BOOL loadingFirstFrame;
@property (nonatomic, assign, getter=isDownloading) BOOL downloading;
@property (nonatomic, weak) id<YBIBVideoDataDelegate> delegate;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,64 @@
//
// YBIBVideoData.h
// YBImageBrowserDemo
//
// Created by 波儿菜 on 2019/7/10.
// Copyright © 2019 杨波. All rights reserved.
//
#import <Photos/Photos.h>
#import "YBIBDataProtocol.h"
#import "YBIBInteractionProfile.h"
NS_ASSUME_NONNULL_BEGIN
@class YBIBVideoData;
/// 单击事件的处理闭包
typedef void (^YBIBVideoSingleTouchBlock)(YBIBVideoData *videoData);
/**
图片数据类,承担配置数据和处理数据的责任
*/
@interface YBIBVideoData : NSObject <YBIBDataProtocol>
/// 视频 URL
@property (nonatomic, copy, nullable) NSURL *videoURL;
/// 相册视频资源
@property (nonatomic, strong, nullable) PHAsset *videoPHAsset;
/// 视频 AVAsset (通常使用 AVURLAsset)
@property (nonatomic, strong, nullable) AVAsset *videoAVAsset;
/// 投影视图,当前数据模型对应外界业务的 UIView (通常为 UIImageView),做转场动效用
@property (nonatomic, weak, nullable) __kindof UIView *projectiveView;
/// 预览图/缩约图,若 projectiveView 存在且是 UIImageView 类型将会自动获取缩约图
@property (nonatomic, strong, nullable) UIImage *thumbImage;
/// 是否允许保存到相册
@property (nonatomic, assign) BOOL allowSaveToPhotoAlbum;
/// 自动播放次数,默认为 0NSUIntegerMax 表示无限次
@property (nonatomic, assign) NSUInteger autoPlayCount;
/// 重复播放次数,默认为 0NSUIntegerMax 表示无限次
@property (nonatomic, assign) NSUInteger repeatPlayCount;
/// 预留属性可随意使用
@property (nonatomic, strong, nullable) id extraData;
/// 手势交互动效配置文件
@property (nonatomic, strong) YBIBInteractionProfile *interactionProfile;
/// 单击的处理(视频未播放时),默认是退出图片浏览器
@property (nonatomic, copy, nullable) YBIBVideoSingleTouchBlock singleTouchBlock;
/// 是否要隐藏播放时的叉叉(取消)按钮
@property (nonatomic, assign) BOOL shouldHideForkButton;
@end
NS_ASSUME_NONNULL_END

296
Pods/YBImageBrowser/Video/YBIBVideoData.m generated Normal file
View File

@@ -0,0 +1,296 @@
//
// YBIBVideoData.m
// YBImageBrowserDemo
//
// Created by on 2019/7/10.
// Copyright © 2019 . All rights reserved.
//
#import "YBIBVideoData.h"
#import "YBIBVideoCell.h"
#import "YBIBVideoData+Internal.h"
#import "YBIBUtilities.h"
#import "YBIBPhotoAlbumManager.h"
#import "YBIBCopywriter.h"
extern CGImageRef YYCGImageCreateDecodedCopy(CGImageRef imageRef, BOOL decodeForDisplay);
@interface YBIBVideoData () <NSURLSessionDelegate>
@end
@implementation YBIBVideoData {
NSURLSessionDownloadTask *_downloadTask;
}
#pragma mark - life cycle
- (instancetype)init {
self = [super init];
if (self) {
[self initValue];
}
return self;
}
- (void)initValue {
_loadingFirstFrame = NO;
_loadingAVAssetFromPHAsset = NO;
_downloading = NO;
_interactionProfile = [YBIBInteractionProfile new];
_repeatPlayCount = 0;
_autoPlayCount = 0;
_shouldHideForkButton = NO;
_allowSaveToPhotoAlbum = YES;
}
#pragma mark - load data
- (void)loadData {
// Always load 'thumbImage'.
[self loadThumbImage];
if (self.videoAVAsset) {
[self.delegate yb_videoData:self readyForAVAsset:self.videoAVAsset];
} else if (self.videoPHAsset) {
[self loadAVAssetFromPHAsset];
} else {
[self.delegate yb_videoIsInvalidForData:self];
}
}
- (void)loadAVAssetFromPHAsset {
if (!self.videoPHAsset) return;
if (self.isLoadingAVAssetFromPHAsset) {
self.loadingAVAssetFromPHAsset = YES;
return;
}
self.loadingAVAssetFromPHAsset = YES;
[YBIBPhotoAlbumManager getAVAssetWithPHAsset:self.videoPHAsset completion:^(AVAsset * _Nullable asset) {
YBIB_DISPATCH_ASYNC_MAIN(^{
self.loadingAVAssetFromPHAsset = NO;
self.videoAVAsset = asset;
[self.delegate yb_videoData:self readyForAVAsset:self.videoAVAsset];
[self loadThumbImage];
})
}];
}
- (void)loadThumbImage {
if (self.thumbImage) {
[self.delegate yb_videoData:self readyForThumbImage:self.thumbImage];
} else if (self.projectiveView && [self.projectiveView isKindOfClass:UIImageView.self] && ((UIImageView *)self.projectiveView).image) {
self.thumbImage = ((UIImageView *)self.projectiveView).image;
[self.delegate yb_videoData:self readyForThumbImage:self.thumbImage];
} else {
[self loadThumbImage_firstFrame];
}
}
- (void)loadThumbImage_firstFrame {
if (!self.videoAVAsset) return;
if (self.isLoadingFirstFrame) {
self.loadingFirstFrame = YES;
return;
}
self.loadingFirstFrame = YES;
CGSize containerSize = self.yb_containerSize(self.yb_currentOrientation());
CGSize maximumSize = containerSize;
__weak typeof(self) wSelf = self;
YBIB_DISPATCH_ASYNC(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
AVAssetImageGenerator *generator = [AVAssetImageGenerator assetImageGeneratorWithAsset:self.videoAVAsset];
generator.appliesPreferredTrackTransform = YES;
generator.maximumSize = maximumSize;
NSError *error = nil;
CGImageRef cgImage = [generator copyCGImageAtTime:CMTimeMake(0, 1) actualTime:NULL error:&error];
CGImageRef decodedImage = YYCGImageCreateDecodedCopy(cgImage, YES);
UIImage *resultImage = [UIImage imageWithCGImage:decodedImage];
if (cgImage) CGImageRelease(cgImage);
if (decodedImage) CGImageRelease(decodedImage);
YBIB_DISPATCH_ASYNC_MAIN(^{
__strong typeof(wSelf) self = wSelf;
if (!self) return;
self.loadingFirstFrame = NO;
if (!error && resultImage) {
self.thumbImage = resultImage;
[self.delegate yb_videoData:self readyForThumbImage:self.thumbImage];
}
})
})
}
#pragma mark - <YBIBDataProtocol>
@synthesize yb_currentOrientation = _yb_currentOrientation;
@synthesize yb_containerView = _yb_containerView;
@synthesize yb_containerSize = _yb_containerSize;
@synthesize yb_isHideTransitioning = _yb_isHideTransitioning;
@synthesize yb_auxiliaryViewHandler = _yb_auxiliaryViewHandler;
- (nonnull Class)yb_classOfCell {
return YBIBVideoCell.self;
}
- (UIView *)yb_projectiveView {
return self.projectiveView;
}
- (CGRect)yb_imageViewFrameWithContainerSize:(CGSize)containerSize imageSize:(CGSize)imageSize orientation:(UIDeviceOrientation)orientation {
if (containerSize.width <= 0 || containerSize.height <= 0 || imageSize.width <= 0 || imageSize.height <= 0) return CGRectZero;
CGFloat x = 0, y = 0, width = 0, height = 0;
if (imageSize.width / imageSize.height >= containerSize.width / containerSize.height) {
width = containerSize.width;
height = containerSize.width * (imageSize.height / imageSize.width);
x = 0;
y = (containerSize.height - height) / 2.0;
} else {
height = containerSize.height;
width = containerSize.height * (imageSize.width / imageSize.height);
x = (containerSize.width - width) / 2.0;
y = 0;
}
return CGRectMake(x, y, width, height);
}
- (void)yb_preload {
if (!self.delegate) {
[self loadData];
}
}
- (BOOL)yb_allowSaveToPhotoAlbum {
return self.allowSaveToPhotoAlbum;
}
- (void)yb_saveToPhotoAlbum {
void(^unableToSave)(void) = ^(){
[self.yb_auxiliaryViewHandler() yb_showIncorrectToastWithContainer:self.yb_containerView text:[YBIBCopywriter sharedCopywriter].unableToSave];
};
if (self.videoAVAsset && [self.videoAVAsset isKindOfClass:AVURLAsset.class]) {
AVURLAsset *asset = (AVURLAsset *)self.videoAVAsset;
NSURL *URL = asset.URL;
if ([URL.scheme isEqualToString:@"file"]) {
NSString *path = URL.path;
if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(path)) {
UISaveVideoAtPathToSavedPhotosAlbum(path, self, @selector(UISaveVideoAtPathToSavedPhotosAlbum_videoPath:didFinishSavingWithError:contextInfo:), nil);
} else {
unableToSave();
}
} else if ([URL.scheme containsString:@"http"]) {
[self downloadWithURL:URL];
} else {
unableToSave();
}
} else {
unableToSave();
}
}
#pragma mark - private
- (void)UISaveVideoAtPathToSavedPhotosAlbum_videoPath:(NSString *)videoPath didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo{
if (error) {
[self.yb_auxiliaryViewHandler() yb_showIncorrectToastWithContainer:self.yb_containerView text:[YBIBCopywriter sharedCopywriter].saveToPhotoAlbumFailed];
} else {
[self.yb_auxiliaryViewHandler() yb_showCorrectToastWithContainer:self.yb_containerView text:[YBIBCopywriter sharedCopywriter].saveToPhotoAlbumSuccess];
}
}
- (void)downloadWithURL:(NSURL *)URL {
if (self.isDownloading) {
self.downloading = YES;
return;
}
self.downloading = YES;
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];
_downloadTask = [session downloadTaskWithURL:URL];
[_downloadTask resume];
}
#pragma mark - <NSURLSessionDelegate>
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
CGFloat progress = totalBytesWritten / (double)totalBytesExpectedToWrite;
if (progress < 0) progress = 0;
if (progress > 1) progress = 1;
[self.delegate yb_videoData:self downloadingWithProgress:progress];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(nullable NSError *)error {
if (error) {
[self.yb_auxiliaryViewHandler() yb_showIncorrectToastWithContainer:self.yb_containerView text:[YBIBCopywriter sharedCopywriter].downloadFailed];
}
self.downloading = NO;
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location {
NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString *file = [cache stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
[[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:file] error:nil];
if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(file)) {
UISaveVideoAtPathToSavedPhotosAlbum(file, self, @selector(UISaveVideoAtPathToSavedPhotosAlbum_videoPath:didFinishSavingWithError:contextInfo:), nil);
} else {
[self.yb_auxiliaryViewHandler() yb_showIncorrectToastWithContainer:self.yb_containerView text:[YBIBCopywriter sharedCopywriter].saveToPhotoAlbumFailed];
}
self.downloading = NO;
}
#pragma mark - getters & setters
- (void)setVideoURL:(NSURL *)videoURL{
_videoURL = [videoURL isKindOfClass:NSString.class] ? [NSURL URLWithString:(NSString *)videoURL] : videoURL;
self.videoAVAsset = [AVURLAsset URLAssetWithURL:_videoURL options:nil];
}
- (void)setDownloading:(BOOL)downloading {
_downloading = downloading;
if (downloading) {
[self.delegate yb_videoData:self downloadingWithProgress:0];
} else {
[self.delegate yb_finishDownloadingForData:self];
}
}
- (void)setLoadingAVAssetFromPHAsset:(BOOL)loadingAVAssetFromPHAsset {
_loadingAVAssetFromPHAsset = loadingAVAssetFromPHAsset;
if (loadingAVAssetFromPHAsset) {
[self.delegate yb_startLoadingAVAssetFromPHAssetForData:self];
} else {
[self.delegate yb_finishLoadingAVAssetFromPHAssetForData:self];
}
}
- (void)setLoadingFirstFrame:(BOOL)loadingFirstFrame {
_loadingFirstFrame = loadingFirstFrame;
if (loadingFirstFrame) {
[self.delegate yb_startLoadingFirstFrameForData:self];
} else {
[self.delegate yb_finishLoadingFirstFrameForData:self];
}
}
@synthesize delegate = _delegate;
- (void)setDelegate:(id<YBIBVideoDataDelegate>)delegate {
_delegate = delegate;
if (delegate) {
[self loadData];
}
}
- (id<YBIBVideoDataDelegate>)delegate {
// Stop sending data to the '_delegate' if it is transiting.
return self.yb_isHideTransitioning() ? nil : _delegate;
}
@end

View File

@@ -0,0 +1,21 @@
//
// YBIBVideoTopBar.h
// YBImageBrowserDemo
//
// Created by 波儿菜 on 2019/7/11.
// Copyright © 2019 杨波. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface YBIBVideoTopBar : UIView
@property (nonatomic, strong, readonly) UIButton *cancelButton;
+ (CGFloat)defaultHeight;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,54 @@
//
// YBIBVideoTopBar.m
// YBImageBrowserDemo
//
// Created by on 2019/7/11.
// Copyright © 2019 . All rights reserved.
//
#import "YBIBVideoTopBar.h"
#import "YBIBIconManager.h"
@interface YBIBVideoTopBar ()
@property (nonatomic, strong) UIButton *cancelButton;
@end
@implementation YBIBVideoTopBar
#pragma mark - life cycle
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self addSubview:self.cancelButton];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
CGFloat buttonWidth = 54;
self.cancelButton.frame = CGRectMake(0, 0, buttonWidth, self.bounds.size.height);
}
#pragma mark - public
+ (CGFloat)defaultHeight {
return 50;
}
#pragma mark - getter
- (UIButton *)cancelButton {
if (!_cancelButton) {
_cancelButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_cancelButton setImage:YBIBIconManager.sharedManager.videoCancelImage() forState:UIControlStateNormal];
_cancelButton.layer.shadowColor = UIColor.darkGrayColor.CGColor;
_cancelButton.layer.shadowOffset = CGSizeMake(0, 1);
_cancelButton.layer.shadowOpacity = 1;
_cancelButton.layer.shadowRadius = 4;
}
return _cancelButton;
}
@end

View File

@@ -0,0 +1,76 @@
//
// YBIBVideoView.h
// YBImageBrowserDemo
//
// Created by 波儿菜 on 2019/7/11.
// Copyright © 2019 杨波. All rights reserved.
//
#import "YBIBVideoActionBar.h"
#import "YBIBVideoTopBar.h"
NS_ASSUME_NONNULL_BEGIN
@class YBIBVideoView;
@protocol YBIBVideoViewDelegate <NSObject>
@required
- (BOOL)yb_isFreezingForVideoView:(YBIBVideoView *)view;
- (void)yb_preparePlayForVideoView:(YBIBVideoView *)view;
- (void)yb_startPlayForVideoView:(YBIBVideoView *)view;
- (void)yb_finishPlayForVideoView:(YBIBVideoView *)view;
- (void)yb_didPlayToEndTimeForVideoView:(YBIBVideoView *)view;
- (void)yb_playFailedForVideoView:(YBIBVideoView *)view;
- (void)yb_respondsToTapGestureForVideoView:(YBIBVideoView *)view;
- (void)yb_cancelledForVideoView:(YBIBVideoView *)view;
- (CGSize)yb_containerSizeForVideoView:(YBIBVideoView *)view;
- (void)yb_autoPlayCountChanged:(NSUInteger)count;
@end
@interface YBIBVideoView : UIView
@property (nonatomic, strong) UIImageView *thumbImageView;
@property (nonatomic, weak) id<YBIBVideoViewDelegate> delegate;
- (void)updateLayoutWithExpectOrientation:(UIDeviceOrientation)orientation containerSize:(CGSize)containerSize;
@property (nonatomic, strong, nullable) AVAsset *asset;
@property (nonatomic, assign, readonly, getter=isPlaying) BOOL playing;
@property (nonatomic, assign, readonly, getter=isPlayFailed) BOOL playFailed;
@property (nonatomic, assign, readonly, getter=isPreparingPlay) BOOL preparingPlay;
@property (nonatomic, strong, readonly) UITapGestureRecognizer *tapGesture;
- (void)reset;
- (void)hideToolBar:(BOOL)hide;
- (void)hidePlayButton;
- (void)preparPlay;
@property (nonatomic, assign) BOOL needAutoPlay;
@property (nonatomic, assign) NSUInteger autoPlayCount;
@property (nonatomic, strong, readonly) YBIBVideoTopBar *topBar;
@property (nonatomic, strong, readonly) YBIBVideoActionBar *actionBar;
@end
NS_ASSUME_NONNULL_END

394
Pods/YBImageBrowser/Video/YBIBVideoView.m generated Normal file
View File

@@ -0,0 +1,394 @@
//
// YBIBVideoView.m
// YBImageBrowserDemo
//
// Created by on 2019/7/11.
// Copyright © 2019 . All rights reserved.
//
#import <AVFoundation/AVFoundation.h>
#import "YBIBVideoView.h"
#import "YBIBVideoActionBar.h"
#import "YBIBVideoTopBar.h"
#import "YBIBUtilities.h"
#import "YBIBIconManager.h"
@interface YBIBVideoView () <YBIBVideoActionBarDelegate>
@property (nonatomic, strong) YBIBVideoTopBar *topBar;
@property (nonatomic, strong) YBIBVideoActionBar *actionBar;
@property (nonatomic, strong) UIButton *playButton;
@end
@implementation YBIBVideoView {
AVPlayer *_player;
AVPlayerItem *_playerItem;
AVPlayerLayer *_playerLayer;
BOOL _active;
}
#pragma mark - life cycle
- (void)dealloc {
[self removeObserverForSystem];
[self reset];
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self initValue];
self.backgroundColor = UIColor.clearColor;
[self addSubview:self.thumbImageView];
[self addSubview:self.topBar];
[self addSubview:self.actionBar];
[self addSubview:self.playButton];
[self addObserverForSystem];
_tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(respondsToTapGesture:)];
[self addGestureRecognizer:_tapGesture];
}
return self;
}
- (void)initValue {
_playing = NO;
_active = YES;
_needAutoPlay = NO;
_autoPlayCount = 0;
_playFailed = NO;
_preparingPlay = NO;
}
#pragma mark - public
- (void)updateLayoutWithExpectOrientation:(UIDeviceOrientation)orientation containerSize:(CGSize)containerSize {
UIEdgeInsets padding = YBIBPaddingByBrowserOrientation(orientation);
CGFloat width = containerSize.width - padding.left - padding.right, height = containerSize.height;
self.topBar.frame = CGRectMake(padding.left, padding.top, width, [YBIBVideoTopBar defaultHeight]);
self.actionBar.frame = CGRectMake(padding.left, height - [YBIBVideoActionBar defaultHeight] - padding.bottom - 10, width, [YBIBVideoActionBar defaultHeight]);
self.playButton.center = CGPointMake(containerSize.width / 2.0, containerSize.height / 2.0);
_playerLayer.frame = (CGRect){CGPointZero, containerSize};
}
- (void)reset {
[self removeObserverForPlayer];
// If set '_playerLayer.player = nil' or '_player = nil', can not cancel observeing of 'addPeriodicTimeObserverForInterval'.
[_player pause];
_playerItem = nil;
[_playerLayer removeFromSuperlayer];
_playerLayer = nil;
[self finishPlay];
}
- (void)hideToolBar:(BOOL)hide {
if (hide) {
self.actionBar.hidden = YES;
self.topBar.hidden = YES;
} else if (self.isPlaying) {
self.actionBar.hidden = NO;
self.topBar.hidden = NO;
}
}
- (void)hidePlayButton {
self.playButton.hidden = YES;
}
#pragma mark - private
- (void)videoJumpWithScale:(float)scale {
CMTime startTime = CMTimeMakeWithSeconds(scale, _player.currentTime.timescale);
AVPlayer *tmpPlayer = _player;
if (CMTIME_IS_INDEFINITE(startTime) || CMTIME_IS_INVALID(startTime)) return;
[_player seekToTime:startTime toleranceBefore:CMTimeMake(1, 1000) toleranceAfter:CMTimeMake(1, 1000) completionHandler:^(BOOL finished) {
if (finished && tmpPlayer == self->_player) {
[self startPlay];
}
}];
}
- (void)preparPlay {
_preparingPlay = YES;
_playFailed = NO;
self.playButton.hidden = YES;
[self.delegate yb_preparePlayForVideoView:self];
if (!_playerLayer) {
_playerItem = [AVPlayerItem playerItemWithAsset:self.asset];
_player = [AVPlayer playerWithPlayerItem:_playerItem];
_playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player];
_playerLayer.frame = (CGRect){CGPointZero, [self.delegate yb_containerSizeForVideoView:self]};
[self.layer insertSublayer:_playerLayer above:self.thumbImageView.layer];
[self addObserverForPlayer];
} else {
[self videoJumpWithScale:0];
}
}
- (void)startPlay {
if (_player) {
_playing = YES;
[_player play];
[self.actionBar play];
self.topBar.hidden = NO;
self.actionBar.hidden = NO;
[self.delegate yb_startPlayForVideoView:self];
}
}
- (void)finishPlay {
self.playButton.hidden = NO;
[self.actionBar setCurrentValue:0];
self.actionBar.hidden = YES;
self.topBar.hidden = YES;
_playing = NO;
[self.delegate yb_finishPlayForVideoView:self];
}
- (void)playerPause {
if (_player) {
[_player pause];
[self.actionBar pause];
}
}
- (BOOL)autoPlay {
if (self.autoPlayCount == NSUIntegerMax) {
[self preparPlay];
} else if (self.autoPlayCount > 0) {
--self.autoPlayCount;
[self.delegate yb_autoPlayCountChanged:self.autoPlayCount];
[self preparPlay];
} else {
return NO;
}
return YES;
}
#pragma mark - <YBIBVideoActionBarDelegate>
- (void)yb_videoActionBar:(YBIBVideoActionBar *)actionBar clickPlayButton:(UIButton *)playButton {
[self startPlay];
}
- (void)yb_videoActionBar:(YBIBVideoActionBar *)actionBar clickPauseButton:(UIButton *)pauseButton {
[self playerPause];
}
- (void)yb_videoActionBar:(YBIBVideoActionBar *)actionBar changeValue:(float)value {
[self videoJumpWithScale:value];
}
#pragma mark - observe
- (void)addObserverForPlayer {
[_playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
__weak typeof(self) wSelf = self;
[_player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
__strong typeof(wSelf) self = wSelf;
if (!self) return;
float currentTime = time.value / time.timescale;
[self.actionBar setCurrentValue:currentTime];
}];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didPlayToEndTime:) name:AVPlayerItemDidPlayToEndTimeNotification object:_playerItem];
}
- (void)removeObserverForPlayer {
[_playerItem removeObserver:self forKeyPath:@"status"];
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:_playerItem];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if (![self.delegate yb_isFreezingForVideoView:self]) {
if (object == _playerItem) {
if ([keyPath isEqualToString:@"status"]) {
[self playerItemStatusChanged];
}
}
}
}
- (void)didPlayToEndTime:(NSNotification *)noti {
if (noti.object == _playerItem) {
[self finishPlay];
[self.delegate yb_didPlayToEndTimeForVideoView:self];
}
}
- (void)playerItemStatusChanged {
if (!_active) return;
_preparingPlay = NO;
switch (_playerItem.status) {
case AVPlayerItemStatusReadyToPlay: {
// Delay to update UI.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.15 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self startPlay];
double max = CMTimeGetSeconds(self->_playerItem.duration);
[self.actionBar setMaxValue:(isnan(max) || isinf(max)) ? 0 : max];
});
}
break;
case AVPlayerItemStatusUnknown: {
_playFailed = YES;
[self.delegate yb_playFailedForVideoView:self];
[self reset];
}
break;
case AVPlayerItemStatusFailed: {
_playFailed = YES;
[self.delegate yb_playFailedForVideoView:self];
[self reset];
}
break;
}
}
- (void)removeObserverForSystem {
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidChangeStatusBarFrameNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionRouteChangeNotification object:nil];
}
- (void)addObserverForSystem {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didChangeStatusBarFrame) name:UIApplicationDidChangeStatusBarFrameNotification object:nil];
[[AVAudioSession sharedInstance] setActive:YES error:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioRouteChangeListenerCallback:) name:AVAudioSessionRouteChangeNotification object:nil];
}
- (void)applicationWillResignActive:(NSNotification *)notification {
_active = NO;
[self playerPause];
}
- (void)applicationDidBecomeActive:(NSNotification *)notification {
_active = YES;
}
- (void)didChangeStatusBarFrame {
if ([UIApplication sharedApplication].statusBarFrame.size.height > YBIBStatusbarHeight()) {
[self playerPause];
}
}
- (void)audioRouteChangeListenerCallback:(NSNotification*)notification {
YBIB_DISPATCH_ASYNC_MAIN(^{
NSDictionary *interuptionDict = notification.userInfo;
NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
switch (routeChangeReason) {
case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
[self playerPause];
break;
}
})
}
#pragma mark - event
- (void)respondsToTapGesture:(UITapGestureRecognizer *)tap {
if (self.isPlaying) {
self.actionBar.hidden = !self.actionBar.isHidden;
self.topBar.hidden = !self.topBar.isHidden;
} else {
[self.delegate yb_respondsToTapGestureForVideoView:self];
}
}
- (void)clickCancelButton:(UIButton *)button {
[self.delegate yb_cancelledForVideoView:self];
}
- (void)clickPlayButton:(UIButton *)button {
[self preparPlay];
}
#pragma mark - getters & setters
- (void)setNeedAutoPlay:(BOOL)needAutoPlay {
if (needAutoPlay && _asset && !self.isPlaying) {
[self autoPlay];
} else {
_needAutoPlay = needAutoPlay;
}
}
@synthesize asset = _asset;
- (void)setAsset:(AVAsset *)asset {
_asset = asset;
if (!asset) return;
if (self.needAutoPlay) {
if (![self autoPlay]) {
self.playButton.hidden = NO;
}
self.needAutoPlay = NO;
} else {
self.playButton.hidden = NO;
}
}
- (AVAsset *)asset {
if ([_asset isKindOfClass:AVURLAsset.class]) {
_asset = [AVURLAsset assetWithURL:((AVURLAsset *)_asset).URL];
}
return _asset;
}
- (YBIBVideoTopBar *)topBar {
if (!_topBar) {
_topBar = [YBIBVideoTopBar new];
[_topBar.cancelButton addTarget:self action:@selector(clickCancelButton:) forControlEvents:UIControlEventTouchUpInside];
_topBar.hidden = YES;
}
return _topBar;
}
- (YBIBVideoActionBar *)actionBar {
if (!_actionBar) {
_actionBar = [YBIBVideoActionBar new];
_actionBar.delegate = self;
_actionBar.hidden = YES;
}
return _actionBar;
}
- (UIButton *)playButton {
if (!_playButton) {
_playButton = [UIButton buttonWithType:UIButtonTypeCustom];
_playButton.bounds = CGRectMake(0, 0, 100, 100);
[_playButton setImage:YBIBIconManager.sharedManager.videoBigPlayImage() forState:UIControlStateNormal];
[_playButton addTarget:self action:@selector(clickPlayButton:) forControlEvents:UIControlEventTouchUpInside];
_playButton.hidden = YES;
_playButton.layer.shadowColor = UIColor.darkGrayColor.CGColor;
_playButton.layer.shadowOffset = CGSizeMake(0, 1);
_playButton.layer.shadowOpacity = 1;
_playButton.layer.shadowRadius = 4;
}
return _playButton;
}
- (UIImageView *)thumbImageView {
if (!_thumbImageView) {
_thumbImageView = [UIImageView new];
_thumbImageView.contentMode = UIViewContentModeScaleAspectFit;
_thumbImageView.layer.masksToBounds = YES;
}
return _thumbImageView;
}
@end

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 850 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 799 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 961 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB