diff --git a/Sources/Player/ControlCenter/NowPlaying.swift b/Sources/Player/ControlCenter/NowPlaying.swift index 29bdb6915..e8eec02d7 100644 --- a/Sources/Player/ControlCenter/NowPlaying.swift +++ b/Sources/Player/ControlCenter/NowPlaying.swift @@ -4,20 +4,33 @@ // License information is available from the LICENSE file. // -/// Metadata describing what is currently being played. -struct NowPlaying { - typealias Info = [String: Any] +import MediaPlayer - static let empty = Self(metadata: .empty, playbackInfo: [:]) +/// Metadata describing what is currently being played. +enum NowPlaying { + case empty + case filled(metadata: PlayerMetadata, playbackInfo: Info) - let metadata: PlayerMetadata - let playbackInfo: Info + typealias Info = [String: Any] - var isEmpty: Bool { - metadata == .empty && playbackInfo.isEmpty + var info: Info { + switch self { + case .empty: + [:] + case let .filled(metadata, playbackInfo): + metadata.nowPlayingInfo + .merging(playbackInfo) { _, new in new } + // For proper Control Center integration at least one metadata key must be filled. + .merging([MPMediaItemPropertyTitle: ""]) { old, _ in old } + } } - var info: Info { - metadata.nowPlayingInfo.merging(playbackInfo) { _, new in new } + var isEmpty: Bool { + switch self { + case .empty: + true + default: + false + } } } diff --git a/Sources/Player/Player+ControlCenter.swift b/Sources/Player/Player+ControlCenter.swift index a9fea3d57..c0a0b4eb0 100644 --- a/Sources/Player/Player+ControlCenter.swift +++ b/Sources/Player/Player+ControlCenter.swift @@ -46,15 +46,8 @@ private extension Player { func playRegistration() -> some RemoteCommandRegistrable { nowPlayingSession.remoteCommandCenter.register(command: \.playCommand) { [weak self] _ in - guard let self else { return .commandFailed } - if canReplay() { - replay() - return .commandFailed - } - else { - play() - return .success - } + self?.play() + return .success } } @@ -120,14 +113,13 @@ extension Player { propertiesPublisher .map { [weak queuePlayer] properties in var nowPlayingInfo = NowPlaying.Info() - // Always fill a key so that the Control Center can be enabled for the item, even if it has no associated metadata. - nowPlayingInfo[MPNowPlayingInfoPropertyIsLiveStream] = (properties.streamType == .live) if properties.streamType != .unknown { nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = properties.isBuffering ? 0 : properties.rate if let time = properties.seekTime ?? queuePlayer?.currentTime(), time.isValid { nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = (time - properties.seekableTimeRange.start).seconds } nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = properties.seekableTimeRange.duration.seconds + nowPlayingInfo[MPNowPlayingInfoPropertyIsLiveStream] = (properties.streamType == .live) } return nowPlayingInfo } @@ -135,14 +127,14 @@ extension Player { } func nowPlayingPublisher() -> AnyPublisher { - $isActive - .map { [weak self] isActive in - guard let self, isActive else { return Just(NowPlaying.empty).eraseToAnyPublisher() } + Publishers.CombineLatest($isActive, queuePublisher) + .map { [weak self] isActive, queue in + guard let self, isActive, !queue.isActive else { return Just(NowPlaying.empty).eraseToAnyPublisher() } return Publishers.CombineLatest( metadataPublisher, nowPlayingInfoPlaybackPublisher() ) - .map { NowPlaying(metadata: $0, playbackInfo: $1) } + .map { NowPlaying.filled(metadata: $0, playbackInfo: $1) } .eraseToAnyPublisher() } .switchToLatest() diff --git a/Sources/Player/Types/Queue.swift b/Sources/Player/Types/Queue.swift index cc164e9fd..1bb47c9b5 100644 --- a/Sources/Player/Types/Queue.swift +++ b/Sources/Player/Types/Queue.swift @@ -30,6 +30,10 @@ struct Queue { itemState.error } + var isActive: Bool { + playerItem == nil + } + private var playerItem: AVPlayerItem? { itemState.item } diff --git a/Tests/PlayerTests/Publishers/NowPlayingInfoPublisherTests.swift b/Tests/PlayerTests/Publishers/NowPlayingInfoPublisherTests.swift index 488674579..16536f55a 100644 --- a/Tests/PlayerTests/Publishers/NowPlayingInfoPublisherTests.swift +++ b/Tests/PlayerTests/Publishers/NowPlayingInfoPublisherTests.swift @@ -15,6 +15,7 @@ final class NowPlayingInfoPublisherTests: TestCase { private static func nowPlayingInfoPublisher(for player: Player) -> AnyPublisher { player.nowPlayingPublisher() .map(\.info) + .removeDuplicates(by: ~~) .eraseToAnyPublisher() } @@ -29,7 +30,7 @@ final class NowPlayingInfoPublisherTests: TestCase { func testToggleActive() { let player = Player(item: .mock(url: Stream.onDemand.url, loadedAfter: 0, withMetadata: AssetMetadataMock(title: "title"))) expectAtLeastSimilarPublished( - values: [[:], [MPNowPlayingInfoPropertyIsLiveStream: false]], + values: [[:], ["title": ""], ["title": "title"]], from: Self.nowPlayingInfoPublisher(for: player) ) { player.isActive = true