VideoDetailCell.m 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. //
  2. // VideoDetailCell.m
  3. // AICity
  4. //
  5. // Created by 张国栋 on 2025/10/22.
  6. // Copyright © 2025 wei.z. All rights reserved.
  7. //
  8. #import "VideoDetailCell.h"
  9. #import "JXSuperPlayer.h"
  10. #import "JXDramaContent.h"
  11. #import "JXInteraction.h"
  12. #import "GuestHelper.h"
  13. #import "JXEpisode.h"
  14. @interface VideoDetailCell () <JXSuperPlayerDelegate>
  15. #pragma mark - UI组件
  16. // 播放器容器
  17. @property (nonatomic, strong) UIView *playerContainerView;
  18. // 右侧交互按钮组
  19. @property (nonatomic, strong) UIButton *likeButton;
  20. @property (nonatomic, strong) UILabel *likeCountLabel;
  21. @property (nonatomic, strong) UIButton *favoriteButton;
  22. @property (nonatomic, strong) UILabel *favoriteCountLabel;
  23. @property (nonatomic, strong) UIButton *commentButton;
  24. @property (nonatomic, strong) UILabel *commentCountLabel;
  25. @property (nonatomic, strong) UIButton *collectionButton;
  26. // 底部信息区
  27. @property (nonatomic, strong) UIView *bottomInfoView;
  28. @property (nonatomic, strong) UILabel *titleLabel;
  29. @property (nonatomic, strong) UILabel *descriptionLabel;
  30. #pragma mark - 数据 JXEpisode
  31. @property (nonatomic, strong) JXEpisode *drama;
  32. #pragma mark - 播放器
  33. @property (nonatomic, strong) JXSuperPlayer *superPlayer;
  34. @property (nonatomic, assign) JXSuperPlayerState playerState; // 播放器状态
  35. // 播放超时检测
  36. @property (nonatomic, strong) NSTimer *playTimeoutTimer; // 播放超时检测定时器
  37. @property (nonatomic, strong) NSDate *playStartTime; // 播放开始时间
  38. @property (strong, nonatomic) UISlider *videoSlider;
  39. @end
  40. @implementation VideoDetailCell
  41. #pragma mark - 初始化
  42. - (instancetype)initWithFrame:(CGRect)frame {
  43. self = [super initWithFrame:frame];
  44. if (self) {
  45. [self setupUI];
  46. }
  47. return self;
  48. }
  49. - (void)prepareForReuse {
  50. [super prepareForReuse];
  51. NSLog(@"[JXShortDramaCell] prepareForReuse - 停止当前播放");
  52. // 只停止播放,不释放播放器实例
  53. // 重用播放器实例可以避免频繁创建销毁导致的连接问题
  54. [self stopPlay];
  55. // 停止超时检测
  56. [self stopPlayTimeoutTimer];
  57. }
  58. #pragma mark - UI设置
  59. - (void)setupUI {
  60. self.contentView.backgroundColor = [UIColor blackColor];
  61. // 创建播放器容器
  62. [self setupPlayerContainer];
  63. // 创建交互按钮组
  64. [self setupInteractionButtons];
  65. // 创建底部信息区
  66. [self setupBottomInfo];
  67. }
  68. - (void)setupPlayerContainer {
  69. self.playerContainerView = [[UIView alloc] init];
  70. self.playerContainerView.backgroundColor = [UIColor blackColor];
  71. [self.contentView addSubview:self.playerContainerView];
  72. // 约束 - 播放器下边界贴近底部导航栏(44pt高)
  73. self.playerContainerView.translatesAutoresizingMaskIntoConstraints = NO;
  74. [NSLayoutConstraint activateConstraints:@[
  75. [self.playerContainerView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor],
  76. [self.playerContainerView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor],
  77. [self.playerContainerView.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor],
  78. [self.playerContainerView.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor constant:-44]
  79. ]];
  80. }
  81. - (void)setupInteractionButtons {
  82. // // 点赞按钮
  83. // [self addLikeButton];
  84. //
  85. // // 收藏按钮
  86. // [self addFavoriteButton];
  87. //
  88. // // 评论按钮
  89. // [self addCommentButton];
  90. //
  91. // // 合集按钮
  92. // [self addCollectionButton];
  93. CGFloat width = self.contentView.bounds.size.width;
  94. UIView *container = [[UIView alloc] initWithFrame:CGRectMake(width - 48, kScreenHeight - safebottom - 190 - 90, 32, 190)];
  95. [self.contentView addSubview:container];
  96. // 点赞
  97. UIButton *likeButton = [UIButton buttonWithType:UIButtonTypeCustom];
  98. likeButton.frame = CGRectMake(0, 0, 32, 32);
  99. [likeButton setImage:[UIImage imageNamed:@"icon 1.1"] forState:UIControlStateNormal];
  100. [likeButton setImage:[UIImage imageNamed:@"icon 1.5"] forState:UIControlStateSelected];
  101. [container addSubview:likeButton];
  102. UILabel *likeCountLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 40, 32, 18)];
  103. likeCountLabel.textAlignment = NSTextAlignmentCenter;
  104. likeCountLabel.font = [UIFont systemFontOfSize:12];
  105. likeCountLabel.textColor = [UIColor whiteColor];
  106. // likeCountLabel.text = [self formatCount:self.interaction.likeCount];
  107. [container addSubview:likeCountLabel];
  108. self.likeButton = likeButton;
  109. self.likeCountLabel = likeCountLabel;
  110. // 收藏
  111. UIButton *favoriteButton = [UIButton buttonWithType:UIButtonTypeCustom];
  112. favoriteButton.frame = CGRectMake(0, CGRectGetMaxY(likeCountLabel.frame) + 20, 32, 32);
  113. [favoriteButton setImage:[UIImage imageNamed:@"icon 1.2"] forState:UIControlStateNormal];
  114. [favoriteButton setImage:[UIImage imageNamed:@"icon 1.5"] forState:UIControlStateSelected];
  115. [container addSubview:favoriteButton];
  116. UILabel *favoriteCountLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, CGRectGetMaxY(favoriteButton.frame), 32, 18)];
  117. favoriteCountLabel.textAlignment = NSTextAlignmentCenter;
  118. favoriteCountLabel.font = [UIFont systemFontOfSize:12];
  119. favoriteCountLabel.textColor = [UIColor whiteColor];
  120. // favoriteCountLabel.text = [self formatCount:self.interaction.favoriteCount];
  121. [container addSubview:favoriteCountLabel];
  122. self.favoriteButton = favoriteButton;
  123. self.favoriteCountLabel = favoriteCountLabel;
  124. // 评论
  125. UIButton *commentButton = [UIButton buttonWithType:UIButtonTypeCustom];
  126. commentButton.frame = CGRectMake(0, CGRectGetMaxY(favoriteCountLabel.frame) + 20, 32, 32);
  127. [commentButton setImage:[UIImage imageNamed:@"icon 1.3"] forState:UIControlStateNormal];
  128. [container addSubview:commentButton];
  129. [commentButton addTarget:self action:@selector(handleCommentTap) forControlEvents:UIControlEventTouchUpInside];
  130. UILabel *commentCountLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, CGRectGetMaxY(commentButton.frame) , 32, 18)];
  131. commentCountLabel.textAlignment = NSTextAlignmentCenter;
  132. commentCountLabel.font = [UIFont systemFontOfSize:12];
  133. commentCountLabel.textColor = [UIColor whiteColor];
  134. // commentCountLabel.text = [self formatCount:self.interaction.commentCount];
  135. [container addSubview:commentCountLabel];
  136. self.commentCountLabel = commentCountLabel;
  137. }
  138. //- (void)addLikeButton {
  139. //
  140. // UIView *likeContainer = [[UIView alloc] initWithFrame:CGRectMake(self.frame.size.width-48, self.frame.size.height-BAR_HEIGHT - 100 - 130, 32, 50)];
  141. // self.likeButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 32, 32)];
  142. // [self.likeButton setImage:[UIImage imageNamed:@"icon 1.1"] forState:UIControlStateNormal];
  143. // [self.likeButton setImage:[UIImage imageNamed:@"icon 1.5"] forState:UIControlStateSelected];
  144. // self.likeButton.tintColor = [UIColor whiteColor];
  145. // [self.likeButton addTarget:self action:@selector(handleLikeTap) forControlEvents:UIControlEventTouchUpInside];
  146. // [likeContainer addSubview:self.likeButton];
  147. // self.likeCountLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 32, 32, 18)];
  148. // self.likeCountLabel.textColor = [UIColor whiteColor];
  149. // self.likeCountLabel.font = [UIFont systemFontOfSize:12];
  150. // self.likeCountLabel.textAlignment = NSTextAlignmentCenter;
  151. // self.likeCountLabel.text = @"0";
  152. // [likeContainer addSubview:self.likeCountLabel];
  153. // [self.contentView addSubview:likeContainer];
  154. //}
  155. //
  156. //- (void)addFavoriteButton {
  157. //
  158. // UIView *favoriteContainer = [[UIView alloc] initWithFrame:CGRectMake(self.frame.size.width-48, self.frame.size.height-BAR_HEIGHT - 100 - 65, 32, 50)];
  159. // self.favoriteButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 32, 32)];
  160. // [self.favoriteButton setImage:[UIImage imageNamed:@"icon 1.2"] forState:UIControlStateNormal];
  161. // [self.favoriteButton setImage:[UIImage imageNamed:@"icon 1.5"] forState:UIControlStateSelected];
  162. // self.favoriteButton.tintColor = [UIColor whiteColor];
  163. // [self.favoriteButton addTarget:self action:@selector(handleFavoriteTap) forControlEvents:UIControlEventTouchUpInside];
  164. // [favoriteContainer addSubview:self.favoriteButton];
  165. // self.favoriteCountLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 32, 32, 18)];
  166. // self.favoriteCountLabel.textColor = [UIColor whiteColor];
  167. // self.favoriteCountLabel.font = [UIFont systemFontOfSize:12];
  168. // self.favoriteCountLabel.textAlignment = NSTextAlignmentCenter;
  169. // self.favoriteCountLabel.text = @"0";
  170. // [favoriteContainer addSubview:self.favoriteCountLabel];
  171. // [self.contentView addSubview:favoriteContainer];
  172. //}
  173. //
  174. //- (void)addCommentButton {
  175. // UIView *commentContainer = [[UIView alloc] initWithFrame:CGRectMake(self.frame.size.width-48, self.frame.size.height-BAR_HEIGHT - 100, 32, 50)];
  176. // self.commentButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 32, 32)];
  177. // [self.commentButton setImage:[UIImage imageNamed:@"icon 1.3"] forState:UIControlStateNormal];
  178. // self.commentButton.tintColor = [UIColor whiteColor];
  179. // [self.commentButton addTarget:self action:@selector(handleCommentTap) forControlEvents:UIControlEventTouchUpInside];
  180. // [commentContainer addSubview:self.commentButton];
  181. // self.commentCountLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 32, 32, 18)];
  182. // self.commentCountLabel.textColor = [UIColor whiteColor];
  183. // self.commentCountLabel.font = [UIFont systemFontOfSize:12];
  184. // self.commentCountLabel.textAlignment = NSTextAlignmentCenter;
  185. // self.commentCountLabel.text = @"0";
  186. // [commentContainer addSubview:self.commentCountLabel];
  187. // [self.contentView addSubview:commentContainer];
  188. //}
  189. //
  190. //- (void)addCollectionButton {
  191. // UIView *bottomView = [[UIView alloc] initWithFrame:CGRectMake(0, self.frame.size.height - 38 - BAR_HEIGHT, SCREEN_WIDTH, 38)];
  192. // bottomView.backgroundColor = [UIColor colorWithRed:255/255.0 green:255/255.0 blue:255/255.0 alpha:0.1];
  193. // [self.contentView addSubview:bottomView];
  194. // UIImageView *sImgView = [[UIImageView alloc] initWithFrame:CGRectMake(16, 10.5, 17, 17)];
  195. // sImgView.image = [UIImage imageNamed:@"icon 2.1 拷贝"];
  196. // [bottomView addSubview:sImgView];
  197. // UILabel *hjL = [[UILabel alloc] initWithFrame:CGRectMake(CGRectGetMaxX(sImgView.frame)+4, 0, 100, 38)];
  198. // [bottomView addSubview:hjL];
  199. // hjL.font = [UIFont boldSystemFontOfSize:18];
  200. // hjL.textColor = UIColor.whiteColor;
  201. // hjL.text = @"合集";
  202. // UIImageView *mImgView = [[UIImageView alloc] initWithFrame:CGRectMake(SCREEN_WIDTH-27, 13.5, 11, 11)];
  203. // mImgView.image = [UIImage imageNamed:@"Frame 9366"];
  204. // [bottomView addSubview:mImgView];
  205. // UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleCollectionTap)];
  206. // [bottomView addGestureRecognizer:tap];
  207. //
  208. //}
  209. - (void)setupBottomInfo {
  210. self.contentView.backgroundColor = [UIColor redColor];
  211. CGFloat width = self.contentView.bounds.size.width;
  212. CGFloat height = self.contentView.bounds.size.height;
  213. // 播放器容器(占据上半部分)
  214. CGFloat playerHeight = height;
  215. UIView *playerContainer = [[UIView alloc] initWithFrame:CGRectMake(0, 0, width, playerHeight)];
  216. playerContainer.backgroundColor = [UIColor blackColor];
  217. [self.contentView addSubview:playerContainer];
  218. [self.contentView sendSubviewToBack:playerContainer];
  219. // 创建播放器
  220. JXSuperPlayer *player = [[JXSuperPlayer alloc] initWithContainerView:playerContainer];
  221. player.delegate = self;
  222. // 右侧互动按钮
  223. UILabel * seeL = [[UILabel alloc] initWithFrame:CGRectMake(16, kScreenHeight - 14-safebottom, SCREEN_WIDTH - 32, 14)];
  224. seeL.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium];
  225. seeL.textColor = [UIColor colorWithRed:173/255.0 green:173/255.0 blue:173/255.0 alpha:1];
  226. [self.contentView addSubview:seeL];
  227. seeL.text = [NSString stringWithFormat:@"%@次播放",@"30万"];
  228. UIView *bottomView = [[UIView alloc] initWithFrame:CGRectMake(0, CGRectGetMinY(seeL.frame) - 58, SCREEN_WIDTH, 38)];
  229. bottomView.backgroundColor = [UIColor colorWithRed:255/255.0 green:255/255.0 blue:255/255.0 alpha:0.1];
  230. [self.contentView addSubview:bottomView];
  231. UIImageView *sImgView = [[UIImageView alloc] initWithFrame:CGRectMake(16, 10.5, 17, 17)];
  232. sImgView.image = [UIImage imageNamed:@"icon 2.1 拷贝"];
  233. [bottomView addSubview:sImgView];
  234. UILabel *hjL = [[UILabel alloc] initWithFrame:CGRectMake(CGRectGetMaxX(sImgView.frame)+4, 0, 100, 38)];
  235. [bottomView addSubview:hjL];
  236. hjL.font = [UIFont boldSystemFontOfSize:18];
  237. hjL.textColor = UIColor.whiteColor;
  238. hjL.text = @"合集";
  239. UIImageView *mImgView = [[UIImageView alloc] initWithFrame:CGRectMake(SCREEN_WIDTH-27, 13.5, 11, 11)];
  240. mImgView.image = [UIImage imageNamed:@"Frame 9366"];
  241. [bottomView addSubview:mImgView];
  242. UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleCollectionTap)];
  243. [bottomView addGestureRecognizer:tap];
  244. UISlider *slider = [[UISlider alloc] initWithFrame:CGRectMake(16,34, SCREEN_WIDTH-32, 2)];
  245. [bottomView addSubview:slider];
  246. slider.maximumValue = 1;
  247. slider.minimumValue = 0;
  248. [slider setThumbImage:[UIImage imageNamed:@"b_jdt.png"] forState:UIControlStateNormal];
  249. [slider setThumbImage:[UIImage imageNamed:@"b_jdtb.png"] forState:UIControlStateHighlighted];
  250. [slider setMinimumTrackImage:[UIImage imageNamed:@"jdt.png"] forState:UIControlStateNormal];
  251. [slider setMinimumTrackImage:[UIImage imageNamed:@"jdt.png"] forState:UIControlStateHighlighted];
  252. [slider setMaximumTrackImage:[UIImage imageNamed:@"jdt.png"] forState:UIControlStateNormal];
  253. [slider setMaximumTrackImage:[UIImage imageNamed:@"jdt.png"] forState:UIControlStateHighlighted] ;
  254. [slider setMinimumTrackTintColor:COLOR(0, 0, 0, 0.1)];
  255. [slider setMaximumTrackTintColor:COLOR(0, 0, 0, 0.1)];
  256. slider.alpha = 0.6;
  257. self.videoSlider = slider;
  258. UILabel *descLabel = [[UILabel alloc] init];
  259. descLabel.font = [UIFont systemFontOfSize:15 weight:UIFontWeightMedium];
  260. descLabel.textColor = [UIColor whiteColor];
  261. descLabel.numberOfLines = 2;
  262. [self.contentView addSubview:descLabel];
  263. [self.contentView bringSubviewToFront:descLabel];
  264. [descLabel mas_makeConstraints:^(MASConstraintMaker *make) {
  265. make.bottom.equalTo(bottomView.mas_top).offset(-15);
  266. make.left.equalTo(self.contentView).offset(16);
  267. make.width.mas_equalTo(SCREEN_WIDTH - 90);
  268. }];
  269. self.descriptionLabel = descLabel;
  270. UILabel *titleLabel = [[UILabel alloc] init];
  271. titleLabel.font = [UIFont boldSystemFontOfSize:18];
  272. titleLabel.textColor = [UIColor whiteColor];
  273. titleLabel.numberOfLines = 2;
  274. [self.contentView addSubview:titleLabel];
  275. [self.contentView bringSubviewToFront:titleLabel];
  276. [titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
  277. make.bottom.equalTo(descLabel.mas_top).offset(-12.5);
  278. make.left.equalTo(self.contentView).offset(16);
  279. make.width.mas_equalTo(SCREEN_WIDTH - 90);
  280. }];
  281. self.titleLabel = titleLabel;
  282. // self.bottomInfoView = [[UIView alloc] init];
  283. // [self.contentView addSubview:self.bottomInfoView];
  284. //
  285. // // 标题
  286. // self.titleLabel = [[UILabel alloc] init];
  287. // self.titleLabel.textColor = [UIColor whiteColor];
  288. // self.titleLabel.font = [UIFont boldSystemFontOfSize:16];
  289. // self.titleLabel.numberOfLines = 2;
  290. // [self.bottomInfoView addSubview:self.titleLabel];
  291. //
  292. // // 描述
  293. // self.descriptionLabel = [[UILabel alloc] init];
  294. // self.descriptionLabel.textColor = [UIColor colorWithWhite:1.0 alpha:1];
  295. // self.descriptionLabel.font = [UIFont systemFontOfSize:15];
  296. // self.descriptionLabel.numberOfLines = 2;
  297. // [self.bottomInfoView addSubview:self.descriptionLabel];
  298. //
  299. // // 约束
  300. // self.bottomInfoView.translatesAutoresizingMaskIntoConstraints = NO;
  301. // self.titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
  302. // self.descriptionLabel.translatesAutoresizingMaskIntoConstraints = NO;
  303. //
  304. // [NSLayoutConstraint activateConstraints:@[
  305. // [self.bottomInfoView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor],
  306. // [self.bottomInfoView.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:-90],
  307. // [self.bottomInfoView.bottomAnchor constraintEqualToAnchor:self.contentView.safeAreaLayoutGuide.bottomAnchor constant:-(BAR_HEIGHT+54)],
  308. // [self.bottomInfoView.heightAnchor constraintGreaterThanOrEqualToConstant:80],
  309. //
  310. // [self.titleLabel.topAnchor constraintEqualToAnchor:self.bottomInfoView.topAnchor constant:12],
  311. // [self.titleLabel.leadingAnchor constraintEqualToAnchor:self.bottomInfoView.leadingAnchor constant:16],
  312. // [self.titleLabel.trailingAnchor constraintEqualToAnchor:self.bottomInfoView.trailingAnchor constant:-16],
  313. //
  314. // [self.descriptionLabel.topAnchor constraintEqualToAnchor:self.titleLabel.bottomAnchor constant:8],
  315. // [self.descriptionLabel.leadingAnchor constraintEqualToAnchor:self.bottomInfoView.leadingAnchor constant:16],
  316. // [self.descriptionLabel.trailingAnchor constraintEqualToAnchor:self.bottomInfoView.trailingAnchor constant:-16],
  317. //
  318. // ]];
  319. }
  320. #pragma mark - 公开方法
  321. - (void)configureWithDrama:(JXEpisode *)drama andDiction:(JXInteraction *)model andTitle:(NSString *)title{
  322. self.drama = drama;
  323. // 更新UI
  324. self.titleLabel.text = title ?: @"未知标题";
  325. self.descriptionLabel.text = drama.title ?: @"暂无描述";
  326. // 更新交互数据
  327. [self updateLikeUI:model.isLiked count:model.likeCount];
  328. [self updateFavoriteUI:model.isFavorited count:model.favoriteCount];
  329. [self updateCommentCount:model.commentCount];
  330. }
  331. - (NSString *)formatCount:(long long)count {
  332. if (count >= 1000000) {
  333. return [NSString stringWithFormat:@"%.1fM", count / 1000000.0];
  334. } else if (count >= 1000) {
  335. return [NSString stringWithFormat:@"%.1fK", count / 1000.0];
  336. }
  337. return [NSString stringWithFormat:@"%lld", count];
  338. }
  339. - (void)startPlay {
  340. if (!self.drama) {
  341. return;
  342. }
  343. // 创建播放器(如果不存在)
  344. if (!self.superPlayer) {
  345. self.superPlayer = [[JXSuperPlayer alloc] initWithContainerView:self.playerContainerView];
  346. self.superPlayer.delegate = self;
  347. } else {
  348. NSLog(@"[JXShortDramaCell] 重用现有播放器实例");
  349. }
  350. // FileID + psign播放(DRM推荐)
  351. NSString *appId = self.drama.appId;
  352. // 如果没有appId,尝试从psign中提取
  353. if (!appId || appId.length == 0) {
  354. appId = [JXSuperPlayer extractAppIdFromPsign:self.drama.psign];
  355. }
  356. if (!appId) {
  357. return;
  358. }
  359. [self.superPlayer playWithAppId:appId
  360. fileId:self.drama.fileId
  361. psign:self.drama.psign];
  362. }
  363. - (void)stopPlay {
  364. if (self.superPlayer) {
  365. [self.superPlayer stop];
  366. NSLog(@"[JXShortDramaCell] 播放器已停止");
  367. }
  368. }
  369. - (void)pausePlay {
  370. if (self.superPlayer) {
  371. [self.superPlayer pause];
  372. }
  373. }
  374. - (void)resumePlay {
  375. if (self.superPlayer) {
  376. [self.superPlayer resume];
  377. }
  378. }
  379. - (void)updateLikeUI:(BOOL)isLiked count:(long long)count {
  380. self.likeButton.selected = isLiked;
  381. self.likeButton.tintColor = isLiked ? [UIColor systemPinkColor] : [UIColor whiteColor];
  382. self.likeCountLabel.text = [VideoDetailCell formattedCountWithCount:count];
  383. }
  384. - (void)updateFavoriteUI:(BOOL)isFavorited count:(long long)count {
  385. self.favoriteButton.selected = isFavorited;
  386. self.favoriteButton.tintColor = isFavorited ? [UIColor systemYellowColor] : [UIColor whiteColor];
  387. self.favoriteCountLabel.text = [VideoDetailCell formattedCountWithCount:count];
  388. }
  389. - (void)updateCommentCount:(long long)count {
  390. self.commentCountLabel.text = [VideoDetailCell formattedCountWithCount:count];
  391. }
  392. /**
  393. * 格式化数字显示(如 1000 -> 1K)
  394. */
  395. + (NSString *)formattedCountWithCount:(long long)count {
  396. if (count >= 1000000) {
  397. return [NSString stringWithFormat:@"%.1fM", count / 1000000.0];
  398. } else if (count >= 1000) {
  399. return [NSString stringWithFormat:@"%.1fK", count / 1000.0];
  400. }
  401. return [NSString stringWithFormat:@"%lld", count];
  402. }
  403. /**
  404. * 与ViewController调用匹配的方法
  405. */
  406. - (void)updateLikeCount:(NSInteger)likeCount isLiked:(BOOL)isLiked {
  407. [self updateLikeUI:isLiked count:likeCount];
  408. }
  409. - (void)updateFavoriteCount:(NSInteger)favoriteCount isFavorited:(BOOL)isFavorited {
  410. [self updateFavoriteUI:isFavorited count:favoriteCount];
  411. }
  412. #pragma mark - 按钮事件
  413. - (void)handleLikeTap {
  414. if ([self.delegate respondsToSelector:@selector(shortDramaCellDidTapLike:)]) {
  415. [self.delegate shortDramaCellDidTapLike:self];
  416. } else {
  417. }
  418. }
  419. - (void)handleFavoriteTap {
  420. if ([self.delegate respondsToSelector:@selector(shortDramaCellDidTapFavorite:)]) {
  421. [self.delegate shortDramaCellDidTapFavorite:self];
  422. } else {
  423. }
  424. }
  425. - (void)handleCommentTap {
  426. if (self.delegate) {
  427. SEL selector = @selector(shortDramaCellDidTapComment:);
  428. BOOL responds = [self.delegate respondsToSelector:selector];
  429. if (responds) {
  430. [self.delegate shortDramaCellDidTapComment:self];
  431. } else {
  432. }
  433. }
  434. }
  435. - (void)handleCollectionTap {
  436. if ([self.delegate respondsToSelector:@selector(shortDramaCellDidTapCollection:)]) {
  437. [self.delegate shortDramaCellDidTapCollection:self];
  438. }
  439. }
  440. - (void)handleDetailTap {
  441. if ([self.delegate respondsToSelector:@selector(shortDramaCellDidTapDetail:)]) {
  442. [self.delegate shortDramaCellDidTapDetail:self];
  443. }
  444. }
  445. #pragma mark - JXSuperPlayerDelegate
  446. - (void)superPlayerDidChangeState:(JXSuperPlayerState)state {
  447. self.playerState = state;
  448. switch (state) {
  449. case JXSuperPlayerStatePreparing:
  450. NSLog(@"[JXShortDramaCell] 播放器状态变更: 准备中");
  451. // 开始播放时启动超时检测
  452. [self startPlayTimeoutTimer];
  453. break;
  454. case JXSuperPlayerStatePlaying:
  455. NSLog(@"[JXShortDramaCell] 播放器状态变更: 播放中");
  456. // 成功开始播放,取消超时检测
  457. [self stopPlayTimeoutTimer];
  458. break;
  459. case JXSuperPlayerStatePaused:
  460. NSLog(@"[JXShortDramaCell] 播放器状态变更: 暂停");
  461. break;
  462. case JXSuperPlayerStateCompleted:
  463. NSLog(@"[JXShortDramaCell] 播放器状态变更: 播放完成");
  464. [self stopPlayTimeoutTimer];
  465. break;
  466. case JXSuperPlayerStateError:
  467. NSLog(@"[JXShortDramaCell] 播放器状态变更: 错误");
  468. [self stopPlayTimeoutTimer];
  469. break;
  470. default:
  471. break;
  472. }
  473. }
  474. // 播放超时检测
  475. - (void)startPlayTimeoutTimer {
  476. [self stopPlayTimeoutTimer]; // 先停止之前的定时器
  477. self.playStartTime = [NSDate date];
  478. // 30秒超时检测
  479. self.playTimeoutTimer = [NSTimer scheduledTimerWithTimeInterval:30.0
  480. target:self
  481. selector:@selector(onPlayTimeout)
  482. userInfo:nil
  483. repeats:NO];
  484. NSLog(@"[JXShortDramaCell] 启动播放超时检测: 30秒");
  485. }
  486. - (void)stopPlayTimeoutTimer {
  487. if (self.playTimeoutTimer) {
  488. [self.playTimeoutTimer invalidate];
  489. self.playTimeoutTimer = nil;
  490. self.playStartTime = nil;
  491. NSLog(@"[JXShortDramaCell] 停止播放超时检测");
  492. }
  493. }
  494. - (void)onPlayTimeout {
  495. NSLog(@"[JXShortDramaCell] 播放超时!30秒内未开始播放,触发恢复机制");
  496. // 停止定时器
  497. [self stopPlayTimeoutTimer];
  498. // 触发播放失败处理
  499. if ([self.delegate respondsToSelector:@selector(shortDramaCellDidFailToPlay:)]) {
  500. [self.delegate shortDramaCellDidFailToPlay:self];
  501. }
  502. }
  503. - (void)superPlayerDidUpdateProgress:(NSTimeInterval)currentTime duration:(NSTimeInterval)duration {
  504. // 播放进度更新(可用于显示进度条)
  505. // NSLog(@"[JXShortDramaCell] 播放进度: %.1f / %.1f", currentTime, duration);
  506. float progress = currentTime/duration;
  507. self.videoSlider.value = progress;
  508. }
  509. - (void)superPlayerDidFailWithError:(NSError *)error {
  510. NSLog(@"[JXShortDramaCell] 播放失败: %@", error.localizedDescription);
  511. // 检查是否是网络相关错误
  512. BOOL isNetworkError = [error.domain isEqualToString:NSURLErrorDomain] &&
  513. (error.code == NSURLErrorTimedOut ||
  514. error.code == NSURLErrorCannotConnectToHost ||
  515. error.code == NSURLErrorNetworkConnectionLost ||
  516. error.code == NSURLErrorNotConnectedToInternet);
  517. // 也检查播放器内部错误
  518. BOOL isPlayerError = [error.domain isEqualToString:@"JXSuperPlayer"] ||
  519. [error.domain isEqualToString:@"TXVodPlayer"];
  520. if (isNetworkError || isPlayerError) {
  521. NSLog(@"[JXShortDramaCell] 检测到播放错误,尝试恢复: domain=%@, code=%ld",
  522. error.domain, (long)error.code);
  523. // 延迟3秒后通知控制器重新获取播放数据
  524. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  525. if ([self.delegate respondsToSelector:@selector(shortDramaCellDidFailToPlay:)]) {
  526. [self.delegate shortDramaCellDidFailToPlay:self];
  527. }
  528. });
  529. } else {
  530. // 其他类型的错误,直接显示
  531. NSLog(@"[JXShortDramaCell] 非网络错误,不进行重试: %@", error);
  532. }
  533. // 显示错误提示(可以在这里添加UI反馈)
  534. // TODO: 在UI上显示播放失败的提示,但不阻塞用户操作
  535. }
  536. #pragma mark - 生命周期
  537. - (void)dealloc {
  538. // 清理定时器
  539. [self stopPlayTimeoutTimer];
  540. // 清理播放器
  541. if (self.superPlayer) {
  542. [self.superPlayer releasePlayer];
  543. }
  544. }
  545. @end