// // JXDetailViewController.m // AICity // // Created by TogetherWatch on 2025-10-13. // 短剧详情+播放器页面(对标Android的JuXingPlayerActivity) // #import "JXDetailViewController.h" #import "JXAPIService.h" #import "JXCacheManager.h" #import "JXDrama.h" #import "JXEpisode.h" #import "JXInteraction.h" #import "JXAVPlayer.h" #import "JXSuperPlayer.h" #import #import "JXCollectionViewController.h" #import "JXCommentViewController.h" #import #import "VideoDetailCell.h" @interface JXDetailViewController () { JXSuperPlayer *_currentPlayer; } // 播放器 @property (nonatomic, strong) UIView *playerContainer; @property (nonatomic, strong) JXSuperPlayer *player; // UI组件 @property (nonatomic, strong) UICollectionView *episodeCollectionView; @property (nonatomic, strong) UILabel *titleLabel; @property (nonatomic, strong) UILabel *descLabel; @property (nonatomic, strong) UILabel *authorLabel; @property (nonatomic, strong) UIButton *likeButton; @property (nonatomic, strong) UILabel *likeCountLabel; @property (nonatomic, strong) UIButton *favoriteButton; @property (nonatomic, strong) UILabel *favoriteCountLabel; @property (nonatomic, strong) UIButton *commentButton; @property (nonatomic, strong) UILabel *commentCountLabel; // 数据 @property (nonatomic, strong) JXDrama *drama; @property (nonatomic, strong) NSArray *episodes; @property (nonatomic, strong) JXInteraction *interaction; @property (nonatomic, assign) NSInteger currentEpisodeIndex; @property (strong, nonatomic) UISlider *videoSlider; @property (nonatomic,assign) BOOL isHuadong; @property (strong, nonatomic) NSMutableArray *playerList; @property (strong, nonatomic) JXInteraction *pageData; @end @implementation JXDetailViewController - (instancetype)initWithDramaId:(NSString *)dramaId { self = [super init]; if (self) { _dramaId = [dramaId copy]; _currentEpisodeIndex = 0; } return self; } - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor blackColor]; // 隐藏导航栏返回按钮 self.navigationItem.hidesBackButton = YES; [self setupUI]; [self loadData]; } - (void)setupUI { CGFloat width = self.view.bounds.size.width; CGFloat height = self.view.bounds.size.height; // 创建垂直滚动的CollectionView(每集占满屏幕) UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; layout.scrollDirection = UICollectionViewScrollDirectionVertical; layout.itemSize = CGSizeMake(width, height); layout.minimumLineSpacing = 0; layout.minimumInteritemSpacing = 0; self.episodeCollectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout]; self.episodeCollectionView.backgroundColor = [UIColor blackColor]; self.episodeCollectionView.delegate = self; self.episodeCollectionView.dataSource = self; self.episodeCollectionView.pagingEnabled = YES; self.episodeCollectionView.showsVerticalScrollIndicator = NO; [self.episodeCollectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"EpisodeCell"]; [self.episodeCollectionView registerClass:[VideoDetailCell class] forCellWithReuseIdentifier:NSStringFromClass(VideoDetailCell.class)]; [self.view addSubview:self.episodeCollectionView]; // 设置约束 self.episodeCollectionView.translatesAutoresizingMaskIntoConstraints = NO; [NSLayoutConstraint activateConstraints:@[ [self.episodeCollectionView.topAnchor constraintEqualToAnchor:self.view.topAnchor], [self.episodeCollectionView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], [self.episodeCollectionView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor], [self.episodeCollectionView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor] ]]; } - (void)loadData { if (!self.dramaId) { return; } // 从网络加载 [[JXAPIService sharedService] getDramaDetailWithDramaId:self.dramaId success:^(id responseObject) { NSDictionary *response = responseObject; NSLog(@"📡 getDramaDetail响应代码: %@", response[@"code"]); NSLog(@"📡 getDramaDetail完整响应: %@", response); if ([response[@"code"] intValue] == 0) { NSDictionary *data = response[@"data"]; NSLog(@"✅ 获取短剧数据成功"); NSLog(@"📊 Drama数据: %@", data[@"drama"]); NSLog(@"📊 Episodes数量: %lu", (unsigned long)[data[@"episodes"] count]); [self updateUIWithData:data]; // 自动滚动到第一集 NSLog(@"⏯️ 自动滚动检查 - 剧集总数: %lu", (unsigned long)self.episodes.count); if (self.episodes.count > 0) { NSLog(@"⏯️ 滚动到第一集"); [self.episodeCollectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:self.playIndex inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:NO]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self playVideoAtIndex:0]; }); } else { NSLog(@"❌ 剧集列表为空,无法播放"); } } else { NSLog(@"❌ API返回错误代码: %@", response[@"code"]); } } failure:^(NSError *error) { NSLog(@"❌ 加载短剧详情失败: %@", error.localizedDescription); }]; } - (void)updateUIWithData:(NSDictionary *)data { // 解析数据 self.drama = [[JXDrama alloc] initWithDictionary:data[@"drama"]]; NSArray *episodesData = data[@"episodes"]; NSMutableArray *episodesList = [NSMutableArray array]; for (NSDictionary *episodeDict in episodesData) { JXEpisode *episode = [[JXEpisode alloc] initWithDictionary:episodeDict]; [episodesList addObject:episode]; } self.episodes = episodesList; self.interaction = [[JXInteraction alloc] initWithDictionary:data[@"interaction"]]; [self.episodeCollectionView reloadData]; } #pragma mark - UICollectionViewDataSource - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return self.episodes.count; } //- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { // UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"EpisodeCell" // forIndexPath:indexPath]; // // // 清理之前的子视图 // for (UIView *view in cell.contentView.subviews) { // [view removeFromSuperview]; // } // // cell.contentView.backgroundColor = [UIColor redColor]; // // CGFloat width = cell.contentView.bounds.size.width; // CGFloat height = cell.contentView.bounds.size.height; // // // 播放器容器(占据上半部分) // CGFloat playerHeight = height; // UIView *playerContainer = [[UIView alloc] initWithFrame:CGRectMake(0, 0, width, playerHeight)]; // playerContainer.backgroundColor = [UIColor blackColor]; // [cell.contentView addSubview:playerContainer]; // [cell.contentView sendSubviewToBack:playerContainer]; // // 创建播放器 // JXSuperPlayer *player = [[JXSuperPlayer alloc] initWithContainerView:playerContainer]; // player.delegate = self; // // 右侧互动按钮 // [self setupInteractionButtonsForCell:cell]; // // UILabel * seeL = [[UILabel alloc] initWithFrame:CGRectMake(16, kScreenHeight - 14-safebottom, SCREEN_WIDTH - 32, 14)]; // seeL.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium]; // seeL.textColor = [UIColor colorWithRed:173/255.0 green:173/255.0 blue:173/255.0 alpha:1]; // [cell.contentView addSubview:seeL]; // seeL.text = [NSString stringWithFormat:@"%@次播放",@"30万"]; // // UIView *bottomView = [[UIView alloc] initWithFrame:CGRectMake(0, CGRectGetMinY(seeL.frame) - 58, SCREEN_WIDTH, 38)]; // bottomView.backgroundColor = [UIColor colorWithRed:255/255.0 green:255/255.0 blue:255/255.0 alpha:0.1]; // [cell.contentView addSubview:bottomView]; // UIImageView *sImgView = [[UIImageView alloc] initWithFrame:CGRectMake(16, 10.5, 17, 17)]; // sImgView.image = [UIImage imageNamed:@"icon 2.1 拷贝"]; // [bottomView addSubview:sImgView]; // UILabel *hjL = [[UILabel alloc] initWithFrame:CGRectMake(CGRectGetMaxX(sImgView.frame)+4, 0, 100, 38)]; // [bottomView addSubview:hjL]; // hjL.font = [UIFont boldSystemFontOfSize:18]; // hjL.textColor = UIColor.whiteColor; // hjL.text = @"合集"; // UIImageView *mImgView = [[UIImageView alloc] initWithFrame:CGRectMake(SCREEN_WIDTH-27, 13.5, 11, 11)]; // mImgView.image = [UIImage imageNamed:@"Frame 9366"]; // [bottomView addSubview:mImgView]; // UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(showHJ:)]; // [bottomView addGestureRecognizer:tap]; // // UISlider *slider = [[UISlider alloc] initWithFrame:CGRectMake(16,34, SCREEN_WIDTH-32, 2)]; // [bottomView addSubview:slider]; // slider.maximumValue = 1; // slider.minimumValue = 0; // [slider setThumbImage:[UIImage imageNamed:@"b_jdt.png"] forState:UIControlStateNormal]; // [slider setThumbImage:[UIImage imageNamed:@"b_jdtb.png"] forState:UIControlStateHighlighted]; // [slider setMinimumTrackImage:[UIImage imageNamed:@"jdt.png"] forState:UIControlStateNormal]; // [slider setMinimumTrackImage:[UIImage imageNamed:@"jdt.png"] forState:UIControlStateHighlighted]; // [slider setMaximumTrackImage:[UIImage imageNamed:@"jdt.png"] forState:UIControlStateNormal]; // [slider setMaximumTrackImage:[UIImage imageNamed:@"jdt.png"] forState:UIControlStateHighlighted] ; // [slider setMinimumTrackTintColor:COLOR(0, 0, 0, 0.1)]; // [slider setMaximumTrackTintColor:COLOR(0, 0, 0, 0.1)]; // slider.alpha = 0.6; // self.videoSlider = slider; // // UILabel *descLabel = [[UILabel alloc] init]; // descLabel.font = [UIFont systemFontOfSize:15 weight:UIFontWeightMedium]; // descLabel.textColor = [UIColor whiteColor]; // descLabel.numberOfLines = 2; // [cell.contentView addSubview:descLabel]; // [cell.contentView bringSubviewToFront:descLabel]; // [descLabel mas_makeConstraints:^(MASConstraintMaker *make) { // make.bottom.equalTo(bottomView.mas_top).offset(-15); // make.left.equalTo(cell.contentView).offset(16); // make.width.mas_equalTo(SCREEN_WIDTH - 90); // }]; // // UILabel *titleLabel = [[UILabel alloc] init]; // titleLabel.font = [UIFont boldSystemFontOfSize:18]; // titleLabel.textColor = [UIColor whiteColor]; // titleLabel.numberOfLines = 2; // [cell.contentView addSubview:titleLabel]; // [cell.contentView bringSubviewToFront:titleLabel]; // [titleLabel mas_makeConstraints:^(MASConstraintMaker *make) { // make.bottom.equalTo(descLabel.mas_top).offset(-12.5); // make.left.equalTo(cell.contentView).offset(16); // make.width.mas_equalTo(SCREEN_WIDTH - 90); // }]; // // JXEpisode *episode = self.episodes[indexPath.item]; // titleLabel.text = self.drama.title ?: @""; // descLabel.text = episode.title ?: @""; // // // 播放视频 // [player playWithAppId:episode.appId fileId:episode.fileId psign:episode.psign]; // // return cell; //} -(__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{ VideoDetailCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass(VideoDetailCell.class) forIndexPath:indexPath]; // 设置代理 cell.delegate = self; NSLog(@"[JXShortDrama] ✅ cellForItemAtIndexPath - Cell delegate 已设置,index: %ld", (long)indexPath.item); // 配置数据 JXEpisode *episode = self.episodes[indexPath.item]; [cell configureWithDrama:episode andDiction:self.interaction andTitle:self.drama.title]; return cell; } - (void)showHJ:(UIGestureRecognizer *)gtr{ JXEpisode *episode = self.episodes[self.currentEpisodeIndex]; [self showCollectionDialogWithDramaId:[NSString stringWithFormat:@"%ld",episode.dramaId]]; } - (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]; } - (void)commentDidClicked{ JXEpisode *episode = self.episodes[self.currentEpisodeIndex]; JXCommentViewController *commentVC = [[JXCommentViewController alloc] initWithDramaId:[NSString stringWithFormat:@"%ld",episode.dramaId]]; // 设置弹窗样式(底部弹出) if (@available(iOS 15.0, *)) { UISheetPresentationController *sheet = commentVC.sheetPresentationController; sheet.detents = @[ [UISheetPresentationControllerDetent mediumDetent], [UISheetPresentationControllerDetent largeDetent] ]; sheet.prefersGrabberVisible = YES; } [self presentViewController:commentVC animated:YES completion:^{ NSLog(@"[JXShortDrama] 🗨️ commentVC present 完成!"); }]; } - (void)shortDramaCellDidTapComment:(UICollectionViewCell *)cell { [self commentDidClicked]; } - (void)shortDramaCellDidTapCollection:(UICollectionViewCell *)cell { JXCollectionViewController *collectionVC = [[JXCollectionViewController alloc] initWithDramaId:self.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]; } - (void)superPlayerDidUpdateProgress:(NSTimeInterval)currentTime duration:(NSTimeInterval)duration { // 播放进度更新(可用于显示进度条) // NSLog(@"[JXShortDramaCell] 播放进度: %.1f / %.1f", currentTime, duration); float progress = currentTime/duration; self.videoSlider.value = progress; } - (void)collectionViewControllerDidSelectEpisode:(NSString *)episodeId { NSLog(@"[JXShortDrama] 选择播放剧集: %@", episodeId); // TODO: 切换到选定的剧集播放 // 可以找到对应的短剧,然后滚动到该位置并播放 } #pragma mark - UICollectionViewDelegate - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { // 获取当前可见的cell索引 CGFloat pageWidth = scrollView.frame.size.width; CGFloat pageHeight = scrollView.frame.size.height; NSInteger currentPage = (NSInteger)(scrollView.contentOffset.y / pageHeight); if (currentPage != self.currentEpisodeIndex) { [self stopVideoAtIndex:self.currentEpisodeIndex]; // 播放新视频 self.currentEpisodeIndex = currentPage; [self playVideoAtIndex:currentPage]; }else{ [self resumeCurrentVideo]; } } - (void)playVideoAtIndex:(NSInteger)index { if (index < 0 || index >= self.episodes.count) { return; } [self doPlayVideoAtIndex:index]; } - (void)doPlayVideoAtIndex:(NSInteger)index { // 启动播放 VideoDetailCell *cell = (VideoDetailCell *)[self.episodeCollectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]]; if (cell) { [cell startPlay]; } } - (void)stopVideoAtIndex:(NSInteger)index { if (index < 0 || index >= self.episodes.count) { return; } VideoDetailCell *cell = (VideoDetailCell *)[self.episodeCollectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]]; [cell stopPlay]; } - (void)pauseCurrentVideo { if (self.currentEpisodeIndex < 0 || self.currentEpisodeIndex >= self.episodes.count) { return; } VideoDetailCell *cell = (VideoDetailCell *)[self.episodeCollectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:self.currentEpisodeIndex inSection:0]]; [cell pausePlay]; } - (void)resumeCurrentVideo { if (self.currentEpisodeIndex < 0 || self.currentEpisodeIndex >= self.episodes.count) { return; } VideoDetailCell *cell = (VideoDetailCell *)[self.episodeCollectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:self.currentEpisodeIndex inSection:0]]; [cell resumePlay]; } - (void)pauseAllVideos { // 暂停所有可见的Cell for (NSIndexPath *indexPath in self.episodeCollectionView.indexPathsForVisibleItems) { VideoDetailCell *cell = (VideoDetailCell *)[self.episodeCollectionView cellForItemAtIndexPath:indexPath]; [cell pausePlay]; } } #pragma mark - Helper Methods - (void)setupInteractionButtonsForCell:(UICollectionViewCell *)cell { CGFloat width = cell.contentView.bounds.size.width; UIView *container = [[UIView alloc] initWithFrame:CGRectMake(width - 48, kScreenHeight - safebottom - 190 - 90, 32, 190)]; [cell.contentView addSubview:container]; // 点赞 UIButton *likeButton = [UIButton buttonWithType:UIButtonTypeCustom]; likeButton.frame = CGRectMake(0, 0, 32, 32); [likeButton setImage:[UIImage imageNamed:@"icon 1.1"] forState:UIControlStateNormal]; [likeButton setImage:[UIImage imageNamed:@"icon 1.5"] forState:UIControlStateSelected]; [container addSubview:likeButton]; UILabel *likeCountLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 40, 32, 18)]; likeCountLabel.textAlignment = NSTextAlignmentCenter; likeCountLabel.font = [UIFont systemFontOfSize:12]; likeCountLabel.textColor = [UIColor whiteColor]; likeCountLabel.text = [self formatCount:self.interaction.likeCount]; [container addSubview:likeCountLabel]; // 收藏 UIButton *favoriteButton = [UIButton buttonWithType:UIButtonTypeCustom]; favoriteButton.frame = CGRectMake(0, CGRectGetMaxY(likeCountLabel.frame) + 20, 32, 32); [favoriteButton setImage:[UIImage imageNamed:@"icon 1.2"] forState:UIControlStateNormal]; [favoriteButton setImage:[UIImage imageNamed:@"icon 1.5"] forState:UIControlStateSelected]; [container addSubview:favoriteButton]; UILabel *favoriteCountLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, CGRectGetMaxY(favoriteButton.frame), 32, 18)]; favoriteCountLabel.textAlignment = NSTextAlignmentCenter; favoriteCountLabel.font = [UIFont systemFontOfSize:12]; favoriteCountLabel.textColor = [UIColor whiteColor]; favoriteCountLabel.text = [self formatCount:self.interaction.favoriteCount]; [container addSubview:favoriteCountLabel]; // 评论 UIButton *commentButton = [UIButton buttonWithType:UIButtonTypeCustom]; commentButton.frame = CGRectMake(0, CGRectGetMaxY(favoriteCountLabel.frame) + 20, 32, 32); [commentButton setImage:[UIImage imageNamed:@"icon 1.3"] forState:UIControlStateNormal]; [container addSubview:commentButton]; [commentButton addTarget:self action:@selector(commentDidClicked) forControlEvents:UIControlEventTouchUpInside]; UILabel *commentCountLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, CGRectGetMaxY(commentButton.frame) , 32, 18)]; commentCountLabel.textAlignment = NSTextAlignmentCenter; commentCountLabel.font = [UIFont systemFontOfSize:12]; commentCountLabel.textColor = [UIColor whiteColor]; commentCountLabel.text = [self formatCount:self.interaction.commentCount]; [container addSubview:commentCountLabel]; } - (NSString *)formatCount:(long long)count { if (count >= 1000000) { return [NSString stringWithFormat:@"%.1fM", count / 1000000.0]; } else if (count >= 1000) { return [NSString stringWithFormat:@"%.1fK", count / 1000.0]; } return [NSString stringWithFormat:@"%lld", count]; } - (void)dealloc { // 停止所有播放器 } - (NSMutableArray *)playerList{ if (!_playerList) { _playerList = [NSMutableArray array]; } return _playerList; } @end