2025-08-20 14:19:32 +08:00
|
|
|
|
//
|
|
|
|
|
// BannerScheduler.m
|
|
|
|
|
// YuMi
|
|
|
|
|
//
|
|
|
|
|
// Created by AI Assistant on 2025/1/13.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
#import "BannerScheduler.h"
|
|
|
|
|
|
|
|
|
|
@interface BannerScheduler ()
|
|
|
|
|
|
|
|
|
|
@property (nonatomic, strong) NSMutableArray *bannerQueue;
|
|
|
|
|
@property (nonatomic, assign) BOOL isPlaying;
|
|
|
|
|
@property (nonatomic, assign) BOOL isPaused;
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation BannerScheduler
|
|
|
|
|
|
|
|
|
|
#pragma mark - Initialization
|
|
|
|
|
|
|
|
|
|
- (instancetype)initWithDelegate:(id<BannerSchedulerDelegate>)delegate {
|
|
|
|
|
if (self = [super init]) {
|
|
|
|
|
_delegate = delegate;
|
|
|
|
|
_bannerQueue = [NSMutableArray array];
|
|
|
|
|
_isPlaying = NO;
|
|
|
|
|
_isPaused = NO;
|
|
|
|
|
}
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#pragma mark - Public Methods
|
|
|
|
|
|
|
|
|
|
- (void)enqueueBanner:(id)banner {
|
|
|
|
|
if (!banner) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[self.bannerQueue addObject:banner];
|
|
|
|
|
|
|
|
|
|
// 如果当前没有在播放且未暂停,则开始处理
|
|
|
|
|
if (!self.isPlaying && !self.isPaused) {
|
|
|
|
|
[self processNextBanner];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)processNextBanner {
|
2025-08-20 16:37:48 +08:00
|
|
|
|
if (self.isPaused) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 🔧 新增:检查 delegate 是否有效
|
|
|
|
|
if (!self.delegate) {
|
|
|
|
|
[self clearQueue];
|
|
|
|
|
self.isPlaying = NO;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-20 14:19:32 +08:00
|
|
|
|
if (self.isPaused) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (self.bannerQueue.count == 0) {
|
|
|
|
|
self.isPlaying = NO;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (self.isPlaying) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 对队列进行优先级排序
|
|
|
|
|
[self sortQueueByPriority];
|
|
|
|
|
|
|
|
|
|
// 取出队列中的第一个 Banner
|
|
|
|
|
id nextBanner = [self.bannerQueue firstObject];
|
|
|
|
|
[self.bannerQueue removeObjectAtIndex:0];
|
2025-09-23 14:56:52 +08:00
|
|
|
|
|
2025-08-20 14:19:32 +08:00
|
|
|
|
self.isPlaying = YES;
|
|
|
|
|
|
|
|
|
|
// 通知代理开始播放
|
|
|
|
|
if ([self.delegate respondsToSelector:@selector(bannerScheduler:didStartPlayingBanner:)]) {
|
|
|
|
|
[self.delegate bannerScheduler:self didStartPlayingBanner:nextBanner];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 通知代理播放 Banner
|
|
|
|
|
if ([self.delegate respondsToSelector:@selector(bannerScheduler:shouldPlayBanner:)]) {
|
|
|
|
|
[self.delegate bannerScheduler:self shouldPlayBanner:nextBanner];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)clearQueue {
|
|
|
|
|
[self.bannerQueue removeAllObjects];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)sortQueueByPriority {
|
|
|
|
|
// 保持先进先出(FIFO)策略,不需要排序
|
|
|
|
|
// 队列顺序就是添加顺序,确保公平性
|
|
|
|
|
NSLog(@"🔄 BannerScheduler: 使用先进先出策略,保持队列原有顺序");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)pause {
|
|
|
|
|
if (self.isPaused) {
|
|
|
|
|
NSLog(@"⏸️ BannerScheduler: 调度器已经处于暂停状态");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NSLog(@"⏸️ BannerScheduler: 暂停调度器");
|
|
|
|
|
self.isPaused = YES;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)resume {
|
2025-09-16 15:55:38 +08:00
|
|
|
|
NSLog(@"▶️ BannerScheduler: 恢复调度器");
|
|
|
|
|
self.isPaused = NO;
|
|
|
|
|
|
|
|
|
|
// 如果当前没有在播放,则开始处理队列
|
|
|
|
|
if (!self.isPlaying) {
|
|
|
|
|
[self processNextBanner];
|
2025-08-20 14:19:32 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL)isQueueEmpty {
|
|
|
|
|
return self.bannerQueue.count == 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (nullable id)bannerAtIndex:(NSInteger)index {
|
|
|
|
|
if (index < 0 || index >= self.bannerQueue.count) {
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
return [self.bannerQueue objectAtIndex:index];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL)removeBannerAtIndex:(NSInteger)index {
|
|
|
|
|
if (index < 0 || index >= self.bannerQueue.count) {
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
id removedBanner = [self.bannerQueue objectAtIndex:index];
|
|
|
|
|
[self.bannerQueue removeObjectAtIndex:index];
|
|
|
|
|
|
|
|
|
|
NSLog(@"🗑️ BannerScheduler: 从队列中移除 Banner - 索引: %ld, 类型: %@",
|
|
|
|
|
(long)index, [removedBanner class]);
|
|
|
|
|
|
|
|
|
|
return YES;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSString *)queueStatusDescription {
|
|
|
|
|
NSMutableString *description = [NSMutableString string];
|
|
|
|
|
[description appendFormat:@"BannerScheduler 状态:\n"];
|
|
|
|
|
[description appendFormat:@"- 播放状态: %@\n", self.isPlaying ? @"播放中" : @"空闲"];
|
|
|
|
|
[description appendFormat:@"- 暂停状态: %@\n", self.isPaused ? @"已暂停" : @"运行中"];
|
|
|
|
|
[description appendFormat:@"- 队列长度: %ld\n", (long)self.bannerQueue.count];
|
|
|
|
|
|
|
|
|
|
if (self.bannerQueue.count > 0) {
|
|
|
|
|
[description appendString:@"- 队列内容:\n"];
|
|
|
|
|
for (NSInteger i = 0; i < self.bannerQueue.count; i++) {
|
|
|
|
|
id banner = self.bannerQueue[i];
|
|
|
|
|
[description appendFormat:@" [%ld] 类型: %@\n", (long)i, [banner class]];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return description;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#pragma mark - Public Properties
|
|
|
|
|
|
|
|
|
|
- (NSInteger)queueCount {
|
|
|
|
|
return self.bannerQueue.count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#pragma mark - Internal Methods
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 标记 Banner 播放完成
|
|
|
|
|
* 这个方法应该由代理在 Banner 播放完成后调用
|
|
|
|
|
*/
|
|
|
|
|
- (void)markBannerFinished {
|
|
|
|
|
if (!self.isPlaying) {
|
|
|
|
|
NSLog(@"⚠️ BannerScheduler: 尝试标记未播放的 Banner 为完成");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-20 16:37:48 +08:00
|
|
|
|
// 防止过快标记完成,确保 banner 有足够的显示时间
|
|
|
|
|
static NSTimeInterval lastFinishTime = 0;
|
|
|
|
|
NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970];
|
|
|
|
|
|
|
|
|
|
// 如果距离上次完成时间太短,延迟处理
|
|
|
|
|
if (currentTime - lastFinishTime < 0.3) {
|
|
|
|
|
NSLog(@"⏳ BannerScheduler: Banner 完成间隔过短,延迟处理 (间隔: %.2f秒)", currentTime - lastFinishTime);
|
|
|
|
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
|
|
|
[self markBannerFinished];
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
lastFinishTime = currentTime;
|
|
|
|
|
|
2025-08-20 14:19:32 +08:00
|
|
|
|
NSLog(@"✅ BannerScheduler: Banner 播放完成");
|
|
|
|
|
self.isPlaying = NO;
|
|
|
|
|
|
|
|
|
|
// 通知代理播放完成
|
|
|
|
|
if ([self.delegate respondsToSelector:@selector(bannerSchedulerDidFinishPlaying:)]) {
|
|
|
|
|
[self.delegate bannerSchedulerDidFinishPlaying:self];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 处理队列中的下一个 Banner
|
|
|
|
|
[self processNextBanner];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 调试方法:检查调度器状态
|
|
|
|
|
* @return 调试信息字符串
|
|
|
|
|
*/
|
|
|
|
|
- (NSString *)debugStatus {
|
|
|
|
|
NSMutableString *debugInfo = [NSMutableString string];
|
|
|
|
|
[debugInfo appendFormat:@"BannerScheduler Debug Status:\n"];
|
|
|
|
|
[debugInfo appendFormat:@"- 播放状态: %@\n", self.isPlaying ? @"播放中" : @"空闲"];
|
|
|
|
|
[debugInfo appendFormat:@"- 暂停状态: %@\n", self.isPaused ? @"已暂停" : @"运行中"];
|
|
|
|
|
[debugInfo appendFormat:@"- 队列长度: %ld\n", (long)self.bannerQueue.count];
|
|
|
|
|
|
|
|
|
|
if (self.bannerQueue.count > 0) {
|
|
|
|
|
[debugInfo appendString:@"- 队列内容:\n"];
|
|
|
|
|
for (NSInteger i = 0; i < self.bannerQueue.count; i++) {
|
|
|
|
|
id banner = self.bannerQueue[i];
|
|
|
|
|
[debugInfo appendFormat:@" [%ld] 类型: %@\n", (long)i, [banner class]];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return debugInfo;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@end
|