Skip to content

Commit

Permalink
Return Control Center to the system when not needed anymore (#1156)
Browse files Browse the repository at this point in the history
Co-authored-by: Walid Kayhal <[email protected]>
  • Loading branch information
defagos and waliid authored Mar 4, 2025
1 parent bd708f6 commit e39b5b9
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 26 deletions.
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

0 comments on commit e39b5b9

Please sign in to comment.