| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532 |
- // Created by Tencent on 2023/06/09.
- // Copyright © 2023 Tencent. All rights reserved.
- #import "TUIVideoCollectionCell.h"
- #import <TIMCommon/TIMDefine.h>
- #import "ReactiveObjC/ReactiveObjC.h"
- #import "TUICircleLodingView.h"
- @import MediaPlayer;
- @import AVFoundation;
- @import AVKit;
- @interface TUIVideoCollectionCellScrollView : UIScrollView <UIScrollViewDelegate>
- @property(nonatomic, strong) UIView *videoView;
- @property(assign, nonatomic) CGFloat videoViewNormalWidth;
- @property(assign, nonatomic) CGFloat videoViewNormalHeight;
- - (void)pictureZoomWithScale:(CGFloat)zoomScale;
- @end
- @implementation TUIVideoCollectionCellScrollView
- - (id)initWithFrame:(CGRect)frame {
- self = [super initWithFrame:frame];
- if (self) {
- self.delegate = self;
- self.minimumZoomScale = 1.0f;
- self.maximumZoomScale = 2.0f;
- _videoViewNormalHeight = frame.size.height;
- _videoViewNormalWidth = frame.size.width;
- self.videoView = [[UIView alloc] initWithFrame:frame];
- [self addSubview:self.videoView];
- if (@available(iOS 11.0, *)) {
- self.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
- } else {
- // Fallback on earlier versions
- }
- }
- return self;
- }
- - (void)drawRect:(CGRect)rect {
- [super drawRect:rect];
- }
- #pragma mark-- Help Methods
- - (void)pictureZoomWithScale:(CGFloat)zoomScale {
- CGFloat imageScaleWidth = zoomScale * self.videoViewNormalWidth;
- CGFloat imageScaleHeight = zoomScale * self.videoViewNormalHeight;
- CGFloat imageX = 0;
- CGFloat imageY = 0;
- if (imageScaleWidth < self.frame.size.width) {
- imageX = floorf((self.frame.size.width - imageScaleWidth) / 2.0);
- }
- if (imageScaleHeight < self.frame.size.height) {
- imageY = floorf((self.frame.size.height - imageScaleHeight) / 2.0);
- }
- self.videoView.frame = CGRectMake(imageX, imageY, imageScaleWidth, imageScaleHeight);
- self.contentSize = CGSizeMake(imageScaleWidth, imageScaleHeight);
- }
- #pragma mark-- Setter
- - (void)setVideoViewNormalWidth:(CGFloat)videoViewNormalWidth {
- _videoViewNormalWidth = videoViewNormalWidth;
- self.videoView.frame = CGRectMake(0, 0, _videoViewNormalWidth, _videoViewNormalHeight);
- self.videoView.center = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2);
- }
- - (void)setVideoViewNormalHeight:(CGFloat)videoViewNormalHeight {
- _videoViewNormalHeight = videoViewNormalHeight;
- self.videoView.frame = CGRectMake(0, 0, _videoViewNormalWidth, _videoViewNormalHeight);
- self.videoView.center = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2);
- }
- #pragma mark-- UIScrollViewDelegate
- // Returns the view control that needs to be zoomed. During zooming
- - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
- return self.videoView;
- }
- // BeginZooming
- - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view {
- NSLog(@"BeginZooming");
- }
- // EndZooming
- - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale {
- NSLog(@"EndZooming");
- }
- // zoom
- - (void)scrollViewDidZoom:(UIScrollView *)scrollView {
- CGFloat imageScaleWidth = scrollView.zoomScale * self.videoViewNormalWidth;
- CGFloat imageScaleHeight = scrollView.zoomScale * self.videoViewNormalHeight;
- CGFloat imageX = 0;
- CGFloat imageY = 0;
- if (imageScaleWidth < self.frame.size.width) {
- imageX = floorf((self.frame.size.width - imageScaleWidth) / 2.0);
- }
- if (imageScaleHeight < self.frame.size.height) {
- imageY = floorf((self.frame.size.height - imageScaleHeight) / 2.0);
- }
- self.videoView.frame = CGRectMake(imageX, imageY, imageScaleWidth, imageScaleHeight);
- }
- @end
- @interface TUIVideoCollectionCell ()
- @property(nonatomic, strong) TUIVideoCollectionCellScrollView *scrollView;
- @property(nonatomic, strong) UILabel *duration;
- @property(nonatomic, strong) UILabel *playTime;
- @property(nonatomic, strong) UISlider *playProcess;
- @property(nonatomic, strong) UIButton *mainPlayBtn;
- @property(nonatomic, strong) UIButton *mainDownloadBtn;
- @property(nonatomic, strong) UIButton *playBtn;
- @property(nonatomic, strong) UIButton *closeBtn;
- @property(nonatomic, strong) TUICircleLodingView *animateCircleView;
- @property(nonatomic, strong) AVPlayer *player;
- @property(nonatomic, strong) AVPlayerLayer *playerLayer;
- @property(nonatomic, strong) NSString *videoPath;
- @property(nonatomic, strong) NSURL *videoUrl;
- @property(nonatomic, assign) BOOL isPlay;
- @property(nonatomic, assign) BOOL isSaveVideo;
- @property(nonatomic, strong) TUIVideoMessageCellData *videoData;
- @end
- @implementation TUIVideoCollectionCell
- - (id)initWithFrame:(CGRect)frame {
- self = [super initWithFrame:frame];
- if (self) {
- [self setupViews];
- [self setupRotaionNotifications];
- }
- return self;
- }
- - (void)setupRotaionNotifications {
- if (@available(iOS 16.0, *)) {
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(onDeviceOrientationChange:)
- name:TUIMessageMediaViewDeviceOrientationChangeNotification
- object:nil];
- } else {
- [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(onDeviceOrientationChange:)
- name:UIDeviceOrientationDidChangeNotification
- object:nil];
- }
- }
- - (void)setupViews {
- self.scrollView = [[TUIVideoCollectionCellScrollView alloc] initWithFrame:CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height)];
- [self addSubview:self.scrollView];
- self.imageView = [[UIImageView alloc] init];
- self.imageView.layer.cornerRadius = 5.0;
- [self.imageView.layer setMasksToBounds:YES];
- self.imageView.contentMode = UIViewContentModeScaleAspectFit;
- self.imageView.backgroundColor = [UIColor clearColor];
- [self.scrollView.videoView addSubview:self.imageView];
- self.imageView.mm_fill();
- self.imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
- self.mainPlayBtn = [UIButton buttonWithType:UIButtonTypeCustom];
- self.mainPlayBtn.contentMode = UIViewContentModeScaleToFill;
- [self.mainPlayBtn setImage:TUIChatCommonBundleImage(@"video_play_big") forState:UIControlStateNormal];
- [self.mainPlayBtn addTarget:self action:@selector(onPlayBtnClick) forControlEvents:UIControlEventTouchUpInside];
- [self addSubview:self.mainPlayBtn];
- self.mainDownloadBtn = [UIButton buttonWithType:UIButtonTypeCustom];
- self.mainDownloadBtn.contentMode = UIViewContentModeScaleToFill;
- [self.mainDownloadBtn setImage:[[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath(@"download")] forState:UIControlStateNormal];
- self.mainDownloadBtn.hidden = YES;
- [self.mainDownloadBtn addTarget:self action:@selector(mainDownloadBtnClick) forControlEvents:UIControlEventTouchUpInside];
- [self addSubview:self.mainDownloadBtn];
- self.animateCircleView = [[TUICircleLodingView alloc] initWithFrame:CGRectMake(0, 0, kScale390(40), kScale390(40))];
- self.animateCircleView.progress = 0;
- self.animateCircleView.hidden = YES;
- [self addSubview:_animateCircleView];
- self.playBtn = [UIButton buttonWithType:UIButtonTypeCustom];
- self.playBtn.contentMode = UIViewContentModeScaleToFill;
- [self.playBtn setImage:TUIChatCommonBundleImage(@"video_play") forState:UIControlStateNormal];
- [self.playBtn addTarget:self action:@selector(onPlayBtnClick) forControlEvents:UIControlEventTouchUpInside];
- [self addSubview:self.playBtn];
- self.closeBtn = [UIButton buttonWithType:UIButtonTypeCustom];
- self.closeBtn.contentMode = UIViewContentModeScaleToFill;
- [self.closeBtn setImage:TUIChatCommonBundleImage(@"video_close") forState:UIControlStateNormal];
- [self.closeBtn addTarget:self action:@selector(onCloseBtnClick) forControlEvents:UIControlEventTouchUpInside];
- [self addSubview:self.closeBtn];
- self.downloadBtn = [UIButton buttonWithType:UIButtonTypeCustom];
- self.downloadBtn.contentMode = UIViewContentModeScaleToFill;
- [self.downloadBtn setImage:TUIChatCommonBundleImage(@"download") forState:UIControlStateNormal];
- [self.downloadBtn addTarget:self action:@selector(onDownloadBtnClick) forControlEvents:UIControlEventTouchUpInside];
- [self addSubview:self.downloadBtn];
- self.playTime = [[UILabel alloc] init];
- self.playTime.textColor = [UIColor whiteColor];
- self.playTime.font = [UIFont systemFontOfSize:12];
- self.playTime.text = @"00:00";
- [self addSubview:self.playTime];
- self.duration = [[UILabel alloc] init];
- self.duration.textColor = [UIColor whiteColor];
- self.duration.font = [UIFont systemFontOfSize:12];
- self.duration.text = @"00:00";
- [self addSubview:self.duration];
- self.playProcess = [[UISlider alloc] init];
- self.playProcess.minimumValue = 0;
- self.playProcess.maximumValue = 1;
- self.playProcess.minimumTrackTintColor = [UIColor whiteColor];
- [self.playProcess addTarget:self action:@selector(onSliderValueChangedBegin:) forControlEvents:UIControlEventTouchDown];
- [self.playProcess addTarget:self action:@selector(onSliderValueChanged:) forControlEvents:UIControlEventTouchUpInside];
- [self addSubview:self.playProcess];
- }
- - (void)fillWithData:(TUIVideoMessageCellData *)data;
- {
- [super fillWithData:data];
- self.videoData = data;
- self.isSaveVideo = NO;
- BOOL hasRiskContent = data.innerMessage.hasRiskContent;
- if (hasRiskContent) {
- self.imageView.image = TIMCommonBundleThemeImage(@"", @"icon_security_strike");
- for (UIView *subview in self.subviews) {
- if (subview != self.scrollView && subview != self.closeBtn){
- subview.hidden = YES;
- }
- }
- return;
- }
-
- CGFloat duration = data.videoItem.duration;
- self.duration.text = [NSString stringWithFormat:@"%.2d:%.2d", (int)duration / 60, (int)duration % 60];
- self.imageView.image = nil;
- if (data.thumbImage == nil) {
- [data downloadThumb];
- }
- @weakify(self);
- [[RACObserve(data, thumbImage) takeUntil:self.rac_prepareForReuseSignal] subscribeNext:^(UIImage *thumbImage) {
- @strongify(self);
- if (thumbImage) {
- self.imageView.image = thumbImage;
- }
- }];
- if (![self.videoData isVideoExist]) {
- self.mainDownloadBtn.hidden = NO;
- self.mainPlayBtn.hidden = YES;
- self.animateCircleView.hidden = YES;
- } else {
- /**
- *
- * Downloaded videos can be played directly using local video files
- */
- self.videoPath = self.videoData.videoPath;
- if (self.videoPath) {
- [self addPlayer:[NSURL fileURLWithPath:self.videoPath]];
- }
- }
- [[[RACObserve(self.videoData, videoProgress) takeUntil:self.rac_prepareForReuseSignal] distinctUntilChanged] subscribeNext:^(id _Nullable x) {
- @strongify(self);
- int progress = [x intValue];
- if (progress == 100) {
- self.animateCircleView.progress = 99;
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
- self.animateCircleView.progress = 100;
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
- self.animateCircleView.progress = 0;
- self.mainDownloadBtn.hidden = YES;
- self.animateCircleView.hidden = YES;
- self.mainPlayBtn.hidden = NO;
- });
- });
- } else if (progress > 1 && progress < 100) {
- self.animateCircleView.progress = progress;
- self.mainDownloadBtn.hidden = YES;
- self.animateCircleView.hidden = NO;
- } else {
- self.animateCircleView.progress = progress;
- }
- }];
- [[[RACObserve(self.videoData, videoPath) filter:^BOOL(NSString *path) {
- return [path length] > 0;
- }] take:1] subscribeNext:^(NSString *path) {
- @strongify(self);
- self.videoPath = path;
- if (self.isSaveVideo) {
- [self saveVideo];
- }
- self.animateCircleView.hidden = YES;
- [self addPlayer:[NSURL fileURLWithPath:self.videoPath]];
- }];
- }
- - (void)layoutSubviews {
- [super layoutSubviews];
- [self.mainDownloadBtn sizeToFit];
- self.mainDownloadBtn.mm_width(65).mm_height(65).mm__centerX(self.mm_w / 2).mm__centerY(self.mm_h / 2);
- self.mainDownloadBtn.layer.cornerRadius = (self.mainDownloadBtn.mm_h * 0.5);
- self.animateCircleView.tui_mm_center();
- self.mainPlayBtn.mm_width(65).mm_height(65).mm__centerX(self.mm_w / 2).mm__centerY(self.mm_h / 2);
- self.closeBtn.mm_width(31).mm_height(31).mm_left(16).mm_bottom(47);
- self.downloadBtn.mm_width(31).mm_height(31).mm_right(16).mm_bottom(48);
- self.playBtn.mm_width(30).mm_height(30).mm_left(32).mm_bottom(108);
- self.playTime.mm_width(40).mm_height(21).mm_left(self.playBtn.mm_maxX + 12).mm__centerY(self.playBtn.mm_centerY);
- self.duration.mm_width(40).mm_height(21).mm_right(15).mm__centerY(self.playBtn.mm_centerY);
- self.playProcess.mm_sizeToFit()
- .mm_left(self.playTime.mm_maxX + 10)
- .mm_flexToRight(self.duration.mm_r + self.duration.mm_w + 10)
- .mm__centerY(self.playBtn.mm_centerY);
- self.scrollView.mm_width(self.mm_w).mm_height(self.mm_h).mm__centerX(self.mm_w / 2).mm__centerY(self.mm_h / 2);
- self.scrollView.videoViewNormalWidth = self.mm_w;
- self.scrollView.videoViewNormalHeight = self.mm_h;
- self.playerLayer.frame = self.scrollView.bounds;
- [self.scrollView.videoView.layer layoutIfNeeded];
- }
- - (void)addPlayer:(NSURL *)url {
- self.videoUrl = url;
- if (!self.player) {
- self.player = [AVPlayer playerWithURL:self.videoUrl];
- self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
- self.playerLayer.frame = self.scrollView.videoView.bounds;
- [self.scrollView.videoView.layer insertSublayer:self.playerLayer atIndex:0];
- @weakify(self);
- [self.player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.05, 30)
- queue:NULL
- usingBlock:^(CMTime time) {
- @strongify(self);
- CGFloat curTime = CMTimeGetSeconds(self.player.currentItem.currentTime);
- CGFloat duration = CMTimeGetSeconds(self.player.currentItem.duration);
- CGFloat progress = curTime / duration;
- [self.playProcess setValue:progress];
- self.playTime.text = [NSString stringWithFormat:@"%.2d:%.2d", (int)curTime / 60, (int)curTime % 60];
- }];
- [self addPlayerItemObserver];
- } else {
- [self removePlayerItemObserver];
- AVPlayerItem *item = [AVPlayerItem playerItemWithURL:self.videoUrl];
- [self.player replaceCurrentItemWithPlayerItem:item];
- [self addPlayerItemObserver];
- }
- }
- - (void)addPlayerItemObserver {
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onVideoPlayEnd) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
- }
- - (void)removePlayerItemObserver {
- [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
- }
- - (void)stopVideoPlayAndSave {
- [self stopPlay];
- self.isSaveVideo = NO;
- [TUITool hideToast];
- }
- #pragma player event
- - (void)onPlayBtnClick {
- if (![self.videoData isVideoExist]) {
- [self.videoData downloadVideo];
- } else {
- if (self.isPlay) {
- [self stopPlay];
- } else {
- [self play];
- }
- }
- }
- - (void)onCloseBtnClick {
- [self stopPlay];
- if (self.delegate && [self.delegate respondsToSelector:@selector(onCloseMedia:)]) {
- [self.delegate onCloseMedia:self];
- }
- }
- - (void)onVideoPlayEnd {
- if (1 == self.playProcess.value) {
- [self.player seekToTime:CMTimeMakeWithSeconds(0, 30)];
- [self stopPlay];
- }
- }
- - (void)onSliderValueChangedBegin:(id)sender {
- [self.player pause];
- }
- - (void)onSliderValueChanged:(id)sender {
- UISlider *slider = (UISlider *)sender;
- CGFloat curTime = CMTimeGetSeconds(self.player.currentItem.duration) * slider.value;
- [self.player seekToTime:CMTimeMakeWithSeconds(curTime, 30)];
- [self play];
- }
- - (void)play {
- self.isPlay = YES;
- [self.player play];
- self.imageView.hidden = YES;
- self.mainPlayBtn.hidden = YES;
- [self.playBtn setImage:TUIChatCommonBundleImage(@"video_pause") forState:UIControlStateNormal];
- }
- - (void)stopPlay {
- BOOL hasRiskContent = self.videoData.innerMessage.hasRiskContent;
- self.isPlay = NO;
- [self.player pause];
- self.imageView.hidden = NO;
- if (!hasRiskContent) {
- self.mainPlayBtn.hidden = NO;
- }
- [self.playBtn setImage:TUIChatCommonBundleImage(@"video_play") forState:UIControlStateNormal];
- }
- - (void)mainDownloadBtnClick {
- if (![self.videoData isVideoExist]) {
- [self.videoData downloadVideo];
- }
- }
- #pragma video save
- - (void)onDownloadBtnClick {
- if (![self.videoData isVideoExist]) {
- self.isSaveVideo = YES;
- [TUITool makeToast:TIMCommonLocalizableString(TUIKitVideoDownloading) duration:CGFLOAT_MAX];
- } else {
- [self saveVideo];
- }
- }
- - (void)saveVideo {
- [TUITool hideToast];
- [[PHPhotoLibrary sharedPhotoLibrary]
- performChanges:^{
- PHAssetChangeRequest *request = [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:[NSURL fileURLWithPath:self.videoPath]];
- request.creationDate = [NSDate date];
- }
- completionHandler:^(BOOL success, NSError *_Nullable error) {
- dispatch_async(dispatch_get_main_queue(), ^{
- if (success) {
- [TUITool makeToast:TIMCommonLocalizableString(TUIKitVideoSavedSuccess) duration:1];
- } else {
- [TUITool makeToastError:-1 msg:TIMCommonLocalizableString(TUIKitVideoSavedFailed)];
- }
- });
- }];
- }
- - (void)onDeviceOrientationChange:(NSNotification *)noti {
- UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;
- [self reloadAllView];
- }
- - (void)reloadAllView {
- if (self.player) {
- [self stopPlay];
- self.player = nil;
- }
- if (self.playerLayer) {
- [self.playerLayer removeFromSuperlayer];
- self.playerLayer = nil;
- }
- for (UIView *subview in self.scrollView.subviews) {
- if (subview) {
- [subview removeFromSuperview];
- }
- }
- for (UIView *subview in self.subviews) {
- if (subview) {
- [subview removeFromSuperview];
- }
- }
- [self setupViews];
- if (self.videoData) {
- [self fillWithData:self.videoData];
- }
- }
- #pragma mark - TUIMessageProgressManagerDelegate
- - (void)onUploadProgress:(NSString *)msgID progress:(NSInteger)progress {
- if (![msgID isEqualToString:self.videoData.msgID]) {
- return;
- }
- if (self.videoData.direction == MsgDirectionOutgoing) {
- self.videoData.uploadProgress = progress;
- }
- }
- #pragma mark - V2TIMAdvancedMsgListener
- - (void)onRecvMessageModified:(V2TIMMessage *)msg {
- V2TIMMessage *imMsg = msg;
- if (imMsg == nil || ![imMsg isKindOfClass:V2TIMMessage.class]) {
- return;
- }
- if ([self.videoData.innerMessage.msgID isEqualToString:imMsg.msgID]) {
- BOOL hasRiskContent = msg.hasRiskContent;
- if (hasRiskContent) {
- self.videoData.innerMessage = imMsg;
- [self showRiskAlert];
- }
- }
- }
- - (void)showRiskAlert {
- if (self.player) {
- [self stopPlay];
- }
- UIAlertController *ac = [UIAlertController alertControllerWithTitle:nil
- message:TIMCommonLocalizableString(TUIKitVideoCheckRisk)
- preferredStyle:UIAlertControllerStyleAlert];
- __weak typeof(self) weakSelf = self;
- [ac tuitheme_addAction:[UIAlertAction actionWithTitle:TIMCommonLocalizableString(TUIKitVideoCheckRiskCancel)
- style:UIAlertActionStyleCancel
- handler:^(UIAlertAction *_Nonnull action) {
- __strong typeof(weakSelf) strongSelf = weakSelf;
- [strongSelf reloadAllView];
- }]];
- [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:ac animated:YES completion:nil];
- }
- @end
|