diff --git a/kplayer.xcodeproj/project.pbxproj b/kplayer.xcodeproj/project.pbxproj index 466fd30..e01775f 100644 --- a/kplayer.xcodeproj/project.pbxproj +++ b/kplayer.xcodeproj/project.pbxproj @@ -16,7 +16,6 @@ 1C73633C00C18FDA2E9F0A2F /* KNetworkProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736DCD945ABAE984FF43EF /* KNetworkProtocol.swift */; }; 1C73635138BBD2BB480A308F /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C736777456388CA571DA17B /* MediaPlayer.framework */; }; 1C7363C2E744C2318879127D /* FlexibleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736D50A22FC4553165199D /* FlexibleView.swift */; }; - 1C7363D4C34EBBD5C7AAD0A8 /* scratch.txt in Resources */ = {isa = PBXBuildFile; fileRef = 1C7363E0DDA5854D55F8836E /* scratch.txt */; }; 1C73640D928DE56D35175D39 /* UploadOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736260E748CF136FF37EA7 /* UploadOperation.swift */; }; 1C73646F87B495A47D7943C7 /* NetData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7369EC16B19B32B515169E /* NetData.swift */; }; 1C736503B656C999E5E12081 /* NetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7365B06FA66294E99AC2D3 /* NetworkManager.swift */; }; @@ -48,6 +47,7 @@ 1C736A7B6221A1D50FB3904C /* ItemType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73631C96E6C860833052CA /* ItemType.swift */; }; 1C736B4B0889BD35DC566124 /* nspersistentcontainer-defaults-swift.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7361F01841F546FA7AFD58 /* nspersistentcontainer-defaults-swift.swift */; }; 1C736C8DAD6C2FBB9A2EA625 /* SearchItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73654AB95A2D629833BEC5 /* SearchItemView.swift */; }; + 1C736C9821DA743C2E3F3B07 /* kplayer.txt in Resources */ = {isa = PBXBuildFile; fileRef = 1C7360F9649E40B7C2EAB581 /* kplayer.txt */; }; 1C736D16E81BA1FB325200E0 /* HanekeFetchOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7360744ABACC3557D05760 /* HanekeFetchOperation.swift */; }; 1C736D24891597F2728230EE /* ImageLoadOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7360A94DBECA685ED8602F /* ImageLoadOperation.swift */; }; 1C736D24B49451141CD4B64D /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7369F53095B7A4D65679C2 /* DetailViewController.swift */; }; @@ -94,6 +94,7 @@ 1C7360744ABACC3557D05760 /* HanekeFetchOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HanekeFetchOperation.swift; sourceTree = ""; }; 1C7360A94DBECA685ED8602F /* ImageLoadOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageLoadOperation.swift; sourceTree = ""; }; 1C7360B6D0757D4FB6433E7B /* AsyncImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncImage.swift; sourceTree = ""; }; + 1C7360F9649E40B7C2EAB581 /* kplayer.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = kplayer.txt; sourceTree = ""; }; 1C73611D226B48C24DB37535 /* MasterViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterViewController.swift; sourceTree = ""; }; 1C73615FFA2AA98BD1C56CD4 /* links.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = links.html; sourceTree = ""; }; 1C7361F01841F546FA7AFD58 /* nspersistentcontainer-defaults-swift.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "nspersistentcontainer-defaults-swift.swift"; sourceTree = ""; }; @@ -104,7 +105,6 @@ 1C736260E748CF136FF37EA7 /* UploadOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UploadOperation.swift; sourceTree = ""; }; 1C7362DE1D6BE634D7C2ACBF /* KPersistentContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KPersistentContainer.swift; sourceTree = ""; }; 1C73631C96E6C860833052CA /* ItemType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemType.swift; sourceTree = ""; }; - 1C7363E0DDA5854D55F8836E /* scratch.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = scratch.txt; sourceTree = ""; }; 1C73645DBD6499A726D34973 /* links.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = links.html; sourceTree = ""; }; 1C73647019E6C2E822127BA3 /* DatabaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseManager.swift; sourceTree = ""; }; 1C7364709899FF62774B0199 /* VideoHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoHelper.swift; sourceTree = ""; }; @@ -133,7 +133,6 @@ 1C736B794396F2E50387B8F2 /* stringutil.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = stringutil.swift; sourceTree = ""; }; 1C736BC4450890C45F8FBC63 /* LayoutTools.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LayoutTools.swift; sourceTree = ""; }; 1C736C94157754DE1C808173 /* KSettingsModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KSettingsModel.swift; sourceTree = ""; }; - 1C736CF935C2A6AB916BE494 /* scratch.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = scratch.txt; sourceTree = ""; }; 1C736D50A22FC4553165199D /* FlexibleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlexibleView.swift; sourceTree = ""; }; 1C736D9BB5498E7E8F11C754 /* HeaderCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderCell.swift; sourceTree = ""; }; 1C736DBB6986A8B62963FBB3 /* HtmlParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HtmlParser.swift; sourceTree = ""; }; @@ -202,7 +201,6 @@ 1C73625012D50E457D18A785 /* kplayer.js */, 1C73615FFA2AA98BD1C56CD4 /* links.html */, 1C73645DBD6499A726D34973 /* links.html */, - 1C736CF935C2A6AB916BE494 /* scratch.txt */, ); path = server; sourceTree = ""; @@ -316,8 +314,8 @@ 1C736059262A57AADE6AB761 /* Kirschkeks-256x256.png */, 8052F5B3AAC2535E5C08A529 /* Pods */, 1C73685B4BBFDAFBF08C032C /* readme.md */, - 1C7363E0DDA5854D55F8836E /* scratch.txt */, 1C7369BED02028D8564E82D5 /* pathfinder.scpt */, + 1C7360F9649E40B7C2EAB581 /* kplayer.txt */, ); sourceTree = ""; }; @@ -470,7 +468,7 @@ 1C736A5FA5BA53B2597F2ED7 /* Kirschkeks-256x256.png in Resources */, 1C73696E4C0353053BF98031 /* links.html in Resources */, 1C736FAE5D3E5D3BA3C1FAE5 /* links.html in Resources */, - 1C7363D4C34EBBD5C7AAD0A8 /* scratch.txt in Resources */, + 1C736C9821DA743C2E3F3B07 /* kplayer.txt in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/kplayer/video/SVideoModel.swift b/kplayer/video/SVideoModel.swift index efaf0d3..808baf4 100644 --- a/kplayer/video/SVideoModel.swift +++ b/kplayer/video/SVideoModel.swift @@ -22,6 +22,7 @@ class SVideoModel : ObservableObject { @Published var paused = false @Published var edit = false + @Published var dirty = false @Published var loop = false @Published var zoomed = false @Published var favorite = false diff --git a/kplayer/video/SVideoPlayer.swift b/kplayer/video/SVideoPlayer.swift index 5eaf734..dc69a88 100644 --- a/kplayer/video/SVideoPlayer.swift +++ b/kplayer/video/SVideoPlayer.swift @@ -23,6 +23,7 @@ struct SVideoPlayer: View, EditItemDelegate { @State var savetext = "save" @State var confirmationShown = false + @State var dirtyShown = false @State var seekSmoothly = false @State var upsidedown = false @State var more = false @@ -30,6 +31,7 @@ struct SVideoPlayer: View, EditItemDelegate { @State var smoothTime = -1.0 @State var smoothSeekTime = -1.0 @State var timeCounter = 0 + @State var timeSlomoCounter = 0 @State var lastScale = 1.25 @State var xoffs = 0.0 @State var rotazero = -1000.0 @@ -60,21 +62,29 @@ struct SVideoPlayer: View, EditItemDelegate { HStack { Group { Button(action: { - model.baseItem.favorite = model.favorite - player.pause() - player.replaceCurrentItem(with: nil) - model.currentURL = nil - cleanup() - completionHandler!(model.edit) + if model.dirty { + dirtyShown = true + } else { + let withSave = model.edit + closePlayer(withSave: withSave) + } }, label: { Text("cancel") }) - .buttonStyle(BorderlessButtonStyle()) + .buttonStyle(BorderlessButtonStyle()).confirmationDialog("Save?", isPresented: $dirtyShown) { + Button("save") { + closePlayer(withSave: true) + } + Button("cancel", role: .cancel) { + closePlayer(withSave: false) + } + } KToggleButton(text: "more", binding: $more) Button(action: { model.favorite.toggle() + model.dirty = true }, label: { Image(systemName: "heart.fill") }) @@ -139,16 +149,6 @@ struct SVideoPlayer: View, EditItemDelegate { .scaleEffect(model.scale) .rotation3DEffect(.degrees(upsidedown ? 180 : 0), axis: (x: 1, y: 0, z: 0)) .offset(model.dragOffset).offset(x: xoffs, y: 0) - // .onTapGesture(count: 2) { - // print("Double tapped!") - // if model.paused { - // player.play() - // } - // else { - // player.pause() - // } - // model.paused = !model.paused - // } .onTapGesture(count: 2) { print("3 tapped!") if model.scale == 1 { @@ -159,7 +159,6 @@ struct SVideoPlayer: View, EditItemDelegate { model.dragOffset = CGSize.zero } } - .gesture( DragGesture() .onChanged { gesture in @@ -179,7 +178,6 @@ struct SVideoPlayer: View, EditItemDelegate { let delta = val / self.lastScaleValue self.lastScaleValue = val self.model.scale = self.model.scale * delta -//... anything else e.g. clamping the newScale } .onEnded { val in // without this the next gesture will be broken @@ -212,17 +210,23 @@ struct SVideoPlayer: View, EditItemDelegate { KToggleButton(text: "flip", binding: $upsidedown).frame(height: 30) Button(action: { - for s in steps { - if model.speed < s { - model.speed = s - break - } else { - if s == 2.0 { - model.speed = steps[0] + if model.speed == 1.0 && timeSlomoCounter == 0 { + model.speed = 0.5 + timeSlomoCounter = 200 + } else { + for s in steps { + if model.speed < s { + model.speed = s + break + } else { + if s == 2.0 { + model.speed = steps[0] + } } } } player.rate = model.speed + }, label: { Text("\(model.speed, specifier: "%.2f")") }) @@ -231,8 +235,7 @@ struct SVideoPlayer: View, EditItemDelegate { Button(action: { if model.paused { player.play() - } - else { + } else { player.pause() } model.paused = !model.paused @@ -242,6 +245,35 @@ struct SVideoPlayer: View, EditItemDelegate { .frame(height: 30) .foregroundColor(model.paused ? Color.yellow : Color.blue).buttonStyle(BorderlessButtonStyle()) + Button(action: {}) { + Text("start") + } + .frame(height: 30).buttonStyle(BorderlessButtonStyle()) + .simultaneousGesture( + LongPressGesture() + .onEnded { _ in + print("Loooong") + } + ) + .highPriorityGesture(TapGesture() + .onEnded { _ in + seekTimeSmoothly(model.currentSnapshot.time) + }) + + Button(action: {}) { + Text("end") + } + .frame(height: 30).buttonStyle(BorderlessButtonStyle()).foregroundColor(model.currentSnapshot.length > 0 ? Color.yellow : Color.blue) + .simultaneousGesture( + LongPressGesture() + .onEnded { _ in + setEnd() + } + ) + .highPriorityGesture(TapGesture() + .onEnded { _ in + seekTimeSmoothly(model.currentSnapshot.time + model.currentSnapshot.length) + }) } .frame(width: 50, alignment: .top).offset(x: 0, y: 0), alignment: .topLeading) } else { @@ -270,6 +302,9 @@ struct SVideoPlayer: View, EditItemDelegate { if timeCounter >= 1 { timeCounter -= 1 } + if timeSlomoCounter >= 1 { + timeSlomoCounter -= 1 + } if model.loop && model.currentSnapshot.length > 0 { if time.seconds > model.currentSnapshot.time + model.currentSnapshot.length { seekTime(model.currentSnapshot.time) @@ -339,6 +374,15 @@ struct SVideoPlayer: View, EditItemDelegate { } + private func closePlayer(withSave: Bool) { + model.baseItem.favorite = model.favorite + player.pause() + player.replaceCurrentItem(with: nil) + model.currentURL = nil + cleanup() + completionHandler!(withSave) + } + func doSnapshot() { let currentItem = player.currentItem! let asset = currentItem.asset @@ -349,6 +393,7 @@ struct SVideoPlayer: View, EditItemDelegate { let time = currentItem.currentTime() let cgImage = try imgGenerator.copyCGImage(at: time, actualTime: nil) let thumbnail = UIImage(cgImage: cgImage) + model.dirty = true showThumbnail(currentItem: model.baseItem, thumbnail: thumbnail, time: time) @@ -378,7 +423,7 @@ struct SVideoPlayer: View, EditItemDelegate { } else { gotoSnapshot(model.allItems[0]) } - timeCounter = 15 + timeCounter = 50 print("jump") } } @@ -392,13 +437,23 @@ struct SVideoPlayer: View, EditItemDelegate { } if (start.y < 130) { - let delta = dragged.width / 1000.0 + let delta = dragged.width / 500.0 // print(delta) seekTimeSmoothly(smoothTime + delta) return false } else { - return true + if model.scale != 1.0 { + return true + } + else { + let dragWidth = 20.0 + if dragged.width > dragWidth { + seekTime(time + 4.0) + } else if dragged.width < -dragWidth { + seekTime(time - 5.0) + } + } } } else { return true @@ -406,10 +461,6 @@ struct SVideoPlayer: View, EditItemDelegate { } if let time = getCurrentTime() { - // let sk = model.seeking - // model.seeking = false - - // print("Start \(start.y) Drag \(dragged.width)))") let dragWidth = 20.0 if !model.seeking { if (start.y < 130) { @@ -459,8 +510,10 @@ struct SVideoPlayer: View, EditItemDelegate { // player.seek(to: CMTime(seconds: time, preferredTimescale: CMTimeScale(10000)), // toleranceBefore: CMTime(seconds: 0.3, preferredTimescale: CMTimeScale(10000)), // toleranceAfter: CMTime(seconds: 1.0, preferredTimescale: CMTimeScale(10000))){ _ in - player.play() - player.rate = model.speed + if !model.paused { + player.play() + player.rate = model.speed + } self.model.seeking = false } @@ -527,11 +580,13 @@ struct SVideoPlayer: View, EditItemDelegate { func captureZoom() { model.currentSnapshot.scale = model.scale model.currentSnapshot.offset = CGPoint(x: model.dragOffset.width, y: model.dragOffset.height) + model.dirty = true } func setStart() { if let time = getCurrentTime() { model.currentSnapshot.time = time + model.dirty = true } } @@ -541,6 +596,7 @@ struct SVideoPlayer: View, EditItemDelegate { if time > snapTime { model.currentSnapshot.length = time - snapTime } + model.dirty = true } } diff --git a/kplayer/video/VideoPlayerView.swift b/kplayer/video/VideoPlayerView.swift index 6438dbf..a7eb603 100644 --- a/kplayer/video/VideoPlayerView.swift +++ b/kplayer/video/VideoPlayerView.swift @@ -136,7 +136,12 @@ struct VideoPlayerControlsView : View { .padding(.trailing, 30) } // Current video time - Text("\(Utility.formatSecondsToHMS(model.videoPos * model.videoDuration))").foregroundColor(Color.white) + let postime = model.videoPos * model.videoDuration + let postext = Utility.formatSecondsToHMS(postime) + + let tens = postime.isNaN ? 0 : Int((postime*10.0).truncatingRemainder(dividingBy: 10)) + + Text("\(postext):\(tens)").foregroundColor(Color.white) // Slider for seeking / showing video progress Slider(value: $model.videoPos, in: 0...1, onEditingChanged: sliderEditingChanged) // Video duration