Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return Control Center to the system when not needed anymore #1156

Merged
merged 4 commits into from
Mar 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 23 additions & 10 deletions Sources/Player/ControlCenter/NowPlaying.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
22 changes: 7 additions & 15 deletions Sources/Player/Player+ControlCenter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down Expand Up @@ -120,29 +113,28 @@ 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
}
.eraseToAnyPublisher()
}

func nowPlayingPublisher() -> AnyPublisher<NowPlaying, Never> {
$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()
Expand Down
4 changes: 4 additions & 0 deletions Sources/Player/Types/Queue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ struct Queue {
itemState.error
}

var isActive: Bool {
playerItem == nil
}

private var playerItem: AVPlayerItem? {
itemState.item
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ final class NowPlayingInfoPublisherTests: TestCase {
private static func nowPlayingInfoPublisher(for player: Player) -> AnyPublisher<NowPlaying.Info, Never> {
player.nowPlayingPublisher()
.map(\.info)
.removeDuplicates(by: ~~)
.eraseToAnyPublisher()
}

Expand All @@ -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
Expand Down