Browse Source

Photos

master
marcoschmickler 4 years ago
parent
commit
f7c88a14d5
  1. 8
      kplayer.xcodeproj/project.pbxproj
  2. 8
      kplayer/core/DatabaseManager.swift
  3. 20
      kplayer/detail/DetailViewController+Show.swift
  4. 17
      kplayer/detail/DetailViewController.swift
  5. 11
      kplayer/detail/EditItemView.swift
  6. 15
      kplayer/photo/SPhotoModel.swift
  7. 46
      kplayer/photo/SPhotoView.swift
  8. 20
      kplayer/util/AsyncImage.swift
  9. 14
      kplayer/video/SVideoPlayer.swift

8
kplayer.xcodeproj/project.pbxproj

@ -12,6 +12,7 @@
1C7360C0F2A4F0214FE353BD /* FileHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7367ECBD369A2A0C94C499 /* FileHelper.swift */; }; 1C7360C0F2A4F0214FE353BD /* FileHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7367ECBD369A2A0C94C499 /* FileHelper.swift */; };
1C73613562EB375F53A0BD03 /* ServerDownloadDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736595533B56039C417E0D /* ServerDownloadDelegate.swift */; }; 1C73613562EB375F53A0BD03 /* ServerDownloadDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736595533B56039C417E0D /* ServerDownloadDelegate.swift */; };
1C7361B3AF46CEB30D3F4FA0 /* KSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736AE5021E3D985FE3402D /* KSettings.swift */; }; 1C7361B3AF46CEB30D3F4FA0 /* KSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736AE5021E3D985FE3402D /* KSettings.swift */; };
1C73620BC7F5A1F35FFFF9FC /* SPhotoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736AD9F0A39AD543FC1176 /* SPhotoView.swift */; };
1C73631EACF56BABD3B2BCFB /* LayoutTools.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736BC4450890C45F8FBC63 /* LayoutTools.swift */; }; 1C73631EACF56BABD3B2BCFB /* LayoutTools.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736BC4450890C45F8FBC63 /* LayoutTools.swift */; };
1C73633C00C18FDA2E9F0A2F /* KNetworkProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736DCD945ABAE984FF43EF /* KNetworkProtocol.swift */; }; 1C73633C00C18FDA2E9F0A2F /* KNetworkProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736DCD945ABAE984FF43EF /* KNetworkProtocol.swift */; };
1C73635138BBD2BB480A308F /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C736777456388CA571DA17B /* MediaPlayer.framework */; }; 1C73635138BBD2BB480A308F /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C736777456388CA571DA17B /* MediaPlayer.framework */; };
@ -50,6 +51,7 @@
1C736A622876405F3EE2D043 /* EditItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7366C09381DC0052B52B69 /* EditItemView.swift */; }; 1C736A622876405F3EE2D043 /* EditItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7366C09381DC0052B52B69 /* EditItemView.swift */; };
1C736A7B6221A1D50FB3904C /* ItemType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73631C96E6C860833052CA /* ItemType.swift */; }; 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 */; }; 1C736B4B0889BD35DC566124 /* nspersistentcontainer-defaults-swift.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7361F01841F546FA7AFD58 /* nspersistentcontainer-defaults-swift.swift */; };
1C736BEC4C4263EF6A89E9E3 /* SPhotoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736F83C6190A3A0D5BD1F0 /* SPhotoModel.swift */; };
1C736C00693A05747578DF87 /* grabber.js in Sources */ = {isa = PBXBuildFile; fileRef = 1C73619BBFA9295A11C9ACBA /* grabber.js */; }; 1C736C00693A05747578DF87 /* grabber.js in Sources */ = {isa = PBXBuildFile; fileRef = 1C73619BBFA9295A11C9ACBA /* grabber.js */; };
1C736C8DAD6C2FBB9A2EA625 /* SearchItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73654AB95A2D629833BEC5 /* SearchItemView.swift */; }; 1C736C8DAD6C2FBB9A2EA625 /* SearchItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73654AB95A2D629833BEC5 /* SearchItemView.swift */; };
1C736C9821DA743C2E3F3B07 /* kplayer.txt in Resources */ = {isa = PBXBuildFile; fileRef = 1C7360F9649E40B7C2EAB581 /* kplayer.txt */; }; 1C736C9821DA743C2E3F3B07 /* kplayer.txt in Resources */ = {isa = PBXBuildFile; fileRef = 1C7360F9649E40B7C2EAB581 /* kplayer.txt */; };
@ -134,6 +136,7 @@
1C7369EC16B19B32B515169E /* NetData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetData.swift; sourceTree = "<group>"; }; 1C7369EC16B19B32B515169E /* NetData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetData.swift; sourceTree = "<group>"; };
1C7369F53095B7A4D65679C2 /* DetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = "<group>"; }; 1C7369F53095B7A4D65679C2 /* DetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = "<group>"; };
1C736A6E8396EE306B1AD3A8 /* KSettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KSettingsView.swift; sourceTree = "<group>"; }; 1C736A6E8396EE306B1AD3A8 /* KSettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KSettingsView.swift; sourceTree = "<group>"; };
1C736AD9F0A39AD543FC1176 /* SPhotoView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SPhotoView.swift; sourceTree = "<group>"; };
1C736AE5021E3D985FE3402D /* KSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KSettings.swift; sourceTree = "<group>"; }; 1C736AE5021E3D985FE3402D /* KSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KSettings.swift; sourceTree = "<group>"; };
1C736B17A8E9FB352B90A903 /* DetailViewController+Show.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DetailViewController+Show.swift"; sourceTree = "<group>"; }; 1C736B17A8E9FB352B90A903 /* DetailViewController+Show.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DetailViewController+Show.swift"; sourceTree = "<group>"; };
1C736B41C6AC33F3FA592C63 /* MediaModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaModel.swift; sourceTree = "<group>"; }; 1C736B41C6AC33F3FA592C63 /* MediaModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaModel.swift; sourceTree = "<group>"; };
@ -150,6 +153,7 @@
1C736EA15A11AF7D57F85824 /* ThumbnailCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThumbnailCache.swift; sourceTree = "<group>"; }; 1C736EA15A11AF7D57F85824 /* ThumbnailCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThumbnailCache.swift; sourceTree = "<group>"; };
1C736EEC570C71AAC50F2E95 /* SVideoModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SVideoModel.swift; sourceTree = "<group>"; }; 1C736EEC570C71AAC50F2E95 /* SVideoModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SVideoModel.swift; sourceTree = "<group>"; };
1C736EF64DE56AD058A4F612 /* KBrowserView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KBrowserView.swift; sourceTree = "<group>"; }; 1C736EF64DE56AD058A4F612 /* KBrowserView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KBrowserView.swift; sourceTree = "<group>"; };
1C736F83C6190A3A0D5BD1F0 /* SPhotoModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SPhotoModel.swift; sourceTree = "<group>"; };
1C736F9338CE36708244D42A /* DataLoadOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataLoadOperation.swift; sourceTree = "<group>"; }; 1C736F9338CE36708244D42A /* DataLoadOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataLoadOperation.swift; sourceTree = "<group>"; };
6D522F61736592330F481B4F /* Pods-kplayer.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-kplayer.debug.xcconfig"; path = "Pods/Target Support Files/Pods-kplayer/Pods-kplayer.debug.xcconfig"; sourceTree = "<group>"; }; 6D522F61736592330F481B4F /* Pods-kplayer.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-kplayer.debug.xcconfig"; path = "Pods/Target Support Files/Pods-kplayer/Pods-kplayer.debug.xcconfig"; sourceTree = "<group>"; };
8B75159FFCD5A882E6F167FE /* Pods_kplayer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_kplayer.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 8B75159FFCD5A882E6F167FE /* Pods_kplayer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_kplayer.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@ -220,6 +224,8 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
1C73673DC671535E3A049F54 /* PhotoController.swift */, 1C73673DC671535E3A049F54 /* PhotoController.swift */,
1C736AD9F0A39AD543FC1176 /* SPhotoView.swift */,
1C736F83C6190A3A0D5BD1F0 /* SPhotoModel.swift */,
); );
path = photo; path = photo;
sourceTree = "<group>"; sourceTree = "<group>";
@ -622,6 +628,8 @@
1C736776CF759CA3DB136F33 /* KBrowserView.swift in Sources */, 1C736776CF759CA3DB136F33 /* KBrowserView.swift in Sources */,
1C7369C0A66A8F8CB0E54460 /* WebView.swift in Sources */, 1C7369C0A66A8F8CB0E54460 /* WebView.swift in Sources */,
1C736C00693A05747578DF87 /* grabber.js in Sources */, 1C736C00693A05747578DF87 /* grabber.js in Sources */,
1C73620BC7F5A1F35FFFF9FC /* SPhotoView.swift in Sources */,
1C736BEC4C4263EF6A89E9E3 /* SPhotoModel.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

8
kplayer/core/DatabaseManager.swift

@ -266,7 +266,12 @@ class DatabaseManager {
for s in snapshots { for s in snapshots {
let sitem = loadSnapshot(s: s) let sitem = loadSnapshot(s: s)
sitem.parent = tag sitem.parent = tag
tag.children.append(sitem)
if sitem.name == tag.name {
print(sitem.name)
}
else {
tag.children.append(sitem)
}
} }
res.append(tag) res.append(tag)
@ -300,6 +305,7 @@ class DatabaseManager {
c.offset.y = CGFloat(s.offy) c.offset.y = CGFloat(s.offy)
c.scale = s.scale c.scale = s.scale
c.rating = Int(s.rating) c.rating = Int(s.rating)
c.objectID = s.objectID
if s.thumb != nil { if s.thumb != nil {
c.thumbUrl = s.thumb c.thumbUrl = s.thumb

20
kplayer/detail/DetailViewController+Show.swift

@ -22,6 +22,26 @@ extension DetailViewController {
} }
func showPhotos(_ im: [MediaItem]) { func showPhotos(_ im: [MediaItem]) {
let model = SPhotoModel(allItems: im)
let view = SPhotoView(completionHandler: { saved in
self.collectionView.reloadData()
self.collectionView.collectionViewLayout.invalidateLayout()
self.dismiss(animated: true, completion: nil);
}, model: model)
let pc = UIHostingController(rootView: view)
pc.view.backgroundColor = .black
getWindow().rootViewController!.definesPresentationContext = true
pc.modalPresentationStyle = .overCurrentContext
getWindow().rootViewController!.present(pc, animated: true)
}
func showPhotos2(_ im: [MediaItem]) {
let pc = MediaPhotoController() let pc = MediaPhotoController()
pc.items = im pc.items = im

17
kplayer/detail/DetailViewController.swift

@ -240,14 +240,19 @@ class DetailViewController: UIViewController, UICollectionViewDelegateFlowLayout
let items = detail.children[indexPath!.section] let items = detail.children[indexPath!.section]
if (items.loaded) { if (items.loaded) {
if items.children.count == 0 {
} else {
if indexPath!.item >= items.children.count {
if (items.root == "/tags") {
//
}
else {
if items.children.count == 0 {
} else { } else {
let c = items.children.remove(at: indexPath!.item)
if indexPath!.item >= items.children.count {
} else {
let c = items.children.remove(at: indexPath!.item)
self.delegate!.deleteThumb(selectedItem: c)
self.collectionView.reloadData()
self.delegate!.deleteThumb(selectedItem: c)
self.collectionView.reloadData()
}
} }
} }
} }

11
kplayer/detail/EditItemView.swift

@ -11,6 +11,7 @@ protocol EditItemDelegate {
func setStart() func setStart()
func setEnd() func setEnd()
func cancelEdit() func cancelEdit()
func okEdit()
func seek(_ : Double) func seek(_ : Double)
} }
@ -72,22 +73,26 @@ struct EditItemView: View {
HStack { HStack {
Button(action: delegate.captureZoom, label: { Button(action: delegate.captureZoom, label: {
Text("Zoom") Text("Zoom")
}).padding(10).buttonStyle(BorderlessButtonStyle());
}).padding(5).buttonStyle(BorderlessButtonStyle());
Button(action: { Button(action: {
item.scale = 1.0 item.scale = 1.0
item.offset = CGPoint(x: 0,y: 0) item.offset = CGPoint(x: 0,y: 0)
item.objectWillChange.send() item.objectWillChange.send()
}, label: { }, label: {
Text("Reset") Text("Reset")
}).padding(10).buttonStyle(BorderlessButtonStyle());
}).padding(5).buttonStyle(BorderlessButtonStyle());
Stepper(value:$item.rating, in: -1...5){ Stepper(value:$item.rating, in: -1...5){
Text("*\(item.rating)").frame(width: 25) Text("*\(item.rating)").frame(width: 25)
} }
Button(action: { Button(action: {
delegate.okEdit()
}, label: {
Text("ok")
}).padding(5).buttonStyle(BorderlessButtonStyle());Button(action: {
delegate.cancelEdit() delegate.cancelEdit()
}, label: { }, label: {
Text("cancel") Text("cancel")
}).padding(10).buttonStyle(BorderlessButtonStyle());
}).padding(5).buttonStyle(BorderlessButtonStyle());
} }
FlexibleView( FlexibleView(
data: DatabaseManager.sharedInstance.allTags, data: DatabaseManager.sharedInstance.allTags,

15
kplayer/photo/SPhotoModel.swift

@ -0,0 +1,15 @@
//
// Created by Marco Schmickler on 22.06.22.
// Copyright (c) 2022 Marco Schmickler. All rights reserved.
//
import Foundation
class SPhotoModel : ObservableObject {
@Published var allItems : [MediaItem]
@Published var index = 0
init(allItems: [MediaItem]) {
self.allItems = allItems
}
}

46
kplayer/photo/SPhotoView.swift

@ -0,0 +1,46 @@
//
// Created by Marco Schmickler on 22.06.22.
// Copyright (c) 2022 Marco Schmickler. All rights reserved.
//
import Foundation
import SwiftUI
struct SPhotoView : View {
var completionHandler: ((Bool) -> Void)?
var model: SPhotoModel
var body: some View {
VStack {
HStack {
Group {
Button(action: {
completionHandler!(true)
}, label: {
Text("cancel")
})
.buttonStyle(BorderlessButtonStyle())
}
}.frame(height: 50)
AsyncImage(item: model.allItems[model.index], thumb: false, placeholder: { Text("Loading ...") },
image: { Image(uiImage: $0).resizable() })
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(model.allItems) { item in
Button(action: {
gotoSnapshot(item)
}) {
AsyncImage(item: item, placeholder: { Text("Loading ...") },
image: { Image(uiImage: $0).resizable() })
}
}.frame(height: 70)
}
}
}
}
func gotoSnapshot(_ item: MediaItem) {
}
}

20
kplayer/util/AsyncImage.swift

@ -1,18 +1,22 @@
import SwiftUI import SwiftUI
import Haneke import Haneke
import Alamofire
struct AsyncImage<Placeholder: View>: View { struct AsyncImage<Placeholder: View>: View {
@StateObject private var item: MediaItem @StateObject private var item: MediaItem
private let placeholder: Placeholder private let placeholder: Placeholder
private let image: (UIImage) -> Image private let image: (UIImage) -> Image
private let thumb: Bool
init( init(
item: MediaItem, item: MediaItem,
thumb: Bool = true,
@ViewBuilder placeholder: () -> Placeholder, @ViewBuilder placeholder: () -> Placeholder,
@ViewBuilder image: @escaping (UIImage) -> Image = Image.init(uiImage:) @ViewBuilder image: @escaping (UIImage) -> Image = Image.init(uiImage:)
) { ) {
self.placeholder = placeholder() self.placeholder = placeholder()
self.image = image self.image = image
self.thumb = thumb
_item = StateObject(wrappedValue: item) _item = StateObject(wrappedValue: item)
setImage(newItem: item) setImage(newItem: item)
@ -25,7 +29,10 @@ struct AsyncImage<Placeholder: View>: View {
private var content: some View { private var content: some View {
Group { Group {
if item.thumbImage != nil {
if item.image != nil {
image(item.image!)
}
else if item.thumbImage != nil {
image(item.thumbImage!) image(item.thumbImage!)
} else { } else {
placeholder placeholder
@ -52,5 +59,16 @@ struct AsyncImage<Placeholder: View>: View {
} }
} }
} }
if !thumb {
let URL = Foundation.URL(string: newItem.imageUrlAbsolute)!
Shared.imageCache.fetch(URL: URL).onSuccess {
i in
newItem.image = i
newItem.thumbImage = newItem.image!.scaleToSize(66.0, height: 44.0)
}
}
} }
} }

14
kplayer/video/SVideoPlayer.swift

@ -28,6 +28,7 @@ struct SVideoPlayer: View, EditItemDelegate {
@State var upsidedown = false @State var upsidedown = false
@State var more = false @State var more = false
@State var tilt = false @State var tilt = false
@State var slow = false
@State var smoothTime = -1.0 @State var smoothTime = -1.0
@State var smoothSeekTime = -1.0 @State var smoothSeekTime = -1.0
@State var timeCounter = 0 @State var timeCounter = 0
@ -197,7 +198,7 @@ struct SVideoPlayer: View, EditItemDelegate {
if model.edit && model.videoDuration != Double.nan && model.videoDuration > 0.0 { if model.edit && model.videoDuration != Double.nan && model.videoDuration > 0.0 {
v.overlay(EditItemView(item: model.currentSnapshot, len: model.videoDuration, delegate: self) v.overlay(EditItemView(item: model.currentSnapshot, len: model.videoDuration, delegate: self)
.frame(width: 400, alignment: .top).offset(x: 0, y: -50), alignment: .topTrailing)
.frame(width: 420, alignment: .top).offset(x: 0, y: -50), alignment: .topTrailing)
} else { } else {
if more { if more {
v.overlay(VStack { v.overlay(VStack {
@ -215,6 +216,7 @@ struct SVideoPlayer: View, EditItemDelegate {
.frame(height: 30) .frame(height: 30)
.foregroundColor(tilt ? Color.yellow : Color.blue).buttonStyle(BorderlessButtonStyle()) .foregroundColor(tilt ? Color.yellow : Color.blue).buttonStyle(BorderlessButtonStyle())
KToggleButton(text: "slow", binding: $slow).frame(height: 30)
KToggleButton(text: "zoom", binding: $model.zoomed).frame(height: 30) KToggleButton(text: "zoom", binding: $model.zoomed).frame(height: 30)
KToggleButton(text: "loop", binding: $model.loop).frame(height: 30) KToggleButton(text: "loop", binding: $model.loop).frame(height: 30)
// .fullScreenCover(isPresented: $model.loop) { // .fullScreenCover(isPresented: $model.loop) {
@ -487,8 +489,8 @@ struct SVideoPlayer: View, EditItemDelegate {
if let time = getCurrentTime() { if let time = getCurrentTime() {
let dragWidth = 20.0 let dragWidth = 20.0
if !model.seeking { if !model.seeking {
if (start.y < 130) {
if model.scale > 1.0 {
if (start.y < 130 || slow) {
if model.scale > 1.0 && !slow {
return true return true
} }
if dragged.width > dragWidth { if dragged.width > dragWidth {
@ -659,6 +661,12 @@ struct SVideoPlayer: View, EditItemDelegate {
model.edit = false model.edit = false
} }
func okEdit() {
DatabaseManager.sharedInstance.saveItemMetaData(model.currentSnapshot)
model.edit = false
model.dirty = false
}
func seek(_ v: Double) { func seek(_ v: Double) {
seekTimeSmoothly(v) seekTimeSmoothly(v)
} }

Loading…
Cancel
Save