|
@@ -0,0 +1,644 @@
|
|
|
|
|
+//
|
|
|
|
|
+// VideoDetailCell.m
|
|
|
|
|
+// AICity
|
|
|
|
|
+//
|
|
|
|
|
+// Created by 张国栋 on 2025/10/22.
|
|
|
|
|
+// Copyright © 2025 wei.z. All rights reserved.
|
|
|
|
|
+//
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+#import "VideoDetailCell.h"
|
|
|
|
|
+#import "JXSuperPlayer.h"
|
|
|
|
|
+#import "JXDramaContent.h"
|
|
|
|
|
+#import "JXInteraction.h"
|
|
|
|
|
+#import "GuestHelper.h"
|
|
|
|
|
+#import "JXEpisode.h"
|
|
|
|
|
+@interface VideoDetailCell () <JXSuperPlayerDelegate>
|
|
|
|
|
+
|
|
|
|
|
+#pragma mark - UI组件
|
|
|
|
|
+// 播放器容器
|
|
|
|
|
+@property (nonatomic, strong) UIView *playerContainerView;
|
|
|
|
|
+
|
|
|
|
|
+// 右侧交互按钮组
|
|
|
|
|
+@property (nonatomic, strong) UIButton *likeButton;
|
|
|
|
|
+@property (nonatomic, strong) UILabel *likeCountLabel;
|
|
|
|
|
+@property (nonatomic, strong) UIButton *favoriteButton;
|
|
|
|
|
+@property (nonatomic, strong) UILabel *favoriteCountLabel;
|
|
|
|
|
+@property (nonatomic, strong) UIButton *commentButton;
|
|
|
|
|
+@property (nonatomic, strong) UILabel *commentCountLabel;
|
|
|
|
|
+@property (nonatomic, strong) UIButton *collectionButton;
|
|
|
|
|
+
|
|
|
|
|
+// 底部信息区
|
|
|
|
|
+@property (nonatomic, strong) UIView *bottomInfoView;
|
|
|
|
|
+@property (nonatomic, strong) UILabel *titleLabel;
|
|
|
|
|
+@property (nonatomic, strong) UILabel *descriptionLabel;
|
|
|
|
|
+
|
|
|
|
|
+#pragma mark - 数据 JXEpisode
|
|
|
|
|
+@property (nonatomic, strong) JXEpisode *drama;
|
|
|
|
|
+
|
|
|
|
|
+#pragma mark - 播放器
|
|
|
|
|
+@property (nonatomic, strong) JXSuperPlayer *superPlayer;
|
|
|
|
|
+@property (nonatomic, assign) JXSuperPlayerState playerState; // 播放器状态
|
|
|
|
|
+
|
|
|
|
|
+// 播放超时检测
|
|
|
|
|
+@property (nonatomic, strong) NSTimer *playTimeoutTimer; // 播放超时检测定时器
|
|
|
|
|
+@property (nonatomic, strong) NSDate *playStartTime; // 播放开始时间
|
|
|
|
|
+@property (strong, nonatomic) UISlider *videoSlider;
|
|
|
|
|
+
|
|
|
|
|
+@end
|
|
|
|
|
+
|
|
|
|
|
+@implementation VideoDetailCell
|
|
|
|
|
+
|
|
|
|
|
+#pragma mark - 初始化
|
|
|
|
|
+
|
|
|
|
|
+- (instancetype)initWithFrame:(CGRect)frame {
|
|
|
|
|
+ self = [super initWithFrame:frame];
|
|
|
|
|
+ if (self) {
|
|
|
|
|
+ [self setupUI];
|
|
|
|
|
+ }
|
|
|
|
|
+ return self;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)prepareForReuse {
|
|
|
|
|
+ [super prepareForReuse];
|
|
|
|
|
+
|
|
|
|
|
+ NSLog(@"[JXShortDramaCell] prepareForReuse - 停止当前播放");
|
|
|
|
|
+
|
|
|
|
|
+ // 只停止播放,不释放播放器实例
|
|
|
|
|
+ // 重用播放器实例可以避免频繁创建销毁导致的连接问题
|
|
|
|
|
+ [self stopPlay];
|
|
|
|
|
+
|
|
|
|
|
+ // 停止超时检测
|
|
|
|
|
+ [self stopPlayTimeoutTimer];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#pragma mark - UI设置
|
|
|
|
|
+
|
|
|
|
|
+- (void)setupUI {
|
|
|
|
|
+ self.contentView.backgroundColor = [UIColor blackColor];
|
|
|
|
|
+
|
|
|
|
|
+ // 创建播放器容器
|
|
|
|
|
+ [self setupPlayerContainer];
|
|
|
|
|
+
|
|
|
|
|
+ // 创建交互按钮组
|
|
|
|
|
+ [self setupInteractionButtons];
|
|
|
|
|
+
|
|
|
|
|
+ // 创建底部信息区
|
|
|
|
|
+ [self setupBottomInfo];
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)setupPlayerContainer {
|
|
|
|
|
+ self.playerContainerView = [[UIView alloc] init];
|
|
|
|
|
+ self.playerContainerView.backgroundColor = [UIColor blackColor];
|
|
|
|
|
+ [self.contentView addSubview:self.playerContainerView];
|
|
|
|
|
+
|
|
|
|
|
+ // 约束 - 播放器下边界贴近底部导航栏(44pt高)
|
|
|
|
|
+ self.playerContainerView.translatesAutoresizingMaskIntoConstraints = NO;
|
|
|
|
|
+ [NSLayoutConstraint activateConstraints:@[
|
|
|
|
|
+ [self.playerContainerView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor],
|
|
|
|
|
+ [self.playerContainerView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor],
|
|
|
|
|
+ [self.playerContainerView.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor],
|
|
|
|
|
+ [self.playerContainerView.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor constant:-44]
|
|
|
|
|
+ ]];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)setupInteractionButtons {
|
|
|
|
|
+// // 点赞按钮
|
|
|
|
|
+// [self addLikeButton];
|
|
|
|
|
+//
|
|
|
|
|
+// // 收藏按钮
|
|
|
|
|
+// [self addFavoriteButton];
|
|
|
|
|
+//
|
|
|
|
|
+// // 评论按钮
|
|
|
|
|
+// [self addCommentButton];
|
|
|
|
|
+//
|
|
|
|
|
+// // 合集按钮
|
|
|
|
|
+// [self addCollectionButton];
|
|
|
|
|
+
|
|
|
|
|
+ CGFloat width = self.contentView.bounds.size.width;
|
|
|
|
|
+ UIView *container = [[UIView alloc] initWithFrame:CGRectMake(width - 48, kScreenHeight - safebottom - 190 - 90, 32, 190)];
|
|
|
|
|
+ [self.contentView addSubview:container];
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ // 点赞
|
|
|
|
|
+ UIButton *likeButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
|
|
|
|
+ likeButton.frame = CGRectMake(0, 0, 32, 32);
|
|
|
|
|
+ [likeButton setImage:[UIImage imageNamed:@"icon 1.1"] forState:UIControlStateNormal];
|
|
|
|
|
+ [likeButton setImage:[UIImage imageNamed:@"icon 1.5"] forState:UIControlStateSelected];
|
|
|
|
|
+ [container addSubview:likeButton];
|
|
|
|
|
+
|
|
|
|
|
+ UILabel *likeCountLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 40, 32, 18)];
|
|
|
|
|
+ likeCountLabel.textAlignment = NSTextAlignmentCenter;
|
|
|
|
|
+ likeCountLabel.font = [UIFont systemFontOfSize:12];
|
|
|
|
|
+ likeCountLabel.textColor = [UIColor whiteColor];
|
|
|
|
|
+// likeCountLabel.text = [self formatCount:self.interaction.likeCount];
|
|
|
|
|
+ [container addSubview:likeCountLabel];
|
|
|
|
|
+ self.likeButton = likeButton;
|
|
|
|
|
+ self.likeCountLabel = likeCountLabel;
|
|
|
|
|
+
|
|
|
|
|
+ // 收藏
|
|
|
|
|
+ UIButton *favoriteButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
|
|
|
|
+ favoriteButton.frame = CGRectMake(0, CGRectGetMaxY(likeCountLabel.frame) + 20, 32, 32);
|
|
|
|
|
+ [favoriteButton setImage:[UIImage imageNamed:@"icon 1.2"] forState:UIControlStateNormal];
|
|
|
|
|
+ [favoriteButton setImage:[UIImage imageNamed:@"icon 1.5"] forState:UIControlStateSelected];
|
|
|
|
|
+ [container addSubview:favoriteButton];
|
|
|
|
|
+
|
|
|
|
|
+ UILabel *favoriteCountLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, CGRectGetMaxY(favoriteButton.frame), 32, 18)];
|
|
|
|
|
+ favoriteCountLabel.textAlignment = NSTextAlignmentCenter;
|
|
|
|
|
+ favoriteCountLabel.font = [UIFont systemFontOfSize:12];
|
|
|
|
|
+ favoriteCountLabel.textColor = [UIColor whiteColor];
|
|
|
|
|
+// favoriteCountLabel.text = [self formatCount:self.interaction.favoriteCount];
|
|
|
|
|
+ [container addSubview:favoriteCountLabel];
|
|
|
|
|
+ self.favoriteButton = favoriteButton;
|
|
|
|
|
+ self.favoriteCountLabel = favoriteCountLabel;
|
|
|
|
|
+
|
|
|
|
|
+ // 评论
|
|
|
|
|
+ UIButton *commentButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
|
|
|
|
+ commentButton.frame = CGRectMake(0, CGRectGetMaxY(favoriteCountLabel.frame) + 20, 32, 32);
|
|
|
|
|
+ [commentButton setImage:[UIImage imageNamed:@"icon 1.3"] forState:UIControlStateNormal];
|
|
|
|
|
+ [container addSubview:commentButton];
|
|
|
|
|
+ [commentButton addTarget:self action:@selector(handleCommentTap) forControlEvents:UIControlEventTouchUpInside];
|
|
|
|
|
+ UILabel *commentCountLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, CGRectGetMaxY(commentButton.frame) , 32, 18)];
|
|
|
|
|
+ commentCountLabel.textAlignment = NSTextAlignmentCenter;
|
|
|
|
|
+ commentCountLabel.font = [UIFont systemFontOfSize:12];
|
|
|
|
|
+ commentCountLabel.textColor = [UIColor whiteColor];
|
|
|
|
|
+// commentCountLabel.text = [self formatCount:self.interaction.commentCount];
|
|
|
|
|
+ [container addSubview:commentCountLabel];
|
|
|
|
|
+ self.commentCountLabel = commentCountLabel;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+//- (void)addLikeButton {
|
|
|
|
|
+//
|
|
|
|
|
+// UIView *likeContainer = [[UIView alloc] initWithFrame:CGRectMake(self.frame.size.width-48, self.frame.size.height-BAR_HEIGHT - 100 - 130, 32, 50)];
|
|
|
|
|
+// self.likeButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 32, 32)];
|
|
|
|
|
+// [self.likeButton setImage:[UIImage imageNamed:@"icon 1.1"] forState:UIControlStateNormal];
|
|
|
|
|
+// [self.likeButton setImage:[UIImage imageNamed:@"icon 1.5"] forState:UIControlStateSelected];
|
|
|
|
|
+// self.likeButton.tintColor = [UIColor whiteColor];
|
|
|
|
|
+// [self.likeButton addTarget:self action:@selector(handleLikeTap) forControlEvents:UIControlEventTouchUpInside];
|
|
|
|
|
+// [likeContainer addSubview:self.likeButton];
|
|
|
|
|
+// self.likeCountLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 32, 32, 18)];
|
|
|
|
|
+// self.likeCountLabel.textColor = [UIColor whiteColor];
|
|
|
|
|
+// self.likeCountLabel.font = [UIFont systemFontOfSize:12];
|
|
|
|
|
+// self.likeCountLabel.textAlignment = NSTextAlignmentCenter;
|
|
|
|
|
+// self.likeCountLabel.text = @"0";
|
|
|
|
|
+// [likeContainer addSubview:self.likeCountLabel];
|
|
|
|
|
+// [self.contentView addSubview:likeContainer];
|
|
|
|
|
+//}
|
|
|
|
|
+//
|
|
|
|
|
+//- (void)addFavoriteButton {
|
|
|
|
|
+//
|
|
|
|
|
+// UIView *favoriteContainer = [[UIView alloc] initWithFrame:CGRectMake(self.frame.size.width-48, self.frame.size.height-BAR_HEIGHT - 100 - 65, 32, 50)];
|
|
|
|
|
+// self.favoriteButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 32, 32)];
|
|
|
|
|
+// [self.favoriteButton setImage:[UIImage imageNamed:@"icon 1.2"] forState:UIControlStateNormal];
|
|
|
|
|
+// [self.favoriteButton setImage:[UIImage imageNamed:@"icon 1.5"] forState:UIControlStateSelected];
|
|
|
|
|
+// self.favoriteButton.tintColor = [UIColor whiteColor];
|
|
|
|
|
+// [self.favoriteButton addTarget:self action:@selector(handleFavoriteTap) forControlEvents:UIControlEventTouchUpInside];
|
|
|
|
|
+// [favoriteContainer addSubview:self.favoriteButton];
|
|
|
|
|
+// self.favoriteCountLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 32, 32, 18)];
|
|
|
|
|
+// self.favoriteCountLabel.textColor = [UIColor whiteColor];
|
|
|
|
|
+// self.favoriteCountLabel.font = [UIFont systemFontOfSize:12];
|
|
|
|
|
+// self.favoriteCountLabel.textAlignment = NSTextAlignmentCenter;
|
|
|
|
|
+// self.favoriteCountLabel.text = @"0";
|
|
|
|
|
+// [favoriteContainer addSubview:self.favoriteCountLabel];
|
|
|
|
|
+// [self.contentView addSubview:favoriteContainer];
|
|
|
|
|
+//}
|
|
|
|
|
+//
|
|
|
|
|
+//- (void)addCommentButton {
|
|
|
|
|
+// UIView *commentContainer = [[UIView alloc] initWithFrame:CGRectMake(self.frame.size.width-48, self.frame.size.height-BAR_HEIGHT - 100, 32, 50)];
|
|
|
|
|
+// self.commentButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 32, 32)];
|
|
|
|
|
+// [self.commentButton setImage:[UIImage imageNamed:@"icon 1.3"] forState:UIControlStateNormal];
|
|
|
|
|
+// self.commentButton.tintColor = [UIColor whiteColor];
|
|
|
|
|
+// [self.commentButton addTarget:self action:@selector(handleCommentTap) forControlEvents:UIControlEventTouchUpInside];
|
|
|
|
|
+// [commentContainer addSubview:self.commentButton];
|
|
|
|
|
+// self.commentCountLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 32, 32, 18)];
|
|
|
|
|
+// self.commentCountLabel.textColor = [UIColor whiteColor];
|
|
|
|
|
+// self.commentCountLabel.font = [UIFont systemFontOfSize:12];
|
|
|
|
|
+// self.commentCountLabel.textAlignment = NSTextAlignmentCenter;
|
|
|
|
|
+// self.commentCountLabel.text = @"0";
|
|
|
|
|
+// [commentContainer addSubview:self.commentCountLabel];
|
|
|
|
|
+// [self.contentView addSubview:commentContainer];
|
|
|
|
|
+//}
|
|
|
|
|
+//
|
|
|
|
|
+//- (void)addCollectionButton {
|
|
|
|
|
+// UIView *bottomView = [[UIView alloc] initWithFrame:CGRectMake(0, self.frame.size.height - 38 - BAR_HEIGHT, SCREEN_WIDTH, 38)];
|
|
|
|
|
+// bottomView.backgroundColor = [UIColor colorWithRed:255/255.0 green:255/255.0 blue:255/255.0 alpha:0.1];
|
|
|
|
|
+// [self.contentView addSubview:bottomView];
|
|
|
|
|
+// UIImageView *sImgView = [[UIImageView alloc] initWithFrame:CGRectMake(16, 10.5, 17, 17)];
|
|
|
|
|
+// sImgView.image = [UIImage imageNamed:@"icon 2.1 拷贝"];
|
|
|
|
|
+// [bottomView addSubview:sImgView];
|
|
|
|
|
+// UILabel *hjL = [[UILabel alloc] initWithFrame:CGRectMake(CGRectGetMaxX(sImgView.frame)+4, 0, 100, 38)];
|
|
|
|
|
+// [bottomView addSubview:hjL];
|
|
|
|
|
+// hjL.font = [UIFont boldSystemFontOfSize:18];
|
|
|
|
|
+// hjL.textColor = UIColor.whiteColor;
|
|
|
|
|
+// hjL.text = @"合集";
|
|
|
|
|
+// UIImageView *mImgView = [[UIImageView alloc] initWithFrame:CGRectMake(SCREEN_WIDTH-27, 13.5, 11, 11)];
|
|
|
|
|
+// mImgView.image = [UIImage imageNamed:@"Frame 9366"];
|
|
|
|
|
+// [bottomView addSubview:mImgView];
|
|
|
|
|
+// UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleCollectionTap)];
|
|
|
|
|
+// [bottomView addGestureRecognizer:tap];
|
|
|
|
|
+//
|
|
|
|
|
+//}
|
|
|
|
|
+
|
|
|
|
|
+- (void)setupBottomInfo {
|
|
|
|
|
+
|
|
|
|
|
+ self.contentView.backgroundColor = [UIColor redColor];
|
|
|
|
|
+
|
|
|
|
|
+ CGFloat width = self.contentView.bounds.size.width;
|
|
|
|
|
+ CGFloat height = self.contentView.bounds.size.height;
|
|
|
|
|
+
|
|
|
|
|
+ // 播放器容器(占据上半部分)
|
|
|
|
|
+ CGFloat playerHeight = height;
|
|
|
|
|
+ UIView *playerContainer = [[UIView alloc] initWithFrame:CGRectMake(0, 0, width, playerHeight)];
|
|
|
|
|
+ playerContainer.backgroundColor = [UIColor blackColor];
|
|
|
|
|
+ [self.contentView addSubview:playerContainer];
|
|
|
|
|
+ [self.contentView sendSubviewToBack:playerContainer];
|
|
|
|
|
+ // 创建播放器
|
|
|
|
|
+ JXSuperPlayer *player = [[JXSuperPlayer alloc] initWithContainerView:playerContainer];
|
|
|
|
|
+ player.delegate = self;
|
|
|
|
|
+ // 右侧互动按钮
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ UILabel * seeL = [[UILabel alloc] initWithFrame:CGRectMake(16, kScreenHeight - 14-safebottom, SCREEN_WIDTH - 32, 14)];
|
|
|
|
|
+ seeL.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium];
|
|
|
|
|
+ seeL.textColor = [UIColor colorWithRed:173/255.0 green:173/255.0 blue:173/255.0 alpha:1];
|
|
|
|
|
+ [self.contentView addSubview:seeL];
|
|
|
|
|
+ seeL.text = [NSString stringWithFormat:@"%@次播放",@"30万"];
|
|
|
|
|
+
|
|
|
|
|
+ UIView *bottomView = [[UIView alloc] initWithFrame:CGRectMake(0, CGRectGetMinY(seeL.frame) - 58, SCREEN_WIDTH, 38)];
|
|
|
|
|
+ bottomView.backgroundColor = [UIColor colorWithRed:255/255.0 green:255/255.0 blue:255/255.0 alpha:0.1];
|
|
|
|
|
+ [self.contentView addSubview:bottomView];
|
|
|
|
|
+ UIImageView *sImgView = [[UIImageView alloc] initWithFrame:CGRectMake(16, 10.5, 17, 17)];
|
|
|
|
|
+ sImgView.image = [UIImage imageNamed:@"icon 2.1 拷贝"];
|
|
|
|
|
+ [bottomView addSubview:sImgView];
|
|
|
|
|
+ UILabel *hjL = [[UILabel alloc] initWithFrame:CGRectMake(CGRectGetMaxX(sImgView.frame)+4, 0, 100, 38)];
|
|
|
|
|
+ [bottomView addSubview:hjL];
|
|
|
|
|
+ hjL.font = [UIFont boldSystemFontOfSize:18];
|
|
|
|
|
+ hjL.textColor = UIColor.whiteColor;
|
|
|
|
|
+ hjL.text = @"合集";
|
|
|
|
|
+ UIImageView *mImgView = [[UIImageView alloc] initWithFrame:CGRectMake(SCREEN_WIDTH-27, 13.5, 11, 11)];
|
|
|
|
|
+ mImgView.image = [UIImage imageNamed:@"Frame 9366"];
|
|
|
|
|
+ [bottomView addSubview:mImgView];
|
|
|
|
|
+ UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleCollectionTap)];
|
|
|
|
|
+ [bottomView addGestureRecognizer:tap];
|
|
|
|
|
+
|
|
|
|
|
+ UISlider *slider = [[UISlider alloc] initWithFrame:CGRectMake(16,34, SCREEN_WIDTH-32, 2)];
|
|
|
|
|
+ [bottomView addSubview:slider];
|
|
|
|
|
+ slider.maximumValue = 1;
|
|
|
|
|
+ slider.minimumValue = 0;
|
|
|
|
|
+ [slider setThumbImage:[UIImage imageNamed:@"b_jdt.png"] forState:UIControlStateNormal];
|
|
|
|
|
+ [slider setThumbImage:[UIImage imageNamed:@"b_jdtb.png"] forState:UIControlStateHighlighted];
|
|
|
|
|
+ [slider setMinimumTrackImage:[UIImage imageNamed:@"jdt.png"] forState:UIControlStateNormal];
|
|
|
|
|
+ [slider setMinimumTrackImage:[UIImage imageNamed:@"jdt.png"] forState:UIControlStateHighlighted];
|
|
|
|
|
+ [slider setMaximumTrackImage:[UIImage imageNamed:@"jdt.png"] forState:UIControlStateNormal];
|
|
|
|
|
+ [slider setMaximumTrackImage:[UIImage imageNamed:@"jdt.png"] forState:UIControlStateHighlighted] ;
|
|
|
|
|
+ [slider setMinimumTrackTintColor:COLOR(0, 0, 0, 0.1)];
|
|
|
|
|
+ [slider setMaximumTrackTintColor:COLOR(0, 0, 0, 0.1)];
|
|
|
|
|
+ slider.alpha = 0.6;
|
|
|
|
|
+ self.videoSlider = slider;
|
|
|
|
|
+
|
|
|
|
|
+ UILabel *descLabel = [[UILabel alloc] init];
|
|
|
|
|
+ descLabel.font = [UIFont systemFontOfSize:15 weight:UIFontWeightMedium];
|
|
|
|
|
+ descLabel.textColor = [UIColor whiteColor];
|
|
|
|
|
+ descLabel.numberOfLines = 2;
|
|
|
|
|
+ [self.contentView addSubview:descLabel];
|
|
|
|
|
+ [self.contentView bringSubviewToFront:descLabel];
|
|
|
|
|
+ [descLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
+ make.bottom.equalTo(bottomView.mas_top).offset(-15);
|
|
|
|
|
+ make.left.equalTo(self.contentView).offset(16);
|
|
|
|
|
+ make.width.mas_equalTo(SCREEN_WIDTH - 90);
|
|
|
|
|
+ }];
|
|
|
|
|
+ self.descriptionLabel = descLabel;
|
|
|
|
|
+ UILabel *titleLabel = [[UILabel alloc] init];
|
|
|
|
|
+ titleLabel.font = [UIFont boldSystemFontOfSize:18];
|
|
|
|
|
+ titleLabel.textColor = [UIColor whiteColor];
|
|
|
|
|
+ titleLabel.numberOfLines = 2;
|
|
|
|
|
+ [self.contentView addSubview:titleLabel];
|
|
|
|
|
+ [self.contentView bringSubviewToFront:titleLabel];
|
|
|
|
|
+ [titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
+ make.bottom.equalTo(descLabel.mas_top).offset(-12.5);
|
|
|
|
|
+ make.left.equalTo(self.contentView).offset(16);
|
|
|
|
|
+ make.width.mas_equalTo(SCREEN_WIDTH - 90);
|
|
|
|
|
+ }];
|
|
|
|
|
+ self.titleLabel = titleLabel;
|
|
|
|
|
+
|
|
|
|
|
+// self.bottomInfoView = [[UIView alloc] init];
|
|
|
|
|
+// [self.contentView addSubview:self.bottomInfoView];
|
|
|
|
|
+//
|
|
|
|
|
+// // 标题
|
|
|
|
|
+// self.titleLabel = [[UILabel alloc] init];
|
|
|
|
|
+// self.titleLabel.textColor = [UIColor whiteColor];
|
|
|
|
|
+// self.titleLabel.font = [UIFont boldSystemFontOfSize:16];
|
|
|
|
|
+// self.titleLabel.numberOfLines = 2;
|
|
|
|
|
+// [self.bottomInfoView addSubview:self.titleLabel];
|
|
|
|
|
+//
|
|
|
|
|
+// // 描述
|
|
|
|
|
+// self.descriptionLabel = [[UILabel alloc] init];
|
|
|
|
|
+// self.descriptionLabel.textColor = [UIColor colorWithWhite:1.0 alpha:1];
|
|
|
|
|
+// self.descriptionLabel.font = [UIFont systemFontOfSize:15];
|
|
|
|
|
+// self.descriptionLabel.numberOfLines = 2;
|
|
|
|
|
+// [self.bottomInfoView addSubview:self.descriptionLabel];
|
|
|
|
|
+//
|
|
|
|
|
+// // 约束
|
|
|
|
|
+// self.bottomInfoView.translatesAutoresizingMaskIntoConstraints = NO;
|
|
|
|
|
+// self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
|
|
|
|
+// self.descriptionLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
|
|
|
|
+//
|
|
|
|
|
+// [NSLayoutConstraint activateConstraints:@[
|
|
|
|
|
+// [self.bottomInfoView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor],
|
|
|
|
|
+// [self.bottomInfoView.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:-90],
|
|
|
|
|
+// [self.bottomInfoView.bottomAnchor constraintEqualToAnchor:self.contentView.safeAreaLayoutGuide.bottomAnchor constant:-(BAR_HEIGHT+54)],
|
|
|
|
|
+// [self.bottomInfoView.heightAnchor constraintGreaterThanOrEqualToConstant:80],
|
|
|
|
|
+//
|
|
|
|
|
+// [self.titleLabel.topAnchor constraintEqualToAnchor:self.bottomInfoView.topAnchor constant:12],
|
|
|
|
|
+// [self.titleLabel.leadingAnchor constraintEqualToAnchor:self.bottomInfoView.leadingAnchor constant:16],
|
|
|
|
|
+// [self.titleLabel.trailingAnchor constraintEqualToAnchor:self.bottomInfoView.trailingAnchor constant:-16],
|
|
|
|
|
+//
|
|
|
|
|
+// [self.descriptionLabel.topAnchor constraintEqualToAnchor:self.titleLabel.bottomAnchor constant:8],
|
|
|
|
|
+// [self.descriptionLabel.leadingAnchor constraintEqualToAnchor:self.bottomInfoView.leadingAnchor constant:16],
|
|
|
|
|
+// [self.descriptionLabel.trailingAnchor constraintEqualToAnchor:self.bottomInfoView.trailingAnchor constant:-16],
|
|
|
|
|
+//
|
|
|
|
|
+// ]];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#pragma mark - 公开方法
|
|
|
|
|
+
|
|
|
|
|
+- (void)configureWithDrama:(JXEpisode *)drama andDiction:(JXInteraction *)model andTitle:(NSString *)title{
|
|
|
|
|
+ self.drama = drama;
|
|
|
|
|
+
|
|
|
|
|
+ // 更新UI
|
|
|
|
|
+ self.titleLabel.text = title ?: @"未知标题";
|
|
|
|
|
+ self.descriptionLabel.text = drama.title ?: @"暂无描述";
|
|
|
|
|
+
|
|
|
|
|
+ // 更新交互数据
|
|
|
|
|
+ [self updateLikeUI:model.isLiked count:model.likeCount];
|
|
|
|
|
+ [self updateFavoriteUI:model.isFavorited count:model.favoriteCount];
|
|
|
|
|
+ [self updateCommentCount:model.commentCount];
|
|
|
|
|
+}
|
|
|
|
|
+- (NSString *)formatCount:(long long)count {
|
|
|
|
|
+ if (count >= 1000000) {
|
|
|
|
|
+ return [NSString stringWithFormat:@"%.1fM", count / 1000000.0];
|
|
|
|
|
+ } else if (count >= 1000) {
|
|
|
|
|
+ return [NSString stringWithFormat:@"%.1fK", count / 1000.0];
|
|
|
|
|
+ }
|
|
|
|
|
+ return [NSString stringWithFormat:@"%lld", count];
|
|
|
|
|
+}
|
|
|
|
|
+- (void)startPlay {
|
|
|
|
|
+ if (!self.drama) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 创建播放器(如果不存在)
|
|
|
|
|
+ if (!self.superPlayer) {
|
|
|
|
|
+ self.superPlayer = [[JXSuperPlayer alloc] initWithContainerView:self.playerContainerView];
|
|
|
|
|
+ self.superPlayer.delegate = self;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ NSLog(@"[JXShortDramaCell] 重用现有播放器实例");
|
|
|
|
|
+ }
|
|
|
|
|
+ // FileID + psign播放(DRM推荐)
|
|
|
|
|
+ NSString *appId = self.drama.appId;
|
|
|
|
|
+ // 如果没有appId,尝试从psign中提取
|
|
|
|
|
+ if (!appId || appId.length == 0) {
|
|
|
|
|
+ appId = [JXSuperPlayer extractAppIdFromPsign:self.drama.psign];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!appId) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ [self.superPlayer playWithAppId:appId
|
|
|
|
|
+ fileId:self.drama.fileId
|
|
|
|
|
+ psign:self.drama.psign];
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)stopPlay {
|
|
|
|
|
+ if (self.superPlayer) {
|
|
|
|
|
+ [self.superPlayer stop];
|
|
|
|
|
+ NSLog(@"[JXShortDramaCell] 播放器已停止");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)pausePlay {
|
|
|
|
|
+ if (self.superPlayer) {
|
|
|
|
|
+ [self.superPlayer pause];
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)resumePlay {
|
|
|
|
|
+ if (self.superPlayer) {
|
|
|
|
|
+ [self.superPlayer resume];
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)updateLikeUI:(BOOL)isLiked count:(long long)count {
|
|
|
|
|
+ self.likeButton.selected = isLiked;
|
|
|
|
|
+ self.likeButton.tintColor = isLiked ? [UIColor systemPinkColor] : [UIColor whiteColor];
|
|
|
|
|
+ self.likeCountLabel.text = [VideoDetailCell formattedCountWithCount:count];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)updateFavoriteUI:(BOOL)isFavorited count:(long long)count {
|
|
|
|
|
+ self.favoriteButton.selected = isFavorited;
|
|
|
|
|
+ self.favoriteButton.tintColor = isFavorited ? [UIColor systemYellowColor] : [UIColor whiteColor];
|
|
|
|
|
+ self.favoriteCountLabel.text = [VideoDetailCell formattedCountWithCount:count];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)updateCommentCount:(long long)count {
|
|
|
|
|
+ self.commentCountLabel.text = [VideoDetailCell formattedCountWithCount:count];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 格式化数字显示(如 1000 -> 1K)
|
|
|
|
|
+ */
|
|
|
|
|
++ (NSString *)formattedCountWithCount:(long long)count {
|
|
|
|
|
+ if (count >= 1000000) {
|
|
|
|
|
+ return [NSString stringWithFormat:@"%.1fM", count / 1000000.0];
|
|
|
|
|
+ } else if (count >= 1000) {
|
|
|
|
|
+ return [NSString stringWithFormat:@"%.1fK", count / 1000.0];
|
|
|
|
|
+ }
|
|
|
|
|
+ return [NSString stringWithFormat:@"%lld", count];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 与ViewController调用匹配的方法
|
|
|
|
|
+ */
|
|
|
|
|
+- (void)updateLikeCount:(NSInteger)likeCount isLiked:(BOOL)isLiked {
|
|
|
|
|
+ [self updateLikeUI:isLiked count:likeCount];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)updateFavoriteCount:(NSInteger)favoriteCount isFavorited:(BOOL)isFavorited {
|
|
|
|
|
+ [self updateFavoriteUI:isFavorited count:favoriteCount];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#pragma mark - 按钮事件
|
|
|
|
|
+
|
|
|
|
|
+- (void)handleLikeTap {
|
|
|
|
|
+ if ([self.delegate respondsToSelector:@selector(shortDramaCellDidTapLike:)]) {
|
|
|
|
|
+ [self.delegate shortDramaCellDidTapLike:self];
|
|
|
|
|
+ } else {
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)handleFavoriteTap {
|
|
|
|
|
+ if ([self.delegate respondsToSelector:@selector(shortDramaCellDidTapFavorite:)]) {
|
|
|
|
|
+ [self.delegate shortDramaCellDidTapFavorite:self];
|
|
|
|
|
+ } else {
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)handleCommentTap {
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ if (self.delegate) {
|
|
|
|
|
+ SEL selector = @selector(shortDramaCellDidTapComment:);
|
|
|
|
|
+ BOOL responds = [self.delegate respondsToSelector:selector];
|
|
|
|
|
+ if (responds) {
|
|
|
|
|
+ [self.delegate shortDramaCellDidTapComment:self];
|
|
|
|
|
+ } else {
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)handleCollectionTap {
|
|
|
|
|
+ if ([self.delegate respondsToSelector:@selector(shortDramaCellDidTapCollection:)]) {
|
|
|
|
|
+ [self.delegate shortDramaCellDidTapCollection:self];
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)handleDetailTap {
|
|
|
|
|
+ if ([self.delegate respondsToSelector:@selector(shortDramaCellDidTapDetail:)]) {
|
|
|
|
|
+ [self.delegate shortDramaCellDidTapDetail:self];
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#pragma mark - JXSuperPlayerDelegate
|
|
|
|
|
+
|
|
|
|
|
+- (void)superPlayerDidChangeState:(JXSuperPlayerState)state {
|
|
|
|
|
+ self.playerState = state;
|
|
|
|
|
+
|
|
|
|
|
+ switch (state) {
|
|
|
|
|
+ case JXSuperPlayerStatePreparing:
|
|
|
|
|
+ NSLog(@"[JXShortDramaCell] 播放器状态变更: 准备中");
|
|
|
|
|
+ // 开始播放时启动超时检测
|
|
|
|
|
+ [self startPlayTimeoutTimer];
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ case JXSuperPlayerStatePlaying:
|
|
|
|
|
+ NSLog(@"[JXShortDramaCell] 播放器状态变更: 播放中");
|
|
|
|
|
+ // 成功开始播放,取消超时检测
|
|
|
|
|
+ [self stopPlayTimeoutTimer];
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ case JXSuperPlayerStatePaused:
|
|
|
|
|
+ NSLog(@"[JXShortDramaCell] 播放器状态变更: 暂停");
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ case JXSuperPlayerStateCompleted:
|
|
|
|
|
+ NSLog(@"[JXShortDramaCell] 播放器状态变更: 播放完成");
|
|
|
|
|
+ [self stopPlayTimeoutTimer];
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ case JXSuperPlayerStateError:
|
|
|
|
|
+ NSLog(@"[JXShortDramaCell] 播放器状态变更: 错误");
|
|
|
|
|
+ [self stopPlayTimeoutTimer];
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ default:
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 播放超时检测
|
|
|
|
|
+- (void)startPlayTimeoutTimer {
|
|
|
|
|
+ [self stopPlayTimeoutTimer]; // 先停止之前的定时器
|
|
|
|
|
+
|
|
|
|
|
+ self.playStartTime = [NSDate date];
|
|
|
|
|
+
|
|
|
|
|
+ // 30秒超时检测
|
|
|
|
|
+ self.playTimeoutTimer = [NSTimer scheduledTimerWithTimeInterval:30.0
|
|
|
|
|
+ target:self
|
|
|
|
|
+ selector:@selector(onPlayTimeout)
|
|
|
|
|
+ userInfo:nil
|
|
|
|
|
+ repeats:NO];
|
|
|
|
|
+ NSLog(@"[JXShortDramaCell] 启动播放超时检测: 30秒");
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)stopPlayTimeoutTimer {
|
|
|
|
|
+ if (self.playTimeoutTimer) {
|
|
|
|
|
+ [self.playTimeoutTimer invalidate];
|
|
|
|
|
+ self.playTimeoutTimer = nil;
|
|
|
|
|
+ self.playStartTime = nil;
|
|
|
|
|
+ NSLog(@"[JXShortDramaCell] 停止播放超时检测");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)onPlayTimeout {
|
|
|
|
|
+ NSLog(@"[JXShortDramaCell] 播放超时!30秒内未开始播放,触发恢复机制");
|
|
|
|
|
+
|
|
|
|
|
+ // 停止定时器
|
|
|
|
|
+ [self stopPlayTimeoutTimer];
|
|
|
|
|
+
|
|
|
|
|
+ // 触发播放失败处理
|
|
|
|
|
+ if ([self.delegate respondsToSelector:@selector(shortDramaCellDidFailToPlay:)]) {
|
|
|
|
|
+ [self.delegate shortDramaCellDidFailToPlay:self];
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)superPlayerDidUpdateProgress:(NSTimeInterval)currentTime duration:(NSTimeInterval)duration {
|
|
|
|
|
+ // 播放进度更新(可用于显示进度条)
|
|
|
|
|
+ // NSLog(@"[JXShortDramaCell] 播放进度: %.1f / %.1f", currentTime, duration);
|
|
|
|
|
+ float progress = currentTime/duration;
|
|
|
|
|
+ self.videoSlider.value = progress;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)superPlayerDidFailWithError:(NSError *)error {
|
|
|
|
|
+ NSLog(@"[JXShortDramaCell] 播放失败: %@", error.localizedDescription);
|
|
|
|
|
+
|
|
|
|
|
+ // 检查是否是网络相关错误
|
|
|
|
|
+ BOOL isNetworkError = [error.domain isEqualToString:NSURLErrorDomain] &&
|
|
|
|
|
+ (error.code == NSURLErrorTimedOut ||
|
|
|
|
|
+ error.code == NSURLErrorCannotConnectToHost ||
|
|
|
|
|
+ error.code == NSURLErrorNetworkConnectionLost ||
|
|
|
|
|
+ error.code == NSURLErrorNotConnectedToInternet);
|
|
|
|
|
+
|
|
|
|
|
+ // 也检查播放器内部错误
|
|
|
|
|
+ BOOL isPlayerError = [error.domain isEqualToString:@"JXSuperPlayer"] ||
|
|
|
|
|
+ [error.domain isEqualToString:@"TXVodPlayer"];
|
|
|
|
|
+
|
|
|
|
|
+ if (isNetworkError || isPlayerError) {
|
|
|
|
|
+ NSLog(@"[JXShortDramaCell] 检测到播放错误,尝试恢复: domain=%@, code=%ld",
|
|
|
|
|
+ error.domain, (long)error.code);
|
|
|
|
|
+
|
|
|
|
|
+ // 延迟3秒后通知控制器重新获取播放数据
|
|
|
|
|
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
|
|
|
+ if ([self.delegate respondsToSelector:@selector(shortDramaCellDidFailToPlay:)]) {
|
|
|
|
|
+ [self.delegate shortDramaCellDidFailToPlay:self];
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 其他类型的错误,直接显示
|
|
|
|
|
+ NSLog(@"[JXShortDramaCell] 非网络错误,不进行重试: %@", error);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 显示错误提示(可以在这里添加UI反馈)
|
|
|
|
|
+ // TODO: 在UI上显示播放失败的提示,但不阻塞用户操作
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#pragma mark - 生命周期
|
|
|
|
|
+
|
|
|
|
|
+- (void)dealloc {
|
|
|
|
|
+ // 清理定时器
|
|
|
|
|
+ [self stopPlayTimeoutTimer];
|
|
|
|
|
+
|
|
|
|
|
+ // 清理播放器
|
|
|
|
|
+ if (self.superPlayer) {
|
|
|
|
|
+ [self.superPlayer releasePlayer];
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@end
|
|
|
|
|
+
|