master
Bill 2 years ago
parent 6c6af5a7e6
commit 42598d11f5

@ -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 body: some View { var parent : ContentView? = nil
placeholder == nil ? //@ObservedObject var timeout = Timeout(timeout: 5)
nil : placeholder!.resizable().scaledToFill() 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() { }
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

@ -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>

@ -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>

Loading…
Cancel
Save