Skip to content

Commit

Permalink
refactor: improve loading speed for online files
Browse files Browse the repository at this point in the history
feat: loading indicator for online files
  • Loading branch information
godly-devotion committed Mar 26, 2024
1 parent 5c36f44 commit 488b927
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 27 deletions.
4 changes: 3 additions & 1 deletion Front Row/FrontRowApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ struct FrontRowApp: App {
class AppDelegate: NSObject, NSApplicationDelegate {
func application(_ application: NSApplication, open urls: [URL]) {
guard urls.count == 1, let url = urls.first else { return }
PlayEngine.shared.openFile(url: url)
Task {
await PlayEngine.shared.openFile(url: url)
}
}

func applicationDidFinishLaunching(_ notification: Notification) {
Expand Down
2 changes: 1 addition & 1 deletion Front Row/Main Menu/PlaybackCommands.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ struct PlaybackCommands: Commands {
playEngine.playPause()
} label: {
Text(
playEngine.isPlaying ? "Pause" : "Play",
playEngine.timeControlStatus == .playing ? "Pause" : "Play",
comment: "Toggle playback status"
)
}
Expand Down
46 changes: 30 additions & 16 deletions Front Row/Support/PlayEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ import SwiftUI
.wav,
]

private var asset: AVAsset?

private(set) var player = AVPlayer()

private(set) var isLoaded = false

private(set) var isPlaying = false
private(set) var timeControlStatus: AVPlayer.TimeControlStatus = .paused

private(set) var isLocalFile = false

Expand Down Expand Up @@ -75,9 +77,8 @@ import SwiftUI

player.publisher(for: \.timeControlStatus)
.receive(on: DispatchQueue.main)
.map { $0 == AVPlayer.TimeControlStatus.playing }
.sink { isPlaying in
self.isPlaying = isPlaying
.sink { status in
self.timeControlStatus = status
}
.store(in: &subs)

Expand All @@ -98,15 +99,6 @@ import SwiftUI
removePeriodicTimeObserver()
}

func isURLPlayable(url: URL) async -> Bool {
let asset = AVAsset(url: url)
do {
return try await asset.load(.isPlayable)
} catch {
return false
}
}

@MainActor
func showOpenFileDialog() async {
let panel = NSOpenPanel()
Expand All @@ -120,10 +112,26 @@ import SwiftUI
}

guard let url = panel.url else { return }
openFile(url: url)
await openFile(url: url)
}

func openFile(url: URL) {
/// Attempts to open file at url. If its not playable, returns false.
/// - Parameter url: A URL to a local, remote, or HTTP Live Streaming media resource.
/// - Returns: A Boolean value that indicates whether an asset contains playable content.
@discardableResult func openFile(url: URL) async -> Bool {
if asset != nil {
asset?.cancelLoading()
}
asset = AVAsset(url: url)
do {
let isPlayable = try await asset!.load(.isPlayable)
guard isPlayable else {
return false
}
} catch {
return false
}

for sub in currentItemSubs { sub.cancel() }
currentItemSubs.removeAll()

Expand Down Expand Up @@ -161,10 +169,16 @@ import SwiftUI

player.replaceCurrentItem(with: playerItem)
player.play()
return true
}

func cancelLoading() {
guard let asset else { return }
asset.cancelLoading()
}

func playPause() {
if isPlaying {
if timeControlStatus == .playing {
player.pause()
} else {
player.play()
Expand Down
10 changes: 9 additions & 1 deletion Front Row/Views/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@
import SwiftUI

struct ContentView: View {
@Environment(PlayEngine.self) var playEngine: PlayEngine
@State private var playerControlsShown = true
@State private var mouseIdleTimer: Timer!

var body: some View {
@Bindable var playEngine = playEngine

ZStack(alignment: .bottom) {
PlayerView(player: PlayEngine.shared.player)
.onDrop(
Expand All @@ -27,7 +30,7 @@ struct ContentView: View {

Task {
guard let url = await provider.getURL() else { return }
PlayEngine.shared.openFile(url: url)
await PlayEngine.shared.openFile(url: url)
}

return true
Expand All @@ -40,6 +43,11 @@ struct ContentView: View {
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
.ignoresSafeArea()

if playEngine.timeControlStatus == .waitingToPlayAtSpecifiedRate {
ProgressView()
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
}

if playerControlsShown {
PlayerControlsView()
.animation(.linear(duration: 0.4), value: playerControlsShown)
Expand Down
17 changes: 14 additions & 3 deletions Front Row/Views/OpenURLView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,16 @@ import SwiftUI
struct OpenURLView: View {
@Environment(\.dismiss) private var dismiss
@State private var url = ""
@State private var displayLoading = false
@State private var displayError = false

var body: some View {
HStack(spacing: 8) {
HStack(spacing: 14) {
if displayLoading {
ProgressView()
.controlSize(.small)
}

if displayError {
Image(systemName: "play.slash")
.resizable()
Expand All @@ -29,28 +35,33 @@ struct OpenURLView: View {
)
) {}
.onChange(of: url) {
PlayEngine.shared.cancelLoading()
withAnimation {
displayLoading = false
displayError = false
}
}
.onSubmit {
Task {
guard let url = URL(string: url) else {
withAnimation {
displayLoading = false
displayError = true
}
return
}
if await !PlayEngine.shared.isURLPlayable(url: url) {
displayLoading = true
guard await PlayEngine.shared.openFile(url: url) else {
withAnimation {
displayLoading = false
displayError = true
}
return
}
withAnimation {
displayLoading = false
displayError = false
}
PlayEngine.shared.openFile(url: url)
dismiss()
}
}
Expand Down
14 changes: 9 additions & 5 deletions Front Row/Views/PlayerControlsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,15 @@ struct PlayerControlsView: View {
Button {
PlayEngine.shared.playPause()
} label: {
Image(systemName: playEngine.isPlaying ? "pause.fill" : "play.fill")
.resizable()
.scaledToFit()
.foregroundStyle(foregroundColor)
.frame(width: 24, height: 24)
Image(
systemName: playEngine.timeControlStatus == .playing
? "pause.fill"
: "play.fill"
)
.resizable()
.scaledToFit()
.foregroundStyle(foregroundColor)
.frame(width: 24, height: 24)
}
.buttonStyle(PlainButtonStyle())
.keyboardShortcut("K", modifiers: [])
Expand Down

0 comments on commit 488b927

Please sign in to comment.