From b315146d4eb652d6eca1059fd3f7444243a81eae Mon Sep 17 00:00:00 2001 From: Lee Jun Kit Date: Thu, 3 Jun 2021 10:27:40 +0800 Subject: [PATCH] implement scrubbing --- Spottie/Backend/SpotifyAPI.swift | 10 ++++++ Spottie/ViewModels/FakePlayerViewModel.swift | 1 + Spottie/ViewModels/PlayerStateProtocol.swift | 1 + Spottie/ViewModels/PlayerViewModel.swift | 13 +++++++- Spottie/Views/Components/PlayerControls.swift | 2 +- .../Components/TrackProgressSlider.swift | 31 +++++++++++++++---- 6 files changed, 50 insertions(+), 8 deletions(-) diff --git a/Spottie/Backend/SpotifyAPI.swift b/Spottie/Backend/SpotifyAPI.swift index 56d08dd..29268c5 100644 --- a/Spottie/Backend/SpotifyAPI.swift +++ b/Spottie/Backend/SpotifyAPI.swift @@ -44,4 +44,14 @@ extension SpotifyAPI { req.httpMethod = "POST" return client.run(req).print().map(\.value).eraseToAnyPublisher() } + + static func seek(posMs: Int) -> AnyPublisher { + let queryItems = [URLQueryItem(name: "pos", value: "\(posMs)")] + var urlComponents = URLComponents(url: base.appendingPathComponent("/player/seek"), resolvingAgainstBaseURL: false)! + urlComponents.queryItems = queryItems + + var req = URLRequest(url: urlComponents.url!) + req.httpMethod = "POST" + return client.run(req).print().map(\.value).eraseToAnyPublisher() + } } diff --git a/Spottie/ViewModels/FakePlayerViewModel.swift b/Spottie/ViewModels/FakePlayerViewModel.swift index b1c743d..f1ad799 100644 --- a/Spottie/ViewModels/FakePlayerViewModel.swift +++ b/Spottie/ViewModels/FakePlayerViewModel.swift @@ -19,4 +19,5 @@ final class FakePlayerViewModel: PlayerStateProtocol { func previousTrack() {} func nextTrack() {} func togglePlayPause() {} + func seek(toPercent: Double) {} } diff --git a/Spottie/ViewModels/PlayerStateProtocol.swift b/Spottie/ViewModels/PlayerStateProtocol.swift index 1f986b1..dc913b6 100644 --- a/Spottie/ViewModels/PlayerStateProtocol.swift +++ b/Spottie/ViewModels/PlayerStateProtocol.swift @@ -19,4 +19,5 @@ protocol PlayerStateProtocol: ObservableObject { func togglePlayPause() -> Void func nextTrack() -> Void func previousTrack() -> Void + func seek(toPercent: Double) -> Void } diff --git a/Spottie/ViewModels/PlayerViewModel.swift b/Spottie/ViewModels/PlayerViewModel.swift index 0e812e1..6eb62c5 100644 --- a/Spottie/ViewModels/PlayerViewModel.swift +++ b/Spottie/ViewModels/PlayerViewModel.swift @@ -42,6 +42,8 @@ class PlayerViewModel: PlayerStateProtocol { case let .playbackResumed(playbackResumedEvent): self.isPlaying = true self.progressMs = playbackResumedEvent.trackTime + case let .trackSeeked(trackSeekedEvent): + self.progressMs = trackSeekedEvent.trackTime case let .trackChanged(trackChangedEvent): self.progressMs = 0 self.isPlaying = true @@ -93,6 +95,15 @@ class PlayerViewModel: PlayerStateProtocol { } func previousTrack() { - SpotifyAPI.previousTrack().sink { _ in } receiveValue: { _ in }.store(in: &cancellables) + SpotifyAPI.previousTrack().sink { _ in + // manually reset the progress to 0 + self.progressMs = 0 + } receiveValue: { _ in }.store(in: &cancellables) + } + + func seek(toPercent: Double) { + // calculate posMs + let posMs = Int(Double(self.durationMs) * toPercent) + SpotifyAPI.seek(posMs: posMs).sink { _ in } receiveValue: { _ in }.store(in: &cancellables) } } diff --git a/Spottie/Views/Components/PlayerControls.swift b/Spottie/Views/Components/PlayerControls.swift index f588a49..f5cba61 100644 --- a/Spottie/Views/Components/PlayerControls.swift +++ b/Spottie/Views/Components/PlayerControls.swift @@ -26,7 +26,7 @@ struct PlayerControls: View { ) RepeatButton() } - TrackProgressSlider(viewModel: TrackProgressSlider.ViewModel(isPlaying: viewModel.isPlaying, progressMs: viewModel.progressMs, durationMs: viewModel.durationMs)) + TrackProgressSlider(viewModel: TrackProgressSlider.ViewModel(isPlaying: viewModel.isPlaying, progressMs: viewModel.progressMs, durationMs: viewModel.durationMs, onScrubToNewProgressPercent: viewModel.seek)) .padding(.leading) .padding(.trailing) } diff --git a/Spottie/Views/Components/TrackProgressSlider.swift b/Spottie/Views/Components/TrackProgressSlider.swift index 9f085f3..fc01347 100644 --- a/Spottie/Views/Components/TrackProgressSlider.swift +++ b/Spottie/Views/Components/TrackProgressSlider.swift @@ -14,7 +14,13 @@ struct TrackProgressSlider: View { var prettyProgress: String { get { - return TrackProgressSlider.DurationFormatter.shared().string(from: Double(self.viewModel.progressMs) / 1000.0)! + let formatter = TrackProgressSlider.DurationFormatter.shared() + if (self.viewModel.isScrubbing) { + let scrubbedProgress = self.viewModel.progressPercent * Double(self.viewModel.durationMs) + return formatter.string(from: scrubbedProgress / 1000.0)! + } else { + return formatter.string(from: Double(self.viewModel.progressMs) / 1000.0)! + } } } @@ -27,10 +33,16 @@ struct TrackProgressSlider: View { var body: some View { Slider( value: $viewModel.progressPercent, + onEditingChanged: { editing in + if (!editing) { + viewModel.onScrubToNewProgressPercent(viewModel.progressPercent) + } + viewModel.isScrubbing = editing + }, minimumValueLabel: Text(prettyProgress).foregroundColor(.secondary), maximumValueLabel: Text(prettyDuration).foregroundColor(.secondary) ) { - Text("") + EmptyView() } .onReceive(timer) { _ in viewModel.updateProgress() @@ -58,12 +70,15 @@ extension TrackProgressSlider { @Published var progressMs: Int @Published var durationMs: Int @Published var progressPercent: Double - - init(isPlaying: Bool, progressMs: Int, durationMs: Int) { + var isScrubbing = false + var onScrubToNewProgressPercent: (_ progressPercent: Double) -> Void + + init(isPlaying: Bool, progressMs: Int, durationMs: Int, onScrubToNewProgressPercent: @escaping (_ progressPercent: Double) -> Void) { self.isPlaying = isPlaying self.progressMs = progressMs self.durationMs = durationMs self.progressPercent = Double(progressMs) / Double(durationMs) + self.onScrubToNewProgressPercent = onScrubToNewProgressPercent } func calculateProgressPercent() { @@ -74,7 +89,9 @@ extension TrackProgressSlider { if (self.isPlaying) { if (self.progressMs < self.durationMs) { self.progressMs += 1000 - self.calculateProgressPercent() + if (!self.isScrubbing) { + self.calculateProgressPercent() + } } } } @@ -82,7 +99,9 @@ extension TrackProgressSlider { } struct TrackProgressSlider_Previews: PreviewProvider { - static let viewModel = TrackProgressSlider.ViewModel(isPlaying: true, progressMs: 20000, durationMs: 120000) + static let viewModel = TrackProgressSlider.ViewModel(isPlaying: true, progressMs: 20000, durationMs: 120000) { progressPercent in + + } static var previews: some View { TrackProgressSlider(viewModel: viewModel) }