JXShortDramaCell.m 23 KB

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