diff --git a/Aural.xcodeproj/project.pbxproj b/Aural.xcodeproj/project.pbxproj index 18809f552..557c19b98 100644 --- a/Aural.xcodeproj/project.pbxproj +++ b/Aural.xcodeproj/project.pbxproj @@ -458,6 +458,7 @@ 3E1572AC2C28C0C2006E9965 /* AudioUnitEditorDialogController+PresetsMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E1572AB2C28C0C2006E9965 /* AudioUnitEditorDialogController+PresetsMenu.swift */; }; 3E1741462801EA0A00772ED1 /* TableCellBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E1741452801EA0A00772ED1 /* TableCellBuilder.swift */; }; 3E1741482801FDE500772ED1 /* TrackListTableViewController+DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E1741472801FDE500772ED1 /* TrackListTableViewController+DataSource.swift */; }; + 3E1D255A2D161E98005EA767 /* PlaybackFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E1D25592D161E98005EA767 /* PlaybackFormat.swift */; }; 3E1DA0D62BEC2B68004670B3 /* ContextHelpButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E1DA0D52BEC2B68004670B3 /* ContextHelpButton.swift */; }; 3E2000BF267CDFFF008BAB70 /* MediaKeysPreferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3E2000BE267CDFFF008BAB70 /* MediaKeysPreferences.xib */; }; 3E2000C1267CE00E008BAB70 /* GesturesPreferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3E2000C0267CE00E008BAB70 /* GesturesPreferences.xib */; }; @@ -1529,6 +1530,7 @@ 3E16BE11280DD01200CE9FE9 /* PlaylistNamesTableViewController+TextFieldDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PlaylistNamesTableViewController+TextFieldDelegate.swift"; sourceTree = ""; }; 3E1741452801EA0A00772ED1 /* TableCellBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableCellBuilder.swift; sourceTree = ""; }; 3E1741472801FDE500772ED1 /* TrackListTableViewController+DataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TrackListTableViewController+DataSource.swift"; sourceTree = ""; }; + 3E1D25592D161E98005EA767 /* PlaybackFormat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackFormat.swift; sourceTree = ""; }; 3E1DA0D52BEC2B68004670B3 /* ContextHelpButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextHelpButton.swift; sourceTree = ""; }; 3E2000BE267CDFFF008BAB70 /* MediaKeysPreferences.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MediaKeysPreferences.xib; sourceTree = ""; }; 3E2000C0267CE00E008BAB70 /* GesturesPreferences.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = GesturesPreferences.xib; sourceTree = ""; }; @@ -2972,6 +2974,7 @@ 3E02175C2C23490E00865AC2 /* PrimaryMetadata.swift */, 3E5701952C6EB16A007B8611 /* ReplayGain.swift */, 3E8145C52C7A3850005BA9B9 /* AlbumReplayGain.swift */, + 3E1D25592D161E98005EA767 /* PlaybackFormat.swift */, ); path = Model; sourceTree = ""; @@ -6603,6 +6606,7 @@ 3EFAA8D12B710C30001A6682 /* ChaptersListViewController+Theming.swift in Sources */, 3EFA0C162C67FB63006FB326 /* UnifiedPlayerWaveformContainerViewController.swift in Sources */, 3E0218532C23490E00865AC2 /* FixedSizeLRUArray.swift in Sources */, + 3E1D255A2D161E98005EA767 /* PlaybackFormat.swift in Sources */, 3ED373DB2C70CC8100836511 /* ReplayGainUnit.swift in Sources */, 3EF72D6E2B71AB2B005166BF /* DiscreteCircularSlider+Support.swift in Sources */, 3E0219502C23490E00865AC2 /* CoverArtReader.swift in Sources */, diff --git a/Aural.xcodeproj/project.xcworkspace/xcuserdata/kven.xcuserdatad/UserInterfaceState.xcuserstate b/Aural.xcodeproj/project.xcworkspace/xcuserdata/kven.xcuserdatad/UserInterfaceState.xcuserstate index 9b54cf42a..9236c4e9f 100644 Binary files a/Aural.xcodeproj/project.xcworkspace/xcuserdata/kven.xcuserdatad/UserInterfaceState.xcuserstate and b/Aural.xcodeproj/project.xcworkspace/xcuserdata/kven.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Source/Core/LastFM/LastFM_WSClient+Scrobble.swift b/Source/Core/LastFM/LastFM_WSClient+Scrobble.swift index e32d655a1..ed64445bd 100644 --- a/Source/Core/LastFM/LastFM_WSClient+Scrobble.swift +++ b/Source/Core/LastFM/LastFM_WSClient+Scrobble.swift @@ -12,6 +12,42 @@ import Foundation extension LastFM_WSClient { + private static let maxPlaybackTime: Double = 240 // 4 minutes + + func scrobbleTrackIfEligible(_ track: Track) { + + /* + + From: https://www.last.fm/api/scrobbling + ---------------------------------------- + + A track should only be scrobbled when the following conditions have been met: + + - The track must be longer than 30 seconds. + - And the track has been played for at least half its duration, or for 4 minutes (whichever occurs earlier.) + + */ + + guard self.scrobblingEnabled, + track.canBeScrobbledOnLastFM, + let historyLastPlayedItem = historyDelegate.lastPlayedItem, + historyLastPlayedItem.track == track else { + + NSLog("Cannot scrobble track '\(track)' on Last.fm because scrobbling eligibility conditions were not met.") + return + } + + let now = Date() + let playbackTime = now.timeIntervalSince(historyLastPlayedItem.lastEventTime) + + if playbackTime >= min(track.duration / 2, Self.maxPlaybackTime) { + + DispatchQueue.global(qos: .background).async { + self.scrobbleTrack(track: track, timestamp: historyLastPlayedItem.lastEventTime.epochTime) + } + } + } + func scrobbleTrack(track: Track, timestamp: Int) { guard let sessionKey = self.sessionKey else { diff --git a/Source/Core/LastFM/LastFM_WSClientProtocol.swift b/Source/Core/LastFM/LastFM_WSClientProtocol.swift index da4330602..4d22ac1d4 100644 --- a/Source/Core/LastFM/LastFM_WSClientProtocol.swift +++ b/Source/Core/LastFM/LastFM_WSClientProtocol.swift @@ -18,6 +18,8 @@ protocol LastFM_WSClientProtocol { func getSession(forToken token: LastFMToken) throws -> LastFMSession + func scrobbleTrackIfEligible(_ track: Track) + func scrobbleTrack(track: Track, timestamp: Int) func scrobbleTrack(artist: String, title: String, album: String?, timestamp: Int) diff --git a/Source/Core/Persistence/Model/MetadataPersistentState.swift b/Source/Core/Persistence/Model/MetadataPersistentState.swift index 5b3d71764..0063dbb99 100644 --- a/Source/Core/Persistence/Model/MetadataPersistentState.swift +++ b/Source/Core/Persistence/Model/MetadataPersistentState.swift @@ -18,6 +18,8 @@ struct MetadataPersistentState: Codable { struct PrimaryMetadataPersistentState: Codable { + let playbackFormat: PlaybackFormatPersistentState? + let title: String? let artist: String? let albumArtist: String? @@ -52,6 +54,8 @@ struct PrimaryMetadataPersistentState: Codable { init(metadata: PrimaryMetadata) { + self.playbackFormat = .init(format: metadata.playbackFormat) + self.title = metadata.title self.artist = metadata.artist self.album = metadata.album @@ -85,6 +89,23 @@ struct PrimaryMetadataPersistentState: Codable { } } +struct PlaybackFormatPersistentState: Codable { + + let sampleRate: Double? + let channelCount: AVAudioChannelCount? + + let layoutTag: AudioChannelLayoutTag? + let channelBitmapRawValue: UInt32? + + init(format: PlaybackFormat) { + + self.sampleRate = format.sampleRate + self.channelCount = format.channelCount + self.layoutTag = format.layoutTag + self.channelBitmapRawValue = format.channelBitmapRawValue + } +} + struct ChapterPersistentState: Codable { let title: String? diff --git a/Source/Core/Playback/Delegates/PlaybackChain/StopPlaybackChain/LastFMScrobbleAction.swift b/Source/Core/Playback/Delegates/PlaybackChain/StopPlaybackChain/LastFMScrobbleAction.swift index b3bcbb9f8..b52d03810 100644 --- a/Source/Core/Playback/Delegates/PlaybackChain/StopPlaybackChain/LastFMScrobbleAction.swift +++ b/Source/Core/Playback/Delegates/PlaybackChain/StopPlaybackChain/LastFMScrobbleAction.swift @@ -12,37 +12,10 @@ import Foundation class LastFMScrobbleAction: PlaybackChainAction { - private var lastFMPreferences: LastFMPreferences {preferences.metadataPreferences.lastFM} - private static let maxPlaybackTime: Double = 240 // 4 minutes - func execute(_ context: PlaybackRequestContext, _ chain: PlaybackChain) { - /* - - From: https://www.last.fm/api/scrobbling - ---------------------------------------- - - A track should only be scrobbled when the following conditions have been met: - - - The track must be longer than 30 seconds. - - And the track has been played for at least half its duration, or for 4 minutes (whichever occurs earlier.) - - */ - - if lastFMPreferences.enableScrobbling.value, - let stoppedTrack = context.currentTrack, - stoppedTrack.canBeScrobbledOnLastFM, - let historyLastPlayedItem = historyDelegate.lastPlayedItem, historyLastPlayedItem.track == stoppedTrack { - - let now = Date() - let playbackTime = now.timeIntervalSince(historyLastPlayedItem.lastEventTime) - - if playbackTime >= min(stoppedTrack.duration / 2, Self.maxPlaybackTime) { - - DispatchQueue.global(qos: .background).async { - lastFMClient.scrobbleTrack(track: stoppedTrack, timestamp: historyLastPlayedItem.lastEventTime.epochTime) - } - } + if let stoppedTrack = context.currentTrack { + lastFMClient.scrobbleTrackIfEligible(stoppedTrack) } chain.proceed(context) diff --git a/Source/Core/Playback/Delegates/PlaybackDelegate.swift b/Source/Core/Playback/Delegates/PlaybackDelegate.swift index 8fcb14c43..1a67b83b9 100644 --- a/Source/Core/Playback/Delegates/PlaybackDelegate.swift +++ b/Source/Core/Playback/Delegates/PlaybackDelegate.swift @@ -116,6 +116,7 @@ class PlaybackDelegate: PlaybackDelegateProtocol { DispatchQueue.global(qos: .userInteractive).async { playQueueDelegate.prepareForGaplessPlayback() } +// doBeginGaplessPlayback() } private func gaplessPlaybackAnalysisCompleted(notif: GaplessPlaybackAnalysisNotification) { diff --git a/Source/Core/Playback/Scheduling/AVFoundation/AVFScheduler+Gapless.swift b/Source/Core/Playback/Scheduling/AVFoundation/AVFScheduler+Gapless.swift index e05124ba0..9f605658f 100644 --- a/Source/Core/Playback/Scheduling/AVFoundation/AVFScheduler+Gapless.swift +++ b/Source/Core/Playback/Scheduling/AVFoundation/AVFScheduler+Gapless.swift @@ -61,6 +61,8 @@ extension AVFScheduler { guard let subsequentTrack = gaplessTracksQueue.dequeue() else {return} + // TODO: Prepare for playback here + if let file = (subsequentTrack.playbackContext as? AVFPlaybackContext)?.audioFile { self.playerNode.scheduleFile(session: session, diff --git a/Source/Core/TrackIO/AVFoundation/AVFFileReader.swift b/Source/Core/TrackIO/AVFoundation/AVFFileReader.swift index 1bd0acb09..824fafab7 100644 --- a/Source/Core/TrackIO/AVFoundation/AVFFileReader.swift +++ b/Source/Core/TrackIO/AVFoundation/AVFFileReader.swift @@ -40,23 +40,16 @@ class AVFFileReader: FileReaderProtocol { func getPrimaryMetadata(for file: URL) throws -> PrimaryMetadata { // Construct a metadata map for this file. - let metadataMap = AVFMappedMetadata(file: file) + guard let metadataMap = AVFMappedMetadata(file: file) else {throw NoAudioTracksError(file)} return try doGetPrimaryMetadata(for: file, fromMap: metadataMap) } private func doGetPrimaryMetadata(for file: URL, fromMap metadataMap: AVFMappedMetadata) throws -> PrimaryMetadata { - // Make sure the file has at least one audio track. - guard metadataMap.hasAudioTracks else {throw NoAudioTracksError(file)} - // Make sure track is not DRM protected. guard !metadataMap.avAsset.hasProtectedContent else {throw DRMProtectionError(file)} - // Make sure track is playable. - // TODO: What does isPlayable actually mean ? -// guard metadataMap.audioTrack.isPlayable else {throw TrackNotPlayableError(file)} - - let metadata = PrimaryMetadata() + let metadata = PrimaryMetadata(playbackFormat: .init(audioFormat: metadataMap.audioFormat)) // Obtain the parsers relevant to this track, based on the metadata present. let parsers = metadataMap.keySpaces.compactMap {parsersMap[$0]} @@ -109,7 +102,7 @@ class AVFFileReader: FileReaderProtocol { func getArt(for file: URL) -> CoverArt? { - let metadataMap = AVFMappedMetadata(file: file) + guard let metadataMap = AVFMappedMetadata(file: file) else {return nil} let parsers = metadataMap.keySpaces.compactMap {parsersMap[$0]} return parsers.firstNonNilMappedValue {$0.getArt(metadataMap)} @@ -124,7 +117,7 @@ class AVFFileReader: FileReaderProtocol { func getAudioInfo(for file: URL, loadingAudioInfoFrom playbackContext: PlaybackContextProtocol? = nil) -> AudioInfo { // Construct a metadata map for this file. - let metadataMap = AVFMappedMetadata(file: file) + guard let metadataMap = AVFMappedMetadata(file: file) else {return .init()} return doGetAudioInfo(for: file, fromMap: metadataMap, loadingAudioInfoFrom: playbackContext) } diff --git a/Source/Core/TrackIO/AVFoundation/Utils/AVFMappedMetadata.swift b/Source/Core/TrackIO/AVFoundation/Utils/AVFMappedMetadata.swift index bb88372e2..f4bf9eede 100644 --- a/Source/Core/TrackIO/AVFoundation/Utils/AVFMappedMetadata.swift +++ b/Source/Core/TrackIO/AVFoundation/Utils/AVFMappedMetadata.swift @@ -27,15 +27,12 @@ struct AVFMappedMetadata { /// let avAsset: AVURLAsset - /// - /// Whether or not the represented file contains any audio tracks. Used for track validation. - /// - var hasAudioTracks: Bool {avAsset.tracks.first(where: {$0.mediaType == .audio}) != nil} + let audioFormat: AVAudioFormat /// /// The AVFoundation audio track object that contains track-level information (such as bit rate). /// - var audioTrack: AVAssetTrack {avAsset.tracks.first(where: {$0.mediaType == .audio})!} + let audioTrack: AVAssetTrack /// /// The following dictionaries contain mappings of key -> AVMetadataItem for each of the supported metadata key spaces. @@ -52,11 +49,16 @@ struct AVFMappedMetadata { /// var keySpaces: [AVMetadataKeySpace] = [] - init(file: URL) { + init?(file: URL) { self.file = file self.avAsset = AVURLAsset(url: file, options: nil) + guard let audioTrack = avAsset.tracks.first(where: {$0.mediaType == .audio}) else {return nil} + + self.audioTrack = audioTrack + self.audioFormat = .init(cmAudioFormatDescription: audioTrack.formatDescription) + // Iterate through all metadata items, and group them based on // key space. diff --git a/Source/Core/TrackIO/FFmpeg/FFmpegFileReader.swift b/Source/Core/TrackIO/FFmpeg/FFmpegFileReader.swift index 5a5dbe31c..9578a857c 100644 --- a/Source/Core/TrackIO/FFmpeg/FFmpegFileReader.swift +++ b/Source/Core/TrackIO/FFmpeg/FFmpegFileReader.swift @@ -7,7 +7,7 @@ // This software is licensed under the MIT software license. // See the file "LICENSE" in the project root directory for license terms. // -import Foundation +import AVFoundation /// /// Handles loading of track metadata from non-native tracks, using **FFmpeg*. @@ -93,7 +93,10 @@ class FFmpegFileReader: FileReaderProtocol { private func doGetPrimaryMetadata(for file: URL, fromCtx fctx: FFmpegFileContext, stream: FFmpegAudioStream, codec: FFmpegAudioCodec, andMap metadataMap: FFmpegMappedMetadata, usingParsers relevantParsers: [FFmpegMetadataParser]) throws -> PrimaryMetadata { - let metadata = PrimaryMetadata() + let audioFormat: AVAudioFormat = .init(standardFormatWithSampleRate: Double(codec.sampleRate), + channelLayout: codec.channelLayout.avfLayout) + + let metadata = PrimaryMetadata(playbackFormat: .init(audioFormat: audioFormat)) // Read all essential metadata fields. diff --git a/Source/Core/TrackIO/Model/PlaybackFormat.swift b/Source/Core/TrackIO/Model/PlaybackFormat.swift new file mode 100644 index 000000000..eaebafeee --- /dev/null +++ b/Source/Core/TrackIO/Model/PlaybackFormat.swift @@ -0,0 +1,84 @@ +// +// PlaybackFormat.swift +// Aural +// +// Copyright © 2024 Kartik Venugopal. All rights reserved. +// +// This software is licensed under the MIT software license. +// See the file "LICENSE" in the project root directory for license terms. +// + +import AVFoundation + +struct PlaybackFormat { + + let sampleRate: Double + let channelCount: AVAudioChannelCount + + let layoutTag: AudioChannelLayoutTag? + let channelBitmapRawValue: UInt32? + + init(audioFormat: AVAudioFormat) { + + self.sampleRate = audioFormat.sampleRate + self.channelCount = audioFormat.channelCount + self.layoutTag = audioFormat.channelLayout?.layoutTag + + if self.layoutTag == kAudioChannelLayoutTag_UseChannelBitmap { + self.channelBitmapRawValue = audioFormat.channelLayout?.layout.pointee.mChannelBitmap.rawValue + } else { + self.channelBitmapRawValue = 0 + } + } + + init?(persistentState: PlaybackFormatPersistentState) { + + guard let sampleRate = persistentState.sampleRate, + let channelCount = persistentState.channelCount else {return nil} + + self.sampleRate = sampleRate + self.channelCount = channelCount + + self.layoutTag = persistentState.layoutTag + self.channelBitmapRawValue = persistentState.channelBitmapRawValue + } +} + +extension PlaybackFormat: Hashable { + + static func == (lhs: PlaybackFormat, rhs: PlaybackFormat) -> Bool { + + if lhs.sampleRate != rhs.sampleRate { + return false + } + + if lhs.channelCount != rhs.channelCount { + return false + } + + if lhs.channelCount <= 2 { + return true + } + + // MARK: Channel count > 2 -------------------------------------------------- + + if lhs.layoutTag != rhs.layoutTag { + return false + } + + if lhs.layoutTag == kAudioChannelLayoutTag_UseChannelBitmap { + return lhs.channelBitmapRawValue == rhs.channelBitmapRawValue + + } else { + return true + } + } + + func hash(into hasher: inout Hasher) { + + hasher.combine(sampleRate) + hasher.combine(channelCount) + hasher.combine(layoutTag) + hasher.combine(channelBitmapRawValue) + } +} diff --git a/Source/Core/TrackIO/Model/PrimaryMetadata.swift b/Source/Core/TrackIO/Model/PrimaryMetadata.swift index 3774a3d9a..ee39dfc3e 100644 --- a/Source/Core/TrackIO/Model/PrimaryMetadata.swift +++ b/Source/Core/TrackIO/Model/PrimaryMetadata.swift @@ -18,6 +18,8 @@ import Foundation /// class PrimaryMetadata { + let playbackFormat: PlaybackFormat + var title: String? var artist: String? var albumArtist: String? @@ -72,9 +74,16 @@ class PrimaryMetadata { var replayGain: ReplayGain? - init() {} + init(playbackFormat: PlaybackFormat) { + self.playbackFormat = playbackFormat + } - init(persistentState: PrimaryMetadataPersistentState, persistentCoverArt: CoverArt?) { + init?(persistentState: PrimaryMetadataPersistentState, persistentCoverArt: CoverArt?) { + + guard let playbackFormatState = persistentState.playbackFormat, + let playbackFormat = PlaybackFormat(persistentState: playbackFormatState) else {return nil} + + self.playbackFormat = playbackFormat self.title = persistentState.title self.artist = persistentState.artist diff --git a/Source/Core/TrackIO/Model/Track+Metadata.swift b/Source/Core/TrackIO/Model/Track+Metadata.swift index d728cc50b..8d8f07a7b 100644 --- a/Source/Core/TrackIO/Model/Track+Metadata.swift +++ b/Source/Core/TrackIO/Model/Track+Metadata.swift @@ -22,6 +22,10 @@ extension Track { metadata.isPlayable } + var playbackFormat: PlaybackFormat? { + metadata.primary?.playbackFormat + } + var displayName: String { artistTitleString ?? defaultDisplayName } diff --git a/Source/Core/Utils/Extensions/AVAudioFormatExtensions.swift b/Source/Core/Utils/Extensions/AVAudioFormatExtensions.swift index 1a62ebe12..9697ee8d6 100644 --- a/Source/Core/Utils/Extensions/AVAudioFormatExtensions.swift +++ b/Source/Core/Utils/Extensions/AVAudioFormatExtensions.swift @@ -20,36 +20,4 @@ extension AVAudioFormat { let layout = AVAudioChannelLayout(layoutTag: layoutTag) return layout?.layout.pointee.description ?? AVAudioChannelLayout.defaultDescription(channelCount: channelCount) } - - /// - /// A convenient way to instantiate an AVAudioFormat given an ffmpeg sample format, sample rate, and channel layout identifier. - /// - convenience init?(from ffmpegFormat: FFmpegAudioFormat) { - - var commonFmt: AVAudioCommonFormat - - switch ffmpegFormat.avSampleFormat { - - case AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_S16P: - - commonFmt = .pcmFormatInt16 - - case AV_SAMPLE_FMT_S32, AV_SAMPLE_FMT_S32P: - - commonFmt = .pcmFormatInt32 - - case AV_SAMPLE_FMT_FLT, AV_SAMPLE_FMT_FLTP: - - commonFmt = .pcmFormatFloat32 - - default: - - return nil - } - - self.init(commonFormat: commonFmt, - sampleRate: Double(ffmpegFormat.sampleRate), - interleaved: ffmpegFormat.isInterleaved, - channelLayout: ffmpegFormat.channelLayout.avfLayout) - } } diff --git a/Source/UI/PlayQueue/ExpandedView/PlayQueueExpandedViewController.swift b/Source/UI/PlayQueue/ExpandedView/PlayQueueExpandedViewController.swift index 3ac18312c..ac7a69797 100644 --- a/Source/UI/PlayQueue/ExpandedView/PlayQueueExpandedViewController.swift +++ b/Source/UI/PlayQueue/ExpandedView/PlayQueueExpandedViewController.swift @@ -131,14 +131,7 @@ class PlayQueueListTrackNameCell: NSTableCellView { } if needsTooltip, lblTitle.isTruncatingText || lblArtistAlbum.isTruncatingText { - toolTip = "\(lblTitle.stringValue)\n\(lblArtistAlbum.stringValue)" - - if lblTitle.isTruncatingText { - print("title truncated, toolTip = \(self.toolTip)") - } else { - print("artist/album truncated, toolTip = \(self.toolTip)") - } } } else { diff --git a/Source/UI/Player/PlayerViewController.swift b/Source/UI/Player/PlayerViewController.swift index 300bf44e3..2e8e9a874 100644 --- a/Source/UI/Player/PlayerViewController.swift +++ b/Source/UI/Player/PlayerViewController.swift @@ -470,8 +470,8 @@ class PlayerViewController: NSViewController { func beginGaplessPlayback() { - gaplessPlaybackProgressController.forceLoadingOfWindow() - gaplessPlaybackProgressController.showWindow(self) +// gaplessPlaybackProgressController.forceLoadingOfWindow() +// gaplessPlaybackProgressController.showWindow(self) playbackDelegate.beginGaplessPlayback() }