JXShortDramaCell.m 24 KB

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