// // JXShortDramaViewController.m // AICity // // Created by TogetherWatch on 2025-10-20. // #import "JXShortDramaViewController.h" #import "JXShortDramaCell.h" #import "JXCommentViewController.h" #import "JXCollectionViewController.h" #import "JXAPIService.h" #import "JXDramaContent.h" #import "JXCommentContent.h" #import "JXEpisodeInfo.h" #import "GuestHelper.h" #import "JXDetailViewController.h" // Added for detail view #import "SearchCommonBarView.h" #import "JXSearchViewController.h" // 复用标识符 static NSString * const kDramaCellIdentifier = @"JXShortDramaCell"; @interface JXShortDramaViewController () #pragma mark - UI组件 @property (nonatomic, strong) UICollectionView *collectionView; @property (nonatomic, strong) UICollectionViewFlowLayout *flowLayout; @property (nonatomic, strong) UIRefreshControl *refreshControl; #pragma mark - 数据 @property (nonatomic, strong) NSMutableArray *dramaList; @property (nonatomic, copy) NSString *categoryId; @property (nonatomic, assign) NSInteger currentPage; @property (nonatomic, assign) NSInteger currentIndex; // 当前播放的索引 #pragma mark - 状态 @property (nonatomic, assign) BOOL isLoading; @property (nonatomic, assign) BOOL hasMoreData; @property (nonatomic, assign) BOOL hasUserVisited; // 用户是否已访问过 @property (nonatomic, assign) BOOL isFirstLoad; // 是否首次加载 @property (nonatomic, strong) SearchCommonBarView *searchBar; @end @implementation JXShortDramaViewController #pragma mark - 初始化 - (instancetype)initWithCategoryId:(NSString *)categoryId { self = [super init]; if (self) { _categoryId = categoryId; _currentPage = 1; _currentIndex = 0; _hasMoreData = YES; _hasUserVisited = NO; // 初始化为未访问 _isFirstLoad = YES; // 初始化为首次加载 _dramaList = [NSMutableArray array]; } return self; } - (instancetype)init { return [self initWithCategoryId:nil]; } #pragma mark - 生命周期 - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor blackColor]; [self setupUI]; [self loadInitialData]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; // 隐藏导航栏(全屏沉浸式) [self.navigationController setNavigationBarHidden:YES animated:animated]; // 标记用户已访问 self.hasUserVisited = YES; // 如果是首次加载且有数据,开始播放 if (self.isFirstLoad && self.dramaList.count > 0) { [self playVideoAtIndex:0]; self.isFirstLoad = NO; } else { // 非首次,恢复当前视频播放 [self resumeCurrentVideo]; } } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; // 暂停所有视频 [self pauseAllVideos]; } - (void)dealloc { NSLog(@"[JXShortDrama] dealloc"); } #pragma mark - UI设置 - (void)setupUI { // 创建FlowLayout(垂直分页) self.flowLayout = [[UICollectionViewFlowLayout alloc] init]; self.flowLayout.scrollDirection = UICollectionViewScrollDirectionVertical; self.flowLayout.minimumLineSpacing = 0; self.flowLayout.minimumInteritemSpacing = 0; // 创建CollectionView self.collectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:self.flowLayout]; self.collectionView.backgroundColor = [UIColor blackColor]; self.collectionView.delegate = self; self.collectionView.dataSource = self; self.collectionView.pagingEnabled = YES; // 分页滚动 self.collectionView.showsVerticalScrollIndicator = NO; self.collectionView.showsHorizontalScrollIndicator = NO; // 注册Cell [self.collectionView registerClass:[JXShortDramaCell class] forCellWithReuseIdentifier:kDramaCellIdentifier]; [self.view addSubview:self.collectionView]; // 添加下拉刷新 self.refreshControl = [[UIRefreshControl alloc] init]; self.refreshControl.tintColor = [UIColor whiteColor]; [self.refreshControl addTarget:self action:@selector(handleRefresh) forControlEvents:UIControlEventValueChanged]; [self.collectionView addSubview:self.refreshControl]; self.searchBar = [[SearchCommonBarView alloc] init]; self.searchBar.backgroundColor = [UIColor clearColor]; __weak typeof(self) weakSelf = self; self.searchBar.searchAction = ^(NSString * _Nonnull text) { [weakSelf performSearchWithKeyword:text]; }; [self.view addSubview:self.searchBar]; [self.view bringSubviewToFront:self.searchBar]; [self.searchBar mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(self.view.mas_safeAreaLayoutGuideTop); make.left.right.equalTo(self.view); make.height.mas_equalTo(44); }]; } - (void)performSearchWithKeyword:(NSString *)keyword { if (keyword.length == 0) { return; } JXSearchViewController *vc = [[JXSearchViewController alloc] initWithKeyword:keyword]; [self.navigationController pushViewController:vc animated:YES]; } #pragma mark - 数据加载 - (void)loadInitialData { self.currentPage = 1; [self loadDramaListWithPage:1 append:NO]; } - (void)loadMoreData { if (self.isLoading || !self.hasMoreData) { return; } self.currentPage++; [self loadDramaListWithPage:self.currentPage append:YES]; } - (void)loadDramaListWithPage:(NSInteger)page append:(BOOL)append { if (self.isLoading) { return; } self.isLoading = YES; [[JXAPIService sharedService] getDramaListWithCategoryId:self.categoryId page:page pageSize:10 success:^(id response) { self.isLoading = NO; [self.refreshControl endRefreshing]; NSDictionary *data = response[@"data"]; NSArray *items = data[@"items"]; // 后端返回的字段是 "items" 不是 "list" if (items.count == 0) { self.hasMoreData = NO; return; } // 转换为模型 NSMutableArray *models = [NSMutableArray array]; for (NSDictionary *dict in items) { JXDramaContent *drama = [[JXDramaContent alloc] initWithDictionary:dict]; [models addObject:drama]; } if (append) { [self.dramaList addObjectsFromArray:models]; } else { self.dramaList = models; } [self.collectionView reloadData]; [self.collectionView setContentOffset:CGPointMake(0, 0) animated:YES]; // 首次加载后,只有用户已访问才自动播放 if (!append && models.count > 0) { if (self.hasUserVisited) { // 获取第一个短剧的详情(包含播放源) // 注:使用 jx_drama_id(剧星平台ID)而不是本地 dramaId JXDramaContent *firstDrama = models[0]; [[JXAPIService sharedService] getDramaDetailWithDramaId:[@(firstDrama.dramaId) stringValue] success:^(id response) { NSDictionary *data = response[@"data"]; NSDictionary *dramaDict = data[@"drama"]; NSArray *episodes = data[@"episodes"]; if (dramaDict) { firstDrama.videoUrl = dramaDict[@"video_url"]; // 从第一集中提取播放源(tcplayer字段在episodes中) if (episodes && episodes.count > 0) { NSDictionary *firstEpisode = episodes[0]; firstDrama.appId = firstEpisode[@"tcplayer_app_id"]; firstDrama.fileId = firstEpisode[@"tcplayer_file_id"]; firstDrama.psign = firstEpisode[@"tcplayer_sign"] ?: firstEpisode[@"tcplayer_sign_265"] ?: firstEpisode[@"tcplayer_sign_264"]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self playVideoAtIndex:0]; }); } else { // 如果详情接口没返回episodes,尝试单独获取剧集列表 [[JXAPIService sharedService] getEpisodesWithDramaId:[@(firstDrama.dramaId) stringValue] success:^(id episodesResponse) { NSArray *episodesList = episodesResponse[@"data"][@"episodes"]; if (episodesList && episodesList.count > 0) { NSDictionary *firstEp = episodesList[0]; firstDrama.appId = firstEp[@"tcplayer_app_id"]; firstDrama.fileId = firstEp[@"tcplayer_file_id"]; firstDrama.psign = firstEp[@"tcplayer_sign"] ?: firstEp[@"tcplayer_sign_265"] ?: firstEp[@"tcplayer_sign_264"]; } dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self playVideoAtIndex:0]; }); } failure:^(NSError *error) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self playVideoAtIndex:0]; }); }]; } } } failure:^(NSError *error) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self playVideoAtIndex:0]; }); }]; self.isFirstLoad = NO; } } } failure:^(NSError *error) { self.isLoading = NO; [self.refreshControl endRefreshing]; NSLog(@"[JXShortDrama] 加载失败: %@", error.localizedDescription); // TODO: 显示错误提示 }]; } - (void)handleRefresh { self.hasMoreData = YES; [self loadInitialData]; } #pragma mark - UICollectionViewDataSource - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return self.dramaList.count; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { JXShortDramaCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kDramaCellIdentifier forIndexPath:indexPath]; // 设置代理 cell.delegate = self; NSLog(@"[JXShortDrama] ✅ cellForItemAtIndexPath - Cell delegate 已设置,index: %ld", (long)indexPath.item); // 配置数据 JXDramaContent *drama = self.dramaList[indexPath.item]; [cell configureWithDrama:drama]; NSLog(@"[JXShortDrama] ✅ cellForItemAtIndexPath - Drama 已配置,dramaId: %lld, title: %@", drama.dramaId, drama.title); return cell; } #pragma mark - UICollectionViewDelegateFlowLayout - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { // 每个Cell占满全屏 return self.view.bounds.size; } #pragma mark - UIScrollViewDelegate - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { // 计算当前显示的索引 CGFloat pageHeight = scrollView.bounds.size.height; NSInteger newIndex = (NSInteger)(scrollView.contentOffset.y / pageHeight); NSLog(@"[JXShortDrama] ========== 滑动结束 =========="); NSLog(@"[JXShortDrama] 旧索引: %ld, 新索引: %ld", (long)self.currentIndex, (long)newIndex); if (newIndex != self.currentIndex) { // 停止旧视频 NSLog(@"[JXShortDrama] 停止旧视频: 索引 %ld", (long)self.currentIndex); [self stopVideoAtIndex:self.currentIndex]; // 播放新视频 NSLog(@"[JXShortDrama] 播放新视频: 索引 %ld", (long)newIndex); self.currentIndex = newIndex; [self playVideoAtIndex:newIndex]; } else { // 索引没变,说明用户滑动后又回到原位 // 需要恢复播放(之前在scrollViewWillBeginDragging中暂停了) NSLog(@"[JXShortDrama] 索引未变化,恢复当前视频播放"); [self resumeCurrentVideo]; } // 预加载逻辑 if (newIndex >= self.dramaList.count - 3 && self.hasMoreData) { NSLog(@"[JXShortDrama] 触发预加载逻辑"); [self loadMoreData]; } } - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { // 用户开始滑动时,暂停当前视频 [self pauseCurrentVideo]; } #pragma mark - 播放器管理 - (void)playVideoAtIndex:(NSInteger)index { NSLog(@"[JXShortDrama] playVideoAtIndex:%ld", (long)index); if (index < 0 || index >= self.dramaList.count) { NSLog(@"[JXShortDrama] ❌ 索引越界: index=%ld, count=%ld", (long)index, (long)self.dramaList.count); return; } JXDramaContent *drama = self.dramaList[index]; // 检查是否已有播放信息 if ([self hasPlayInfo:drama]) { // 已有播放信息,直接播放 [self doPlayVideoAtIndex:index]; } else { // 需要获取播放信息 NSLog(@"[JXShortDrama] 需要获取播放信息: dramaId=%lld", drama.dramaId); [self loadPlayInfoForDrama:drama atIndex:index]; } } - (BOOL)hasPlayInfo:(JXDramaContent *)drama { // 检查是否有播放所需的信息 if ([drama isFileIdMode]) { return drama.fileId && drama.appId && drama.psign; } else if ([drama isUrlMode]) { return drama.videoUrl; } return NO; } - (void)doPlayVideoAtIndex:(NSInteger)index { // 启动播放 JXShortDramaCell *cell = (JXShortDramaCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]]; if (cell) { NSLog(@"[JXShortDrama] 找到Cell,开始播放"); [cell startPlay]; } else { NSLog(@"[JXShortDrama] ❌ 未找到Cell"); } } - (void)loadPlayInfoForDrama:(JXDramaContent *)drama atIndex:(NSInteger)index { // 显示加载指示器 [self showLoadingIndicatorForCellAtIndex:index]; // 获取剧集详情,包含播放信息 [[JXAPIService sharedService] getDramaDetailWithDramaId:[@(drama.dramaId) stringValue] success:^(id response) { NSLog(@"[JXShortDrama] 获取到剧集详情响应: %@", response); NSDictionary *data = response[@"data"]; NSArray *episodes = data[@"episodes"]; NSLog(@"[JXShortDrama] 解析数据:"); NSLog(@"[JXShortDrama] - 完整响应: %@", response); NSLog(@"[JXShortDrama] - data字典: %@", data); NSLog(@"[JXShortDrama] - episodes数组: %@", episodes); NSLog(@"[JXShortDrama] - episodes数量: %lu", (unsigned long)episodes.count); if (episodes) { for (int i = 0; i < MIN(2, episodes.count); i++) { NSDictionary *episode = episodes[i]; NSLog(@"[JXShortDrama] - 第%d集: %@", i+1, episode); } } if (episodes && episodes.count > 0) { NSDictionary *firstEpisode = episodes[0]; // 提取播放信息 drama.appId = firstEpisode[@"tcplayer_app_id"]; drama.fileId = firstEpisode[@"tcplayer_file_id"]; // 优先使用H.264格式 drama.psign = firstEpisode[@"tcplayer_sign_264"] ?: firstEpisode[@"tcplayer_sign"] ?: firstEpisode[@"tcplayer_sign_265"]; // 如果都没有,尝试其他可能的字段名 if (!drama.psign) { drama.psign = firstEpisode[@"sign"] ?: firstEpisode[@"psign"]; } // 备用URL模式 drama.videoUrl = firstEpisode[@"video_url"]; NSLog(@"[JXShortDrama] 播放信息已加载: appId=%@, fileId=%@, psign长度=%lu", drama.appId, drama.fileId, (unsigned long)drama.psign.length); // 隐藏加载指示器 [self hideLoadingIndicatorForCellAtIndex:index]; // 现在可以播放了 [self doPlayVideoAtIndex:index]; } else { [self hideLoadingIndicatorForCellAtIndex:index]; NSLog(@"[JXShortDrama] ❌ 获取播放信息失败: 剧集数据为空"); } } failure:^(NSError *error) { [self hideLoadingIndicatorForCellAtIndex:index]; NSLog(@"[JXShortDrama] ❌ 获取播放信息失败: %@", error.localizedDescription); }]; } - (void)stopVideoAtIndex:(NSInteger)index { if (index < 0 || index >= self.dramaList.count) { return; } JXShortDramaCell *cell = (JXShortDramaCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]]; [cell stopPlay]; } - (void)pauseCurrentVideo { if (self.currentIndex < 0 || self.currentIndex >= self.dramaList.count) { return; } JXShortDramaCell *cell = (JXShortDramaCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:self.currentIndex inSection:0]]; [cell pausePlay]; } - (void)resumeCurrentVideo { if (self.currentIndex < 0 || self.currentIndex >= self.dramaList.count) { return; } JXShortDramaCell *cell = (JXShortDramaCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:self.currentIndex inSection:0]]; [cell resumePlay]; } - (void)pauseAllVideos { // 暂停所有可见的Cell for (NSIndexPath *indexPath in self.collectionView.indexPathsForVisibleItems) { JXShortDramaCell *cell = (JXShortDramaCell *)[self.collectionView cellForItemAtIndexPath:indexPath]; [cell pausePlay]; } } #pragma mark - 交互按钮处理 - (void)handleLikeButtonAtIndex:(NSInteger)index { // 登录检查 BOOL isLoggedIn = [[GuestHelper sharedHelper] checkLoginWithViewController:self action:^{ // 登录成功后重新执行点赞操作 // [self handleLikeButtonAtIndex:index]; }]; if (!isLoggedIn) { return; // 未登录,等待用户登录 } JXDramaContent *drama = self.dramaList[index]; // 点赞动画 [self animateLikeButtonAtIndex:index]; [[JXAPIService sharedService] toggleLikeWithDramaId:[@(drama.dramaId) stringValue] episodeId:drama.episodeId success:^(id response) { // 更新UI BOOL isLiked = [response[@"data"][@"isLiked"] boolValue]; long long likeCount = [response[@"data"][@"likeCount"] longLongValue]; drama.isLiked = isLiked; drama.likeCount = likeCount; // 更新Cell UI(直接更新,不刷新列表) [self updateLikeCountUI:likeCount isLiked:isLiked atIndex:index]; } failure:^(NSError *error) { NSLog(@"[JXShortDrama] 点赞失败: %@", error.localizedDescription); }]; } - (void)handleFavoriteButtonAtIndex:(NSInteger)index { // 登录检查 BOOL isLoggedIn = [[GuestHelper sharedHelper] checkLoginWithViewController:self action:^{ // 登录成功后重新执行收藏操作 // [self handleFavoriteButtonAtIndex:index]; }]; if (!isLoggedIn) { return; // 未登录,等待用户登录 } JXDramaContent *drama = self.dramaList[index]; // 收藏动画 [self animateFavoriteButtonAtIndex:index]; [[JXAPIService sharedService] toggleFavoriteWithDramaId:[@(drama.dramaId) stringValue] success:^(id response) { // 更新UI BOOL isFavorited = [response[@"data"][@"isFavorited"] boolValue]; long long favoriteCount = [response[@"data"][@"favoriteCount"] longLongValue]; drama.isFavorited = isFavorited; drama.favoriteCount = favoriteCount; // 更新Cell UI(直接更新,不刷新列表) [self updateFavoriteCountUI:favoriteCount isFavorited:isFavorited atIndex:index]; } failure:^(NSError *error) { NSLog(@"[JXShortDrama] 收藏失败: %@", error.localizedDescription); }]; } - (void)handleCommentButtonAtIndex:(NSInteger)index { NSLog(@"[JXShortDrama] ========== 💬 handleCommentButtonAtIndex 开始 =========="); NSLog(@"[JXShortDrama] 💬 index: %ld", (long)index); NSLog(@"[JXShortDrama] 💬 dramaList count: %lu", (unsigned long)self.dramaList.count); // 登录检查 NSLog(@"[JXShortDrama] 💬 开始登录检查..."); BOOL isLoggedIn = [[GuestHelper sharedHelper] checkLoginWithViewController:self action:^{ NSLog(@"[JXShortDrama] 💬 登录完成,重新调用 handleCommentButtonAtIndex"); // 登录成功后重新打开评论 }]; NSLog(@"[JXShortDrama] 💬 登录检查结果: %@", isLoggedIn ? @"已登录 ✅" : @"未登录,等待登录"); if (!isLoggedIn) { NSLog(@"[JXShortDrama] 💬 用户未登录,等待登录完成后再处理"); return; // 未登录,等待用户登录 } NSLog(@"[JXShortDrama] 💬 用户已登录,获取 drama 数据"); if (index < 0 || index >= self.dramaList.count) { NSLog(@"[JXShortDrama] ❌ 索引越界,index: %ld, count: %lu", (long)index, (unsigned long)self.dramaList.count); return; } JXDramaContent *drama = self.dramaList[index]; NSLog(@"[JXShortDrama] 💬 drama: %@, dramaId: %lld", drama.title, drama.dramaId); // 显示评论弹窗 NSLog(@"[JXShortDrama] 💬 显示评论弹窗..."); [self showCommentDialogWithDramaId:[@(drama.dramaId) stringValue]]; NSLog(@"[JXShortDrama] 💬 评论弹窗已显示"); } - (void)handleCollectionButtonAtIndex:(NSInteger)index { JXDramaContent *drama = self.dramaList[index]; // 显示合集弹窗 [self showCollectionDialogWithDramaId:[@(drama.dramaId) stringValue]]; } #pragma mark - JXShortDramaCellDelegate - (void)shortDramaCellDidTapLike:(UICollectionViewCell *)cell { NSLog(@"[JXShortDramaViewController] 📍 shortDramaCellDidTapLike 被调用"); NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell]; NSLog(@"[JXShortDramaViewController] indexPath: %@", indexPath); if (indexPath) { [self handleLikeButtonAtIndex:indexPath.item]; } else { NSLog(@"[JXShortDramaViewController] ❌ 无法获取 indexPath"); } } - (void)shortDramaCellDidTapFavorite:(UICollectionViewCell *)cell { NSLog(@"[JXShortDramaViewController] 📍 shortDramaCellDidTapFavorite 被调用"); NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell]; NSLog(@"[JXShortDramaViewController] indexPath: %@", indexPath); if (indexPath) { [self handleFavoriteButtonAtIndex:indexPath.item]; } else { NSLog(@"[JXShortDramaViewController] ❌ 无法获取 indexPath"); } } - (void)shortDramaCellDidTapComment:(UICollectionViewCell *)cell { NSLog(@"[JXShortDramaViewController] ========== 📍 shortDramaCellDidTapComment 被调用 =========="); NSLog(@"[JXShortDramaViewController] 📍 cell: %@", cell); NSLog(@"[JXShortDramaViewController] 📍 cell class: %@", [cell class]); NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell]; NSLog(@"[JXShortDramaViewController] 📍 indexPath: %@", indexPath); if (indexPath) { NSLog(@"[JXShortDramaViewController] 📍 indexPath.item: %ld", (long)indexPath.item); [self handleCommentButtonAtIndex:indexPath.item]; } else { NSLog(@"[JXShortDramaViewController] ❌ 无法获取 indexPath"); } } - (void)shortDramaCellDidTapCollection:(UICollectionViewCell *)cell { NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell]; if (indexPath) { [self handleCollectionButtonAtIndex:indexPath.item]; } } - (void)shortDramaCellDidTapDetail:(UICollectionViewCell *)cell { NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell]; if (indexPath) { JXDramaContent *drama = self.dramaList[indexPath.item]; NSLog(@"🎬 点击查看全部/详情,dramaId: %lld", drama.dramaId); // 跳转到详情页 JXDetailViewController *detailVC = [[JXDetailViewController alloc] initWithDramaId:[@(drama.dramaId) stringValue]]; [self.navigationController pushViewController:detailVC animated:YES]; } } // 加载指示器管理 - (void)showLoadingIndicatorForCellAtIndex:(NSInteger)index { NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0]; JXShortDramaCell *cell = (JXShortDramaCell *)[self.collectionView cellForItemAtIndexPath:indexPath]; if (cell) { // 在Cell上显示加载指示器 UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; indicator.center = CGPointMake(cell.bounds.size.width / 2, cell.bounds.size.height / 2); indicator.tag = 999; // 用于标识和移除 [cell.contentView addSubview:indicator]; [indicator startAnimating]; // 添加半透明背景 UIView *backgroundView = [[UIView alloc] initWithFrame:cell.bounds]; backgroundView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5]; backgroundView.tag = 998; [cell.contentView insertSubview:backgroundView belowSubview:indicator]; } } - (void)hideLoadingIndicatorForCellAtIndex:(NSInteger)index { NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0]; JXShortDramaCell *cell = (JXShortDramaCell *)[self.collectionView cellForItemAtIndexPath:indexPath]; if (cell) { // 移除加载指示器 UIActivityIndicatorView *indicator = (UIActivityIndicatorView *)[cell.contentView viewWithTag:999]; [indicator removeFromSuperview]; // 移除背景 UIView *backgroundView = (UIView *)[cell.contentView viewWithTag:998]; [backgroundView removeFromSuperview]; } } - (void)shortDramaCellDidFailToPlay:(UICollectionViewCell *)cell { NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell]; if (indexPath) { JXDramaContent *drama = self.dramaList[indexPath.item]; NSLog(@"[JXShortDrama] 播放失败,重新获取剧集数据: dramaId=%lld", drama.dramaId); // 显示加载指示器 [self showLoadingIndicatorForCellAtIndex:indexPath.item]; // 重新获取剧集详情,确保获取最新的播放签名 [[JXAPIService sharedService] getDramaDetailWithDramaId:[@(drama.dramaId) stringValue] success:^(id response) { NSDictionary *data = response[@"data"]; NSArray *episodes = data[@"episodes"]; if (episodes && episodes.count > 0) { NSDictionary *firstEpisode = episodes[0]; // 更新drama对象的播放数据 drama.appId = firstEpisode[@"tcplayer_app_id"]; drama.fileId = firstEpisode[@"tcplayer_file_id"]; // 优先使用H.264格式(更兼容) drama.psign = firstEpisode[@"tcplayer_sign_264"] ?: firstEpisode[@"tcplayer_sign"] ?: firstEpisode[@"tcplayer_sign_265"]; NSLog(@"[JXShortDrama] 剧集数据已刷新,重新尝试播放: appId=%@, fileId=%@", drama.appId, drama.fileId); // 隐藏加载指示器 [self hideLoadingIndicatorForCellAtIndex:indexPath.item]; // 重新尝试播放 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self playVideoAtIndex:indexPath.item]; }); } else { [self hideLoadingIndicatorForCellAtIndex:indexPath.item]; NSLog(@"[JXShortDrama] 刷新后无可用剧集数据"); } } failure:^(NSError *error) { [self hideLoadingIndicatorForCellAtIndex:indexPath.item]; NSLog(@"[JXShortDrama] 刷新剧集数据失败: %@", error.localizedDescription); }]; } } #pragma mark - 弹窗显示 - (void)showCommentDialogWithDramaId:(NSString *)dramaId { NSLog(@"[JXShortDrama] ========== 🗨️ showCommentDialogWithDramaId 开始 =========="); NSLog(@"[JXShortDrama] 🗨️ dramaId: %@", dramaId); NSLog(@"[JXShortDrama] 🗨️ 创建 JXCommentViewController..."); JXCommentViewController *commentVC = [[JXCommentViewController alloc] initWithDramaId:dramaId]; NSLog(@"[JXShortDrama] 🗨️ commentVC 已创建: %@", commentVC); // 设置弹窗样式(底部弹出) if (@available(iOS 15.0, *)) { NSLog(@"[JXShortDrama] 🗨️ 配置 iOS 15+ 的 Sheet 弹窗样式"); UISheetPresentationController *sheet = commentVC.sheetPresentationController; sheet.detents = @[ [UISheetPresentationControllerDetent mediumDetent], [UISheetPresentationControllerDetent largeDetent] ]; sheet.prefersGrabberVisible = YES; NSLog(@"[JXShortDrama] 🗨️ Sheet 样式配置完成"); } else { NSLog(@"[JXShortDrama] 🗨️ iOS 版本 < 15.0,跳过 Sheet 样式配置"); } NSLog(@"[JXShortDrama] 🗨️ 正在 present commentVC..."); [self presentViewController:commentVC animated:YES completion:^{ NSLog(@"[JXShortDrama] 🗨️ commentVC present 完成!"); }]; } - (void)showCollectionDialogWithDramaId:(NSString *)dramaId { JXCollectionViewController *collectionVC = [[JXCollectionViewController alloc] initWithDramaId:dramaId]; collectionVC.delegate = self; // 设置弹窗样式(底部弹出) if (@available(iOS 15.0, *)) { UISheetPresentationController *sheet = collectionVC.sheetPresentationController; sheet.detents = @[ [UISheetPresentationControllerDetent mediumDetent], [UISheetPresentationControllerDetent largeDetent] ]; sheet.prefersGrabberVisible = YES; } [self presentViewController:collectionVC animated:YES completion:nil]; } #pragma mark - JXCollectionViewControllerDelegate - (void)collectionViewControllerDidSelectEpisode:(NSString *)episodeId { NSLog(@"[JXShortDrama] 选择播放剧集: %@", episodeId); JXDramaContent *drama = self.dramaList[self.currentIndex]; JXDetailViewController *detailVC = [[JXDetailViewController alloc] initWithDramaId:[NSString stringWithFormat:@"%lld",drama.dramaId]]; detailVC.playIndex = (episodeId.intValue-1 < 0) ? 0 : episodeId.intValue-1; [self.navigationController pushViewController:detailVC animated:YES]; // JXDetailViewController *detailVC = [[JXDetailViewController alloc] initWithDramaId:episodeId]; // [self.navigationController pushViewController:detailVC animated:YES]; // TODO: 切换到选定的剧集播放 // 可以找到对应的短剧,然后滚动到该位置并播放 } #pragma mark - 动画效果 - (void)animateLikeButtonAtIndex:(NSInteger)index { NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0]; JXShortDramaCell *cell = (JXShortDramaCell *)[self.collectionView cellForItemAtIndexPath:indexPath]; if (cell && [cell respondsToSelector:@selector(likeButton)]) { UIButton *likeButton = [cell valueForKey:@"likeButton"]; if (likeButton) { // 缩放动画:放大 -> 恢复 [UIView animateWithDuration:0.15 animations:^{ likeButton.transform = CGAffineTransformMakeScale(1.3, 1.3); } completion:^(BOOL finished) { [UIView animateWithDuration:0.15 animations:^{ likeButton.transform = CGAffineTransformIdentity; }]; }]; } } } - (void)animateFavoriteButtonAtIndex:(NSInteger)index { NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0]; JXShortDramaCell *cell = (JXShortDramaCell *)[self.collectionView cellForItemAtIndexPath:indexPath]; if (cell && [cell respondsToSelector:@selector(favoriteButton)]) { UIButton *favoriteButton = [cell valueForKey:@"favoriteButton"]; if (favoriteButton) { // 缩放动画:放大 -> 恢复 [UIView animateWithDuration:0.15 animations:^{ favoriteButton.transform = CGAffineTransformMakeScale(1.3, 1.3); } completion:^(BOOL finished) { [UIView animateWithDuration:0.15 animations:^{ favoriteButton.transform = CGAffineTransformIdentity; }]; }]; } } } #pragma mark - UI 更新(避免视频停止) - (void)updateLikeCountUI:(NSInteger)likeCount isLiked:(BOOL)isLiked atIndex:(NSInteger)index { NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0]; JXShortDramaCell *cell = (JXShortDramaCell *)[self.collectionView cellForItemAtIndexPath:indexPath]; if (cell && [cell respondsToSelector:@selector(updateLikeCount:isLiked:)]) { [cell updateLikeCount:likeCount isLiked:isLiked]; } } - (void)updateFavoriteCountUI:(NSInteger)favoriteCount isFavorited:(BOOL)isFavorited atIndex:(NSInteger)index { NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0]; JXShortDramaCell *cell = (JXShortDramaCell *)[self.collectionView cellForItemAtIndexPath:indexPath]; if (cell && [cell respondsToSelector:@selector(updateFavoriteCount:isFavorited:)]) { [cell updateFavoriteCount:favoriteCount isFavorited:isFavorited]; } } @end