Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions Melodee/Classes/FilesystemManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ class FilesystemManager {
case "txt": return .text
case "pdf": return .pdf
case "zip": return .zip
case "melodee": return .playlist
case "icloud": return fileType(for: url.deletingPathExtension())
default: return nil
}
Expand All @@ -126,6 +127,7 @@ class FilesystemManager {
case "txt": return .text
case "pdf": return .pdf
case "zip": return .zip
case "melodee": return .playlist
default: return .notSet
}
}
Expand Down
3 changes: 1 addition & 2 deletions Melodee/Classes/MediaPlayerManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -270,8 +270,7 @@ class MediaPlayerManager: NSObject, AVAudioPlayerDelegate {
let albumArt = await albumArt()
var nowPlayingInfo = [String: Any]()
nowPlayingInfo[MPMediaItemPropertyTitle] = currentlyPlayingTitle() ?? ""
nowPlayingInfo[MPMediaItemPropertyArtist] = Bundle.main
.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String ?? ""
nowPlayingInfo[MPMediaItemPropertyArtist] = "Melodee"
nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: albumArt.size) { _ in
return albumArt
}
Expand Down
87 changes: 87 additions & 0 deletions Melodee/Classes/PlaylistManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//
// PlaylistManager.swift
// Melodee
//
// Created by シン・ジャスティン on 2024/03/16.
//

import Foundation

@Observable
class PlaylistManager {

static let playlistExtension = "melodee"

// MARK: - Load / Save

static func load(from url: URL) -> Playlist? {
guard let data = try? Data(contentsOf: url) else { return nil }
return try? JSONDecoder().decode(Playlist.self, from: data)
}

static func save(_ playlist: Playlist, to url: URL) {
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
guard let data = try? encoder.encode(playlist) else { return }
try? data.write(to: url, options: .atomic)
}

// MARK: - Create

static func create(name: String, in directoryURL: URL, audioFiles: [FSFile]) -> URL {
let sanitizedName = name.replacingOccurrences(of: "/", with: "-")
let fileURL = directoryURL
.appendingPathComponent(sanitizedName)
.appendingPathExtension(playlistExtension)

let playlistFiles = audioFiles.compactMap { file -> PlaylistFile? in
guard let relativePath = relativePath(
from: directoryURL,
to: URL(fileURLWithPath: file.path)
) else { return nil }
return PlaylistFile(relativePath: relativePath)
}
let playlist = Playlist(name: name, files: playlistFiles)
save(playlist, to: fileURL)
return fileURL
}

/// Computes a relative path from a directory to a file.
static func relativePath(from base: URL, to target: URL) -> String? {
let basePath = base.standardizedFileURL.path(percentEncoded: false)
let targetPath = target.standardizedFileURL.path(percentEncoded: false)

let baseComponents = basePath.split(separator: "/", omittingEmptySubsequences: true)
let targetComponents = targetPath.split(separator: "/", omittingEmptySubsequences: true)

// Find common prefix length
var commonLength = 0
while commonLength < baseComponents.count && commonLength < targetComponents.count
&& baseComponents[commonLength] == targetComponents[commonLength] {
commonLength += 1
}

// Number of ".." needed to go up from base
let ups = baseComponents.count - commonLength
var parts: [String] = Array(repeating: "..", count: ups)
// Append remaining target components
parts.append(contentsOf: targetComponents[commonLength...].map(String.init))

let result = parts.joined(separator: "/")
return result.isEmpty ? nil : result
}

// MARK: - Helpers

static func directoryURL(for playlistFileURL: URL) -> URL {
playlistFileURL.deletingLastPathComponent()
}

static func isPlaylistFile(_ url: URL) -> Bool {
url.pathExtension.lowercased() == playlistExtension
}

static func isPlaylistFile(_ file: FSFile) -> Bool {
file.extension.lowercased() == playlistExtension
}
}
2 changes: 2 additions & 0 deletions Melodee/Enums/FileType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ enum FileType: String, Codable {
case text
case pdf
case zip
case playlist
case notSet

func icon() -> Image {
Expand All @@ -22,6 +23,7 @@ enum FileType: String, Codable {
case .image: return Image("File.Image")
case .pdf: return Image("File.PDF")
case .zip: return Image("File.Archive")
case .playlist: return Image(systemName: "music.note.list")
default: return Image("File.Generic")
}
}
Expand Down
1 change: 1 addition & 0 deletions Melodee/Enums/ViewPath.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ enum ViewPath: Hashable {
case imageViewer(file: FSFile)
case textViewer(file: FSFile)
case pdfViewer(file: FSFile)
case playlistViewer(file: FSFile)
case tagEditorSingle(file: FSFile)
case tagEditorMultiple(files: [FSFile])
case moreAttributions
Expand Down
14 changes: 1 addition & 13 deletions Melodee/InfoPlist.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,6 @@
"state" : "translated",
"value" : "Melodee"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "メロディー"
}
}
}
},
Expand All @@ -26,12 +20,6 @@
"state" : "translated",
"value" : "Melodee"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "メロディー"
}
}
}
},
Expand All @@ -54,4 +42,4 @@
}
},
"version" : "1.0"
}
}
Loading