Skip to content

Remember folder #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions Coldwave.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
8723BCF32A78C706007BC8FF /* LocationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8723BCF22A78C706007BC8FF /* LocationView.swift */; };
F823C8CB2709C06E00CB6F31 /* AlbumCoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F823C8CA2709C06E00CB6F31 /* AlbumCoverView.swift */; };
F823C8CD2709CBA500CB6F31 /* PlaybackControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F823C8CC2709CBA500CB6F31 /* PlaybackControlView.swift */; };
F84AB2DB269329D400D69D4F /* ColdwaveApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = F84AB2DA269329D400D69D4F /* ColdwaveApp.swift */; };
Expand Down Expand Up @@ -37,6 +38,7 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
8723BCF22A78C706007BC8FF /* LocationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationView.swift; sourceTree = "<group>"; };
F823C8CA2709C06E00CB6F31 /* AlbumCoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumCoverView.swift; sourceTree = "<group>"; };
F823C8CC2709CBA500CB6F31 /* PlaybackControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackControlView.swift; sourceTree = "<group>"; };
F84AB2D7269329D400D69D4F /* Coldwave.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Coldwave.app; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -110,6 +112,7 @@
F86C1E3A270C476600D602A6 /* SearchView.swift */,
F86C1E3C270C8F5800D602A6 /* ColdwaveState.swift */,
F86C1E3E270CA5B200D602A6 /* SingleAlbumView.swift */,
8723BCF22A78C706007BC8FF /* LocationView.swift */,
);
path = Coldwave;
sourceTree = "<group>";
Expand Down Expand Up @@ -202,6 +205,7 @@
F86C1E3D270C8F5800D602A6 /* ColdwaveState.swift in Sources */,
F823C8CB2709C06E00CB6F31 /* AlbumCoverView.swift in Sources */,
F84AB2FC26934D5000D69D4F /* ContentView.swift in Sources */,
8723BCF32A78C706007BC8FF /* LocationView.swift in Sources */,
F823C8CD2709CBA500CB6F31 /* PlaybackControlView.swift in Sources */,
F86C1E3F270CA5B200D602A6 /* SingleAlbumView.swift in Sources */,
);
Expand Down Expand Up @@ -341,7 +345,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.1;
MACOSX_DEPLOYMENT_TARGET = 11.0;
PRODUCT_BUNDLE_IDENTIFIER = abyrd.Coldwave;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
Expand All @@ -364,7 +368,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.1;
MACOSX_DEPLOYMENT_TARGET = 11.0;
PRODUCT_BUNDLE_IDENTIFIER = abyrd.Coldwave;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
Expand Down
4 changes: 2 additions & 2 deletions Coldwave/Album.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class Album: Identifiable, Equatable {
init (_ albumFullPath: String) {
albumPath = albumFullPath
let fileManager = FileManager.default
let contents = try! fileManager.contentsOfDirectory(atPath: albumFullPath)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Order folders by path

let contents = try! fileManager.contentsOfDirectory(atPath: albumFullPath).sorted()
for file in contents as [String] {
var isDir: ObjCBool = false;
let lcFile = file.lowercased()
Expand Down Expand Up @@ -94,7 +94,7 @@ class Album: Identifiable, Equatable {
// Return an array of URLs, one for each music file in this album's folder.
func getPlaylist () -> [URL] {
let fileManager = FileManager.default
let contents = try! fileManager.contentsOfDirectory(atPath: albumPath)
let contents = try! fileManager.contentsOfDirectory(atPath: albumPath).sorted()
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Order tracks by name (usually the track starts with a track number)

var musicFileURLs: [URL] = []
for file in contents as [String] {
let lcFile = file.lowercased()
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
4 changes: 4 additions & 0 deletions Coldwave/Assets.xcassets/AppIcon.appiconset/Contents.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,25 @@
"size" : "32x32"
},
{
"filename" : "AppIcon128x128.png",
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add some icons, so I know where the app is.

"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"filename" : "[email protected]",
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"filename" : "AppIcon256x256.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"filename" : "[email protected]",
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
Expand Down
12 changes: 8 additions & 4 deletions Coldwave/Coldwave.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.app-sandbox</key>
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of this is needed in order to make the "remember folder" work (since there is no user action.
I think there is a way to make the application ask for access to Documents, Downloads and maybe Music folder instead of forcing them here, but I am not sure of how to do that.

<true/>
<key>com.apple.security.assets.music.read-only</key>
<true/>
<key>com.apple.security.files.downloads.read-only</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
</dict>
</plist>
43 changes: 41 additions & 2 deletions Coldwave/ColdwaveApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ import SwiftUI

@main
struct ColdwaveApp: App {

@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

@StateObject var state: ColdwaveState = ColdwaveState()

// Only available in macOS 13.0 and later
//@Environment(\.openWindow) private var openWindow

var body: some Scene {
WindowGroup {
ContentView(state: state)
Expand All @@ -13,6 +17,11 @@ struct ColdwaveApp: App {
CommandGroup(before: CommandGroupPlacement.newItem) {
Button(action: { openFolder() }, label: { Label("Open directory...", systemImage: "doc") })
.keyboardShortcut("o")

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to add a "modal" dialog to ask for a remote location (audio stream or remote file)

/*
Button(action: { openWindow(id: "open-location") }, label: { Label("Open location...", systemImage: "doc")})
.keyboardShortcut("l")
*/
}
CommandMenu("Utilities") {
Button("Bigger") {
Expand All @@ -23,8 +32,14 @@ struct ColdwaveApp: App {
}.keyboardShortcut("-")
}
}

/*
Window("Open location", id: "open-location") {
LocationView()
}.windowResizability(.contentSize)
*/
}

private func openFolder () {
let dialog = NSOpenPanel();
dialog.title = "Choose root music directory | ABCD";
Expand All @@ -44,3 +59,27 @@ struct ColdwaveApp: App {
}

}

final class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
let unwantedMenus = ["Edit","View" ]
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove a couple of menu entries that don't do much for a music player


let removeMenus = {
unwantedMenus.forEach {
guard let menu = NSApp.mainMenu?.item(withTitle: $0) else { return }
NSApp.mainMenu?.removeItem(menu)
}
}

NotificationCenter.default.addObserver(
forName: NSMenu.didAddItemNotification,
object: nil,
queue: .main
) { _ in
// Must refresh after every time SwiftUI re adds
removeMenus()
}

removeMenus()
}
}
20 changes: 15 additions & 5 deletions Coldwave/ColdwaveState.swift
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import Foundation
import AVFoundation
import SwiftUI

class ColdwaveState: ObservableObject {


@AppStorage("music.folder") var path = ""
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

retrieve last folder path from application store

@Published var albums: [Album] = []
@Published var path: String = "";
//@Published var path = "";
@Published var currentAlbum: Album?
@Published var currentTrack = 0
@Published var coverSize: CGFloat = DEFAULT_IMAGE_SIZE
@Published var playlist: [URL] = []
@Published var amountPlayed: Double = 0.0 // in range 0...1
@Published var playing: Bool = false
@Published var searchText: String = ""
@Published var timePlayed = 0
@Published var timeRemaining = 0

let player: AVPlayer = AVPlayer()

Expand All @@ -24,10 +28,17 @@ class ColdwaveState: ObservableObject {
// converting the track duration units to match the amountPlayed units.
if let duration = self.player.currentItem?.duration.convertScale(t.timescale, method: CMTimeRoundingMethod.default) {
self.amountPlayed = Double(t.value) / Double(duration.value)
self.timePlayed = Int(t.seconds)
let d = duration.seconds
self.timeRemaining = d.isNaN ? 0 : Int(d - t.seconds)
}
}

if path != "" {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If app is starting up and there is a stored folder path, scan library

albums = Album.scanLibrary(at: path)
}
}

// It doesn't seem clean to put this (or the AVPlayer) on the state, but notification
// targets have to be objc functions which have to be members of an NSObject or protocol.
// I could probably factor the player field and these methods out into another class.
Expand Down Expand Up @@ -68,12 +79,11 @@ class ColdwaveState: ObservableObject {
player.pause()
playing = false
}

func play () {
if (player.currentItem != nil) {
player.play()
playing = true
}
}

}
41 changes: 41 additions & 0 deletions Coldwave/LocationView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// LocationView.swift
// Coldwave
//
// Created by Raffaele Sena on 7/31/23.
//

import SwiftUI

struct LocationView: View {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Location "dialog"

// Only available in macOS 12.0 or later
//@Environment(\.dismiss) private var dismiss
@State private var location : String = ""

var body: some View {
VStack {
HStack {
Text("Location:")
TextField("Enter location of audio file, stream or playlist", text: $location)
}
HStack {
Button("Cancel") {
location = ""
//dismiss()
}
Button("Open") {
print(location)
//dismiss()
}
}
.frame(maxWidth: .infinity, alignment: .trailing)
}
.padding(.all)
}
}

struct LocationView_Previews: PreviewProvider {
static var previews: some View {
LocationView()
}
}
10 changes: 7 additions & 3 deletions Coldwave/PlaybackControlView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ struct PlaybackControlView: View {
}.id(trackIndex)
}
}
}.padding(PADDING)
}.padding(.horizontal)
// When slider is moved, trailing closure is called with true, then false when released.
// Dragging is quite unresponsive, maybe because the UI is recomputed when state changes.
Slider(value: $state.amountPlayed, in: 0...1) {editing in
Expand All @@ -49,8 +49,12 @@ struct PlaybackControlView: View {
state.player.seek(to: CMTimeMake(value: Int64(newPosition), timescale: d.timescale))
}
}
}.padding(PADDING)
// Text(String(state.amountPlayed))
}.padding(.horizontal)
HStack {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add track play and remaining time

Text(mmss(seconds: state.timePlayed))
Spacer()
Text(mmss(seconds: state.timeRemaining))
}.padding(.horizontal).padding(.bottom)
}

private func mmss (seconds: Int) -> String {
Expand Down