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 {
|
||||
@ObservedObject var music = ListViewModel()
|
||||
@State var pushState = false
|
||||
@State var geo :CGSize = .zero
|
||||
@State var _curr_sel_music : IDStr = IDStr()
|
||||
let pbv : PlaybackViewProxy
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
NavigationView(){
|
||||
@@ -61,18 +69,21 @@ struct ContentView: View {
|
||||
m.m!.seek(to: .zero)
|
||||
}
|
||||
}
|
||||
self._curr_sel_music = m
|
||||
self.pbv.pbv.update(music: _curr_sel_music)
|
||||
pushState = true
|
||||
_curr_sel_music = m
|
||||
}).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()
|
||||
}
|
||||
}
|
||||
}
|
||||
Label("\(music.music.count) Files. ", systemImage: "heart.fill").background(.clear).labelStyle(.titleAndIcon).frame(width: geometry.size.width, alignment: .center)
|
||||
}
|
||||
}.onAppear {
|
||||
geo = geometry.size
|
||||
self.pbv.pbv.parent = self
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,7 +91,7 @@ struct ContentView: View {
|
||||
var player : AVQueuePlayer? = nil
|
||||
|
||||
init() {
|
||||
print("'sibal'");
|
||||
self.pbv = PlaybackViewProxy(v: PlaybackView())
|
||||
let base = "https://billsun.dev/webdav/music-test"
|
||||
let url = URL(string: base)
|
||||
let request: URLRequest = URLRequest(url: url!)
|
||||
@@ -89,27 +100,38 @@ struct ContentView: View {
|
||||
|
||||
session.dataTask(with: request, completionHandler:
|
||||
{ (data, response, error) -> Void in
|
||||
if (error == nil) { return }
|
||||
if (error != nil) { return }
|
||||
let reply = String(data: data!, encoding: String.Encoding.utf8)!
|
||||
|
||||
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)
|
||||
|
||||
var s = Set<String>()
|
||||
for match in matched {
|
||||
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
|
||||
var download = true
|
||||
if(FileManager.default.fileExists(atPath: filepath)) {
|
||||
let sz = try! FileManager.default.attributesOfItem(atPath: filepath)[FileAttributeKey.size] as! UInt64
|
||||
download = sz < 1024
|
||||
let check_file = { fpath -> Void in
|
||||
if(FileManager.default.fileExists(atPath: fpath)) {
|
||||
let sz = try! FileManager.default.attributesOfItem(atPath: fpath)[FileAttributeKey.size] as! UInt64
|
||||
download = sz < 40960 // (ignore files <40k)
|
||||
}
|
||||
}
|
||||
if (download)
|
||||
{
|
||||
session.dataTask(with: URLRequest(url: URL(string: base + "/" + file)!)) {
|
||||
|
||||
check_file(filepath)
|
||||
check_file("\(dir)/Documents/\(_file)")
|
||||
if (download) {
|
||||
session.dataTask(with: URLRequest(url: URL(string: base + "/" + _file)!)) {
|
||||
(data, response, error) -> Void in
|
||||
if (error == nil) {
|
||||
let fp = fopen(filepath, "wb")
|
||||
@@ -120,7 +142,6 @@ struct ContentView: View {
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
|
||||
}
|
||||
}catch{}
|
||||
}
|
||||
@@ -143,14 +164,15 @@ struct ContentView: View {
|
||||
let geo = self.geo
|
||||
asset.loadMetadata(for: .iTunesMetadata) {
|
||||
items, b in
|
||||
if (items == nil) { return }
|
||||
for i in items! {
|
||||
if(i.identifier == .iTunesMetadataCoverArt) {
|
||||
Task{
|
||||
let imageData = try await i.load(.dataValue)
|
||||
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)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
import SwiftUI
|
||||
import WatchKit
|
||||
import UserNotifications
|
||||
|
||||
@main
|
||||
struct MusicPlayer_Watch_AppApp: App {
|
||||
|
||||
@@ -6,23 +6,113 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
|
||||
|
||||
struct PlaybackView: View {
|
||||
|
||||
var placeholder: Image? = nil
|
||||
|
||||
var body: some View {
|
||||
placeholder == nil ?
|
||||
nil : placeholder!.resizable().scaledToFill()
|
||||
|
||||
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 {
|
||||
if parent != nil {
|
||||
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(parent:ContentView, music: IDStr) {
|
||||
if music.art != nil {
|
||||
self.placeholder = music.art!
|
||||
init() { }
|
||||
init(parent:ContentView, music: IDStr? = nil) {
|
||||
if music != nil && music!.art != nil {
|
||||
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 {
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
# TODO
|
||||
# Roadmap
|
||||
|
||||
- push to new view
|
||||
- view with media control and background
|
||||
- control menu
|
||||
- with option to delete song
|
||||
- remove all songs
|
||||
- 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
|
||||
value = "match.output[0].substring!.cString(using: String.Encoding.utf8)">
|
||||
</PersistentString>
|
||||
<PersistentString
|
||||
value = "String(match.output[2].substring!)">
|
||||
</PersistentString>
|
||||
</PersistentStrings>
|
||||
</ContextState>
|
||||
</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"
|
||||
type = "1"
|
||||
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>
|
||||
|
||||
@@ -10,5 +10,18 @@
|
||||
<integer>0</integer>
|
||||
</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>
|
||||
</plist>
|
||||
|
||||
Reference in New Issue
Block a user