|
|
|
@ -11,7 +11,7 @@ import CoreMotion |
|
|
|
struct SVideoPlayer: View, EditItemDelegate { |
|
|
|
// url: URL(string: "https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8")! |
|
|
|
var player = AVQueuePlayer(items: [AVPlayerItem]()) |
|
|
|
var playerLooper : AVPlayerLooper |
|
|
|
var playerLooper: AVPlayerLooper |
|
|
|
var completionHandler: ((Bool) -> Void)? |
|
|
|
let motionManager = CMMotionManager() |
|
|
|
|
|
|
|
@ -25,10 +25,12 @@ struct SVideoPlayer: View, EditItemDelegate { |
|
|
|
@State var confirmationShown = false |
|
|
|
@State var seekSmoothly = false |
|
|
|
@State var upsidedown = false |
|
|
|
@State var more = false |
|
|
|
@State var tilt = false |
|
|
|
@State var smoothTime = -1.0 |
|
|
|
@State var smoothSeekTime = -1.0 |
|
|
|
@State var timeCounter = 0 |
|
|
|
@State var lastScale = 1.25 |
|
|
|
@State var xoffs = 0.0 |
|
|
|
@State var rotazero = -1000.0 |
|
|
|
|
|
|
|
@ -38,12 +40,12 @@ struct SVideoPlayer: View, EditItemDelegate { |
|
|
|
.makeConnectable() |
|
|
|
.autoconnect() |
|
|
|
|
|
|
|
let steps : [Float] = [0.25, 0.5, 1.0, 2.0 ] |
|
|
|
let steps: [Float] = [0.25, 0.5, 1.0, 2.0] |
|
|
|
|
|
|
|
init(completionHandler: ((Bool) -> ())?, model: SVideoModel) { |
|
|
|
self.completionHandler = completionHandler |
|
|
|
self.model = model |
|
|
|
self.playerLooper = AVPlayerLooper(player: player , templateItem: model.currentPlayerItem()) |
|
|
|
self.playerLooper = AVPlayerLooper(player: player, templateItem: model.currentPlayerItem()) |
|
|
|
} |
|
|
|
|
|
|
|
func cleanup() { |
|
|
|
@ -68,62 +70,21 @@ struct SVideoPlayer: View, EditItemDelegate { |
|
|
|
Text("cancel") |
|
|
|
}) |
|
|
|
.buttonStyle(BorderlessButtonStyle()) |
|
|
|
Button(action: { |
|
|
|
model.loop.toggle() |
|
|
|
}, label: { |
|
|
|
Text("loop") |
|
|
|
}) |
|
|
|
.foregroundColor(model.loop ? Color.yellow : Color.blue).buttonStyle(BorderlessButtonStyle()) |
|
|
|
Button(action: { |
|
|
|
model.zoomed.toggle() |
|
|
|
}, label: { |
|
|
|
Text("zoom") |
|
|
|
}) |
|
|
|
.foregroundColor(model.zoomed ? Color.yellow : Color.blue).buttonStyle(BorderlessButtonStyle()) |
|
|
|
Button(action: { |
|
|
|
upsidedown.toggle() |
|
|
|
}, label: { |
|
|
|
Text("flip") |
|
|
|
}).foregroundColor(upsidedown ? Color.yellow : Color.blue).buttonStyle(BorderlessButtonStyle()) |
|
|
|
Button(action: { |
|
|
|
tilt.toggle() |
|
|
|
if tilt { |
|
|
|
motionManager.startDeviceMotionUpdates(using: .xMagneticNorthZVertical) |
|
|
|
} |
|
|
|
else { |
|
|
|
motionManager.stopDeviceMotionUpdates() |
|
|
|
} |
|
|
|
}, label: { |
|
|
|
Text("tilt") |
|
|
|
}).foregroundColor(tilt ? Color.yellow : Color.blue).buttonStyle(BorderlessButtonStyle()) |
|
|
|
|
|
|
|
KToggleButton(text: "more", binding: $more) |
|
|
|
|
|
|
|
Button(action: { |
|
|
|
model.favorite.toggle() |
|
|
|
}, label: { |
|
|
|
Image(systemName: "heart.fill") |
|
|
|
}) |
|
|
|
.foregroundColor(model.favorite ? Color.yellow : Color.blue).buttonStyle(BorderlessButtonStyle()) |
|
|
|
} |
|
|
|
|
|
|
|
Button(action: { |
|
|
|
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")") |
|
|
|
}).buttonStyle(BorderlessButtonStyle()) |
|
|
|
|
|
|
|
Button(action: { confirmationShown = true }, label: { |
|
|
|
Text(savetext) |
|
|
|
}).buttonStyle(BorderlessButtonStyle()) |
|
|
|
}) |
|
|
|
.buttonStyle(BorderlessButtonStyle()) |
|
|
|
.confirmationDialog("Save to folder", isPresented: $confirmationShown) { |
|
|
|
Button("1") { |
|
|
|
save(currentSnapshot: model.currentSnapshot, name: "1") |
|
|
|
@ -135,7 +96,8 @@ struct SVideoPlayer: View, EditItemDelegate { |
|
|
|
save(currentSnapshot: model.currentSnapshot, name: "3") |
|
|
|
} |
|
|
|
|
|
|
|
Button("cancel", role: .cancel) {} |
|
|
|
Button("cancel", role: .cancel) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -158,13 +120,17 @@ struct SVideoPlayer: View, EditItemDelegate { |
|
|
|
|
|
|
|
Button(action: { model.edit.toggle() }, label: { |
|
|
|
Text("edit") |
|
|
|
}).buttonStyle(BorderlessButtonStyle()); |
|
|
|
}) |
|
|
|
.buttonStyle(BorderlessButtonStyle()); |
|
|
|
if !model.baseItem.compilation { |
|
|
|
Button(action: doSnapshot, label: { |
|
|
|
Text("snap") |
|
|
|
}).buttonStyle(BorderlessButtonStyle()); |
|
|
|
}) |
|
|
|
.buttonStyle(BorderlessButtonStyle()); |
|
|
|
} |
|
|
|
} |
|
|
|
.frame(height: 50) |
|
|
|
} |
|
|
|
}.frame(height: 50) |
|
|
|
|
|
|
|
GeometryReader { geometry in |
|
|
|
VStack { |
|
|
|
@ -172,7 +138,28 @@ struct SVideoPlayer: View, EditItemDelegate { |
|
|
|
player: player) |
|
|
|
.scaleEffect(model.scale) |
|
|
|
.rotation3DEffect(.degrees(upsidedown ? 180 : 0), axis: (x: 1, y: 0, z: 0)) |
|
|
|
.offset(model.dragOffset).offset(x: xoffs, y: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 { |
|
|
|
model.scale = lastScale |
|
|
|
} else { |
|
|
|
lastScale = model.scale |
|
|
|
model.scale = 1 |
|
|
|
model.dragOffset = CGSize.zero |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.gesture( |
|
|
|
DragGesture() |
|
|
|
.onChanged { gesture in |
|
|
|
@ -203,15 +190,76 @@ struct SVideoPlayer: View, EditItemDelegate { |
|
|
|
if model.edit && model.videoDuration != Double.nan && model.videoDuration > 0.0 { |
|
|
|
v.overlay(EditItemView(item: model.currentSnapshot, len: model.videoDuration, delegate: self) |
|
|
|
.frame(width: 400, alignment: .top).offset(x: 0, y: -50), alignment: .topTrailing) |
|
|
|
} else { |
|
|
|
if more { |
|
|
|
v.overlay(VStack { |
|
|
|
|
|
|
|
Button(action: { |
|
|
|
tilt.toggle() |
|
|
|
if tilt { |
|
|
|
motionManager.startDeviceMotionUpdates(using: .xMagneticNorthZVertical) |
|
|
|
} else { |
|
|
|
motionManager.stopDeviceMotionUpdates() |
|
|
|
} |
|
|
|
}, label: { |
|
|
|
Text("tilt") |
|
|
|
}) |
|
|
|
.frame(height: 30) |
|
|
|
.foregroundColor(tilt ? Color.yellow : Color.blue).buttonStyle(BorderlessButtonStyle()) |
|
|
|
|
|
|
|
KToggleButton(text: "zoom", binding: $model.zoomed).frame(height: 30) |
|
|
|
KToggleButton(text: "loop", binding: $model.loop).frame(height: 30) |
|
|
|
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] |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
player.rate = model.speed |
|
|
|
}, label: { |
|
|
|
Text("\(model.speed, specifier: "%.2f")") |
|
|
|
}) |
|
|
|
.frame(height: 30) |
|
|
|
.buttonStyle(BorderlessButtonStyle()) |
|
|
|
Button(action: { |
|
|
|
if model.paused { |
|
|
|
player.play() |
|
|
|
} |
|
|
|
else { |
|
|
|
player.pause() |
|
|
|
} |
|
|
|
model.paused = !model.paused |
|
|
|
}, label: { |
|
|
|
Text("pause") |
|
|
|
}) |
|
|
|
.frame(height: 30) |
|
|
|
.foregroundColor(model.paused ? Color.yellow : Color.blue).buttonStyle(BorderlessButtonStyle()) |
|
|
|
|
|
|
|
} |
|
|
|
.frame(width: 50, alignment: .top).offset(x: 0, y: 0), alignment: .topLeading) |
|
|
|
} else { |
|
|
|
v |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
VideoPlayerControlsView(model: model, |
|
|
|
let controlsView = VideoPlayerControlsView(model: model, |
|
|
|
player: player) |
|
|
|
}.onAppear { |
|
|
|
if model.scale <= 1.0 { |
|
|
|
controlsView |
|
|
|
} else { |
|
|
|
controlsView.hidden() |
|
|
|
} |
|
|
|
} |
|
|
|
.onAppear { |
|
|
|
model.proxy = geometry |
|
|
|
print ("geo \(geometry.size.height)"); |
|
|
|
print("geo \(geometry.size.height)"); |
|
|
|
} |
|
|
|
.clipped() |
|
|
|
} |
|
|
|
@ -252,8 +300,8 @@ struct SVideoPlayer: View, EditItemDelegate { |
|
|
|
// print(xoffs) |
|
|
|
} |
|
|
|
// print(Int(rotation * 100.0) ) |
|
|
|
}} |
|
|
|
else { |
|
|
|
} |
|
|
|
} else { |
|
|
|
xoffs = 0.0 |
|
|
|
rotazero = -1000 |
|
|
|
} |
|
|
|
@ -288,6 +336,7 @@ struct SVideoPlayer: View, EditItemDelegate { |
|
|
|
.onReceive(orientationChanged) { _ in |
|
|
|
self.orientation = UIDevice.current.orientation |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
func doSnapshot() { |
|
|
|
@ -325,9 +374,8 @@ struct SVideoPlayer: View, EditItemDelegate { |
|
|
|
if timeCounter == 0 { |
|
|
|
if let i = model.allItems.index(where: { m in m === model.currentSnapshot }) { |
|
|
|
if i + 1 < model.allItems.count { |
|
|
|
gotoSnapshot(model.allItems[i+1]) |
|
|
|
} |
|
|
|
else { |
|
|
|
gotoSnapshot(model.allItems[i + 1]) |
|
|
|
} else { |
|
|
|
gotoSnapshot(model.allItems[0]) |
|
|
|
} |
|
|
|
timeCounter = 15 |
|
|
|
@ -352,8 +400,7 @@ struct SVideoPlayer: View, EditItemDelegate { |
|
|
|
} else { |
|
|
|
return true |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
} else { |
|
|
|
return true |
|
|
|
} |
|
|
|
} |
|
|
|
@ -370,9 +417,9 @@ struct SVideoPlayer: View, EditItemDelegate { |
|
|
|
return true |
|
|
|
} |
|
|
|
if dragged.width > dragWidth { |
|
|
|
seekTime(time + 8.0) |
|
|
|
seekTime(time + 5.0) |
|
|
|
} else if dragged.width < -dragWidth { |
|
|
|
seekTime(time - 10.0) |
|
|
|
seekTime(time - 8.0) |
|
|
|
} |
|
|
|
} else { |
|
|
|
if dragged.width > dragWidth { |
|
|
|
@ -408,7 +455,7 @@ struct SVideoPlayer: View, EditItemDelegate { |
|
|
|
item.cancelPendingSeeks() |
|
|
|
} |
|
|
|
|
|
|
|
player.seek(to: CMTime(seconds: time, preferredTimescale: CMTimeScale(600))){ _ in |
|
|
|
player.seek(to: CMTime(seconds: time, preferredTimescale: CMTimeScale(600))) { _ in |
|
|
|
// 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 |
|
|
|
@ -453,8 +500,7 @@ struct SVideoPlayer: View, EditItemDelegate { |
|
|
|
player.replaceCurrentItem(with: model.currentPlayerItem()) |
|
|
|
playerLooper |
|
|
|
seekTime(currentSnapshot.time) |
|
|
|
} |
|
|
|
else { |
|
|
|
} else { |
|
|
|
seekTime(currentSnapshot.time) |
|
|
|
} |
|
|
|
let height = player.currentItem!.presentationSize.height |
|
|
|
@ -467,8 +513,7 @@ struct SVideoPlayer: View, EditItemDelegate { |
|
|
|
model.dragOffset.width = currentSnapshot.offset.x |
|
|
|
model.dragOffset.height = currentSnapshot.offset.y |
|
|
|
// player.transformLayer() |
|
|
|
} |
|
|
|
else { |
|
|
|
} else { |
|
|
|
if model.zoomed && f > 0.0 { |
|
|
|
if f < 0.6 { |
|
|
|
f = f * CGFloat(LocalManager.sharedInstance.settings.scale) |
|
|
|
@ -516,7 +561,7 @@ struct SVideoPlayer: View, EditItemDelegate { |
|
|
|
if file.pathExtension != "mp4" { |
|
|
|
file = file.appendingPathExtension("mp4") |
|
|
|
} |
|
|
|
print (file) |
|
|
|
print(file) |
|
|
|
var dur = c.length |
|
|
|
if (dur < 0) { |
|
|
|
return |
|
|
|
@ -526,11 +571,10 @@ struct SVideoPlayer: View, EditItemDelegate { |
|
|
|
progress: { p in |
|
|
|
let percent = Int(p * 100) |
|
|
|
savetext = "\(percent)" |
|
|
|
}) |
|
|
|
{ url in |
|
|
|
}) { url in |
|
|
|
savetext = "saved" |
|
|
|
} |
|
|
|
var s : MediaModel; |
|
|
|
var s: MediaModel; |
|
|
|
if (c.type == ItemType.SNAPSHOT && c.parent != nil) { |
|
|
|
s = c.parent!.toMediaModel() |
|
|
|
do { |
|
|
|
@ -543,12 +587,10 @@ struct SVideoPlayer: View, EditItemDelegate { |
|
|
|
.appendingPathExtension("jpg") |
|
|
|
try local.write(to: tfile) |
|
|
|
} |
|
|
|
} |
|
|
|
catch { |
|
|
|
} catch { |
|
|
|
// ignore |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
} else { |
|
|
|
s = c.toMediaModel() |
|
|
|
} |
|
|
|
|
|
|
|
@ -571,7 +613,7 @@ struct SVideoPlayer: View, EditItemDelegate { |
|
|
|
|
|
|
|
struct SVideoPlayer_Previews: PreviewProvider { |
|
|
|
static var previews: some View { |
|
|
|
SVideoPlayer(completionHandler: {b in}, model: SVideoModel(allItems: [MediaItem](), |
|
|
|
SVideoPlayer(completionHandler: { b in }, model: SVideoModel(allItems: [MediaItem](), |
|
|
|
currentSnapshot: MediaItem(name: "extern", path: "", root: "", type: ItemType.FAVROOT), |
|
|
|
baseItem: MediaItem(name: "extern", path: "", root: "", type: ItemType.FAVROOT) |
|
|
|
)) |
|
|
|
|