需取得 AVPlayer 播放線上音樂的總時長,嘗試直接存取 player.duration 失敗。
AVPlayer 本身無 duration 屬性,時長資訊在 AVPlayerItem 中:
// 正確取得時長 (CMTime)
CMTime duration = player.currentItem.duration;
// 或
CMTime duration = player.currentItem.asset.duration;
// 1. 建立 Player
AVPlayer *player = [AVPlayer playerWithURL:url];
self.player = player;
// 2. 監聽狀態就緒 (非同步載入)
[player.currentItem addObserver:self forKeyPath:@"status" options:0 context:nil];
// 3. KVO 回調中取得時長
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:@"status"] && player.currentItem.status == AVPlayerItemStatusReadyToPlay) {
CMTime duration = player.currentItem.duration;
if (CMTIME_IS_VALID(duration) && !CMTIME_IS_INDEFINITE(duration)) {
double seconds = CMTimeGetSeconds(duration);
// 格式化顯示
NSString *timeStr = [NSString stringWithFormat:@"%02d:%02d",
(int)seconds / 60, (int)seconds % 60];
self.durationLabel.text = timeStr;
}
}
}
// 4. 移除觀察者 (dealloc/viewDidDisappear)
[player.currentItem removeObserver:self forKeyPath:@"status"];
@Published var durationText: String = "--:--"
func loadPlayer(url: URL) {
let player = AVPlayer(url: url)
self.player = player
// 使用 Publisher 監聽 status
player.currentItem.publisher(for: \.status)
.compactMap { $0 }
.filter { $0 == .readyToPlay }
.map { _ in player.currentItem.duration }
.filter { $0.isValid && !$0.isIndefinite }
.map { CMTimeGetSeconds($0) }
.map { seconds in
String(format: "%02d:%02d", Int(seconds) / 60, Int(seconds) % 60)
}
.assign(to: &$durationText)
}
AVPlayerItem 載入元資料需時間,必須等 status == ReadyToPlay 才能讀取有效時長。CMTIME_IS_VALID 且 !CMTIME_IS_INDEFINITE(直播流無確定時長)。CMTimeGetSeconds(duration) 回傳 Double,精確到毫秒。player.duration:不存在此屬性。player.currentItem.duration 在 status != ReadyToPlay 時為 kCMTimeInvalid 或 indefinite。duration 為 indefinite,需顯示「直播中」而非時間。掌握 AVPlayerItem 狀態機制與 CMTime 操作,是 iOS 影音播放開發的核心基本功。