Browse Source

BMPlayer removed

master
marcoschmickler 4 years ago
parent
commit
1e952247bb
  1. 52
      kplayer.xcodeproj/project.pbxproj
  2. 68
      kplayer/core/DatabaseManager.swift
  3. 2
      kplayer/core/MediaItem.swift
  4. 1
      kplayer/core/MediaModel.swift
  5. 4
      kplayer/detail/DetailViewController.swift
  6. 15
      kplayer/master/MasterViewController.swift
  7. 126
      kplayer/timeline/CenterLine.swift
  8. 451
      kplayer/timeline/FrameImagesView.swift
  9. 206
      kplayer/timeline/TimelineMeasure.swift
  10. 178
      kplayer/timeline/TimelineScroller.swift
  11. 460
      kplayer/timeline/TimelineView.swift
  12. 773
      kplayer/timeline/TrimView.swift
  13. 301
      kplayer/timeline/VideoTimelineView.swift
  14. 0
      kplayer/video/SVideoModel.swift
  15. 0
      kplayer/video/SVideoPlayer.swift
  16. 0
      kplayer/video/VideoPlayerView.swift

52
kplayer.xcodeproj/project.pbxproj

@ -12,11 +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 */; };
1C7361D3BA77C40275F89D4A /* TimelineMeasure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7368C7B946BC9E067D37E7 /* TimelineMeasure.swift */; };
1C7361F376DA11F17CD3250B /* TrimView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736ABA0E14A51ACAC84AB5 /* TrimView.swift */; };
1C7362AF931E0F228E5D2AED /* VideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7360B53C4C1496320953C2 /* VideoPlayerView.swift */; };
1C73631EACF56BABD3B2BCFB /* LayoutTools.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736BC4450890C45F8FBC63 /* LayoutTools.swift */; }; 1C73631EACF56BABD3B2BCFB /* LayoutTools.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736BC4450890C45F8FBC63 /* LayoutTools.swift */; };
1C73633AAF0D77F8AC3557B9 /* SVideoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7362603E8588B4D1A8C617 /* SVideoModel.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 */; };
1C7363D4C34EBBD5C7AAD0A8 /* scratch.txt in Resources */ = {isa = PBXBuildFile; fileRef = 1C7363E0DDA5854D55F8836E /* scratch.txt */; }; 1C7363D4C34EBBD5C7AAD0A8 /* scratch.txt in Resources */ = {isa = PBXBuildFile; fileRef = 1C7363E0DDA5854D55F8836E /* scratch.txt */; };
@ -25,11 +21,11 @@
1C736503B656C999E5E12081 /* NetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7365B06FA66294E99AC2D3 /* NetworkManager.swift */; }; 1C736503B656C999E5E12081 /* NetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7365B06FA66294E99AC2D3 /* NetworkManager.swift */; };
1C73654C9EA6D255CFC039C5 /* NetworkHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73620D01687FB4F1811C5C /* NetworkHelper.swift */; }; 1C73654C9EA6D255CFC039C5 /* NetworkHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73620D01687FB4F1811C5C /* NetworkHelper.swift */; };
1C7365885FAF292F2221ED44 /* PhotoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73673DC671535E3A049F54 /* PhotoController.swift */; }; 1C7365885FAF292F2221ED44 /* PhotoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73673DC671535E3A049F54 /* PhotoController.swift */; };
1C7365BEFFB35E8DE8F04CCF /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73661561AD069C92FE3B15 /* TimelineView.swift */; };
1C7365C59F72C29EA41C8717 /* SVideoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736EEC570C71AAC50F2E95 /* SVideoModel.swift */; };
1C7365CE76693E7772585CA8 /* SVideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73621E431C9BEC1440B936 /* SVideoPlayer.swift */; };
1C73666A07CF2416B1B8D3F0 /* KSettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736C94157754DE1C808173 /* KSettingsModel.swift */; }; 1C73666A07CF2416B1B8D3F0 /* KSettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736C94157754DE1C808173 /* KSettingsModel.swift */; };
1C7366A0CFD2B55BF8C3BAF0 /* NetworkDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7364F10BED5DA0F1C0423C /* NetworkDelegate.swift */; }; 1C7366A0CFD2B55BF8C3BAF0 /* NetworkDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7364F10BED5DA0F1C0423C /* NetworkDelegate.swift */; };
1C73671FC2CCCACAA2FFC153 /* ThumbnailCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736EA15A11AF7D57F85824 /* ThumbnailCache.swift */; }; 1C73671FC2CCCACAA2FFC153 /* ThumbnailCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736EA15A11AF7D57F85824 /* ThumbnailCache.swift */; };
1C73672CEAE1B9DA7805D4F2 /* CenterLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7361C26ED27AB54594317D /* CenterLine.swift */; };
1C73675C34BE0990D44570BE /* ItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736253AB7A95EA41B605B7 /* ItemModel.swift */; }; 1C73675C34BE0990D44570BE /* ItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736253AB7A95EA41B605B7 /* ItemModel.swift */; };
1C736771C503FB0D52AEB8F7 /* kplayer.js in Sources */ = {isa = PBXBuildFile; fileRef = 1C73625012D50E457D18A785 /* kplayer.js */; }; 1C736771C503FB0D52AEB8F7 /* kplayer.js in Sources */ = {isa = PBXBuildFile; fileRef = 1C73625012D50E457D18A785 /* kplayer.js */; };
1C7367AF39961D2BA72480ED /* DataLoadOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736F9338CE36708244D42A /* DataLoadOperation.swift */; }; 1C7367AF39961D2BA72480ED /* DataLoadOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736F9338CE36708244D42A /* DataLoadOperation.swift */; };
@ -51,17 +47,14 @@
1C736D24891597F2728230EE /* ImageLoadOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7360A94DBECA685ED8602F /* ImageLoadOperation.swift */; }; 1C736D24891597F2728230EE /* ImageLoadOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7360A94DBECA685ED8602F /* ImageLoadOperation.swift */; };
1C736D24B49451141CD4B64D /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7369F53095B7A4D65679C2 /* DetailViewController.swift */; }; 1C736D24B49451141CD4B64D /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7369F53095B7A4D65679C2 /* DetailViewController.swift */; };
1C736D89CF86841F4C98A1F7 /* KPersistentContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7362DE1D6BE634D7C2ACBF /* KPersistentContainer.swift */; }; 1C736D89CF86841F4C98A1F7 /* KPersistentContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7362DE1D6BE634D7C2ACBF /* KPersistentContainer.swift */; };
1C736DFA8544C773E3C22F10 /* VideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73675F8DDFA82DEADB542E /* VideoPlayerView.swift */; };
1C736DFD076D9CC30F0B9D58 /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736677D4EF2437358B2387 /* Utility.swift */; }; 1C736DFD076D9CC30F0B9D58 /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736677D4EF2437358B2387 /* Utility.swift */; };
1C736E21B246C0BE7E123FD3 /* MediaModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736B41C6AC33F3FA592C63 /* MediaModel.swift */; }; 1C736E21B246C0BE7E123FD3 /* MediaModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736B41C6AC33F3FA592C63 /* MediaModel.swift */; };
1C736EC45EE7DA5F7FCE63DA /* LocalManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73659CC9B523B957E58DC6 /* LocalManager.swift */; }; 1C736EC45EE7DA5F7FCE63DA /* LocalManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73659CC9B523B957E58DC6 /* LocalManager.swift */; };
1C736ECAE78F5C722423D7ED /* TimelineScroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736362946D7A8585B0D875 /* TimelineScroller.swift */; };
1C736F1C31D1EC23F59125F0 /* VideoTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736FC4180B42C3A357E9BF /* VideoTimelineView.swift */; };
1C736F3D082067948BA4DE84 /* FrameImagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736A5140D9B25BEC266B72 /* FrameImagesView.swift */; };
1C736F6A223D4ADB2E1BA733 /* ItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736069C214E9522BB1BD97 /* ItemCell.swift */; }; 1C736F6A223D4ADB2E1BA733 /* ItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736069C214E9522BB1BD97 /* ItemCell.swift */; };
1C736F7D29B76C7037CEF778 /* DatabaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73647019E6C2E822127BA3 /* DatabaseManager.swift */; }; 1C736F7D29B76C7037CEF778 /* DatabaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73647019E6C2E822127BA3 /* DatabaseManager.swift */; };
1C736FAE5D3E5D3BA3C1FAE5 /* links.html in Resources */ = {isa = PBXBuildFile; fileRef = 1C73645DBD6499A726D34973 /* links.html */; }; 1C736FAE5D3E5D3BA3C1FAE5 /* links.html in Resources */ = {isa = PBXBuildFile; fileRef = 1C73645DBD6499A726D34973 /* links.html */; };
1C736FB92B19FE17E4357C85 /* MediaItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73688DAB88F9360FB62A49 /* MediaItem.swift */; }; 1C736FB92B19FE17E4357C85 /* MediaItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73688DAB88F9360FB62A49 /* MediaItem.swift */; };
1C736FF8FF423F01F880F94D /* SVideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73624617102E0DEB001C25 /* SVideoPlayer.swift */; };
AA74B07A01F0E99E6DEC7D1B /* Pods_kplayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B75159FFCD5A882E6F167FE /* Pods_kplayer.framework */; }; AA74B07A01F0E99E6DEC7D1B /* Pods_kplayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B75159FFCD5A882E6F167FE /* Pods_kplayer.framework */; };
C91E05892795AC5C0003AB79 /* KTag+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C91E05832795AC5C0003AB79 /* KTag+CoreDataClass.swift */; }; C91E05892795AC5C0003AB79 /* KTag+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C91E05832795AC5C0003AB79 /* KTag+CoreDataClass.swift */; };
C91E058A2795AC5C0003AB79 /* KTag+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C91E05842795AC5C0003AB79 /* KTag+CoreDataProperties.swift */; }; C91E058A2795AC5C0003AB79 /* KTag+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C91E05842795AC5C0003AB79 /* KTag+CoreDataProperties.swift */; };
@ -93,21 +86,17 @@
1C736069C214E9522BB1BD97 /* ItemCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemCell.swift; sourceTree = "<group>"; }; 1C736069C214E9522BB1BD97 /* ItemCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemCell.swift; sourceTree = "<group>"; };
1C7360744ABACC3557D05760 /* HanekeFetchOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HanekeFetchOperation.swift; sourceTree = "<group>"; }; 1C7360744ABACC3557D05760 /* HanekeFetchOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HanekeFetchOperation.swift; sourceTree = "<group>"; };
1C7360A94DBECA685ED8602F /* ImageLoadOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageLoadOperation.swift; sourceTree = "<group>"; }; 1C7360A94DBECA685ED8602F /* ImageLoadOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageLoadOperation.swift; sourceTree = "<group>"; };
1C7360B53C4C1496320953C2 /* VideoPlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = VideoPlayerView.swift; path = svideo/VideoPlayerView.swift; sourceTree = "<group>"; };
1C7360B6D0757D4FB6433E7B /* AsyncImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncImage.swift; sourceTree = "<group>"; }; 1C7360B6D0757D4FB6433E7B /* AsyncImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncImage.swift; sourceTree = "<group>"; };
1C73611D226B48C24DB37535 /* MasterViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterViewController.swift; sourceTree = "<group>"; }; 1C73611D226B48C24DB37535 /* MasterViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterViewController.swift; sourceTree = "<group>"; };
1C73615FFA2AA98BD1C56CD4 /* links.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = links.html; sourceTree = "<group>"; }; 1C73615FFA2AA98BD1C56CD4 /* links.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = links.html; sourceTree = "<group>"; };
1C7361C26ED27AB54594317D /* CenterLine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CenterLine.swift; path = timeline/CenterLine.swift; sourceTree = "<group>"; };
1C7361F01841F546FA7AFD58 /* nspersistentcontainer-defaults-swift.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "nspersistentcontainer-defaults-swift.swift"; sourceTree = "<group>"; }; 1C7361F01841F546FA7AFD58 /* nspersistentcontainer-defaults-swift.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "nspersistentcontainer-defaults-swift.swift"; sourceTree = "<group>"; };
1C73620D01687FB4F1811C5C /* NetworkHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkHelper.swift; sourceTree = "<group>"; }; 1C73620D01687FB4F1811C5C /* NetworkHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkHelper.swift; sourceTree = "<group>"; };
1C73624617102E0DEB001C25 /* SVideoPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SVideoPlayer.swift; path = svideo/SVideoPlayer.swift; sourceTree = "<group>"; };
1C73621E431C9BEC1440B936 /* SVideoPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SVideoPlayer.swift; sourceTree = "<group>"; };
1C73625012D50E457D18A785 /* kplayer.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = kplayer.js; sourceTree = "<group>"; }; 1C73625012D50E457D18A785 /* kplayer.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = kplayer.js; sourceTree = "<group>"; };
1C736253AB7A95EA41B605B7 /* ItemModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemModel.swift; sourceTree = "<group>"; }; 1C736253AB7A95EA41B605B7 /* ItemModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemModel.swift; sourceTree = "<group>"; };
1C7362603E8588B4D1A8C617 /* SVideoModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SVideoModel.swift; path = svideo/SVideoModel.swift; sourceTree = "<group>"; };
1C736260E748CF136FF37EA7 /* UploadOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UploadOperation.swift; sourceTree = "<group>"; }; 1C736260E748CF136FF37EA7 /* UploadOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UploadOperation.swift; sourceTree = "<group>"; };
1C7362DE1D6BE634D7C2ACBF /* KPersistentContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KPersistentContainer.swift; sourceTree = "<group>"; }; 1C7362DE1D6BE634D7C2ACBF /* KPersistentContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KPersistentContainer.swift; sourceTree = "<group>"; };
1C73631C96E6C860833052CA /* ItemType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemType.swift; sourceTree = "<group>"; }; 1C73631C96E6C860833052CA /* ItemType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemType.swift; sourceTree = "<group>"; };
1C736362946D7A8585B0D875 /* TimelineScroller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TimelineScroller.swift; path = timeline/TimelineScroller.swift; sourceTree = "<group>"; };
1C7363E0DDA5854D55F8836E /* scratch.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = scratch.txt; sourceTree = "<group>"; }; 1C7363E0DDA5854D55F8836E /* scratch.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = scratch.txt; sourceTree = "<group>"; };
1C73645DBD6499A726D34973 /* links.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = links.html; sourceTree = "<group>"; }; 1C73645DBD6499A726D34973 /* links.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = links.html; sourceTree = "<group>"; };
1C73647019E6C2E822127BA3 /* DatabaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseManager.swift; sourceTree = "<group>"; }; 1C73647019E6C2E822127BA3 /* DatabaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseManager.swift; sourceTree = "<group>"; };
@ -117,20 +106,17 @@
1C736595533B56039C417E0D /* ServerDownloadDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerDownloadDelegate.swift; sourceTree = "<group>"; }; 1C736595533B56039C417E0D /* ServerDownloadDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerDownloadDelegate.swift; sourceTree = "<group>"; };
1C73659CC9B523B957E58DC6 /* LocalManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalManager.swift; sourceTree = "<group>"; }; 1C73659CC9B523B957E58DC6 /* LocalManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalManager.swift; sourceTree = "<group>"; };
1C7365B06FA66294E99AC2D3 /* NetworkManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkManager.swift; sourceTree = "<group>"; }; 1C7365B06FA66294E99AC2D3 /* NetworkManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkManager.swift; sourceTree = "<group>"; };
1C73661561AD069C92FE3B15 /* TimelineView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TimelineView.swift; path = timeline/TimelineView.swift; sourceTree = "<group>"; };
1C736677D4EF2437358B2387 /* Utility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utility.swift; sourceTree = "<group>"; }; 1C736677D4EF2437358B2387 /* Utility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utility.swift; sourceTree = "<group>"; };
1C7366C09381DC0052B52B69 /* EditItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditItemView.swift; sourceTree = "<group>"; }; 1C7366C09381DC0052B52B69 /* EditItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditItemView.swift; sourceTree = "<group>"; };
1C73673DC671535E3A049F54 /* PhotoController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoController.swift; sourceTree = "<group>"; }; 1C73673DC671535E3A049F54 /* PhotoController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoController.swift; sourceTree = "<group>"; };
1C73675F8DDFA82DEADB542E /* VideoPlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoPlayerView.swift; sourceTree = "<group>"; };
1C736777456388CA571DA17B /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = System/Library/Frameworks/MediaPlayer.framework; sourceTree = SDKROOT; }; 1C736777456388CA571DA17B /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = System/Library/Frameworks/MediaPlayer.framework; sourceTree = SDKROOT; };
1C7367ECBD369A2A0C94C499 /* FileHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileHelper.swift; sourceTree = "<group>"; }; 1C7367ECBD369A2A0C94C499 /* FileHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileHelper.swift; sourceTree = "<group>"; };
1C73685B4BBFDAFBF08C032C /* readme.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = readme.md; sourceTree = "<group>"; }; 1C73685B4BBFDAFBF08C032C /* readme.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = readme.md; sourceTree = "<group>"; };
1C73688DAB88F9360FB62A49 /* MediaItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaItem.swift; sourceTree = "<group>"; }; 1C73688DAB88F9360FB62A49 /* MediaItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaItem.swift; sourceTree = "<group>"; };
1C7368C7B946BC9E067D37E7 /* TimelineMeasure.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TimelineMeasure.swift; path = timeline/TimelineMeasure.swift; sourceTree = "<group>"; };
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>"; };
1C736A5140D9B25BEC266B72 /* FrameImagesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FrameImagesView.swift; path = timeline/FrameImagesView.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>"; };
1C736ABA0E14A51ACAC84AB5 /* TrimView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TrimView.swift; path = timeline/TrimView.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>"; };
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>"; };
1C736B794396F2E50387B8F2 /* stringutil.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = stringutil.swift; sourceTree = "<group>"; }; 1C736B794396F2E50387B8F2 /* stringutil.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = stringutil.swift; sourceTree = "<group>"; };
@ -142,8 +128,8 @@
1C736DCCE3AA9993E15F7652 /* UIImageExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIImageExtension.swift; sourceTree = "<group>"; }; 1C736DCCE3AA9993E15F7652 /* UIImageExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIImageExtension.swift; sourceTree = "<group>"; };
1C736DCD945ABAE984FF43EF /* KNetworkProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KNetworkProtocol.swift; sourceTree = "<group>"; }; 1C736DCD945ABAE984FF43EF /* KNetworkProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KNetworkProtocol.swift; sourceTree = "<group>"; };
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>"; };
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>"; };
1C736FC4180B42C3A357E9BF /* VideoTimelineView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = VideoTimelineView.swift; path = timeline/VideoTimelineView.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; };
C91E05832795AC5C0003AB79 /* KTag+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KTag+CoreDataClass.swift"; sourceTree = "<group>"; }; C91E05832795AC5C0003AB79 /* KTag+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KTag+CoreDataClass.swift"; sourceTree = "<group>"; };
@ -278,6 +264,9 @@
1C736F3946A38499113D351A /* video */ = { 1C736F3946A38499113D351A /* video */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
1C736EEC570C71AAC50F2E95 /* SVideoModel.swift */,
1C73621E431C9BEC1440B936 /* SVideoPlayer.swift */,
1C73675F8DDFA82DEADB542E /* VideoPlayerView.swift */,
); );
path = video; path = video;
sourceTree = "<group>"; sourceTree = "<group>";
@ -339,16 +328,6 @@
1C7363B608460DED4F522D1C /* photo */, 1C7363B608460DED4F522D1C /* photo */,
1C736F3946A38499113D351A /* video */, 1C736F3946A38499113D351A /* video */,
1C73633B55AC5378053BDCE2 /* server */, 1C73633B55AC5378053BDCE2 /* server */,
1C736FC4180B42C3A357E9BF /* VideoTimelineView.swift */,
1C7361C26ED27AB54594317D /* CenterLine.swift */,
1C736A5140D9B25BEC266B72 /* FrameImagesView.swift */,
1C7368C7B946BC9E067D37E7 /* TimelineMeasure.swift */,
1C736362946D7A8585B0D875 /* TimelineScroller.swift */,
1C73661561AD069C92FE3B15 /* TimelineView.swift */,
1C736ABA0E14A51ACAC84AB5 /* TrimView.swift */,
1C73624617102E0DEB001C25 /* SVideoPlayer.swift */,
1C7362603E8588B4D1A8C617 /* SVideoModel.swift */,
1C7360B53C4C1496320953C2 /* VideoPlayerView.swift */,
); );
path = kplayer; path = kplayer;
sourceTree = "<group>"; sourceTree = "<group>";
@ -580,28 +559,21 @@
1C7361B3AF46CEB30D3F4FA0 /* KSettings.swift in Sources */, 1C7361B3AF46CEB30D3F4FA0 /* KSettings.swift in Sources */,
1C73600CB93F16F4F28C116F /* KSettingsView.swift in Sources */, 1C73600CB93F16F4F28C116F /* KSettingsView.swift in Sources */,
1C73666A07CF2416B1B8D3F0 /* KSettingsModel.swift in Sources */, 1C73666A07CF2416B1B8D3F0 /* KSettingsModel.swift in Sources */,
1C736F1C31D1EC23F59125F0 /* VideoTimelineView.swift in Sources */,
1C73672CEAE1B9DA7805D4F2 /* CenterLine.swift in Sources */,
C91E058E2795AC5C0003AB79 /* KSnapshot+CoreDataProperties.swift in Sources */, C91E058E2795AC5C0003AB79 /* KSnapshot+CoreDataProperties.swift in Sources */,
1C736F3D082067948BA4DE84 /* FrameImagesView.swift in Sources */,
C91E058A2795AC5C0003AB79 /* KTag+CoreDataProperties.swift in Sources */, C91E058A2795AC5C0003AB79 /* KTag+CoreDataProperties.swift in Sources */,
1C7361D3BA77C40275F89D4A /* TimelineMeasure.swift in Sources */,
C91E058B2795AC5C0003AB79 /* KItem+CoreDataClass.swift in Sources */, C91E058B2795AC5C0003AB79 /* KItem+CoreDataClass.swift in Sources */,
1C736ECAE78F5C722423D7ED /* TimelineScroller.swift in Sources */,
1C7365BEFFB35E8DE8F04CCF /* TimelineView.swift in Sources */,
1C7361F376DA11F17CD3250B /* TrimView.swift in Sources */,
1C736A622876405F3EE2D043 /* EditItemView.swift in Sources */, 1C736A622876405F3EE2D043 /* EditItemView.swift in Sources */,
1C73613562EB375F53A0BD03 /* ServerDownloadDelegate.swift in Sources */, 1C73613562EB375F53A0BD03 /* ServerDownloadDelegate.swift in Sources */,
1C736EC45EE7DA5F7FCE63DA /* LocalManager.swift in Sources */, 1C736EC45EE7DA5F7FCE63DA /* LocalManager.swift in Sources */,
1C736FF8FF423F01F880F94D /* SVideoPlayer.swift in Sources */,
1C73633AAF0D77F8AC3557B9 /* SVideoModel.swift in Sources */,
1C7362AF931E0F228E5D2AED /* VideoPlayerView.swift in Sources */,
1C736DFD076D9CC30F0B9D58 /* Utility.swift in Sources */, 1C736DFD076D9CC30F0B9D58 /* Utility.swift in Sources */,
1C736998044A9A7D89411892 /* AsyncImage.swift in Sources */, 1C736998044A9A7D89411892 /* AsyncImage.swift in Sources */,
1C73633C00C18FDA2E9F0A2F /* KNetworkProtocol.swift in Sources */, 1C73633C00C18FDA2E9F0A2F /* KNetworkProtocol.swift in Sources */,
1C736B4B0889BD35DC566124 /* nspersistentcontainer-defaults-swift.swift in Sources */, 1C736B4B0889BD35DC566124 /* nspersistentcontainer-defaults-swift.swift in Sources */,
1C736D89CF86841F4C98A1F7 /* KPersistentContainer.swift in Sources */, 1C736D89CF86841F4C98A1F7 /* KPersistentContainer.swift in Sources */,
1C736F7D29B76C7037CEF778 /* DatabaseManager.swift in Sources */, 1C736F7D29B76C7037CEF778 /* DatabaseManager.swift in Sources */,
1C7365C59F72C29EA41C8717 /* SVideoModel.swift in Sources */,
1C7365CE76693E7772585CA8 /* SVideoPlayer.swift in Sources */,
1C736DFA8544C773E3C22F10 /* VideoPlayerView.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

68
kplayer/core/DatabaseManager.swift

@ -73,14 +73,17 @@ class DatabaseManager {
new.local = item.local new.local = item.local
new.favorite = item.favorite new.favorite = item.favorite
new.type = item.type.rawValue new.type = item.type.rawValue
save()
return new;
}
private func save() {
do { do {
try managedObjectContext.save() try managedObjectContext.save()
} catch { } catch {
print("Error") print("Error")
} }
return new;
} }
func saveItemMetaData(_ item: MediaItem) { func saveItemMetaData(_ item: MediaItem) {
@ -136,11 +139,7 @@ class DatabaseManager {
} }
} }
do {
try managedObjectContext.save()
} catch {
print("Error")
}
save()
} }
} }
@ -148,11 +147,7 @@ class DatabaseManager {
let tag = KTag(context: managedObjectContext) let tag = KTag(context: managedObjectContext)
tag.name = answer tag.name = answer
do {
try managedObjectContext.save()
} catch {
print("Error")
}
save()
} }
func loadTags(completionHandler: @escaping Weiter) -> Void { func loadTags(completionHandler: @escaping Weiter) -> Void {
@ -163,12 +158,26 @@ class DatabaseManager {
let results = try! managedObjectContext.fetch(fetchRequest) let results = try! managedObjectContext.fetch(fetchRequest)
for t in results { for t in results {
let tag = MediaItem(name: t.name!, path: t.name!, root: "tags", type: ItemType.DETAILS)
let tag = MediaItem(name: t.name!, path: t.name!, root: "tags", type: ItemType.VIDEO)
tag.loaded = true
let snapshots = t.tagged as! Set<KSnapshot> let snapshots = t.tagged as! Set<KSnapshot>
for s in snapshots { for s in snapshots {
let i = s.item! let i = s.item!
let sitem = MediaItem(name: i.name!, path: i.path!, root: i.root!, type: ItemType.VIDEO) let sitem = MediaItem(name: i.name!, path: i.path!, root: i.root!, type: ItemType.VIDEO)
let c = MediaItem(name: i.name!, path: i.path!, root: i.root!, type: ItemType.SNAPSHOT)
c.time = s.time
c.length = s.length
c.loop = s.loop
c.size.width = CGFloat(s.sizex)
c.size.height = CGFloat(s.sizey)
c.offset.x = CGFloat(s.offx)
c.offset.y = CGFloat(s.offy)
c.scale = s.scale
c.rating = Int(s.rating)
c.thumbUrl = s.thumb
sitem.children.append(c)
tag.children.append(sitem) tag.children.append(sitem)
} }
@ -182,4 +191,37 @@ class DatabaseManager {
completionHandler(res) completionHandler(res)
} }
func addTag(_ name: String, _ item: MediaItem) {
let kFetch = KTag.fetchRequest()
kFetch.predicate = NSPredicate(format: "name == %@", name)
let tags = try! managedObjectContext.fetch(kFetch)
if tags.isEmpty {
return
}
let tag = tags[0]
let fetchRequest = KItem.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "name == %@", item.name)
let results = try! managedObjectContext.fetch(fetchRequest)
for r in results {
if "\(item.root)\(item.path)" == "\(r.root!)\(r.path!)" {
let ksitems = r.snapshots as! Set<KSnapshot>
for snap in ksitems {
if snap.index == item.indexId {
snap.addToTags(tag)
print(item.name)
}
}
}
}
save()
}
} }

2
kplayer/core/MediaItem.swift

@ -98,6 +98,7 @@ class MediaItem: CustomDebugStringConvertible, ObservableObject, Identifiable {
self.favorite = model.favorite self.favorite = model.favorite
self.loaded = true self.loaded = true
self.local = true self.local = true
self.indexId = model.indexId
for m in model.children { for m in model.children {
let item = MediaItem(model: m) let item = MediaItem(model: m)
@ -128,6 +129,7 @@ class MediaItem: CustomDebugStringConvertible, ObservableObject, Identifiable {
model.offset = offset model.offset = offset
model.scale = scale model.scale = scale
model.favorite = favorite model.favorite = favorite
model.indexId = indexId
return model return model
} }

1
kplayer/core/MediaModel.swift

@ -21,6 +21,7 @@ public struct MediaModel : Codable {
var scale = 0.0 var scale = 0.0
var offset = CGPoint() var offset = CGPoint()
var size = CGSize() var size = CGSize()
var indexId = 0
var children: [MediaModel] var children: [MediaModel]
let type: ItemType let type: ItemType

4
kplayer/detail/DetailViewController.swift

@ -508,11 +508,11 @@ class DetailViewController: UIViewController, UICollectionViewDelegateFlowLayout
var selectedItem = detail.children[indexPath.section] var selectedItem = detail.children[indexPath.section]
if selectedItem.children.count > indexPath.item { if selectedItem.children.count > indexPath.item {
selectedItem = selectedItem.children[indexPath.count]
selectedItem = selectedItem.children[indexPath.item]
} }
if !selectedItem.local { if !selectedItem.local {
return []
// return []
} }
let itemProvider = NSItemProvider(object: selectedItem.toJSON() as NSString) let itemProvider = NSItemProvider(object: selectedItem.toJSON() as NSString)

15
kplayer/master/MasterViewController.swift

@ -308,11 +308,14 @@ class MasterViewController: UITableViewController, UISearchResultsUpdating, UITa
if model.items.count <= d.row { if model.items.count <= d.row {
return UITableViewDropProposal(operation: .forbidden) return UITableViewDropProposal(operation: .forbidden)
} }
if (model.items[d.row].path == "new") {
let element = model.items[d.row]
if (element.path == "new") {
return UITableViewDropProposal(operation: .forbidden) return UITableViewDropProposal(operation: .forbidden)
} }
if (!model.items[d.row].local) {
return UITableViewDropProposal(operation: .forbidden)
if (!element.local) {
if element.root != "/tags" {
return UITableViewDropProposal(operation: .forbidden)
}
} }
} }
else { else {
@ -352,6 +355,12 @@ class MasterViewController: UITableViewController, UISearchResultsUpdating, UITa
let m = MediaItem(model: items) let m = MediaItem(model: items)
m.local = true m.local = true
let at = m.playerURL! let at = m.playerURL!
if destinationItem.root == "/tags" {
DatabaseManager.sharedInstance.addTag(destinationItem.name, m)
return
}
m.path = destinationItem.path m.path = destinationItem.path
var to = m.playerURL! var to = m.playerURL!
print(destinationItem.name) print(destinationItem.name)

126
kplayer/timeline/CenterLine.swift

@ -1,126 +0,0 @@
//
// CenterLine.swift
// Examplay
//
// Created by Tomohiro Yamashita on 2020/03/09.
// Copyright © 2020 Tom. All rights reserved.
//
import UIKit
import AVFoundation
class CenterLine: UIView {
var mainView:VideoTimelineView!
let timeLabel = UILabel()
var parentView:TimelineView? = nil
var duration:Float64 = 0
var currentTime:Float64 = 0
let margin:CGFloat = 6
override init (frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = .clear
self.isUserInteractionEnabled = false
}
required init(coder aDecoder: NSCoder) {
fatalError("CenterLine init(coder:) has not been implemented")
}
func configure(parent:TimelineView) {
parentView = parent
self.setNeedsDisplay()
self.timeLabel.adjustsFontSizeToFitWidth = true
self.timeLabel.textAlignment = .center
self.timeLabel.text = String("00:00.00")
self.addSubview(timeLabel)
}
func update() {
self.setNeedsDisplay()
let textMargin = margin + 3
self.timeLabel.frame.size.width = self.bounds.size.width - textMargin * 2
self.timeLabel.frame.origin = CGPoint(x:textMargin,y:0)
self.timeLabel.textColor = UIColor(hue: 0.94, saturation:0.68, brightness:0.95, alpha: 0.94)
setTimeText()
timeLabel.font = UIFont(name:"HelveticaNeue-CondensedBold" ,size:timeLabel.frame.size.height * 0.9)
}
override func draw(_ rect: CGRect) {
gradient()
let context = UIGraphicsGetCurrentContext()!
context.saveGState()
context.setShadow(offset: CGSize(width: 0,height: 0), blur: 6, color: UIColor(hue: 0, saturation:0, brightness:0.0, alpha: 0.30).cgColor)
UIColor(hue: 0, saturation:0, brightness:1, alpha: 0.92).setFill()
let labelRect = CGRect(x: margin,y: 0.3,width: self.frame.size.width - margin * 2,height: timeLabel.frame.size.height + 1)
let rectPath = UIBezierPath(roundedRect:labelRect, cornerRadius:timeLabel.frame.size.height)
rectPath.fill()
context.restoreGState()
let path = UIBezierPath()
path.move(to: CGPoint(x: self.frame.size.width / 2 , y:timeLabel.frame.size.height))
path.addLine(to: CGPoint(x:self.frame.size.width / 2 , y:self.frame.size.height))
path.lineWidth = 1.4
UIColor(hue: 0, saturation:0, brightness:1.0, alpha: 0.7).setStroke()
path.stroke()
}
func gradient() {
let width = self.frame.size.width
let context = UIGraphicsGetCurrentContext()!
let startColor = UIColor(hue: 0, saturation:0, brightness:0.0, alpha: 0.06).cgColor
let endColor = UIColor.clear.cgColor
let colors = [startColor, endColor] as CFArray
let locations = [0, 1] as [CGFloat]
let space = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: space, colors: colors, locations: locations)!
context.drawLinearGradient(gradient, start: CGPoint(x:(width / 2) - 0.7, y:0), end: CGPoint(x: (width / 2) - 4, y: 0), options: [])
context.drawLinearGradient(gradient, start: CGPoint(x:(width / 2) + 0.7, y:0), end: CGPoint(x: (width / 2) + 4, y: 0), options: [])
}
var ignoreSendScrollToParent = false
func setScrollPoint(_ scrollPoint:CGFloat) {
currentTime = Float64(scrollPoint) * duration
setTimeText()
if !ignoreSendScrollToParent {
if let parent = parentView {
parent.moved(currentTime)
}
}
mainView!.currentTime = currentTime
ignoreSendScrollToParent = false
}
func setTimeText() {
let minute = Int(currentTime / 60)
let second = (currentTime - Float64(minute) * 60)
let milliSec = Int((second - Float64(Int(second))) * 100)
let text = String(format: "%02d:%02d.%02d", minute, (Int(second)), milliSec)
timeLabel.text = text
}
}

451
kplayer/timeline/FrameImagesView.swift

@ -1,451 +0,0 @@
//
// FrameImagesView.swift
// Examplay
//
// Created by Tomohiro Yamashita on 2020/03/01.
// Copyright © 2020 Tom. All rights reserved.
//
import Foundation
import UIKit
import AVFoundation
class FrameImage: UIImageView {
var tolerance:Float64? = nil
}
// MARK: - FrameImagesView
class FrameImagesView: UIScrollView {
var mainView:VideoTimelineView!
var frameImagesArray:[FrameImage] = []
var thumbnailFrameSize:CGSize = CGSize(width: 640,height: 480)
let preferredTimescale:Int32 = 100
var timeTolerance = CMTimeMakeWithSeconds(10 , preferredTimescale:100)
var maxWidth:CGFloat = 0
var minWidth:CGFloat = 0
var parentScroller:TimelineScroller? = nil
override init (frame: CGRect) {
super.init(frame: frame)
self.isScrollEnabled = false
self.isUserInteractionEnabled = false
self.backgroundColor = UIColor(hue: 0, saturation:0, brightness:0.0, alpha: 0.02)
}
required init(coder aDecoder: NSCoder) {
fatalError("FrameImagesView init(coder:) has not been implemented")
}
func reset() {
discardAllFrameImages()
cancelImageGenerator()
prepareFrameViews()
layout()
requestVisible(depth:0, wide:2, direction:0)
}
//MARK: - timer for animation
var animationTimer = Timer()
var animating = false
func startAnimation() {
animating = true
displayFrames()
animationTimer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(self.animate(_:)), userInfo: nil, repeats: true)
RunLoop.main.add(animationTimer, forMode:RunLoop.Mode.common)
}
func stopAnimation() {
animationTimer.invalidate()
animating = false
if let parent = parentScroller {
frame.origin = CGPoint(x: parent.frame.size.width / 2, y: parent.measureHeight)
parent.addSubview(self)
}
displayFrames()
}
@objc func animate(_ timer:Timer) {
if animating == false {
return
}
displayFrames()
}
//MARK: - layout
var uponFrames = Set<Int>()
var belowFrames = Set<Int>()
var deepFrames = Set<Int>()
var hiddenFrames = Set<Int>()
func layout() {
setThumnailFrameSize()
let coordinated = coordinateFrames()
uponFrames = coordinated.upon
belowFrames = coordinated.below
hiddenFrames = coordinated.hidden
deepFrames = coordinated.deep
displayFrames()
}
func displayFrames() {
var baseView:UIView = self
var offset:CGPoint = CGPoint.zero
if let parent = parentScroller {
let visibleHalf = parent.frame.size.width / 2
if animating {
if let layer = parent.layer.presentation() {
offset.x = visibleHalf - layer.bounds.origin.x
offset.y = parent.frame.origin.y + parent.measureHeight
frame.origin = offset
mainView!.timelineView.viewForAnimate.addSubview(self)
}
}
}
let visibleSide = indexOfVisibleSide()
func locate(_ index:Int, visible:Bool) {
if frameImagesArray.count > index && index >= 0 {
let frameImg = frameImagesArray[index]
if index >= visibleSide.left && index <= visibleSide.right {
let position = positionWithIndex(index)
frameImg.frame = CGRect(x: position, y:0, width: thumbnailFrameSize.width, height: thumbnailFrameSize.height)
if visible {
self.addSubview(frameImg)
} else {
frameImg.removeFromSuperview()
}
} else {
frameImg.removeFromSuperview()
}
}
}
for element in belowFrames {//do addSubview under the upon
locate(element, visible:true)
}
for element in uponFrames {
locate(element, visible:true)
}
for element in hiddenFrames {// includes deep
locate(element, visible:false)
}
}
func positionWithIndex(_ index:Int) -> CGFloat {
let position = frame.size.width * (CGFloat(index) / CGFloat(thumbnailCountFloat()))
return position
}
func coordinateFrames() -> (upon:Set<Int>, below:Set<Int>, hidden:Set<Int>, deep:Set<Int>) {
let keyDivision = Int(pow(2,Double(Int(log2(maxWidth * 2 / (self.frame.size.width + 1))))))
if keyDivision <= 0 {
return (Set<Int>(), Set<Int>(), Set<Int>(), Set<Int>())
}
let keyCount = ((frameImagesArray.count - 1) / keyDivision) + 1
var visibleIndexes = Set<Int>()
var uponElements = Set<Int>()
for index in 0 ... (keyCount - 1) {
let uponIndex = index * keyDivision
uponElements.insert(uponIndex)
visibleIndexes.insert(uponIndex)
}
var belowElements = Set<Int>()
let belowDivision = keyDivision / 2
if belowDivision >= 1 {
for index in 0 ..< (keyCount - 1) {
let belowIndex = (index * keyDivision) + (keyDivision / 2)
belowElements.insert(belowIndex)
visibleIndexes.insert(belowIndex)
}
}
var deepElements = Set<Int>()
let deepDivision = belowDivision / 2
if deepDivision >= 1 {
if let max = visibleIndexes.max() {
for index in visibleIndexes {
if max > index {
deepElements.insert(index + deepDivision)
}
}
}
}
var hiddenElements = Set<Int>()
for index in 0 ..< frameImagesArray.count {
if !visibleIndexes.contains(index) {
let hiddenIndex = index
hiddenElements.insert(hiddenIndex)
}
}
return (uponElements,belowElements,hiddenElements, deepElements)
}
func setThumnailFrameSize() {
if let asset = mainView!.asset {
var frameSize = CGSize(width: 640,height: 480)
let tracks:Array = asset.tracks(withMediaType:AVMediaType.video)
if tracks.count > 0 {
let track = tracks[0]
frameSize = track.naturalSize.applying(track.preferredTransform)
frameSize.width = abs(frameSize.width)
frameSize.height = abs(frameSize.height)
}
thumbnailFrameSize = mainView!.resizeHeightKeepRatio(frameSize, height:self.frame.size.height)
}
}
func assetDuration() -> Float64? {
if let asset = mainView!.asset {
return CMTimeGetSeconds(asset.duration)
}
return nil
}
func prepareFrameViews() {
frameImagesArray = []
for _ in 0 ... Int(thumbnailCountFloat()) {
let view = FrameImage()
view.alpha = 0
frameImagesArray += [view]
}
}
func thumbnailCountFloat() -> CGFloat {
return maxWidth / thumbnailFrameSize.width
}
func indexWithTime(_ time:Float64) -> Int? {
if let assetDuration = assetDuration() {
let value = time / (assetDuration / Float64(thumbnailCountFloat()))
var intValue = Int(value)
if value - Float64(intValue) >= 0.5 {
intValue += 1
}
return intValue
}
return nil
}
func timeWithIndex(_ index:Int) -> Float64 {
if let assetDuration = assetDuration() {
return assetDuration * ((Float64(index)) / Float64(thumbnailCountFloat()))
}
return 0
}
//MARK: - frame images
func cancelImageGenerator() {
if let asset = mainView!.asset {
let assetImgGenerate : AVAssetImageGenerator = AVAssetImageGenerator(asset: asset)
assetImgGenerate.cancelAllCGImageGeneration()
}
}
func discardAllFrameImages() {
for index in 0 ..< frameImagesArray.count {
let view = frameImagesArray[index]
UIView.animate(withDuration: 0.5,delay:Double(0.0),options:UIView.AnimationOptions.curveEaseOut, animations: { () -> Void in
view.alpha = 0
},completion: { finished in
view.image = nil
view.removeFromSuperview()
view.tolerance = nil
})
}
frameImagesArray = []
}
func requestAll(){
var timesArray = [NSValue]()
for index in 0 ..< frameImagesArray.count {
timesArray += [NSValue(time:CMTimeMakeWithSeconds(timeWithIndex(index) , preferredTimescale:preferredTimescale))]
}
requestImageGeneration(timesArray:timesArray)
}
var requesting = Set<Int>()
func requestVisible(depth:Int, wide:Float, direction:Float) {
var timesArray = [NSValue]()
func request(_ index:Int) {
if self.requesting.count > 0 && self.requesting.contains(index) {
return
}
if frameImagesArray.count > index {
let imageView = frameImagesArray[index]
var needsUpdate = false
if let tolerance = imageView.tolerance {
if tolerance > CMTimeGetSeconds(timeTolerance) * 1.2 {
needsUpdate = true
}
}
if imageView.image == nil || needsUpdate {
timesArray += [NSValue(time:CMTimeMakeWithSeconds(timeWithIndex(index) , preferredTimescale:preferredTimescale))]
self.requesting.insert(index)
}
}
}
let visibleSide = indexOfVisibleSide()
let width = visibleSide.right - visibleSide.left
var additionLeft = Int(Float(width) * wide)
var additionRight = additionLeft
if direction > 0 {
additionLeft = Int(Float(-width) * direction)
} else if direction < 0 {
additionRight = Int(Float(width) * direction)
}
for index in uponFrames {
if index >= visibleSide.left - additionLeft && index <= visibleSide.right + additionRight {
request(index)
}
}
let belowAddLeft = Int(Float(additionLeft) * 0.5) - Int(Float(width) * 0.7)
let belowAddRight = Int(Float(additionRight) * 0.5) - Int(Float(width) * 0.7)
if depth > 0 {
for index in belowFrames {
if index >= visibleSide.left - belowAddLeft && index <= visibleSide.right + belowAddRight {
request(index)
}
}
}
let deepAddLeft = Int(Float(additionLeft) * 0.2) - Int(Float(width) * 0.4)
let deepAddRight = Int(Float(additionRight) * 0.2) - Int(Float(width) * 0.4)
if depth > 1 {
for index in deepFrames {
if index >= visibleSide.left - deepAddLeft && index <= visibleSide.right + deepAddRight {
request(index)
}
}
}
if timesArray.count > 0 {
requestImageGeneration(timesArray:timesArray)
}
}
func indexOfVisibleSide() -> (left:Int, right:Int) {
func indexWithPosition(_ position:CGFloat) -> Int{
let index = position * CGFloat(thumbnailCountFloat()) / frame.size.width
var indexInt = Int(index)
if index - CGFloat(indexInt) > 0.5 {
indexInt += 1
}
return indexInt
}
var visibleLeft = indexWithPosition(-(parentScroller!.frame.width / 2) + parentScroller!.contentOffset.x - thumbnailFrameSize.width) - 1
var visibleRight = indexWithPosition(parentScroller!.contentOffset.x + (parentScroller!.frame.size.width * 0.5) + thumbnailFrameSize.width)
if visibleLeft < 0 {
visibleLeft = 0
}
let max = frameImagesArray.count - 1
if visibleRight > max {
visibleRight = max
}
return (visibleLeft, visibleRight)
}
func updateTolerance() {
if mainView!.asset == nil {
return
}
let thumbDuration = Float64(thumbnailFrameSize.width / self.frame.size.width) * mainView!.duration * 2
timeTolerance = CMTimeMakeWithSeconds(thumbDuration , preferredTimescale:100)
}
func requestImageGeneration(timesArray:[NSValue]) {
if let asset = mainView!.asset {
let assetImgGenerate : AVAssetImageGenerator = AVAssetImageGenerator(asset: asset)
assetImgGenerate.appliesPreferredTrackTransform = true
let maxsize = CGSize(width: thumbnailFrameSize.width * 1.5,height: thumbnailFrameSize.height * 1.5)
assetImgGenerate.maximumSize = maxsize
assetImgGenerate.requestedTimeToleranceAfter = timeTolerance
assetImgGenerate.requestedTimeToleranceBefore = timeTolerance
assetImgGenerate.generateCGImagesAsynchronously(forTimes: timesArray,
completionHandler:
{ time,resultImage,actualTime,result,error in
let timeValue = CMTimeGetSeconds(time)
if let image = resultImage {
DispatchQueue.main.async {
self.setFrameImage(image:UIImage(cgImage:image), time:timeValue)
}
}
if let index = self.indexWithTime(timeValue) {
self.requesting.remove(index)
}
})
}
}
func setFrameImage(image:UIImage, time:Float64) {
if let index = indexWithTime(time) {
if frameImagesArray.count > index && index >= 0 {
let imageView = frameImagesArray[index]
imageView.image = image
imageView.tolerance = CMTimeGetSeconds(timeTolerance)
UIView.animate(withDuration: 0.2,delay:Double(0),options:UIView.AnimationOptions.curveEaseOut, animations: { () -> Void in
imageView.alpha = 1
},completion: { finished in
})
imageView.alpha = 1
imageView.backgroundColor = .red
}
}
}
}

206
kplayer/timeline/TimelineMeasure.swift

@ -1,206 +0,0 @@
//
// TimelineMeasure.swift
// Examplay
//
// Created by Tomohiro Yamashita on 2020/03/08.
// Copyright © 2020 Tom. All rights reserved.
//
import UIKit
class TimelineMeasure: UIView {
var unitSize:CGFloat = 100
var frameImagesView:FrameImagesView? = nil
var parentScroller:TimelineScroller? = nil
var parentView:TimelineView? = nil
var stringColor:UIColor = UIColor(hue: 0.0, saturation:0.0, brightness:0.35, alpha: 1)
var animating = false
override init (frame: CGRect) {
super.init(frame: frame)
self.isMultipleTouchEnabled = true
self.isUserInteractionEnabled = true
}
required init(coder aDecoder: NSCoder) {
fatalError("TimelineMeasure init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
if frameImagesView == nil {
return
}
let max = frameImagesView!.maxWidth
var width = frameImagesView!.frame.size.width
if animating {
if let layer = frameImagesView!.layer.presentation() {
width = layer.frame.size.width
}
}
if width == 0 {
return
}
var unit = (width / max) * unitSize
let unitWidth = CGFloat(80)
var division = 0
while (unit <= unitWidth) {
unit *= 2
division += 1
if division > 1000 {
break
}
}
let unitLength = pow(2,Double(division + 1)) / 2
func index(_ position:CGFloat) -> Int {
return Int(position / unit)
}
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
let attributes = [NSAttributedString.Key.font: UIFont(name: "HelveticaNeue", size: self.frame.size.height * 0.7)!, NSAttributedString.Key.paragraphStyle: paragraphStyle, NSAttributedString.Key.foregroundColor: stringColor]
var string = ""
var visibleRect = rect
if let parent = parentScroller {
if animating {
if let layer = parent.layer.presentation() {
let offset = layer.bounds.origin
visibleRect.origin = offset
visibleRect.size = parent.frame.size
} else {
visibleRect = parent.visibleRect()
}
} else {
visibleRect = parent.visibleRect()
}
}
let startIndex = index(visibleRect.origin.x) - 10 * (division + 1)
let endIndex = index(visibleRect.origin.x + visibleRect.size.width)
for index in startIndex ... endIndex {
if index < 0 {
continue
}
let position = CGFloat(index) * unit - visibleRect.origin.x + (visibleRect.size.width / 2)
if division == 0 {
let path = UIBezierPath()
path.move(to: CGPoint(x: position + (unit / 2 ) - (unitWidth / 4) , y:(self.frame.size.height / 2)))
path.addLine(to: CGPoint(x: position + (unit / 2) + (unitWidth / 4), y:(self.frame.size.height / 2)))
path.lineWidth = 1
stringColor.setStroke()
path.stroke()
} else {
for i in 1 ... 3 {
let point = position + ((unit / 4 ) * CGFloat(i))
let r:CGFloat = 0.8
let pointRect = CGRect(x: point - r, y: (self.frame.size.height / 2) - r, width: r * 2,height: r * 2)
let path = UIBezierPath(ovalIn:pointRect)
stringColor.setFill()
path.fill()
}
}
let length = unitLength * Double(index)
let minute = Int(length / 60)
let second = Int(length - Double(minute * 60))
string = String(format: "%02d:%02d", minute, (Int(second)))
string.draw(with: CGRect(x: position - (unitWidth / 2), y:0, width: unitWidth, height: self.frame.size.height), options: .usesLineFragmentOrigin, attributes: attributes, context: nil)
}
}
//MARK: - timer for animation
var animationTimer = Timer()
func startAnimation() {
animationTimer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(self.animate(_:)), userInfo: nil, repeats: true)
RunLoop.main.add(animationTimer, forMode:RunLoop.Mode.common)
animating = true
}
func stopAnimation() {
animating = false
self.setNeedsDisplay()
animationTimer.invalidate()
}
@objc func animate(_ timer:Timer) {
if animating == false {
return
}
self.setNeedsDisplay()
}
//MARK: - Touch Events
var allTouches = [UITouch]()
override open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
for touch in touches {
if !allTouches.contains(touch) {
allTouches += [touch]
}
if !parentView!.allTouches.contains(touch) {
parentView!.allTouches += [touch]
}
}
}
override open func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
{
if parentView!.allTouches.count == 2 {
if parentView!.pinching {
parentView!.updatePinch()
} else {
parentView!.startPinch()
}
}
}
override open func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)
{
for touch in touches {
if let index = allTouches.firstIndex(of:touch) {
allTouches.remove(at: index)
}
if let index = parentView!.allTouches.firstIndex(of:touch) {
parentView!.allTouches.remove(at: index)
}
}
if parentView!.pinching && parentView!.allTouches.count < 2 {
parentView!.endPinch()
}
}
override open func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?)
{
for touch in touches {
if let index = allTouches.firstIndex(of:touch) {
allTouches.remove(at: index)
}
if let index = parentView!.allTouches.firstIndex(of:touch) {
parentView!.allTouches.remove(at: index)
}
}
if parentView!.pinching {
parentView!.endPinch()
}
}
}

178
kplayer/timeline/TimelineScroller.swift

@ -1,178 +0,0 @@
//
// TimelineScroller.swift
// Examplay
//
// Created by Tomohiro Yamashita on 2020/03/01.
// Copyright © 2020 Tom. All rights reserved.
//
import UIKit
class TimelineScroller: UIScrollView {
var parentView:TimelineView? = nil
let frameImagesView = FrameImagesView()
let measure = TimelineMeasure()
let trimView = TrimView()
override init (frame: CGRect) {
super.init(frame: frame)
self.isScrollEnabled = true
self.isDirectionalLockEnabled = true
self.showsHorizontalScrollIndicator = false
self.showsVerticalScrollIndicator = false
self.bounces = false
self.decelerationRate = .fast
self.isMultipleTouchEnabled = true
self.delaysContentTouches = false
self.frameImagesView.parentScroller = self
self.addSubview(frameImagesView)
self.measure.parentScroller = self
self.measure.frameImagesView = self.frameImagesView
self.measure.backgroundColor = .clear
}
required init(coder aDecoder: NSCoder) {
fatalError("TimelineScroller init(coder:) has not been implemented")
}
func configure(parent:TimelineView) {
parentView = parent
trimView.configure(parent, scroller:self)
}
func reset() {
frameImagesView.reset()
}
var ignoreScrollViewDidScroll:Bool = false
func setContentWidth(_ width:CGFloat) {
setContentWidth(width, setOrigin:true)
}
func setContentWidth(_ width: CGFloat, setOrigin:Bool) {
ignoreScrollViewDidScroll = true
self.contentSize = CGSize(width:width + self.frame.size.width, height:self.frame.size.height)
frameImagesView.frame.size.width = width
//measure.frame.size.width = width
if setOrigin {
let halfVisibleWidth = self.frame.size.width / 2
//frameImagesView.frame.size.height = self.frame.size.height
frameImagesView.frame.origin.x = halfVisibleWidth
}
//measure.frame.origin.x = halfVisibleWidth
measure.setNeedsDisplay()
frameImagesView.displayFrames()
}
var measureHeight:CGFloat = 5
func coordinate() {
let measureMin:CGFloat = 10
let wholeHeight = self.frame.size.height
measureHeight = wholeHeight * 0.2
if measureHeight < measureMin {
measureHeight = measureMin
}
frameImagesView.frame.size.height = wholeHeight - measureHeight
if frameImagesView.animating == false {
frameImagesView.frame.origin = CGPoint(x: self.frame.size.width / 2,y: measureHeight)
}
measure.frame.size.height = measureHeight
//frameImagesView.layout()
trimView.frame = self.frame
}
func visibleRect() -> CGRect {
var visibleRect = frame
visibleRect.origin = contentOffset
if contentSize.width < frame.size.width {
visibleRect.size.width = contentSize.width
}
if contentSize.height < frame.size.height {
visibleRect.size.height = contentSize.height
}
if zoomScale != 1 {
let theScale = 1.0 / zoomScale;
visibleRect.origin.x *= theScale;
visibleRect.origin.y *= theScale;
visibleRect.size.width *= theScale;
visibleRect.size.height *= theScale;
}
return visibleRect
}
//MARK: - scroll
func setScrollPoint(_ scrollPoint:CGFloat) {
let offset = (scrollPoint * frameImagesView.frame.size.width) + (self.frame.size.width / 2)
self.contentOffset.x = offset - (self.frame.size.width / 2)
}
//MARK: - Touch Events
var allTouches = [UITouch]()
override open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
for touch in touches {
if !allTouches.contains(touch) {
allTouches += [touch]
}
if !parentView!.allTouches.contains(touch) {
parentView!.allTouches += [touch]
}
}
}
override open func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
{
if parentView!.allTouches.count == 2 {
if parentView!.pinching {
parentView!.updatePinch()
} else {
parentView!.startPinch()
}
}
}
override open func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)
{
for touch in touches {
if let index = allTouches.firstIndex(of:touch) {
allTouches.remove(at: index)
}
if let index = parentView!.allTouches.firstIndex(of:touch) {
parentView!.allTouches.remove(at: index)
}
}
if parentView!.pinching && parentView!.allTouches.count < 2 {
parentView!.endPinch()
}
}
override open func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?)
{
for touch in touches {
if let index = allTouches.firstIndex(of:touch) {
allTouches.remove(at: index)
}
if let index = parentView!.allTouches.firstIndex(of:touch) {
parentView!.allTouches.remove(at: index)
}
}
if parentView!.pinching {
parentView!.endPinch()
}
}
}

460
kplayer/timeline/TimelineView.swift

@ -1,460 +0,0 @@
//
// TimelineView.swift
// Examplay
//
// Created by Tomohiro Yamashita on 2020/02/18.
// Copyright © 2020 Tom. All rights reserved.
//
import Foundation
import UIKit
import AVFoundation
class TimelineView: UIView, UIScrollViewDelegate {
var mainView:VideoTimelineView? = nil
let scroller = TimelineScroller()
let centerLine = CenterLine()
let viewForAnimate = UIScrollView()
let durationPerHeight:Float64 = 0.35
var animating = false
override init (frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor(hue: 0, saturation:0, brightness:0.0, alpha: 0.05)
viewForAnimate.frame.origin = CGPoint.zero
viewForAnimate.isScrollEnabled = false
viewForAnimate.isUserInteractionEnabled = false
self.addSubview(viewForAnimate)
self.addSubview(scroller)
scroller.delegate = self
self.addSubview(scroller.measure)
scroller.configure(parent: self)
centerLine.configure(parent:self)
coordinate()
self.addSubview(scroller.trimView)
self.addSubview(centerLine)
scroller.measure.parentView = self
}
required init(coder aDecoder: NSCoder) {
fatalError("TimelineView init(coder:) has not been implemented")
}
//MARK: - coordinate
func coordinate() {
if mainView == nil {
return
}
frame = mainView!.bounds
viewForAnimate.frame.size = self.frame.size
scroller.frame = self.bounds
scroller.frameImagesView.frame.size.height = scroller.frame.size.height
scroller.measure.frame = scroller.frame
scroller.measure.frame.size.height = 20
scroller.coordinate()
centerLine.timeLabel.frame.size.height = scroller.measure.frame.size.height - 2
let centerLineWidth:CGFloat = scroller.measure.frame.size.height * 5
centerLine.frame = CGRect(x: (self.frame.size.width - centerLineWidth) / 2,y: 0,width: centerLineWidth,height: self.frame.size.height)
centerLine.update()
guard let view = (mainView) else { return }
guard let _ = (view.asset) else { return }
if scroller.frameImagesView.frame.size.width <= 0 {
return
}
let previousThumbSize = scroller.frameImagesView.thumbnailFrameSize
scroller.frameImagesView.setThumnailFrameSize()
let thumbSize = scroller.frameImagesView.thumbnailFrameSize
let unit = (thumbSize.height / CGFloat(durationPerHeight))
scroller.measure.unitSize = unit
let contentMaxWidth = (unit * CGFloat(centerLine.duration))
scroller.frameImagesView.maxWidth = contentMaxWidth
let defineMin = scroller.frame.size.width * 0.8
var contentMinWidth:CGFloat
if scroller.frameImagesView.maxWidth <= defineMin {
contentMinWidth = scroller.frameImagesView.maxWidth
} else {
contentMinWidth = snapWidth(defineMin, max:scroller.frameImagesView.maxWidth)
}
scroller.frameImagesView.minWidth = contentMinWidth
var currentWidth = snapWidth((scroller.frameImagesView.thumbnailFrameSize.width / previousThumbSize.width) * scroller.frameImagesView.frame.size.width, max:scroller.frameImagesView.maxWidth)
if currentWidth < scroller.frameImagesView.minWidth {
currentWidth = scroller.frameImagesView.minWidth
} else if currentWidth > scroller.frameImagesView.maxWidth {
currentWidth = scroller.frameImagesView.maxWidth
}
scroller.setContentWidth(currentWidth)
scroller.reset()
scroller.trimView.layout()
if view.currentTime <= view.duration {
setCurrentTime(view.currentTime, force:false)
}
}
func snapWidth(_ width:CGFloat, max:CGFloat) -> CGFloat {
let n = log2((2 * max) / width)
var intN = CGFloat(Int(n))
if n - intN >= 0.5 {
intN += 1
}
let result = (2 * max) / (pow(2,intN))
return result
}
func scrollPoint() -> CGFloat {
return scroller.contentOffset.x / scroller.frameImagesView.frame.size.width
}
//MARK: new movie set
func newMovieSet() {
coordinate()
if let asset = mainView!.asset{
scroller.frameImagesView.setThumnailFrameSize()
let duration = asset.duration
let durationFloat = CMTimeGetSeconds(duration)
centerLine.duration = durationFloat
let detailThumbSize = scroller.frameImagesView.thumbnailFrameSize
let unit = (detailThumbSize.height / CGFloat(durationPerHeight))
scroller.measure.unitSize = unit
let contentMaxWidth = (unit * CGFloat(centerLine.duration))
scroller.frameImagesView.maxWidth = contentMaxWidth
let defineMin = scroller.frame.size.width * 0.8
var contentMinWidth:CGFloat
if scroller.frameImagesView.maxWidth <= defineMin {
contentMinWidth = scroller.frameImagesView.maxWidth
} else {
contentMinWidth = snapWidth(defineMin, max:scroller.frameImagesView.maxWidth)
}
scroller.frameImagesView.minWidth = contentMinWidth
scroller.setContentWidth(scroller.frameImagesView.minWidth)
scroller.reset()
scroller.trimView.reset(duration:durationFloat)
}
}
//MARK: - currentTime
func setCurrentTime(_ currentTime:Float64, force:Bool) {
if inAction() && force == false {
return
}
if mainView!.asset == nil {
return
}
var scrollPoint:CGFloat = 0
scrollPoint = CGFloat(currentTime / mainView!.duration)
centerLine.ignoreSendScrollToParent = true
centerLine.setScrollPoint(scrollPoint)
scroller.ignoreScrollViewDidScroll = true
scroller.setScrollPoint(scrollPoint)
scroller.frameImagesView.requestVisible(depth:0, wide:0, direction:0)
scroller.frameImagesView.displayFrames()
scroller.measure.setNeedsDisplay()
}
func moved(_ currentTime:Float64) {
mainView!.timelineIsMoved(currentTime, scrub:true)
}
//MARK: - TrimViews
func setTrimmerStatus(enabled:Bool) {
if enabled {
scroller.trimView.alpha = 1
scroller.trimView.startKnob.isUserInteractionEnabled = true
scroller.trimView.alpha = 1
scroller.trimView.endKnob.isUserInteractionEnabled = true
} else {
scroller.trimView.alpha = 0.5
scroller.trimView.startKnob.isUserInteractionEnabled = false
scroller.trimView.alpha = 0.5
scroller.trimView.endKnob.isUserInteractionEnabled = false
}
}
func setTrimmerVisible(_ visible:Bool) {
scroller.trimView.isHidden = !visible
}
func setTrim(start:Float64?, end:Float64?) {
var changed = false
if start != nil {
scroller.trimView.startKnob.knobTimePoint = start!
changed = true
}
if end != nil {
scroller.trimView.endKnob.knobTimePoint = end!
changed = true
}
if changed {
scroller.trimView.layout()
}
}
func setTrimWithAnimation(trim:VideoTimelineTrim, time:Float64) {
scroller.trimView.moveToTimeAndTrimWithAnimation(time, trim:trim)
}
var manualScrolledAfterEnd = false
func setTrimViewInteraction(_ active:Bool) {
if mainView!.trimEnabled == false && active {
return
}
scroller.trimView.startKnob.isUserInteractionEnabled = active
scroller.trimView.endKnob.isUserInteractionEnabled = active
if active {
setManualScrolledAfterEnd()
}
}
func setManualScrolledAfterEnd() {
let trim = currentTrim()
if mainView!.asset != nil {
let currentTime = mainView!.currentTime
if currentTime >= trim.end {
manualScrolledAfterEnd = true
} else {
manualScrolledAfterEnd = false
}
}
}
func currentTrim() -> (start:Float64, end:Float64) {
var start = scroller.trimView.startKnob.knobTimePoint
var end = scroller.trimView.endKnob.knobTimePoint
if mainView!.asset != nil {
if end > mainView!.duration {
end = mainView!.duration
}
if start < 0 {
start = 0
}
}
return (start, end)
}
func swapTrimKnobs() {
let knob = scroller.trimView.endKnob
scroller.trimView.endKnob = scroller.trimView.startKnob
scroller.trimView.startKnob = knob
}
//MARK: - animation
func startAnimation() {
scroller.frameImagesView.startAnimation()
scroller.measure.startAnimation()
scroller.trimView.startAnimation()
}
func stopAnimation() {
scroller.frameImagesView.stopAnimation()
scroller.measure.stopAnimation()
scroller.trimView.stopAnimation()
}
//MARK: - Gestures
func inAction() -> Bool {
if allTouches.count > 0 || scroller.isTracking || scroller.isDecelerating {
return true
} else {
return false
}
}
//MARK: - Scrolling
var allTouches = [UITouch]()
var pinching:Bool = false
func scrollViewDidScroll(_ scrollView:UIScrollView) {
scroller.trimView.layout()
if scroller.ignoreScrollViewDidScroll {
scroller.ignoreScrollViewDidScroll = false
return
}
scroller.measure.setNeedsDisplay()
scroller.frameImagesView.displayFrames()
setTrimViewInteraction(false)
let scrollPoint = scroller.contentOffset.x / scroller.frameImagesView.frame.size.width
self.centerLine.setScrollPoint(scrollPoint)
guard let mView = (mainView) else { return }
if let receiver = mView.playStatusReceiver {
receiver.videoTimelineMoved()
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
scroller.frameImagesView.requestVisible(depth:0, wide:0, direction:0)
setTrimViewInteraction(true)
let scrollPoint = scroller.contentOffset.x / scroller.frameImagesView.frame.size.width
self.centerLine.setScrollPoint(scrollPoint)
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView,
willDecelerate decelerate: Bool) {
scroller.frameImagesView.requestVisible(depth:0, wide:0, direction:0)
if decelerate == false {
setTrimViewInteraction(true)
}
let scrollPoint = scroller.contentOffset.x / scroller.frameImagesView.frame.size.width
self.centerLine.setScrollPoint(scrollPoint)
}
//MARK: - Zooming
var pinchCenterInContent:CGFloat = 0
var pinchStartDistance:CGFloat = 0
var pinchStartContent:(x:CGFloat,width:CGFloat) = (0,0)
func pinchCenter(_ pointA:CGPoint, pointB:CGPoint) -> CGPoint {
return CGPoint(x: (pointA.x + pointB.x) / 2, y: (pointA.y + pointB.y) / 2)
}
func pinchDistance(_ pointA:CGPoint, pointB:CGPoint) -> CGFloat {
return sqrt(pow((pointA.x - pointB.x),2) + pow((pointA.y - pointB.y),2));
}
func startPinch() {
pinching = true
scroller.isScrollEnabled = false
let touch1 = allTouches[0]
let touch2 = allTouches[1]
let center = pinchCenter(touch1.location(in: self),pointB: touch2.location(in: self))
pinchStartDistance = pinchDistance(touch1.location(in: self),pointB: touch2.location(in: self))
let framewidth = scroller.frame.size.width
pinchStartContent = ((framewidth / 2) - scroller.contentOffset.x,scroller.contentSize.width - framewidth)
pinchCenterInContent = (center.x - pinchStartContent.x) / pinchStartContent.width
}
func updatePinch() {
let touch1 = allTouches[0]
let touch2 = allTouches[1]
let center = pinchCenter(touch1.location(in: self), pointB:touch2.location(in: self))
var sizeChange = (1 * pinchDistance(touch1.location(in: self), pointB: touch2.location(in: self))) / pinchStartDistance
var contentWidth = pinchStartContent.width * sizeChange
let sizeMin = scroller.frameImagesView.minWidth
let sizeMax = scroller.frameImagesView.maxWidth
if contentWidth < sizeMin {
let sizeUnit = sizeMin / pinchStartContent.width
sizeChange = ((pow(sizeChange/sizeUnit,2)/4) + 0.75) * sizeUnit
contentWidth = pinchStartContent.width * sizeChange
contentWidth = sizeMin
} else if contentWidth > sizeMax {
sizeChange = sizeMax
contentWidth = sizeMax
} else {
let startRatio = pinchStartContent.width / sizeMax
let currentRatio = startRatio * sizeChange
let effect = ((sin(CGFloat.pi * 2 * log2(2/currentRatio)) * 0.108) - (sin(CGFloat.pi * 6 * log2(2/currentRatio)) * 0.009)) * currentRatio
let resultWidth = sizeMax * (currentRatio + effect)
contentWidth = resultWidth
}
let contentOrigin = center.x - (contentWidth * pinchCenterInContent)
scroller.contentOffset.x = (scroller.frame.size.width / 2) - contentOrigin
scroller.setContentWidth(contentWidth)
scroller.frameImagesView.layout()
scroller.frameImagesView.requestVisible(depth:2, wide:0, direction:0)
self.centerLine.setScrollPoint(scroller.contentOffset.x / scroller.frameImagesView.frame.size.width)
scroller.trimView.layout()
}
func endPinch() {
scroller.frameImagesView.requestVisible(depth:0, wide:1, direction:0)
let width = snapWidth(scroller.frameImagesView.frame.size.width, max:scroller.frameImagesView.maxWidth)
let offset = self.resizedPositionWithKeepOrigin(width:scroller.frameImagesView.frame.size.width, origin:scroller.contentOffset.x, destinationWidth:width)
//startAnimation()
UIView.animate(withDuration: 0.1,delay:Double(0.0),options:UIView.AnimationOptions.curveEaseOut, animations: { () -> Void in
self.scroller.setContentWidth(width, setOrigin:false)
self.scroller.contentOffset.x = offset
self.scroller.frameImagesView.layout()
self.scroller.trimView.layout()
},completion: { finished in
self.pinching = false
self.scroller.isScrollEnabled = true
})
self.scroller.frameImagesView.updateTolerance()
self.centerLine.setScrollPoint(scroller.contentOffset.x / scroller.frameImagesView.frame.size.width)
setTrimViewInteraction(true)
guard let mView = (mainView) else { return }
if let receiver = mView.playStatusReceiver {
receiver.videoTimelineMoved()
}
}
func resizedPositionWithKeepOrigin(width:CGFloat, origin:CGFloat, destinationWidth:CGFloat) -> CGFloat {
let originPoint = origin / width
let result = originPoint * destinationWidth
return result
}
}

773
kplayer/timeline/TrimView.swift

@ -1,773 +0,0 @@
//
// TrimView.swift
// Examplay
//
// Created by Tomohiro Yamashita on 2020/03/12.
// Copyright © 2020 Tom. All rights reserved.
//
import UIKit
class TrimView: UIView {
var mainView:VideoTimelineView!
var timelineView:TimelineView!
var parentScroller:TimelineScroller!
var startKnob = TrimKnob()
var endKnob = TrimKnob()
var movieDuration:Float64 = 0
let canPassThroughEachKnobs = true
override init (frame: CGRect) {
super.init(frame: frame)
self.isUserInteractionEnabled = false
self.backgroundColor = .clear
}
required init(coder aDecoder: NSCoder) {
fatalError("TrimView init(coder:) has not been implemented")
}
func configure(_ timeline:TimelineView, scroller:TimelineScroller) {
timelineView = timeline
parentScroller = scroller
self.frame = timeline.frame
startKnob.configure(timeline, trimmer:self)
endKnob.configure(timeline, trimmer:self)
timelineView.addSubview(startKnob)
timelineView.addSubview(endKnob)
}
func reset(duration:Float64) {
movieDuration = duration
startKnob.knobTimePoint = 0
endKnob.knobTimePoint = 3
if duration < endKnob.knobTimePoint {
endKnob.knobTimePoint = duration
}
layout()
}
let knobWidth:CGFloat = 20
let knobWidthExtend:CGFloat = 5
func layout() {
if self.isHidden {
return
}
swapKnobs()
let knobPositions = knobPositionsAsVisible()
let startPosition = knobPositions.start
let endPosition = knobPositions.end
startKnob.knobPositionOnScreen = startPosition
endKnob.knobPositionOnScreen = endPosition
startKnob.isOutOfScreen = knobPositions.startFixed
endKnob.isOutOfScreen = knobPositions.endFixed
startKnob.frame = CGRect(x:startPosition - knobWidth - knobWidthExtend, y:self.frame.origin.y, width:knobWidth + knobWidthExtend * 2, height:self.frame.size.height)
endKnob.frame = CGRect(x:endPosition - knobWidthExtend, y:self.frame.origin.y, width:knobWidth + knobWidthExtend * 2, height:self.frame.size.height)
self.setNeedsDisplay()
}
//MARK: - draw
override func draw(_ rect: CGRect) {
var startRect = startKnob.frame
var endRect = endKnob.frame
if startRect.size.height <= 0 {
return
}
if animating {
if let layer = startKnob.layer.presentation() {
startRect = layer.frame
}
if let layer = endKnob.layer.presentation() {
endRect = layer.frame
}
}
startRect.origin.x += knobWidthExtend
startRect.size.width -= knobWidthExtend * 2
endRect.origin.x += knobWidthExtend
endRect.size.width -= knobWidthExtend * 2
if startRect.origin.x > endRect.origin.x + endRect.size.width {
let swapRect = startRect
startRect = endRect
endRect = swapRect
}
let beamWidth:CGFloat = 3
var outerRect = CGRect(x: startRect.origin.x,y: 0,width: endRect.origin.x + endRect.size.width - startRect.origin.x,height:startRect.size.height)
var innerRect = CGRect(x:startRect.origin.x + startRect.size.width,y:beamWidth,width: endRect.origin.x - startRect.origin.x - startRect.size.width,height:startRect.size.height - (beamWidth * 2))
let screenLeft = cgToTime(screenToTimelinePosition(0))
let screenRight = cgToTime(screenToTimelinePosition(timelineView.frame.size.width))
var color = UIColor(hue: 0.1, saturation:0.8, brightness:1, alpha: 1)
let outColor = UIColor(hue: 0.1, saturation:0.8, brightness:1, alpha: 0.3)
if (endKnob.knobTimePoint < screenLeft || startKnob.knobTimePoint > screenRight) {
color = outColor
} else {
let addition = knobWidth + 10
let knobWidthTime = cgToTime(knobWidth)
if endKnob.knobTimePoint + knobWidthTime * 0.9 > screenRight {
outerRect.size.width += addition
innerRect.size.width += addition
let outRect = CGRect(x: timelineView.frame.size.width - knobWidth, y: beamWidth,width: knobWidth, height: endRect.size.height - beamWidth * 2)
let path = UIBezierPath(rect:outRect)
outColor.setFill()
path.fill()
}
if startKnob.knobTimePoint - knobWidthTime * 0.9 < screenLeft {
outerRect.origin.x -= addition
innerRect.origin.x -= addition
outerRect.size.width += addition
innerRect.size.width += addition
let outRect = CGRect(x: 0, y: beamWidth,width: knobWidth, height: endRect.size.height - beamWidth * 2)
let path = UIBezierPath(rect:outRect)
outColor.setFill()
path.fill()
}
}
let path = UIBezierPath(roundedRect:outerRect, cornerRadius:5)
path.usesEvenOddFillRule = true
let innerPath = UIBezierPath(roundedRect:innerRect, cornerRadius:2)
path.append(innerPath)
color.setFill()
path.fill()
}
//MARK: - timer for animation
var animationTimer = Timer()
var animating = false
func startAnimation() {
animationTimer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(self.animate(_:)), userInfo: nil, repeats: true)
RunLoop.main.add(animationTimer, forMode:RunLoop.Mode.common)
animating = true
}
func stopAnimation() {
animating = false
self.setNeedsDisplay()
animationTimer.invalidate()
}
@objc func animate(_ timer:Timer) {
if animating == false {
return
}
self.setNeedsDisplay()
}
//MARK: - positioning
func swapKnobs() {
if startKnob.knobTimePoint > endKnob.knobTimePoint {
if canPassThroughEachKnobs {
//timelineView.swapTrimKnobs()
} else {
let start = endKnob.knobTimePoint
endKnob.knobTimePoint = startKnob.knobTimePoint
startKnob.knobTimePoint = start
}
}
}
func anotherKnob(_ knob:TrimKnob) -> TrimKnob {
if knob == startKnob {
return endKnob
}
return startKnob
}
func knobOnScreen(_ knob:TrimKnob) -> CGFloat {
let maxWidth = scrollMaxWidth()
let offset = scrollOffset()
let position = CGFloat(knob.knobTimePoint / movieDuration) * maxWidth
return position - offset + (timelineView.frame.size.width / 2)
}
func knobsMinDistanceTime() -> Float64 {
return Float64(0.1)
}
func knobsMinDistanceFloat() -> CGFloat {
return timeToCG(knobsMinDistanceTime())
}
func timeToCG(_ time:Float64) -> CGFloat {
return CGFloat(time / movieDuration) * scrollMaxWidth()
}
func cgToTime(_ cgFloat:CGFloat) -> Float64 {
return Float64(cgFloat / scrollMaxWidth()) * movieDuration
}
func screenToTimelinePosition(_ onScreen:CGFloat) -> CGFloat {
return onScreen + scrollOffset() - (timelineView.frame.size.width / 2)
}
func timelineToScreenPosition(_ position:CGFloat) -> CGFloat {
return position - scrollOffset() + (timelineView.frame.size.width / 2)
}
func knobPositionsAsVisible() -> (start:CGFloat, end:CGFloat, startFixed:Bool, endFixed:Bool) {
let positionStart = knobOnScreen(startKnob)
let positionEnd = knobOnScreen(endKnob)
var resultStart = positionStart
var resultEnd = positionEnd
var startFixed:Bool = false
var endFixed:Bool = false
let screenRight = timelineView.frame.size.width// + offset
let minDistance = knobsMinDistanceFloat()
if positionStart < knobWidth {
if (positionEnd - minDistance - knobWidth) < 0 {
resultStart = positionEnd - minDistance
} else {
resultStart = knobWidth
}
startFixed = true
} else if positionStart > screenRight {
resultStart = screenRight
startFixed = true
}
if positionEnd < 0 {
resultEnd = 0
endFixed = true
} else if positionEnd + knobWidth > screenRight {
if positionStart + minDistance + knobWidth > screenRight {
resultEnd = positionStart + minDistance
} else {
resultEnd = screenRight - knobWidth
}
}
if true {
let distance = abs(resultStart - resultEnd)
if distance < minDistance {
let value = (minDistance - distance) / 2
resultStart -= value
resultEnd += value
startFixed = true
endFixed = true
}
}
return (resultStart, resultEnd, startFixed, endFixed)
}
func scrollOffset() -> CGFloat {
return parentScroller.contentOffset.x
}
func scrollMaxWidth() -> CGFloat {
return parentScroller.frameImagesView.frame.size.width
}
func knobTimeOnScreen(_ knob:TrimKnob) -> Float64 {
let offset = scrollOffset()
let position = offset + knob.knobPositionOnScreen - (timelineView.frame.size.width / 2)
let maxWidth = scrollMaxWidth()
let time = Float64(position / maxWidth) * movieDuration
return time
}
func knobMoveRange(_ knob:TrimKnob) -> (min:Float64, max:Float64) {
var min:Float64 = 0
var max:Float64 = movieDuration
let minDistance = knobsMinDistanceTime()
if canPassThroughEachKnobs {
if knob == startKnob {
max -= minDistance
} else if knob == endKnob {
min += minDistance
}
} else {
if knob == startKnob {
max = endKnob.knobTimePoint - minDistance
} else if knob == endKnob {
min = startKnob.knobTimePoint + minDistance
}
}
return (min, max)
}
func visibleKnobMoveLimit(_ knob:TrimKnob, margin:CGFloat) -> (min:Bool, max:Bool) {
let range = knobMoveRange(knob)
let minOnScreen = timelineToScreenPosition(timeToCG(range.min))
let maxOnScreen = timelineToScreenPosition(timeToCG(range.max))
var resultMin = false
var resultMax = false
if minOnScreen >= margin && minOnScreen <= timelineView.frame.width - margin {
resultMin = true
}
if maxOnScreen >= margin && maxOnScreen <= timelineView.frame.width - margin {
resultMax = true
}
return (resultMin , resultMax )
}
func directionReachesEnd(_ knob:TrimKnob, direction:CGFloat) -> Bool {
let visibleLimit = visibleKnobMoveLimit(knob, margin:knobWidth)
if direction > 0 {
if visibleLimit.max {
return true
}
} else if direction < 0 {
if visibleLimit.min {
return true
}
}
return false
}
func fixKnobPoint(_ knob:TrimKnob, move:CGFloat, startKnobPoint:Float64) -> (knobPoint:Float64, fixed:Bool) {
var timePoint = startKnobPoint + cgToTime(move)
let moveRange = knobMoveRange(knob)
var fixed = false
if timePoint < moveRange.min {
timePoint = moveRange.min
fixed = true
}
if timePoint > moveRange.max {
timePoint = moveRange.max
fixed = true
}
return (timePoint, fixed)
}
func updateKnob(_ knob:TrimKnob, timePoint:Float64) {
knob.knobTimePoint = timePoint
layout()
timelineView.moved(knob.knobTimePoint)
}
func resetSeek(_ time:Float64) {
if edgeScrolled {
moveToTimeWithAnimation(time)
} else {
mainView!.accurateSeek(time, scrub:true)
}
edgeScrolled = false
}
func moveToTimeWithAnimation(_ time:Float64) {
moveToTimeAndTrimWithAnimation(time, trim:nil)
}
func moveToTimeAndTrimWithAnimation(_ time:Float64, trim:VideoTimelineTrim?) {
let player = mainView!.player
if player == nil {
return
}
if player!.timeControlStatus == .playing {
player!.pause()
}
timelineView.animating = true
mainView!.isUserInteractionEnabled = false
timelineView.startAnimation()
UIView.animate(withDuration: 0.2,delay:Double(0.0),options:UIView.AnimationOptions.curveEaseOut, animations: { () -> Void in
if let pinnedTrim = trim {
self.timelineView.setTrim(start:pinnedTrim.start,end:pinnedTrim.end)
}
self.timelineView.setCurrentTime(time,force:false)
},completion: { finished in
self.mainView!.isUserInteractionEnabled = true
if self.mainView!.playing {
self.mainView!.accurateSeek(time, scrub:false)
player!.play()
} else {
self.mainView!.accurateSeek(time, scrub:true)
}
self.timelineView.animating = false
self.timelineView.setManualScrolledAfterEnd()
self.timelineView.stopAnimation()
if let receiver = self.mainView!.playStatusReceiver {
receiver.videoTimelineMoved()
}
})
}
//MARK: - edgeScroll
var edgeScrollTimer = Timer()
var edgeScrolled = false
var edgeScrollingKnob:TrimKnob? = nil
var edgeScrollStrength:CGFloat = 0
var edgeScrollingKnobPosition:CGFloat = 0
var edgeScrollLastChangedTime:Date = Date()
var edgeScrollLastChangedPosition:CGFloat = 0
func updateEdgeScroll(_ knob:TrimKnob, strength:CGFloat, position:CGFloat) {
var changed = false
if edgeScrollStrength != strength {
edgeScrollStrength = strength
changed = true
}
if edgeScrolling() == false {
startEdgeScroll(knob)
edgeScrolled = true
} else if changed {
edgeScrollLastChangedPosition = scrollOffset()
edgeScrollLastChangedTime = Date()
}
edgeScrollingKnobPosition = position
edgeScrollingKnob = knob
}
func startEdgeScroll(_ knob:TrimKnob) {
edgeScrollTimer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(self.edgeScrollTimer(_:)), userInfo: nil, repeats: true)
RunLoop.main.add(edgeScrollTimer, forMode:RunLoop.Mode.common)
edgeScrollLastChangedTime = Date()
edgeScrollLastChangedPosition = scrollOffset()
}
@objc func edgeScrollTimer(_ timer:Timer) {
if let knob = edgeScrollingKnob {
let movedPosition = currentEdgeScrollMovedPosition()
let destination = currentEdgeScrollPosition(movedPosition)
let moveRange = knobMoveRange(knob)
var knobPoint = cgToTime(edgeScrollingKnobPosition - (timelineView.frame.size.width / 2) + destination)
var overLimit:Float64 = 0
if knobPoint > moveRange.max {
overLimit = knobPoint - moveRange.max
knobPoint = moveRange.max
} else if knobPoint < moveRange.min {
overLimit = knobPoint - moveRange.min
knobPoint = moveRange.min
}
updateKnob(knob, timePoint:knobPoint)
if (knob == startKnob && knobPoint > endKnob.knobTimePoint) || (knob == endKnob && knobPoint < startKnob.knobTimePoint) {
timelineView.swapTrimKnobs()
}
timelineView.setCurrentTime(cgToTime(destination) - overLimit,force:true)
}
}
func stopEdgeScrollTimer() {
edgeScrollTimer.invalidate()
edgeScrollStrength = 0
edgeScrollingKnob = nil
}
func edgeScrolling() -> Bool {
return edgeScrollTimer.isValid
}
func currentEdgeScrollPosition(_ moved:CGFloat) -> CGFloat {
var result:CGFloat = 0
let maxWidth = scrollMaxWidth()
result = edgeScrollLastChangedPosition + moved
if result < 0 {
result = 0
} else if result > maxWidth {
result = maxWidth
}
return result
}
func currentEdgeScrollMovedPosition() -> CGFloat {
let pastTime = -edgeScrollLastChangedTime.timeIntervalSinceNow
return CGFloat(pastTime) * edgeScrollStrength * 5
}
}
//MARK: - TrimKnob
class TrimKnob:UIView {
var timelineView:TimelineView!
var knobPositionOnScreen:CGFloat = 0
var trimView:TrimView!
var knobTimePoint:Float64 = 0
var isOutOfScreen:Bool = false
override init (frame: CGRect) {
super.init(frame: frame)
self.isMultipleTouchEnabled = true
self.isUserInteractionEnabled = true
}
required init(coder aDecoder: NSCoder) {
fatalError("TrimKnob init(coder:) has not been implemented")
}
func configure(_ timeline:TimelineView, trimmer:TrimView) {
timelineView = timeline
trimView = trimmer
}
//MARK: - Touch Events
var allTouches = [UITouch]()
override open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
for touch in touches {
if !allTouches.contains(touch) {
allTouches += [touch]
}
if !timelineView!.allTouches.contains(touch) {
timelineView!.allTouches += [touch]
}
}
if timelineView!.allTouches.count == 1 && allTouches.count == 1 {
if dragging == false {
startDrag()
}
evaluateTap = true
} else {
evaluateTap = false
}
}
override open func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
{
if timelineView!.allTouches.count > 1 {
if dragging {
cancelDrag()
}
}
if timelineView!.allTouches.count == 2 {
if timelineView!.pinching {
timelineView!.updatePinch()
} else {
timelineView!.startPinch()
}
}
if dragging && timelineView!.allTouches.count == 1 && allTouches.count == 1 {
updateDrag()
}
evaluateTap = false
}
override open func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)
{
for touch in touches {
if let index = allTouches.firstIndex(of:touch) {
allTouches.remove(at: index)
}
if let index = timelineView!.allTouches.firstIndex(of:touch) {
timelineView!.allTouches.remove(at: index)
}
}
if timelineView!.pinching && timelineView!.allTouches.count < 2 {
timelineView!.endPinch()
}
if dragging {
endDrag()
}
if evaluateTap && timelineView!.allTouches.count == 0 {
tapped()
}
evaluateTap = false
}
override open func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?)
{
for touch in touches {
if let index = allTouches.firstIndex(of:touch) {
allTouches.remove(at: index)
}
if let index = timelineView!.allTouches.firstIndex(of:touch) {
timelineView!.allTouches.remove(at: index)
}
}
if timelineView!.pinching {
timelineView!.endPinch()
}
if dragging {
endDrag()
}
evaluateTap = false
}
//MARK: - actions
var dragging:Bool = false
var dragStartPoint = CGPoint.zero
var startKnobTimePoint:Float64 = 0
var dragStartOffset:CGFloat = 0
var startTimeOutOfScreen:Float64 = 0
var scrolling = false
var startCurrentTime:Float64 = 0
var evaluateTap:Bool = false
var ignoreEdgeScroll = false
func startDrag() {
dragging = true
let touch = allTouches[0]
dragStartPoint = touch.location(in: timelineView)
startKnobTimePoint = knobTimePoint
dragStartOffset = trimView.scrollOffset()
startTimeOutOfScreen = trimView.knobTimeOnScreen(self) - startKnobTimePoint
startCurrentTime = timelineView.mainView!.currentTime
if edgeScrollStrength(dragStartPoint.x) != 0 {
ignoreEdgeScroll = true
} else {
ignoreEdgeScroll = false
}
}
func updateDrag() {
let touch = allTouches[0]
let currentPoint = touch.location(in: timelineView)
let scrolled = trimView.scrollOffset() - dragStartOffset
let move = currentPoint.x - dragStartPoint.x + scrolled
let startKnobPoint = startKnobTimePoint + startTimeOutOfScreen
let timePoint = startKnobPoint + trimView.cgToTime(move)
let dragPoint = trimView.cgToTime(trimView.screenToTimelinePosition(currentPoint.x))
let onKnob = trimView.timeToCG(timePoint - dragPoint)
let anotherKnob = trimView.anotherKnob(self)
let anotherTimePoint = anotherKnob.knobTimePoint
let knobWidth = trimView.knobWidth
var swapping:CGFloat = 0
if anotherTimePoint < timePoint && startKnobPoint < anotherTimePoint
{
swapping = trimView.timeToCG(anotherTimePoint - timePoint)
if -swapping > knobWidth {
swapping = -knobWidth
}
} else if anotherTimePoint > timePoint && startKnobPoint > anotherTimePoint
{
swapping = trimView.timeToCG(anotherTimePoint - timePoint)
if swapping > knobWidth {
swapping = knobWidth
}
}
var rangeOut = false
let range = trimView.knobMoveRange(self)
if timePoint > range.max || timePoint < range.min {
rangeOut = true
}
if rangeOut == false && ((self == trimView.startKnob && dragPoint > anotherTimePoint) || (self == trimView.endKnob && dragPoint < anotherTimePoint)) {
timelineView.swapTrimKnobs()
}
let strength = edgeScrollStrength(currentPoint.x)
let reachedEnd = trimView.directionReachesEnd(self, direction:strength)
if strength != 0 && ignoreEdgeScroll == false && reachedEnd == false {
trimView.updateEdgeScroll(self, strength:strength, position:currentPoint.x + onKnob + swapping)
} else {
let fixedKnobPoint = trimView.fixKnobPoint(self, move:move + swapping, startKnobPoint:startKnobPoint)
trimView.updateKnob(self, timePoint:fixedKnobPoint.knobPoint)
if strength == 0 {
ignoreEdgeScroll = false
}
if strength == 0 || reachedEnd {
if trimView.edgeScrolling() {
trimView.stopEdgeScrollTimer()
}
}
}
guard let mainView = (timelineView.mainView) else { return }
if let receiver = mainView.playStatusReceiver {
receiver.videoTimelineTrimChanged()
}
}
func endDrag() {
let anotherKnob = trimView.anotherKnob(self)
let distance = abs(anotherKnob.knobTimePoint - knobTimePoint)
let minDistance = trimView.knobsMinDistanceTime()
if distance < minDistance {
if self == trimView.startKnob {
knobTimePoint -= (minDistance - distance)
} else if self == trimView.endKnob {
knobTimePoint += (minDistance - distance)
}
trimView.timelineView.animating = true
trimView.startAnimation()
UIView.animate(withDuration: 0.2,delay:Double(0.0),options:UIView.AnimationOptions.curveEaseOut, animations: { () -> Void in
self.trimView.layout()
},completion: { finished in
self.trimView.stopAnimation()
self.timelineView.animating = false
})
}
timelineView.setManualScrolledAfterEnd()
trimView.resetSeek(startCurrentTime)
dragging = false
trimView.stopEdgeScrollTimer()
if evaluateTap == false {
guard let mainView = (timelineView.mainView) else { return }
if let receiver = mainView.playStatusReceiver {
receiver.videoTimelineTrimChanged()
}
}
}
func cancelDrag() {
knobTimePoint = startKnobTimePoint
timelineView.setManualScrolledAfterEnd()
dragging = false
trimView.layout()
trimView.resetSeek(startCurrentTime)
trimView.stopEdgeScrollTimer()
}
func tapped() {
trimView.moveToTimeWithAnimation(knobTimePoint)
timelineView.setManualScrolledAfterEnd()
}
func edgeScrollStrength(_ position:CGFloat) -> CGFloat {
var strength:CGFloat = 0
let edgeWidth:CGFloat = 40
if position >= timelineView.frame.size.width - edgeWidth {
strength = position + edgeWidth - timelineView.frame.size.width
} else if position <= edgeWidth {
strength = position - edgeWidth
}
return strength
}
}

301
kplayer/timeline/VideoTimelineView.swift

@ -1,301 +0,0 @@
//
// VideoTimelineView.swift
// VideoTimelineView
//
// Created by Tomohiro Yamashita on 2020/03/28.
// Copyright © 2020 Tom. All rights reserved.
//
import UIKit
import AVFoundation
protocol TimelinePlayStatusReceiver: class {
func videoTimelineStopped()
func videoTimelineMoved()
func videoTimelineTrimChanged()
}
struct VideoTimelineTrim {
var start:Float64
var end:Float64
}
class VideoTimelineView: UIView {
public private(set) var asset:AVAsset? = nil
var player:AVPlayer? = nil
weak var playStatusReceiver:TimelinePlayStatusReceiver? = nil
var repeatOn:Bool = false
public private(set) var trimEnabled:Bool = false
var currentTime:Float64 = 0
public private(set) var duration:Float64 = 0
public private(set) var audioPlayer:AVPlayer!
public private(set) var audioPlayer2:AVPlayer!
let timelineView = TimelineView()
override init (frame: CGRect) {
super.init(frame: frame)
timelineView.mainView = self
timelineView.centerLine.mainView = self
timelineView.scroller.frameImagesView.mainView = self
timelineView.scroller.trimView.mainView = self
self.addSubview(timelineView)
}
required init(coder aDecoder: NSCoder) {
fatalError("MainView init(coder:) has not been implemented")
}
func viewDidLayoutSubviews() {
coordinate()
}
func coordinate() {
timelineView.coordinate()
}
func new(asset newAsset:AVAsset?) {
if let new = newAsset {
asset = new
duration = CMTimeGetSeconds(new.duration)
player = AVPlayer(playerItem: AVPlayerItem(asset: asset!))
audioPlayer = AVPlayer(playerItem: AVPlayerItem(asset: asset!))
audioPlayer.volume = 1.0
audioPlayer2 = AVPlayer(playerItem: AVPlayerItem(asset: asset!))
audioPlayer2.volume = 1.0
timelineView.newMovieSet()
}
}
func setTrim(start:Float64, end:Float64, seek:Float64?, animate:Bool) {
var seekTime = currentTime
if let time = seek {
seekTime = time
}
if animate {
timelineView.setTrimWithAnimation(trim:VideoTimelineTrim(start:start, end:end), time:seekTime)
} else {
timelineView.setTrim(start:start, end:end)
if seek != nil {
moveTo(seek!, animate:animate)
}
}
}
func setTrimIsEnabled(_ enabled:Bool) {
trimEnabled = enabled
timelineView.setTrimmerStatus(enabled:enabled)
}
func setTrimmerIsHidden(_ hide:Bool) {
timelineView.setTrimmerVisible(!hide)
}
func currentTrim() -> (start:Float64, end:Float64) {
let trim = timelineView.currentTrim()
return (trim.start,trim.end)
}
func moveTo(_ time:Float64, animate:Bool) {
if animate {
} else {
accurateSeek(time, scrub:false)
timelineView.setCurrentTime(time, force:true)
}
}
//MARK: - seeking
var previousSeektime:Float64 = 0
func timelineIsMoved(_ currentTime:Float64, scrub:Bool) {
let move = abs(currentTime - previousSeektime)
let seekTolerance = CMTimeMakeWithSeconds(move, preferredTimescale:100)
if player != nil {
player!.seek(to:CMTimeMakeWithSeconds(currentTime , preferredTimescale:100), toleranceBefore:seekTolerance,toleranceAfter:seekTolerance)
}
previousSeektime = currentTime
if scrub {
audioScrub()
}
}
func accurateSeek(_ currentTime:Float64, scrub:Bool) {
previousSeektime = currentTime
timelineIsMoved(currentTime, scrub:scrub)
}
var scrubed1 = Date()
var scrubed2 = Date()
var canScrub1 = true
var canScrub2 = true
func audioScrub() {
if player == nil {
return
}
if scrubed2.timeIntervalSinceNow < -0.16 && canScrub1 {
canScrub1 = false
self.scrubed1 = Date()
DispatchQueue.main.async {
if self.audioPlayer.timeControlStatus == .playing {
self.audioPlayer.pause()
self.canScrub1 = true
} else {
self.audioPlayer.seek(to: self.player!.currentTime())
self.audioPlayer.play()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.22) {
self.audioPlayer.pause()
self.audioPlayer.seek(to: self.player!.currentTime())
self.canScrub1 = true
}
}
}
}
if scrubed1.timeIntervalSinceNow < -0.16 && canScrub2 {
canScrub2 = false
self.scrubed2 = Date()
DispatchQueue.main.async {
if self.audioPlayer2.timeControlStatus == .playing {
self.audioPlayer2.pause()
self.canScrub2 = true
} else {
self.audioPlayer2.seek(to: self.player!.currentTime())
self.audioPlayer2.play()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.22) {
self.audioPlayer2.pause()
self.audioPlayer2.seek(to: self.player!.currentTime())
self.canScrub2 = true
}
}
}
}
}
//MARK: - play
var playerTimer = Timer()
@objc dynamic var playing = false
func play() {
if asset == nil {
return
}
let currentTime = timelineView.centerLine.currentTime
let reached = timeReachesEnd(currentTime)
if reached.trimEnd {
accurateSeek(timelineView.currentTrim().start, scrub:false)
timelineView.manualScrolledAfterEnd = false
} else if reached.movieEnd {
accurateSeek(0, scrub:false)
timelineView.manualScrolledAfterEnd = false
}
if player != nil {
player!.play()
}
playerTimer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(self.playerTimerAction(_:)), userInfo: nil, repeats: true)
RunLoop.main.add(playerTimer, forMode:RunLoop.Mode.common)
playing = true
}
func stop() {
playing = false
if asset == nil || player == nil {
return
}
player!.pause()
playerTimer.invalidate()
if let receiver = playStatusReceiver {
receiver.videoTimelineStopped()
}
}
var reachFlg = false
@objc func playerTimerAction(_ timer:Timer) {
if player == nil {
return
}
var currentPlayerTime = CMTimeGetSeconds(player!.currentTime())
let trim = timelineView.currentTrim()
let reached = timeReachesEnd(currentPlayerTime)
if timelineView.inAction() {
if player!.timeControlStatus == .playing {
player!.pause()
}
} else if reached.reached {
if repeatOn && reached.trimEnd {
if player!.timeControlStatus == .playing {
player!.pause()
}
currentPlayerTime = trim.start
accurateSeek(currentPlayerTime, scrub:false)
reachFlg = true
} else {
stop()
}
timelineView.setCurrentTime(currentPlayerTime,force:false)
timelineView.manualScrolledAfterEnd = false
} else if timelineView.animating == false {
timelineView.setCurrentTime(currentPlayerTime,force:false)
if player!.timeControlStatus == .paused {
player!.play()
}
if reachFlg {
if let receiver = playStatusReceiver {
receiver.videoTimelineMoved()
}
reachFlg = false
}
}
}
func timeReachesEnd(_ time:Float64) -> (reached:Bool, trimEnd:Bool, movieEnd:Bool) {
var reached = false
var trimEnd = false
var movieEnd = false
if asset != nil {
let duration = CMTimeGetSeconds(asset!.duration)
let trimTimeEnd = timelineView.currentTrim().end
if (time >= trimTimeEnd && timelineView.manualScrolledAfterEnd == false && trimEnabled) {
trimEnd = true
reached = true
}
if time >= duration {
if trimTimeEnd < duration {
trimEnd = false
}
movieEnd = true
reached = true
}
}
return (reached, trimEnd, movieEnd)
}
//MARK: -
func resizeHeightKeepRatio(_ size:CGSize, height:CGFloat) -> CGSize {
var result = size
let ratio = size.width / size.height
result.height = height
result.width = height * ratio
return result
}
}

0
kplayer/svideo/SVideoModel.swift → kplayer/video/SVideoModel.swift

0
kplayer/svideo/SVideoPlayer.swift → kplayer/video/SVideoPlayer.swift

0
kplayer/svideo/VideoPlayerView.swift → kplayer/video/VideoPlayerView.swift

Loading…
Cancel
Save