// // 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 @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; @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; // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changeProgress:) name:@"videoProgressChanged" object:nil]; [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.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]; } 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 blackColor]; 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]; // if (self.playerList.count > indexPath.item) { // JXSuperPlayer *sp = self.playerList[indexPath.item]; // [sp releasePlayer]; // [self.playerList removeObject:sp]; // } // [self.playerList insertObject:player atIndex:indexPath.item]; 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); }]; // // 作者 // UILabel *authorLabel = [[UILabel alloc] initWithFrame:CGRectMake(16, y, width - 32, 20)]; // authorLabel.font = [UIFont systemFontOfSize:13]; // authorLabel.textColor = [UIColor grayColor]; // [cell.contentView addSubview:authorLabel]; // // [cell.contentView bringSubviewToFront:authorLabel]; // y += 30; // // 剧集信息区域 // UIView *episodeInfoView = [[UIView alloc] initWithFrame:CGRectMake(0, y, width, height - y)]; // episodeInfoView.backgroundColor = [UIColor colorWithWhite:0.1 alpha:1.0]; // [cell.contentView addSubview:episodeInfoView]; JXEpisode *episode = self.episodes[indexPath.item]; // // // 更新标签 titleLabel.text = self.drama.title ?: @""; descLabel.text = episode.title ?: @""; // authorLabel.text = [NSString stringWithFormat:@"@%@", self.drama.authorInfo ?: @"未知"]; // // // 剧集编号和标题 // UILabel *episodeTitleLabel = [[UILabel alloc] initWithFrame:CGRectMake(16, 20, width - 32, 40)]; // NSString *episodeTitle = [NSString stringWithFormat:@"第%ld集", (long)episode.episodeNumber]; // if (episode.title && episode.title.length > 0) { // episodeTitle = [NSString stringWithFormat:@"%@ - %@", episodeTitle, episode.title]; // } // episodeTitleLabel.text = episodeTitle; // episodeTitleLabel.font = [UIFont boldSystemFontOfSize:16]; // episodeTitleLabel.textColor = [UIColor whiteColor]; // episodeTitleLabel.numberOfLines = 2; // [episodeInfoView addSubview:episodeTitleLabel]; // 播放视频 [player playWithAppId:episode.appId fileId:episode.fileId psign:episode.psign]; 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)superPlayerDidUpdateProgress:(NSTimeInterval)currentTime duration:(NSTimeInterval)duration { // 播放进度更新(可用于显示进度条) // NSLog(@"[JXShortDramaCell] 播放进度: %.1f / %.1f", currentTime, duration); float progress = currentTime/duration; self.videoSlider.value = progress; } //- (void)changeProgress:(NSNotification *)niti{ // if (!self.isHuadong){ // NSDictionary *user = niti.userInfo; // JXEpisode *episode = self.episodes[self.currentEpisodeIndex]; // // if ([user[@"fileId"] isEqualToString:episode.fileId] ) { // float progress = [user[@"progress"] floatValue]; // self.videoSlider.value = progress; // NSLog(@"进度--%f",progress); // } // // } // //} - (void)collectionViewControllerDidSelectEpisode:(NSString *)episodeId { NSLog(@"[JXShortDrama] 选择播放剧集: %@", episodeId); // TODO: 切换到选定的剧集播放 // 可以找到对应的短剧,然后滚动到该位置并播放 } #pragma mark - UICollectionViewDelegate - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath { // 停止不可见的播放器 NSLog(@"消失的cell----%d",indexPath.item); // JXSuperPlayer *player = [self.playerList objectAtIndex:indexPath.item]; // [player pause]; } //- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath{ // NSLog(@"出现的cell----%d",indexPath.item); //} - (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.currentEpisodeIndex = currentPage; NSLog(@"📺 滚动到第 %ld 集", (long)currentPage + 1); // UICollectionViewCell *cell = [self.episodeCollectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:currentPage inSection:0]]; // // 创建播放器 // 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]; // JXEpisode *episode = self.episodes[currentPage]; // JXSuperPlayer *player = [[JXSuperPlayer alloc] initWithContainerView:playerContainer]; // player.delegate = self; // if (_currentPlayer) { // [_currentPlayer releasePlayer]; // } // _currentPlayer = player; // // 播放视频 // [player playWithAppId:episode.appId fileId:episode.fileId psign:episode.psign]; } } #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