bug fixes
This commit is contained in:
@@ -33,11 +33,19 @@ class ListViewModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PlaybackViewProxy {
|
||||||
|
var pbv : PlaybackView
|
||||||
|
init(v : PlaybackView) {
|
||||||
|
self.pbv = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
@ObservedObject var music = ListViewModel()
|
@ObservedObject var music = ListViewModel()
|
||||||
@State var pushState = false
|
@State var pushState = false
|
||||||
@State var geo :CGSize = .zero
|
@State var geo :CGSize = .zero
|
||||||
@State var _curr_sel_music : IDStr = IDStr()
|
@State var _curr_sel_music : IDStr = IDStr()
|
||||||
|
let pbv : PlaybackViewProxy
|
||||||
var body: some View {
|
var body: some View {
|
||||||
GeometryReader { geometry in
|
GeometryReader { geometry in
|
||||||
NavigationView(){
|
NavigationView(){
|
||||||
@@ -61,18 +69,21 @@ struct ContentView: View {
|
|||||||
m.m!.seek(to: .zero)
|
m.m!.seek(to: .zero)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self._curr_sel_music = m
|
||||||
|
self.pbv.pbv.update(music: _curr_sel_music)
|
||||||
pushState = true
|
pushState = true
|
||||||
_curr_sel_music = m
|
|
||||||
}).ignoresSafeArea(.all).cornerRadius(.zero).padding(.zero).frame(maxHeight: CGFloat(50)).foregroundColor(.white)
|
}).ignoresSafeArea(.all).cornerRadius(.zero).padding(.zero).frame(maxHeight: CGFloat(50)).foregroundColor(.white)
|
||||||
|
|
||||||
NavigationLink(destination: PlaybackView(parent:self, music: _curr_sel_music), isActive: $pushState) {
|
NavigationLink(destination: self.pbv.pbv, isActive: $pushState) {
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Label("\(music.music.count) Files. ", systemImage: "heart.fill").background(.clear).labelStyle(.titleAndIcon).frame(width: geometry.size.width, alignment: .center)
|
||||||
}
|
}
|
||||||
}.onAppear {
|
}.onAppear {
|
||||||
geo = geometry.size
|
geo = geometry.size
|
||||||
|
self.pbv.pbv.parent = self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,7 +91,7 @@ struct ContentView: View {
|
|||||||
var player : AVQueuePlayer? = nil
|
var player : AVQueuePlayer? = nil
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
print("'sibal'");
|
self.pbv = PlaybackViewProxy(v: PlaybackView())
|
||||||
let base = "https://billsun.dev/webdav/music-test"
|
let base = "https://billsun.dev/webdav/music-test"
|
||||||
let url = URL(string: base)
|
let url = URL(string: base)
|
||||||
let request: URLRequest = URLRequest(url: url!)
|
let request: URLRequest = URLRequest(url: url!)
|
||||||
@@ -89,27 +100,38 @@ struct ContentView: View {
|
|||||||
|
|
||||||
session.dataTask(with: request, completionHandler:
|
session.dataTask(with: request, completionHandler:
|
||||||
{ (data, response, error) -> Void in
|
{ (data, response, error) -> Void in
|
||||||
if (error == nil) { return }
|
if (error != nil) { return }
|
||||||
let reply = String(data: data!, encoding: String.Encoding.utf8)!
|
let reply = String(data: data!, encoding: String.Encoding.utf8)!
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let pattern = try Regex(#".*(<a\s+href=\"(.*.m4a)\">)"#)
|
let pattern = try Regex(#".*(<a\s+href=\"(.*.(m4a|mp3|wav))\">)"#)
|
||||||
let matched = reply.matches(of: pattern)
|
let matched = reply.matches(of: pattern)
|
||||||
|
|
||||||
var s = Set<String>()
|
var s = Set<String>()
|
||||||
for match in matched {
|
for match in matched {
|
||||||
s.insert(String(match.output[2].substring!))
|
s.insert(String(match.output[2].substring!))
|
||||||
}
|
}
|
||||||
for file in s {
|
for _file in s {
|
||||||
|
var file = _file
|
||||||
|
if _file.count > 68 {
|
||||||
|
file = _file.removingPercentEncoding ?? _file
|
||||||
|
if file.count > 36 {
|
||||||
|
file = String(file.prefix(31) + file.suffix(5))
|
||||||
|
}
|
||||||
|
}
|
||||||
let filepath = dir + "/Documents/" + file
|
let filepath = dir + "/Documents/" + file
|
||||||
var download = true
|
var download = true
|
||||||
if(FileManager.default.fileExists(atPath: filepath)) {
|
let check_file = { fpath -> Void in
|
||||||
let sz = try! FileManager.default.attributesOfItem(atPath: filepath)[FileAttributeKey.size] as! UInt64
|
if(FileManager.default.fileExists(atPath: fpath)) {
|
||||||
download = sz < 1024
|
let sz = try! FileManager.default.attributesOfItem(atPath: fpath)[FileAttributeKey.size] as! UInt64
|
||||||
|
download = sz < 40960 // (ignore files <40k)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (download)
|
|
||||||
{
|
check_file(filepath)
|
||||||
session.dataTask(with: URLRequest(url: URL(string: base + "/" + file)!)) {
|
check_file("\(dir)/Documents/\(_file)")
|
||||||
|
if (download) {
|
||||||
|
session.dataTask(with: URLRequest(url: URL(string: base + "/" + _file)!)) {
|
||||||
(data, response, error) -> Void in
|
(data, response, error) -> Void in
|
||||||
if (error == nil) {
|
if (error == nil) {
|
||||||
let fp = fopen(filepath, "wb")
|
let fp = fopen(filepath, "wb")
|
||||||
@@ -120,7 +142,6 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
}.resume()
|
}.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}catch{}
|
}catch{}
|
||||||
}
|
}
|
||||||
@@ -143,14 +164,15 @@ struct ContentView: View {
|
|||||||
let geo = self.geo
|
let geo = self.geo
|
||||||
asset.loadMetadata(for: .iTunesMetadata) {
|
asset.loadMetadata(for: .iTunesMetadata) {
|
||||||
items, b in
|
items, b in
|
||||||
|
if (items == nil) { return }
|
||||||
for i in items! {
|
for i in items! {
|
||||||
if(i.identifier == .iTunesMetadataCoverArt) {
|
if(i.identifier == .iTunesMetadataCoverArt) {
|
||||||
Task{
|
Task{
|
||||||
let imageData = try await i.load(.dataValue)
|
let imageData = try await i.load(.dataValue)
|
||||||
idstr.art = Image(uiImage: UIImage(data: imageData!)!)
|
idstr.art = Image(uiImage: UIImage(data: imageData!)!)
|
||||||
if (idstr.art != nil) {
|
/*if (idstr.art != nil) {
|
||||||
idstr.art!.resizable().scaledToFill().frame(width: geo.width, height: geo.height)
|
idstr.art!.resizable().scaledToFill().frame(width: geo.width, height: geo.height)
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import WatchKit
|
import WatchKit
|
||||||
import UserNotifications
|
|
||||||
|
|
||||||
@main
|
@main
|
||||||
struct MusicPlayer_Watch_AppApp: App {
|
struct MusicPlayer_Watch_AppApp: App {
|
||||||
|
|||||||
@@ -6,23 +6,113 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct PlaybackView: View {
|
struct PlaybackView: View {
|
||||||
|
|
||||||
var placeholder: Image? = nil
|
var placeholder: Image? = nil
|
||||||
|
var music : IDStr? = nil
|
||||||
|
var parent : ContentView? = nil
|
||||||
|
//@ObservedObject var timeout = Timeout(timeout: 5)
|
||||||
|
var title = ""
|
||||||
|
@State var playing = true
|
||||||
|
@State private var appearSelf = true
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
placeholder == nil ?
|
if parent != nil {
|
||||||
nil : placeholder!.resizable().scaledToFill()
|
GeometryReader { geo in
|
||||||
|
ZStack {
|
||||||
|
if(placeholder == nil) {
|
||||||
|
Image(systemName: "square")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFill()
|
||||||
|
.foregroundColor(.black)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
placeholder!.resizable().scaledToFill()
|
||||||
|
}
|
||||||
|
if (appearSelf)
|
||||||
|
{
|
||||||
|
NavigationView{
|
||||||
|
VStack{
|
||||||
|
HStack{
|
||||||
|
Button {
|
||||||
|
if ( parent!.player!.timeControlStatus == .playing ) {
|
||||||
|
parent!.player!.pause()
|
||||||
|
self.playing = false
|
||||||
|
} else {
|
||||||
|
parent!.player!.play()
|
||||||
|
self.playing = true
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
(
|
||||||
|
self.playing ?
|
||||||
|
Image(systemName: "stop") :
|
||||||
|
Image(systemName: "play")
|
||||||
|
)
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: geo.size.width/5.5)
|
||||||
|
}.background(Color(red: 0,green: 0,blue: 0,opacity: 0.2))
|
||||||
|
.frame(width: geo.size.width/2.5)
|
||||||
|
.cornerRadius(90, antialiased: true)
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.opacity(1)
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
Button {
|
||||||
|
let curr = parent!.player!.currentItem
|
||||||
|
parent!.player!.advanceToNextItem()
|
||||||
|
curr!.seek(to: .zero)
|
||||||
|
parent!.player!.play()
|
||||||
|
self.playing = true
|
||||||
|
} label : {
|
||||||
|
Image(systemName: "chevron.forward")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: geo.size.width/7, height: geo.size.height/7)
|
||||||
|
}.background(Color.clear)
|
||||||
|
.clipShape(Circle())
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.frame(width: geo.size.width/4, height: geo.size.height/4)
|
||||||
|
.padding(0)
|
||||||
|
.opacity(1)
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
}
|
||||||
|
}.onAppear(){
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: {
|
||||||
|
appearSelf = false
|
||||||
|
})
|
||||||
|
}.navigationTitle("\(self.title)")
|
||||||
|
}.opacity(0.65).navigationBarBackButtonHidden(false)
|
||||||
|
}
|
||||||
|
}.onTapGesture {
|
||||||
|
appearSelf = true
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 5, execute: {
|
||||||
|
appearSelf = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {}
|
init() { }
|
||||||
init(parent:ContentView, music: IDStr) {
|
init(parent:ContentView, music: IDStr? = nil) {
|
||||||
if music.art != nil {
|
if music != nil && music!.art != nil {
|
||||||
self.placeholder = music.art!
|
self.placeholder = music!.art!
|
||||||
|
self.music = music
|
||||||
|
self.parent = parent
|
||||||
|
self.playing = parent.player!.timeControlStatus == .playing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutating func update (music: IDStr) {
|
||||||
|
self.placeholder = music.art
|
||||||
|
self.music = music
|
||||||
|
self.title = music.s
|
||||||
|
self.playing = self.parent!.player!.timeControlStatus == .playing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PlaybackView_Previews: PreviewProvider {
|
struct PlaybackView_Previews: PreviewProvider {
|
||||||
|
|||||||
@@ -1,10 +1,21 @@
|
|||||||
# TODO
|
# Roadmap
|
||||||
|
|
||||||
- push to new view
|
|
||||||
- view with media control and background
|
|
||||||
- control menu
|
- control menu
|
||||||
- with option to delete song
|
- with option to delete song
|
||||||
- remove all songs
|
- remove all songs
|
||||||
- change data source
|
- change data source
|
||||||
- multiple datasources (smb, webdav, webserver)
|
- multiple datasources (smb, webdav, webserver(better regex))
|
||||||
|
- A download view
|
||||||
|
- That shows files to download (can select which ones to sync)
|
||||||
|
- Download progress bar, pause/cancel/resume
|
||||||
|
- Optimize Detailed Control View
|
||||||
|
- Use customized controls
|
||||||
|
- Performance tuning, reuse one view object!
|
||||||
|
- Volume and Seekbar
|
||||||
|
- UI Improvements
|
||||||
|
- Playback functions Shuffle, Stop after next song, Repeat
|
||||||
|
- Support for multiple formats (flac, possibly others?)
|
||||||
|
- Database for caching
|
||||||
|
- Playlist, creation/management
|
||||||
|
- Lyrics
|
||||||
|
- Tidy up code, credit page, test on real devices and submit to the appstore
|
||||||
|
|||||||
+3
@@ -8,6 +8,9 @@
|
|||||||
<PersistentString
|
<PersistentString
|
||||||
value = "match.output[0].substring!.cString(using: String.Encoding.utf8)">
|
value = "match.output[0].substring!.cString(using: String.Encoding.utf8)">
|
||||||
</PersistentString>
|
</PersistentString>
|
||||||
|
<PersistentString
|
||||||
|
value = "String(match.output[2].substring!)">
|
||||||
|
</PersistentString>
|
||||||
</PersistentStrings>
|
</PersistentStrings>
|
||||||
</ContextState>
|
</ContextState>
|
||||||
</ContextStates>
|
</ContextStates>
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1420"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "A95C117E29C531C100737618"
|
||||||
|
BuildableName = "MusicPlayer Watch App.app"
|
||||||
|
BlueprintName = "MusicPlayer Watch App"
|
||||||
|
ReferencedContainer = "container:MusicPlayer.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "A95C117829C531C100737618"
|
||||||
|
BuildableName = "MusicPlayer.app"
|
||||||
|
BlueprintName = "MusicPlayer"
|
||||||
|
ReferencedContainer = "container:MusicPlayer.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<Testables>
|
||||||
|
</Testables>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
selectedDebuggerIdentifier = ""
|
||||||
|
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
|
||||||
|
enableASanStackUseAfterReturn = "YES"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "A95C117E29C531C100737618"
|
||||||
|
BuildableName = "MusicPlayer Watch App.app"
|
||||||
|
BlueprintName = "MusicPlayer Watch App"
|
||||||
|
ReferencedContainer = "container:MusicPlayer.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "A95C117E29C531C100737618"
|
||||||
|
BuildableName = "MusicPlayer Watch App.app"
|
||||||
|
BlueprintName = "MusicPlayer Watch App"
|
||||||
|
ReferencedContainer = "container:MusicPlayer.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
+18
@@ -3,4 +3,22 @@
|
|||||||
uuid = "E40B0B2F-0F7C-4049-8384-96A6D265F1C3"
|
uuid = "E40B0B2F-0F7C-4049-8384-96A6D265F1C3"
|
||||||
type = "1"
|
type = "1"
|
||||||
version = "2.0">
|
version = "2.0">
|
||||||
|
<Breakpoints>
|
||||||
|
<BreakpointProxy
|
||||||
|
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||||
|
<BreakpointContent
|
||||||
|
uuid = "97AD3DB1-A549-419A-B226-A00CBDB192CF"
|
||||||
|
shouldBeEnabled = "Yes"
|
||||||
|
ignoreCount = "0"
|
||||||
|
continueAfterRunningActions = "No"
|
||||||
|
filePath = "MusicPlayer Watch App/ContentView.swift"
|
||||||
|
startingColumnNumber = "9223372036854775807"
|
||||||
|
endingColumnNumber = "9223372036854775807"
|
||||||
|
startingLineNumber = "135"
|
||||||
|
endingLineNumber = "135"
|
||||||
|
landmarkName = "init()"
|
||||||
|
landmarkType = "7">
|
||||||
|
</BreakpointContent>
|
||||||
|
</BreakpointProxy>
|
||||||
|
</Breakpoints>
|
||||||
</Bucket>
|
</Bucket>
|
||||||
|
|||||||
@@ -10,5 +10,18 @@
|
|||||||
<integer>0</integer>
|
<integer>0</integer>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>SuppressBuildableAutocreation</key>
|
||||||
|
<dict>
|
||||||
|
<key>A95C117829C531C100737618</key>
|
||||||
|
<dict>
|
||||||
|
<key>primary</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
<key>A95C117E29C531C100737618</key>
|
||||||
|
<dict>
|
||||||
|
<key>primary</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
Reference in New Issue
Block a user