Skip to content

Commit

Permalink
mac/remote: add Quick Look thumbnail as cover fallback
Browse files Browse the repository at this point in the history
since we load covers/images directly in a our own thread and the quick
look thumbnailing also works asynchronously on its own thread, we need
sync/coordinate both threads. otherwise it would be possible that a
cover from a different file could overwrite the cover of the current
file.

to prevent this every cover generation has a unique timestamp and only
covers for the last cover timestamp will ever be shown.

we are also not interested in icon type thumbnails, since those are just
generic file type icons.

quick look thumbnails can only be generated if quick look can read those
files. files unknown to macOS don't work out of the box and you need to
install a quick look extension for this, "QuickLook Video" for mkv.
  • Loading branch information
Akemi committed Dec 29, 2024
1 parent 83bb498 commit a0d1978
Showing 1 changed file with 44 additions and 14 deletions.
58 changes: 44 additions & 14 deletions osdep/mac/remote_command_center.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import Cocoa
import MediaPlayer
import QuickLookThumbnailing

extension RemoteCommandCenter {
typealias ConfigHandler = (MPRemoteCommandEvent) -> (MPRemoteCommandHandlerStatus)
Expand Down Expand Up @@ -55,8 +56,12 @@ class RemoteCommandCenter: EventSubscriber {
var album: String? { didSet { updateInfoCenter() } }
var artist: String? { didSet { updateInfoCenter() } }
var path: String?

let coverLock = NSLock()
var coverTime: UInt64 = mach_absolute_time()
var coverPath: String?
var cover: NSImage? { didSet { updateInfoCenter() } }
var coverThumb: NSImage? { didSet { updateInfoCenter() } }
var defaultCover: NSImage

let queue: DispatchQueue = DispatchQueue(label: "io.mpv.remote.queue")
Expand Down Expand Up @@ -150,7 +155,7 @@ class RemoteCommandCenter: EventSubscriber {
}

func updateInfoCenter() {
let cover = cover ?? defaultCover
let cover = cover ?? coverThumb ?? defaultCover
infoCenter.playbackState = isPaused ? .paused : .playing
infoCenter.nowPlayingInfo = (infoCenter.nowPlayingInfo ?? [:]).merging([
MPNowPlayingInfoPropertyMediaType: NSNumber(value: MPNowPlayingInfoMediaType.video.rawValue),
Expand All @@ -166,6 +171,19 @@ class RemoteCommandCenter: EventSubscriber {
}

func updateCover(tracks: [Any?]) {
coverLock.withLock {
coverTime = mach_absolute_time()
coverPath = nil
cover = nil
coverThumb = nil

// generate cover image on separate thread
queue.async { self.generateCover(tracks: tracks, time: self.coverTime) }
generateCoverThumb(time: self.coverTime)
}
}

func generateCover(tracks: [Any?], time: UInt64) {
var imageCoverPath: String?
var externalCoverPath: String?

Expand All @@ -185,21 +203,33 @@ class RemoteCommandCenter: EventSubscriber {
}
}

// read cover image on separate thread
queue.async {
guard let path = imageCoverPath ?? externalCoverPath else {
self.cover = nil
self.coverPath = nil
return
}
if self.coverPath == path { return }
guard let path = imageCoverPath ?? externalCoverPath, coverPath != path else { return }

var image = NSImage(contentsOf: URL(fileURLWithPath: path))
if let url = URL(string: path), image == nil {
image = NSImage(contentsOf: url)
}

coverLock.withLock {
guard time == coverTime else { return }
cover = image
coverPath = path
}
}

func generateCoverThumb(time: UInt64) {
guard let path = path else { return }

var image = NSImage(contentsOf: URL(fileURLWithPath: path))
if let url = URL(string: path), image == nil {
image = NSImage(contentsOf: url)
let request = QLThumbnailGenerator.Request(fileAt: URL(fileURLWithPath: path),
size: CGSize(width: 2000, height: 2000),
scale: 1,
representationTypes: .all)
QLThumbnailGenerator.shared.generateBestRepresentation(for: request) { thumbnail, error in

Check failure on line 227 in osdep/mac/remote_command_center.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Unused Closure Parameter Violation: Unused parameter in a closure should be replaced with _ (unused_closure_parameter)
self.coverLock.withLock {
guard time == self.coverTime else { return }
guard let image = thumbnail?.nsImage, thumbnail?.type != .icon else { return }
self.coverThumb = image
}
self.cover = image
self.coverPath = path
}
}

Expand Down

0 comments on commit a0d1978

Please sign in to comment.