diff --git a/Aural.xcodeproj/project.xcworkspace/xcuserdata/kven.xcuserdatad/UserInterfaceState.xcuserstate b/Aural.xcodeproj/project.xcworkspace/xcuserdata/kven.xcuserdatad/UserInterfaceState.xcuserstate index 7983543bf..7d3428b02 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/Playback/Delegates/PlaybackDelegate.swift b/Source/Core/Playback/Delegates/PlaybackDelegate.swift index 5b9467fcd..865894561 100644 --- a/Source/Core/Playback/Delegates/PlaybackDelegate.swift +++ b/Source/Core/Playback/Delegates/PlaybackDelegate.swift @@ -120,7 +120,7 @@ class PlaybackDelegate: PlaybackDelegateProtocol { doBeginGaplessPlayback() } - private func doBeginGaplessPlayback() { + private func doBeginGaplessPlayback(currentTrack: Track? = nil) { guard let firstTrack = playQueueDelegate.tracks.first else {return} @@ -129,7 +129,7 @@ class PlaybackDelegate: PlaybackDelegateProtocol { player.playGapless(tracks: playQueueDelegate.tracks) // Inform observers of the track change/transition. - messenger.publish(TrackTransitionNotification(beginTrack: nil, beginState: .stopped, + messenger.publish(TrackTransitionNotification(beginTrack: currentTrack, beginState: .stopped, endTrack: firstTrack, endState: player.state)) trackReader.loadArtAsync(for: firstTrack, immediate: true) @@ -498,7 +498,7 @@ class PlaybackDelegate: PlaybackDelegateProtocol { if playQueueDelegate.repeatMode == .all { print("\(Date.nowTimestampString) - finishedLastTrack, repeating all ...") - doBeginGaplessPlayback() + doBeginGaplessPlayback(currentTrack: beginTrack) return } diff --git a/Source/Core/Playback/Scheduling/FFmpeg/FFmpegScheduler+Gapless.swift b/Source/Core/Playback/Scheduling/FFmpeg/FFmpegScheduler+Gapless.swift index e5419aa12..311053f4b 100644 --- a/Source/Core/Playback/Scheduling/FFmpeg/FFmpegScheduler+Gapless.swift +++ b/Source/Core/Playback/Scheduling/FFmpeg/FFmpegScheduler+Gapless.swift @@ -54,6 +54,8 @@ extension FFmpegScheduler { func seekGapless(toTime seconds: Double, currentSession: PlaybackSession, beginPlayback: Bool, otherTracksToSchedule: [Track]) { + print("\n\(Date.nowTimestampString) - seekGapless() toTime: \(seconds), session: \(currentSession.id)") + stop() doPlayGapless(firstTrack: currentSession.track, fromTime: seconds, otherTracks: otherTracksToSchedule, currentSession: currentSession) } @@ -113,9 +115,12 @@ extension FFmpegScheduler { var theTrack = track var theContext = context var theDecoder = decoder + var seekPos: Double? = nil if decoder.eof { + print("\(Date.nowTimestampString) - continueSchedulingGaplessAsync() ... decoder EOF") + decoder.stop() currentGaplessTrack = gaplessTracksQueue.dequeue() @@ -133,17 +138,36 @@ extension FFmpegScheduler { } guard let newContext = nextTrack.playbackContext as? FFmpegPlaybackContext, - let newDecoder = newContext.decoder else {return} + let newDecoder = newContext.decoder else { + + print("\(Date.nowTimestampString) - continueSchedulingGaplessAsync() ... NO CONTEXT, returning ...") + return + } + + // The new track must always start from 0. + do { + try newDecoder.seek(to: 0) + + } catch { + + Messenger.publish(TrackNotPlayedNotification(oldTrack: session.track, errorTrack: nextTrack, + error: error as? DisplayableError ?? TrackNotPlayableError(nextTrack.file))) + return + } theTrack = nextTrack + seekPos = 0 gaplessScheduledBufferCounts[nextTrack] = AtomicIntCounter() theContext = newContext theDecoder = newDecoder } + print("\(Date.nowTimestampString) - continueSchedulingGaplessAsync() ... theDecoder: \(theDecoder === decoder), theTrack: \(theTrack)") + self.schedulingOpQueue.addOperation { - self.decodeAndScheduleOneGaplessBuffer(for: session, track: theTrack, context: theContext, decoder: theDecoder, + + self.decodeAndScheduleOneGaplessBuffer(for: session, track: theTrack, context: theContext, decoder: theDecoder, from: seekPos, immediatePlayback: false, maxSampleCount: theContext.sampleCountForDeferredPlayback) } } @@ -163,6 +187,8 @@ extension FFmpegScheduler { return } + print("\(Date.nowTimestampString) - Scheduling one buffer for: \(context.file.lastPathComponent), fromPos: \(seekPosition)") + playerNode.scheduleBuffer(playbackBuffer, for: session, completionHandler: self.gaplessBufferCompletionHandler(session), seekPosition, immediatePlayback) @@ -179,6 +205,8 @@ extension FFmpegScheduler { fileprivate func gaplessBufferCompleted(_ session: PlaybackSession) { + print("\n\(Date.nowTimestampString) - gaplessBufferCompleted() for: \(session.track). isCurrent ? \(PlaybackSession.isCurrent(session))") + // If the buffer-associated session is not the same as the current session // (possible if stop() was called, eg. old buffers that complete when seeking), don't do anything. guard PlaybackSession.isCurrent(session), let playbackCtx = session.track.playbackContext as? FFmpegPlaybackContext, @@ -189,6 +217,8 @@ extension FFmpegScheduler { if decoder.eof, let bufferCount = gaplessScheduledBufferCounts[session.track], bufferCount.isZero { + playbackCtx.close() + print("\n\(Date.nowTimestampString) - Decoder EOF for track: \(session.track), curGT: \(currentGaplessTrack), queueSize: \(gaplessTracksQueue.size)") // EOF has been reached, and all buffers have completed playback.