// // JXPlayerViewController.m // AICity // // Created by TogetherWatch on 2025-10-13. // #import "JXPlayerViewController.h" #import "JXAPIService.h" #import "JXDrama.h" #import "JXEpisode.h" #import "JXInteraction.h" #import "JXAVPlayer.h" #import "JXMemberService.h" #import "JXMemberPromptViewController.h" @interface JXPlayerViewController () // 播放器 @property (nonatomic, strong) JXAVPlayer *avPlayer; @property (nonatomic, strong) AVPlayerLayer *playerLayer; // UI元素 @property (nonatomic, strong) UIView *titleBanner; @property (nonatomic, strong) UIView *interactionButtons; @property (nonatomic, strong) UIView *bottomOverlay; @property (nonatomic, strong) UIView *bottomNavigation; // UI自动隐藏 @property (nonatomic, assign) BOOL isUIVisible; @property (nonatomic, strong) NSTimer *hideUITimer; // 数据 @property (nonatomic, strong) JXDrama *drama; @property (nonatomic, strong) JXEpisode *episode; @property (nonatomic, strong) NSArray *episodes; @property (nonatomic, strong) JXInteraction *interaction; // 会员服务 @property (nonatomic, strong) JXMemberService *memberService; @end @implementation JXPlayerViewController - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor blackColor]; self.isUIVisible = YES; [self setupPlayer]; [self setupUI]; [self setupGestureRecognizers]; [self loadData]; } - (void)setupPlayer { // 创建JXAVPlayer self.avPlayer = [[JXAVPlayer alloc] init]; self.avPlayer.delegate = self; self.avPlayer.progressDelegate = self; // 创建AVPlayerLayer - 高度要在底部页签上方结束 // 底部页签高度: 44 CGFloat bottomTabBarHeight = 44; CGRect playerFrame = self.view.bounds; playerFrame.size.height -= bottomTabBarHeight; self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer.player]; self.playerLayer.frame = playerFrame; self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; [self.view.layer insertSublayer:self.playerLayer atIndex:0]; NSLog(@"🎬 AVPlayerLayer 创建完成, frame: %@, 底部页签高度: %.0f", NSStringFromCGRect(self.playerLayer.frame), bottomTabBarHeight); } - (void)setupGestureRecognizers { // 添加点击手势,用于显示/隐藏UI UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)]; [self.view addGestureRecognizer:tapGesture]; } - (void)handleTap:(UITapGestureRecognizer *)gesture { [self toggleUIVisibility]; } - (void)setupUI { // 创建UI元素(参考quickstart.md中的实现) [self createTitleBanner]; [self createInteractionButtons]; [self createBottomOverlay]; [self createBottomNavigation]; // 设置自动隐藏 [self setupAutoHideUI]; } - (void)createTitleBanner { self.titleBanner = [[UIView alloc] init]; self.titleBanner.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.8]; self.titleBanner.layer.cornerRadius = 20; // TODO: 添加货币图标和剧集标题 // 参考quickstart.md中的详细实现 [self.view addSubview:self.titleBanner]; self.titleBanner.translatesAutoresizingMaskIntoConstraints = NO; [NSLayoutConstraint activateConstraints:@[ [self.titleBanner.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor], [self.titleBanner.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor constant:50], [self.titleBanner.heightAnchor constraintEqualToConstant:44] ]]; } - (void)createInteractionButtons { self.interactionButtons = [[UIView alloc] init]; // TODO: 添加关注、点赞、评论、收藏、分享按钮 // 参考quickstart.md中的详细实现 [self.view addSubview:self.interactionButtons]; self.interactionButtons.translatesAutoresizingMaskIntoConstraints = NO; [NSLayoutConstraint activateConstraints:@[ [self.interactionButtons.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-16], [self.interactionButtons.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor] ]]; } - (void)createBottomOverlay { self.bottomOverlay = [[UIView alloc] init]; self.bottomOverlay.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.7]; // TODO: 添加弹幕标识、标题、作者、集数、标签 // 参考quickstart.md中的详细实现 [self.view addSubview:self.bottomOverlay]; self.bottomOverlay.translatesAutoresizingMaskIntoConstraints = NO; [NSLayoutConstraint activateConstraints:@[ [self.bottomOverlay.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], [self.bottomOverlay.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor], [self.bottomOverlay.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor constant:-44], [self.bottomOverlay.heightAnchor constraintEqualToConstant:150] ]]; } - (void)createBottomNavigation { self.bottomNavigation = [[UIView alloc] init]; self.bottomNavigation.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5]; // TODO: 添加分类、更新状态、进度指示器 // 参考quickstart.md中的详细实现 [self.view addSubview:self.bottomNavigation]; self.bottomNavigation.translatesAutoresizingMaskIntoConstraints = NO; [NSLayoutConstraint activateConstraints:@[ [self.bottomNavigation.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], [self.bottomNavigation.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor], [self.bottomNavigation.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor], [self.bottomNavigation.heightAnchor constraintEqualToConstant:44] ]]; } - (void)setupAutoHideUI { // 3秒后自动隐藏UI [self scheduleAutoHideUI]; } #pragma mark - UI Auto Hide - (void)toggleUIVisibility { if (self.isUIVisible) { [self hideUIElements]; } else { [self showUIElements]; [self scheduleAutoHideUI]; } } - (void)scheduleAutoHideUI { [self cancelAutoHideUI]; self.hideUITimer = [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(autoHideUI) userInfo:nil repeats:NO]; } - (void)cancelAutoHideUI { [self.hideUITimer invalidate]; self.hideUITimer = nil; } - (void)autoHideUI { [self hideUIElements]; } - (void)hideUIElements { self.isUIVisible = NO; [UIView animateWithDuration:0.3 animations:^{ self.titleBanner.alpha = 0; self.interactionButtons.alpha = 0; self.bottomOverlay.alpha = 0; self.bottomNavigation.alpha = 0; }]; } - (void)showUIElements { self.isUIVisible = YES; [UIView animateWithDuration:0.3 animations:^{ self.titleBanner.alpha = 1; self.interactionButtons.alpha = 1; self.bottomOverlay.alpha = 1; self.bottomNavigation.alpha = 1; }]; } #pragma mark - Data Loading - (void)loadData { // 加载短剧详情 [[JXAPIService sharedService] getDramaDetailWithDramaId:self.dramaId success:^(id responseObject) { NSDictionary *data = responseObject[@"data"]; self.drama = [[JXDrama alloc] initWithDictionary:data[@"drama"]]; NSArray *episodesData = data[@"episodes"]; NSMutableArray *episodesArray = [NSMutableArray array]; for (NSDictionary *episodeDict in episodesData) { JXEpisode *episode = [[JXEpisode alloc] initWithDictionary:episodeDict]; [episodesArray addObject:episode]; } self.episodes = episodesArray; // 找到当前要播放的剧集 for (JXEpisode *ep in self.episodes) { if ([ep.jxEpisodeId isEqualToString:self.episodeId]) { self.episode = ep; break; } } if (!self.episode && self.episodes.count > 0) { self.episode = self.episodes[0]; } self.interaction = [[JXInteraction alloc] initWithDictionary:data[@"interaction"]]; [self updateUIWithData]; [self loadPlayURL]; } failure:^(NSError *error) { NSLog(@"加载短剧详情失败: %@", error.localizedDescription); [self showErrorMessage:error.localizedDescription]; }]; } - (void)loadPlayURL { // 首先检查内容访问权限 [self checkContentAccessBeforePlayWithCompletion:^(BOOL hasAccess, JXAccessCheckResult *result, NSError *error) { if (error) { // 权限检查失败,显示错误但允许播放(降级处理) NSLog(@"权限检查失败: %@", error.localizedDescription); [self proceedToLoadPlayURL]; } else if (hasAccess) { // 有权限,继续播放 [self proceedToLoadPlayURL]; } else { // 无权限,显示会员提示页面 [self showMemberPromptWithResult:result]; } }]; } - (void)proceedToLoadPlayURL { [[JXAPIService sharedService] getPlayURLWithEpisodeId:self.episode.jxEpisodeId success:^(id responseObject) { NSDictionary *data = responseObject[@"data"]; NSString *playURL = data[@"playUrl"]; NSTimeInterval startPosition = [data[@"position"] doubleValue] / 1000.0; // 转换为秒 [self.avPlayer playVideoWithURL:playURL episodeId:self.episode.jxEpisodeId startPosition:startPosition]; } failure:^(NSError *error) { NSLog(@"获取播放地址失败: %@", error.localizedDescription); [self showErrorMessage:@"获取播放地址失败"]; }]; } - (void)updateUIWithData { // TODO: 更新所有UI元素 } #pragma mark - JXAVPlayerDelegate - (void)playerDidReady { NSLog(@"播放器准备就绪"); } - (void)playerDidStartPlaying { NSLog(@"开始播放"); [self scheduleAutoHideUI]; } - (void)playerDidPause { NSLog(@"暂停播放"); [self cancelAutoHideUI]; } - (void)playerDidBuffering { NSLog(@"缓冲中..."); } - (void)playerDidFinishPlaying { NSLog(@"播放结束"); [self playNextEpisode]; } - (void)playerDidFailWithError:(NSError *)error { NSLog(@"播放错误: %@", error.localizedDescription); [self showErrorMessage:error.localizedDescription]; } #pragma mark - JXAVPlayerProgressDelegate - (void)playerDidUpdateProgress:(NSString *)episodeId positionMs:(NSTimeInterval)positionMs durationMs:(NSTimeInterval)durationMs progress:(CGFloat)progress isCompleted:(BOOL)isCompleted { // 上报播放进度到服务器 [[JXAPIService sharedService] reportPlaybackProgressWithEpisodeId:episodeId position:(NSInteger)positionMs duration:(NSInteger)durationMs isCompleted:isCompleted success:^(id responseObject) { NSLog(@"进度上报成功: %.2f%%", progress * 100); } failure:^(NSError *error) { NSLog(@"进度上报失败: %@", error.localizedDescription); }]; } #pragma mark - Playback Control - (void)playNextEpisode { // 找到下一集 NSInteger currentIndex = [self.episodes indexOfObject:self.episode]; if (currentIndex != NSNotFound && currentIndex < self.episodes.count - 1) { self.episode = self.episodes[currentIndex + 1]; self.episodeId = self.episode.jxEpisodeId; [self loadPlayURL]; [self showToast:[NSString stringWithFormat:@"正在播放: %@", self.episode.title]]; } else { [self showToast:@"已是最后一集"]; [self dismissViewControllerAnimated:YES completion:nil]; } } #pragma mark - Interaction Actions - (void)toggleLike { [[JXAPIService sharedService] toggleLikeWithDramaId:self.dramaId episodeId:self.episodeId success:^(id responseObject) { NSDictionary *data = responseObject[@"data"]; if (data && data[@"interaction"]) { self.interaction = [[JXInteraction alloc] initWithDictionary:data[@"interaction"]]; [self updateInteractionUI]; [self showToast:self.interaction.isLiked ? @"已点赞" : @"已取消点赞"]; } } failure:^(NSError *error) { [self showToast:@"操作失败"]; }]; } - (void)toggleFavorite { [[JXAPIService sharedService] toggleFavoriteWithDramaId:self.dramaId success:^(id responseObject) { NSDictionary *data = responseObject[@"data"]; if (data && data[@"interaction"]) { self.interaction = [[JXInteraction alloc] initWithDictionary:data[@"interaction"]]; [self updateInteractionUI]; [self showToast:self.interaction.isFavorited ? @"已收藏" : @"已取消收藏"]; } } failure:^(NSError *error) { [self showToast:@"操作失败"]; }]; } - (void)toggleFollow { [[JXAPIService sharedService] toggleFollowWithDramaId:self.dramaId success:^(id responseObject) { NSDictionary *data = responseObject[@"data"]; if (data && data[@"interaction"]) { self.interaction = [[JXInteraction alloc] initWithDictionary:data[@"interaction"]]; [self updateInteractionUI]; [self showToast:self.interaction.isFollowed ? @"已关注" : @"已取消关注"]; } } failure:^(NSError *error) { [self showToast:@"操作失败"]; }]; } - (void)showComments { // TODO: 实现评论功能 [self showToast:@"评论功能开发中"]; } - (void)shareContent { NSString *shareText = [NSString stringWithFormat:@"推荐一部好剧:%@ - %@", self.drama.title, self.episode.title]; NSString *shareURL = [NSString stringWithFormat:@"https://app.example.com/juxing/drama/%@/episode/%@", self.dramaId, self.episodeId]; UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:@[shareText, shareURL] applicationActivities:nil]; [self presentViewController:activityVC animated:YES completion:nil]; } - (void)updateInteractionUI { // TODO: 更新UI显示交互数据 // 需要在创建交互按钮时保存按钮引用,然后在这里更新它们的状态和数字 } #pragma mark - Helper Methods - (void)showErrorMessage:(NSString *)message { UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"错误" message:message preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil]]; [self presentViewController:alert animated:YES completion:nil]; } - (void)showToast:(NSString *)message { // TODO: 实现Toast提示 NSLog(@"Toast: %@", message); } #pragma mark - Member Access Control /** * 播放前检查内容访问权限 */ - (void)checkContentAccessBeforePlayWithCompletion:(void(^)(BOOL hasAccess, JXAccessCheckResult *result, NSError *error))completion { if (!self.memberService) { self.memberService = [JXMemberService sharedService]; } // 确定所需权限级别 NSInteger requiredLevel = [self determineRequiredAccessLevel]; // 检查剧集访问权限 [self.memberService checkEpisodeAccessWithEpisodeId:self.episode.jxEpisodeId requiredLevel:requiredLevel completion:^(JXAccessCheckResult * _Nullable result, NSError * _Nullable error) { dispatch_async(dispatch_get_main_queue(), ^{ if (error) { completion(NO, nil, error); } else { completion(result.hasAccess, result, nil); } }); }]; } /** * 确定所需的访问权限级别 */ - (NSInteger)determineRequiredAccessLevel { // 根据剧集信息确定权限要求 if (self.episode.isPaid) { // 付费内容,根据内容类型确定级别 if ([self.episode.contentType isEqualToString:@"premium"]) { return 2; // 高级内容 } else if ([self.episode.contentType isEqualToString:@"vip"]) { return 3; // VIP内容 } else { return 1; // 普通付费内容 } } else { return 0; // 免费内容 } } /** * 显示会员提示页面 */ - (void)showMemberPromptWithResult:(JXAccessCheckResult *)result { // 创建会员提示视图控制器 JXMemberPromptViewController *memberPromptVC = [[JXMemberPromptViewController alloc] init]; // 设置数据 memberPromptVC.episodeId = self.episode.jxEpisodeId; memberPromptVC.requiredLevel = result.requiredLevel; memberPromptVC.memberStatus = result.memberStatus; // 设置回调 __weak typeof(self) weakSelf = self; memberPromptVC.onMemberPurchaseSuccess = ^{ // 会员购买成功,重新检查权限并播放 [weakSelf retryPlayAfterMemberPurchase]; }; memberPromptVC.onCancel = ^{ // 用户取消,返回上一页面 [weakSelf.navigationController popViewControllerAnimated:YES]; }; // 以模态方式显示 memberPromptVC.modalPresentationStyle = UIModalPresentationFullScreen; [self presentViewController:memberPromptVC animated:YES completion:nil]; } /** * 会员购买成功后重试播放 */ - (void)retryPlayAfterMemberPurchase { // 刷新会员状态并重新检查权限 [self.memberService getCurrentMemberStatusWithForceRefresh:YES completion:^(JXMemberStatus * _Nullable memberStatus, NSError * _Nullable error) { if (error) { [self showErrorMessage:@"获取会员状态失败"]; return; } // 重新检查权限 [self checkContentAccessBeforePlayWithCompletion:^(BOOL hasAccess, JXAccessCheckResult *result, NSError *error) { if (error) { [self showErrorMessage:@"权限检查失败"]; return; } if (hasAccess) { // 现在有权限了,开始播放 [self proceedToLoadPlayURL]; } else { // 仍然没有权限 [self showErrorMessage:@"会员权限不足,请升级会员"]; } }]; }]; } #pragma mark - Lifecycle - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [self.avPlayer pause]; [self.avPlayer reportCurrentProgress]; } - (void)dealloc { [self cancelAutoHideUI]; [self.avPlayer releasePlayer]; } @end