Browse Source

BMPlayer removed

master
marcoschmickler 4 years ago
parent
commit
a3b438121f
  1. 48
      kplayer.xcodeproj/project.pbxproj
  2. 1
      kplayer/AppDelegate.swift
  3. 39
      kplayer/core/DatabaseManager.swift
  4. 10
      kplayer/core/ItemType.swift
  5. 3
      kplayer/core/KSettings.swift
  6. 2
      kplayer/core/MediaItem.swift
  7. 66
      kplayer/detail/BrowserController.swift
  8. 84
      kplayer/detail/DetailViewController.swift
  9. 12
      kplayer/detail/EditItemView.swift
  10. 821
      kplayer/detail/VideoController.swift
  11. 3
      kplayer/master/KSettingsView.swift
  12. 23
      kplayer/master/MasterViewController.swift
  13. 7
      kplayer/master/NetworkDelegate.swift
  14. 16
      kplayer/photo/PhotoController.swift
  15. 820
      kplayer/video/BMPlayer.swift
  16. 29
      kplayer/video/BMPlayerClearityChooseButton.swift
  17. 19
      kplayer/video/BMPlayerCompositionResourceDefinition.swift
  18. 757
      kplayer/video/BMPlayerControlView.swift
  19. 91
      kplayer/video/BMPlayerItem.swift
  20. 506
      kplayer/video/BMPlayerLayerView.swift
  21. 62
      kplayer/video/BMPlayerManager.swift
  22. 30
      kplayer/video/BMPlayerProtocols.swift
  23. 122
      kplayer/video/BMSubtitles.swift
  24. 26
      kplayer/video/BMTimeSlider.swift
  25. 30
      kplayer/video/KVideoPlayer.swift

48
kplayer.xcodeproj/project.pbxproj

@ -10,10 +10,8 @@
1C73600CB93F16F4F28C116F /* KSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736A6E8396EE306B1AD3A8 /* KSettingsView.swift */; };
1C736048BFA120F5C7D36874 /* readme.md in Sources */ = {isa = PBXBuildFile; fileRef = 1C73685B4BBFDAFBF08C032C /* readme.md */; };
1C7360C0F2A4F0214FE353BD /* FileHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7367ECBD369A2A0C94C499 /* FileHelper.swift */; };
1C7360F1D2CF83ECDD586B84 /* BMPlayerCompositionResourceDefinition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73610B997EBA367C806C1B /* BMPlayerCompositionResourceDefinition.swift */; };
1C73613562EB375F53A0BD03 /* ServerDownloadDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736595533B56039C417E0D /* ServerDownloadDelegate.swift */; };
1C7361B3AF46CEB30D3F4FA0 /* KSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736AE5021E3D985FE3402D /* KSettings.swift */; };
1C7361D2B6E0AE689FAAF4F4 /* VideoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736C7FFBDAC665AE04CB65 /* VideoController.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 */; };
@ -30,14 +28,11 @@
1C7365BEFFB35E8DE8F04CCF /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73661561AD069C92FE3B15 /* TimelineView.swift */; };
1C73666A07CF2416B1B8D3F0 /* KSettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736C94157754DE1C808173 /* KSettingsModel.swift */; };
1C7366A0CFD2B55BF8C3BAF0 /* NetworkDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7364F10BED5DA0F1C0423C /* NetworkDelegate.swift */; };
1C7366DAC06047DE335EFC37 /* BMPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736927EA28AFBEB25D7487 /* BMPlayer.swift */; };
1C73671FC2CCCACAA2FFC153 /* ThumbnailCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736EA15A11AF7D57F85824 /* ThumbnailCache.swift */; };
1C73672CEAE1B9DA7805D4F2 /* CenterLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7361C26ED27AB54594317D /* CenterLine.swift */; };
1C73673F39A34C3275D0230A /* BMPlayerClearityChooseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736DFBD072763248412F74 /* BMPlayerClearityChooseButton.swift */; };
1C73675C34BE0990D44570BE /* ItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736253AB7A95EA41B605B7 /* ItemModel.swift */; };
1C736771C503FB0D52AEB8F7 /* kplayer.js in Sources */ = {isa = PBXBuildFile; fileRef = 1C73625012D50E457D18A785 /* kplayer.js */; };
1C7367AF39961D2BA72480ED /* DataLoadOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736F9338CE36708244D42A /* DataLoadOperation.swift */; };
1C7367FA10AE13598FDDE865 /* BMPlayerProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7365F45D765A218FFC100F /* BMPlayerProtocols.swift */; };
1C736821D6DF2237A3EABCC1 /* ViewControllerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73648CEC974A2500172064 /* ViewControllerExtensions.swift */; };
1C7368242038C0FF6C9631E7 /* VideoHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7364709899FF62774B0199 /* VideoHelper.swift */; };
1C73688D13E5A804880C8768 /* UIImageExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736DCCE3AA9993E15F7652 /* UIImageExtension.swift */; };
@ -50,24 +45,17 @@
1C736A06A2AD75B8C14EEBBE /* HtmlParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736DBB6986A8B62963FBB3 /* HtmlParser.swift */; };
1C736A5FA5BA53B2597F2ED7 /* Kirschkeks-256x256.png in Resources */ = {isa = PBXBuildFile; fileRef = 1C736059262A57AADE6AB761 /* Kirschkeks-256x256.png */; };
1C736A622876405F3EE2D043 /* EditItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7366C09381DC0052B52B69 /* EditItemView.swift */; };
1C736A78C1F8F41E2AEEF278 /* KVideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736E2CD0C1780F4F5AE0C4 /* KVideoPlayer.swift */; };
1C736A7B6221A1D50FB3904C /* ItemType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73631C96E6C860833052CA /* ItemType.swift */; };
1C736B4B0889BD35DC566124 /* nspersistentcontainer-defaults-swift.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7361F01841F546FA7AFD58 /* nspersistentcontainer-defaults-swift.swift */; };
1C736CB96577F6A9A7BA03E8 /* BMPlayerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7364F924BD979294C3EE4A /* BMPlayerItem.swift */; };
1C736CD0E54786D3A2405E51 /* BMPlayerLayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7366D766CDE0C9872E86F5 /* BMPlayerLayerView.swift */; };
1C736D16E81BA1FB325200E0 /* HanekeFetchOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7360744ABACC3557D05760 /* HanekeFetchOperation.swift */; };
1C736D24891597F2728230EE /* ImageLoadOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7360A94DBECA685ED8602F /* ImageLoadOperation.swift */; };
1C736D24B49451141CD4B64D /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7369F53095B7A4D65679C2 /* DetailViewController.swift */; };
1C736D895B75BDCDB35937C1 /* BMTimeSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7360AE55EB115762C42EB9 /* BMTimeSlider.swift */; };
1C736D89CF86841F4C98A1F7 /* KPersistentContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7362DE1D6BE634D7C2ACBF /* KPersistentContainer.swift */; };
1C736DB41BD06D359E6A0DEE /* BMSubtitles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7366AAB82A46086690E164 /* BMSubtitles.swift */; };
1C736DFD076D9CC30F0B9D58 /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736677D4EF2437358B2387 /* Utility.swift */; };
1C736E21B246C0BE7E123FD3 /* MediaModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736B41C6AC33F3FA592C63 /* MediaModel.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 */; };
1C736F278DDC77F40C8CB1D4 /* BMPlayerControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736E51F1A03E3A1200BDB6 /* BMPlayerControlView.swift */; };
1C736F3570EADA086682E6BC /* BMPlayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7360D6580FB5D09C2BBCCB /* BMPlayerManager.swift */; };
1C736F3D082067948BA4DE84 /* FrameImagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736A5140D9B25BEC266B72 /* FrameImagesView.swift */; };
1C736F6A223D4ADB2E1BA733 /* ItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736069C214E9522BB1BD97 /* ItemCell.swift */; };
1C736F7D29B76C7037CEF778 /* DatabaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73647019E6C2E822127BA3 /* DatabaseManager.swift */; };
@ -105,11 +93,8 @@
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>"; };
1C7360A94DBECA685ED8602F /* ImageLoadOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageLoadOperation.swift; sourceTree = "<group>"; };
1C7360AE55EB115762C42EB9 /* BMTimeSlider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMTimeSlider.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>"; };
1C7360D6580FB5D09C2BBCCB /* BMPlayerManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMPlayerManager.swift; sourceTree = "<group>"; };
1C73610B997EBA367C806C1B /* BMPlayerCompositionResourceDefinition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMPlayerCompositionResourceDefinition.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>"; };
1C7361C26ED27AB54594317D /* CenterLine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CenterLine.swift; path = timeline/CenterLine.swift; sourceTree = "<group>"; };
@ -129,23 +114,18 @@
1C7364709899FF62774B0199 /* VideoHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoHelper.swift; sourceTree = "<group>"; };
1C73648CEC974A2500172064 /* ViewControllerExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewControllerExtensions.swift; sourceTree = "<group>"; };
1C7364F10BED5DA0F1C0423C /* NetworkDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkDelegate.swift; sourceTree = "<group>"; };
1C7364F924BD979294C3EE4A /* BMPlayerItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMPlayerItem.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>"; };
1C7365B06FA66294E99AC2D3 /* NetworkManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkManager.swift; sourceTree = "<group>"; };
1C7365F45D765A218FFC100F /* BMPlayerProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMPlayerProtocols.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>"; };
1C7366AAB82A46086690E164 /* BMSubtitles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMSubtitles.swift; sourceTree = "<group>"; };
1C7366C09381DC0052B52B69 /* EditItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditItemView.swift; sourceTree = "<group>"; };
1C7366D766CDE0C9872E86F5 /* BMPlayerLayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMPlayerLayerView.swift; sourceTree = "<group>"; };
1C73673DC671535E3A049F54 /* PhotoController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoController.swift; sourceTree = "<group>"; };
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>"; };
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>"; };
1C7368C7B946BC9E067D37E7 /* TimelineMeasure.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TimelineMeasure.swift; path = timeline/TimelineMeasure.swift; sourceTree = "<group>"; };
1C736927EA28AFBEB25D7487 /* BMPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMPlayer.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>"; };
1C736A5140D9B25BEC266B72 /* FrameImagesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FrameImagesView.swift; path = timeline/FrameImagesView.swift; sourceTree = "<group>"; };
@ -155,16 +135,12 @@
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>"; };
1C736BC4450890C45F8FBC63 /* LayoutTools.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LayoutTools.swift; sourceTree = "<group>"; };
1C736C7FFBDAC665AE04CB65 /* VideoController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoController.swift; sourceTree = "<group>"; };
1C736C94157754DE1C808173 /* KSettingsModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KSettingsModel.swift; sourceTree = "<group>"; };
1C736CF935C2A6AB916BE494 /* scratch.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = scratch.txt; sourceTree = "<group>"; };
1C736D9BB5498E7E8F11C754 /* HeaderCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderCell.swift; sourceTree = "<group>"; };
1C736DBB6986A8B62963FBB3 /* HtmlParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HtmlParser.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>"; };
1C736DFBD072763248412F74 /* BMPlayerClearityChooseButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMPlayerClearityChooseButton.swift; sourceTree = "<group>"; };
1C736E2CD0C1780F4F5AE0C4 /* KVideoPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KVideoPlayer.swift; sourceTree = "<group>"; };
1C736E51F1A03E3A1200BDB6 /* BMPlayerControlView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMPlayerControlView.swift; sourceTree = "<group>"; };
1C736EA15A11AF7D57F85824 /* ThumbnailCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThumbnailCache.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>"; };
@ -215,7 +191,6 @@
1C736069C214E9522BB1BD97 /* ItemCell.swift */,
1C736D9BB5498E7E8F11C754 /* HeaderCell.swift */,
1C7369F53095B7A4D65679C2 /* DetailViewController.swift */,
1C736C7FFBDAC665AE04CB65 /* VideoController.swift */,
1C73602350ACE2436736F981 /* BrowserController.swift */,
1C7366C09381DC0052B52B69 /* EditItemView.swift */,
);
@ -303,17 +278,6 @@
1C736F3946A38499113D351A /* video */ = {
isa = PBXGroup;
children = (
1C736927EA28AFBEB25D7487 /* BMPlayer.swift */,
1C736DFBD072763248412F74 /* BMPlayerClearityChooseButton.swift */,
1C736E51F1A03E3A1200BDB6 /* BMPlayerControlView.swift */,
1C7364F924BD979294C3EE4A /* BMPlayerItem.swift */,
1C7366D766CDE0C9872E86F5 /* BMPlayerLayerView.swift */,
1C7365F45D765A218FFC100F /* BMPlayerProtocols.swift */,
1C7366AAB82A46086690E164 /* BMSubtitles.swift */,
1C7360AE55EB115762C42EB9 /* BMTimeSlider.swift */,
1C7360D6580FB5D09C2BBCCB /* BMPlayerManager.swift */,
1C73610B997EBA367C806C1B /* BMPlayerCompositionResourceDefinition.swift */,
1C736E2CD0C1780F4F5AE0C4 /* KVideoPlayer.swift */,
);
path = video;
sourceTree = "<group>";
@ -601,25 +565,14 @@
1C73675C34BE0990D44570BE /* ItemModel.swift in Sources */,
1C73691A9C7174E0C6B57267 /* stringutil.swift in Sources */,
1C736821D6DF2237A3EABCC1 /* ViewControllerExtensions.swift in Sources */,
1C7361D2B6E0AE689FAAF4F4 /* VideoController.swift in Sources */,
1C736953BDBBAFC40884132A /* BrowserController.swift in Sources */,
1C73671FC2CCCACAA2FFC153 /* ThumbnailCache.swift in Sources */,
1C7366DAC06047DE335EFC37 /* BMPlayer.swift in Sources */,
1C73673F39A34C3275D0230A /* BMPlayerClearityChooseButton.swift in Sources */,
1C736F278DDC77F40C8CB1D4 /* BMPlayerControlView.swift in Sources */,
C91E05892795AC5C0003AB79 /* KTag+CoreDataClass.swift in Sources */,
1C736CB96577F6A9A7BA03E8 /* BMPlayerItem.swift in Sources */,
1C736CD0E54786D3A2405E51 /* BMPlayerLayerView.swift in Sources */,
1C7367FA10AE13598FDDE865 /* BMPlayerProtocols.swift in Sources */,
1C736DB41BD06D359E6A0DEE /* BMSubtitles.swift in Sources */,
1C736D895B75BDCDB35937C1 /* BMTimeSlider.swift in Sources */,
1C736F3570EADA086682E6BC /* BMPlayerManager.swift in Sources */,
1C736E21B246C0BE7E123FD3 /* MediaModel.swift in Sources */,
1C736A7B6221A1D50FB3904C /* ItemType.swift in Sources */,
1C7360C0F2A4F0214FE353BD /* FileHelper.swift in Sources */,
1C7366A0CFD2B55BF8C3BAF0 /* NetworkDelegate.swift in Sources */,
1C7368242038C0FF6C9631E7 /* VideoHelper.swift in Sources */,
1C7360F1D2CF83ECDD586B84 /* BMPlayerCompositionResourceDefinition.swift in Sources */,
1C736A06A2AD75B8C14EEBBE /* HtmlParser.swift in Sources */,
1C736048BFA120F5C7D36874 /* readme.md in Sources */,
1C736771C503FB0D52AEB8F7 /* kplayer.js in Sources */,
@ -640,7 +593,6 @@
1C736A622876405F3EE2D043 /* EditItemView.swift in Sources */,
1C73613562EB375F53A0BD03 /* ServerDownloadDelegate.swift in Sources */,
1C736EC45EE7DA5F7FCE63DA /* LocalManager.swift in Sources */,
1C736A78C1F8F41E2AEEF278 /* KVideoPlayer.swift in Sources */,
1C736FF8FF423F01F880F94D /* SVideoPlayer.swift in Sources */,
1C73633AAF0D77F8AC3557B9 /* SVideoModel.swift in Sources */,
1C7362AF931E0F228E5D2AED /* VideoPlayerView.swift in Sources */,

1
kplayer/AppDelegate.swift

@ -60,6 +60,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele
roots.append(LocalManager.sharedInstance.favorites)
roots.append(MediaItem(name: "extern", path:"", root: "", type: ItemType.FAVROOT))
roots.append(MediaItem(name: "tags", path:"", root: "", type: ItemType.TAGROOT))
roots.append(web)
controller.model.items = roots

39
kplayer/core/DatabaseManager.swift

@ -143,4 +143,43 @@ class DatabaseManager {
}
}
}
func createTag(_ answer: String) {
let tag = KTag(context: managedObjectContext)
tag.name = answer
do {
try managedObjectContext.save()
} catch {
print("Error")
}
}
func loadTags(completionHandler: @escaping Weiter) -> Void {
var res = [MediaItem]()
let fetchRequest = KTag.fetchRequest()
let results = try! managedObjectContext.fetch(fetchRequest)
for t in results {
let tag = MediaItem(name: t.name!, path: t.name!, root: "tags", type: ItemType.DETAILS)
let snapshots = t.tagged as! Set<KSnapshot>
for s in snapshots {
let i = s.item!
let sitem = MediaItem(name: i.name!, path: i.path!, root: i.root!, type: ItemType.VIDEO)
tag.children.append(sitem)
}
res.append(tag)
}
let m = MediaItem(name: "new", path: "new", root: "tags", type: ItemType.TAG)
m.local = true
res.append(m)
completionHandler(res)
}
}

10
kplayer/core/ItemType.swift

@ -16,6 +16,11 @@ enum ItemType: String, Codable, CustomStringConvertible {
*/
case FAVROOT = "favroot"
/**
Repräsentiert eine Wurzel, die lokal gespeichert ist.
*/
case TAGROOT = "tagroot"
/**
Repräsentiert eine Wurzel, in der nur Webseiten angezeigt werden.
*/
@ -46,6 +51,11 @@ enum ItemType: String, Codable, CustomStringConvertible {
*/
case SNAPSHOT = "snapshot"
/**
Snapshots eines Videos.
*/
case TAG = "tag"
/**
Repräsentiert einen Bilder-Ordner.
*/

3
kplayer/core/KSettings.swift

@ -18,9 +18,6 @@ class KSettings: ObservableObject {
@Published
var edit = false
@Published
var newPlayer = true
convenience init(model: KSettingsModel) {
self.init()
scale = model.scale

2
kplayer/core/MediaItem.swift

@ -313,7 +313,7 @@ class MediaItem: CustomDebugStringConvertible, ObservableObject, Identifiable {
}
func isFolder() -> Bool {
type == ItemType.REMOTEROOT || type == ItemType.FOLDER || type == ItemType.WEBROOT || type == ItemType.FAVROOT
type == ItemType.REMOTEROOT || type == ItemType.FOLDER || type == ItemType.WEBROOT || type == ItemType.FAVROOT || type == ItemType.TAGROOT
}
func clone() -> MediaItem {

66
kplayer/detail/BrowserController.swift

@ -8,6 +8,23 @@ import UIKit
import WebBrowser
import WebKit
import Alamofire
import SwiftUI
protocol DownloadDelegate {
func killFFMPEG()
func dlserverlen(result: @escaping (String) -> ())
func download(url: URL, path: String, result: @escaping (URL) -> () )
func downloadToServer(path: String, url: URL, result: @escaping (String) -> ())
func inProgress() -> Int
}
protocol ItemController {
func setCurrentItem(item: MediaItem)
func setItems(items: [MediaItem])
func setCompletionHandler(handler: @escaping (() -> Void))
}
class BrowserController : UIViewController, ItemController, WebBrowserDelegate, UINavigationControllerDelegate, WKScriptMessageHandler, WKHTTPCookieStoreObserver {
var completionHandler: (() -> Void)?
@ -212,26 +229,49 @@ class BrowserController : UIViewController, ItemController, WebBrowserDelegate,
return
}
let vc = VideoController()
let item = MediaItem(name: name, path: name, root: site, type: ItemType.VIDEO)
item.externalURL = url
showVideo(selectedItem: item)
}
vc.setItems(items: [item])
vc.setCurrentItem(item: item)
vc.setCompletionHandler(handler: {
func showVideo(selectedItem: MediaItem) {
var se = selectedItem
var children = [MediaItem]()
var clonedChildren = [MediaItem]()
var baseItem = selectedItem
if baseItem.type == ItemType.SNAPSHOT {
baseItem = selectedItem.parent!
}
children = baseItem.children
clonedChildren = baseItem.clone().children
let model = SVideoModel(allItems: children, currentSnapshot: se, baseItem: baseItem)
model.edit = LocalManager.sharedInstance.settings.edit
model.loop = LocalManager.sharedInstance.settings.autoloop
model.zoomed = LocalManager.sharedInstance.settings.zoomed
let player = SVideoPlayer(completionHandler: { saved in
baseItem.children = clonedChildren
self.dismiss(animated: true, completion: nil);
})
}, model: model)
player
let navController = UINavigationController(rootViewController: (vc as UIViewController))
navController.modalPresentationStyle = .fullScreen
navController.modalPresentationCapturesStatusBarAppearance = true
navController.navigationBar.barTintColor = UIColor.black
(vc as UIViewController).navigationItem.leftItemsSupplementBackButton = true
let pc = UIHostingController(rootView: player)
pc.view.backgroundColor = .black
getWindow().rootViewController!.definesPresentationContext = true
pc.modalPresentationStyle = .overCurrentContext
getWindow().rootViewController!.present(pc, animated: true)
}
self.present(navController, animated: false, completion: nil)
func getWindow() -> UIWindow {
let delegate2 = UIApplication.shared.delegate!
return delegate2.window as! UIWindow
}
private func downloadZip(_ url: URL, path: String) {

84
kplayer/detail/DetailViewController.swift

@ -168,14 +168,9 @@ class DetailViewController: UIViewController, UICollectionViewDelegateFlowLayout
var i = [MediaItem]()
if let d = detailItem {
if delegate!.settings().newPlayer {
showAll(d)
return
}
if (d.local) {
showComposition(d)
return
}
showAll(d)
return
let pc = MediaPhotoController()
for it in d.children {
@ -216,38 +211,7 @@ class DetailViewController: UIViewController, UICollectionViewDelegateFlowLayout
}
}
showNewVideo(selectedItem: composition.children[0])
}
private func showComposition(_ item: MediaItem) {
var assets = [URL]()
for d in item.children {
assets.append(d.playerURL!)
}
let vc = VideoController()
let item = MediaItem(name: item.name, path: item.path, root: item.root, type: ItemType.VIDEO)
vc.detailDelegate = delegate
vc.setItems(items: [item])
vc.setCurrentItem(item: item)
vc.urls = assets
vc.setCompletionHandler(handler: {
self.dismiss(animated: true, completion: nil);
})
let navController = UINavigationController(rootViewController: (vc as UIViewController))
navController.modalPresentationStyle = .fullScreen
navController.modalPresentationCapturesStatusBarAppearance = true
navController.navigationBar.barTintColor = UIColor.black
(vc as UIViewController).navigationItem.leftItemsSupplementBackButton = true
self.present(navController, animated: false, completion: nil)
showVideo(selectedItem: composition.children[0])
}
@objc func refreshItems(_ notification: Notification) {
@ -427,12 +391,7 @@ class DetailViewController: UIViewController, UICollectionViewDelegateFlowLayout
}
if sectionItem.isVideo() {
if self.delegate!.settings().newPlayer {
self.showNewVideo(selectedItem: selectedItem)
}
else {
self.showVideo(selectedItem: selectedItem)
}
self.showVideo(selectedItem: selectedItem)
} else if sectionItem.isPic() {
self.showPhotos(sectionItem.children)
} else if sectionItem.isWeb() {
@ -501,7 +460,7 @@ class DetailViewController: UIViewController, UICollectionViewDelegateFlowLayout
return delegate2.window as! UIWindow
}
func showNewVideo(selectedItem: MediaItem) {
func showVideo(selectedItem: MediaItem) {
var se = selectedItem
var children = [MediaItem]()
var clonedChildren = [MediaItem]()
@ -544,37 +503,6 @@ class DetailViewController: UIViewController, UICollectionViewDelegateFlowLayout
getWindow().rootViewController!.present(pc, animated: true)
}
func showVideo(selectedItem: MediaItem) {
var se = selectedItem
var children = [MediaItem]()
if selectedItem.parent != nil {
children = selectedItem.parent!.children
}
var pc: VideoController
pc = VideoController()
pc.detailDelegate = delegate
pc.setCurrentItem(item: se)
pc.setItems(items: children)
pc.setCompletionHandler(handler: {
self.collectionView.reloadData()
self.collectionView.collectionViewLayout.invalidateLayout()
self.delegate!.saveItem(selectedItem: se)
self.dismiss(animated: true, completion: nil);
})
let navController = UINavigationController(rootViewController: pc)
navController.modalPresentationStyle = .fullScreen
navController.modalPresentationCapturesStatusBarAppearance = true
navController.navigationBar.barTintColor = UIColor.black
pc.navigationItem.leftItemsSupplementBackButton = true
self.present(navController, animated: false, completion: nil)
}
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
if let detail: MediaItem = self.detailItem {
var selectedItem = detail.children[indexPath.section]

12
kplayer/detail/EditItemView.swift

@ -111,10 +111,10 @@ struct EditItemView: View {
return String(format: "%02d:%.1f", min, sec)
}
}
struct EditItemView_Previews: PreviewProvider {
static var previews: some View {
EditItemView(item: MediaItem(name: "extern", path: "", root: "", type: ItemType.FAVROOT), len: 1000, delegate: VideoController())
}
}
//
//struct EditItemView_Previews: PreviewProvider {
// static var previews: some View {
// EditItemView(item: MediaItem(name: "extern", path: "", root: "", type: ItemType.FAVROOT), len: 1000, delegate: SVideoPlayer(completionHandler: nil, model: SVideoModel(allItems: [](),currentSnapshot: nil, )))
// }
//}

821
kplayer/detail/VideoController.swift

@ -1,821 +0,0 @@
//
// Created by Marco Schmickler on 26.05.15.
// Copyright (c) 2015 Marco Schmickler. All rights reserved.
//
import Foundation
import UIKit
import Haneke
import AVFoundation
import SwiftUI
protocol DownloadDelegate {
func killFFMPEG()
func dlserverlen(result: @escaping (String) -> ())
func download(url: URL, path: String, result: @escaping (URL) -> () )
func downloadToServer(path: String, url: URL, result: @escaping (String) -> ())
func inProgress() -> Int
}
protocol ItemController {
func setCurrentItem(item: MediaItem)
func setItems(items: [MediaItem])
func setCompletionHandler(handler: @escaping (() -> Void))
}
// Trimmer: https://github.com/Tomohiro-Yamashita/VideoTimelineView
class VideoController: UIViewController, ItemController, BMPlayerDelegate, EditItemDelegate {
var player = BMPlayer()
// played on startup
var currentItem: MediaItem?
var currentSnapshot: MediaItem?
var allItems = [MediaItem]()
var detailDelegate: DetailDelegate?
var downloadDelegate: DownloadDelegate?
var loopStart = 0.0
var completionHandler: (() -> Void)?
var buttons = Dictionary<UIButton, MediaItem>()
var barbutton: UIBarButtonItem?
var speedButton: UIBarButtonItem?
var loopButton: UIBarButtonItem?
var aspectButton: UIBarButtonItem?
var playButton: UIBarButtonItem?
var favButton: UIBarButtonItem?
var backButton: UIBarButtonItem?
var reviewButton: UIBarButtonItem?
let speedOptions = [ 0.25, 0.5, 1, 2 ]
var speedOption = 2
var aspect = 1
var loopMode = false
var thumbnailTime: TimeInterval = 0.0
var edit = false
var allowEdit = true
var index = 0
var ffmpeg = false
var urls : [URL]?
var kv : EditItemView?
var hc : UIHostingController<EditItemView>?
override func viewDidLoad() {
super.viewDidLoad()
barbutton = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(VideoController.captureThumbnail));
navigationItem.rightBarButtonItems = [barbutton!]
backButton = UIBarButtonItem(title: "0:00", style:UIBarButtonItem.Style.plain, target: self, action: #selector(VideoController.back(_:)))
speedButton = UIBarButtonItem(title:"1.0", style:UIBarButtonItem.Style.plain, target: self, action: #selector(VideoController.speed(_:)))
loopButton = UIBarButtonItem(title:"1.0", style:UIBarButtonItem.Style.plain, target: self, action: #selector(VideoController.loop(_:)))
aspectButton = UIBarButtonItem(title:"1", style:UIBarButtonItem.Style.plain, target: self, action: #selector(VideoController.aspect(_:)))
playButton = UIBarButtonItem(barButtonSystemItem: .play, target: self, action: #selector(VideoController.startstop(_:)))
favButton = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(VideoController.favorite(_:)))
reviewButton = UIBarButtonItem(title:"Edit ", style:UIBarButtonItem.Style.plain, target: self, action: #selector(VideoController.doEdit(_:)))
navigationItem.leftBarButtonItems = [backButton!, speedButton!, playButton!, loopButton!, aspectButton!, favButton!, reviewButton!]
//MediaItem(name: "extern", path: "", root: "", type: ItemType.FAVROOT)
// pc.view.isHidden = true
view.addSubview(player)
player.snp.makeConstraints { (make) in
// make.top.equalTo(self.view).offset(100)
make.left.right.equalTo(self.view)
make.height.equalTo(self.view)
// Note here, the aspect ratio 16:9 priority is lower than 1000 on the line, because the 4S iPhone aspect ratio is not 16:9
// make.height.equalTo(player.snp.width).multipliedBy(9.0/16.0).priority(750)
}
player.delegate = self
// Back button event
player.backBlock = { (b) in
let _ = self.navigationController?.popViewController(animated: true)
}
if let c = currentItem, let url = c.playerURL {
print(url)
play(url as URL)
// player.playerLayer!.player!.volume = 0.0
update()
}
loopMode = detailDelegate!.settings().autoloop
if detailDelegate!.settings().edit {
doEdit(self)
}
updateLoop()
}
func editItem() {
cancelEdit()
let currentItem = player.playerLayer?.player?.currentItem
let totalTime : Double
if let playerItem = currentItem {
totalTime = TimeInterval(playerItem.duration.value) / TimeInterval(playerItem.duration.timescale)
}
else {
totalTime = 1000
}
if (currentSnapshot!.length < 0.001) {
setEnd()
}
kv = EditItemView(item: currentSnapshot!, len: totalTime, delegate: self)
hc = UIHostingController(rootView: kv!)
addChild(hc!)
view.addSubview(hc!.view)
hc!.view.backgroundColor = .clear
hc!.view.snp.makeConstraints { (make) in
make.width.equalTo(400)
make.height.equalTo(300)
make.right.equalToSuperview()
make.top.equalToSuperview()
}
}
func captureZoom() {
if let item = currentSnapshot {
item.scale = Double(self.player.zoom)
item.offset = CGPoint(x: self.player.xpos, y: self.player.ypos)
}
}
func setStart() {
if let item = currentSnapshot {
let ctime = currentTime()
item.time = ctime
item.objectWillChange.send()
}
}
private func currentTime() -> Double {
CMTimeGetSeconds(player.playerLayer!.playerItem!.currentTime())
}
func setEnd() {
if let item = currentSnapshot {
let ctime = currentTime()
if (ctime > item.time) {
item.length = ctime - item.time
item.objectWillChange.send()
}
}
}
func cancelEdit() {
if hc == nil {
return
}
hc!.view.removeFromSuperview()
kv = nil
hc = nil
}
func seek(_ value: Double) {
self.player.seekSmoothlyToTime(newChaseTime: value)
}
override var prefersStatusBarHidden: Bool {
return true
}
func setItems(items: [MediaItem]) {
allItems = items
}
func setCurrentItem(item: MediaItem) {
currentItem = item
}
func setCompletionHandler(handler: @escaping (() -> Void)) {
completionHandler = handler
}
@objc func doEdit(_ sender: AnyObject) {
if (!allowEdit) {
return
}
if (edit) {
edit = false
reviewButton!.tintColor = UIColor.blue
}
else {
edit = true
loopMode = false
updateLoop()
reviewButton!.tintColor = UIColor.yellow
}
}
func showAlert(title:String, message:String ) {
let alertVC = UIAlertController(title: title, message: message, preferredStyle: .alert)
let okAction = UIAlertAction(title: "Ok", style: .default, handler: nil)
alertVC.addAction(okAction)
DispatchQueue.main.async() { () -> Void in
self.present(alertVC, animated: true, completion: nil)
}
}
@objc func favorite(_ sender: AnyObject) {
downloadDelegate?.killFFMPEG()
print("favorite")
let inProgress = downloadDelegate?.inProgress()
let alertController = UIAlertController(title: "In Progress: \(inProgress)", message: "Download", preferredStyle: .alert)
downloadDelegate?.dlserverlen { c in
alertController.title = "On Server: \(c)";
}
if let c = currentSnapshot {
if (c.loop) {
let oneAction = UIAlertAction(title: "One", style: .default) { (action) in
self.save(currentSnapshot: c, name: "1")
}
let twoAction = UIAlertAction(title: "Two", style: .default) { (action) in
self.save(currentSnapshot: c, name: "2")
}
let threeAction = UIAlertAction(title: "Three", style: .default) { (action) in
self.save(currentSnapshot: c, name: "3")
}
alertController.addAction(oneAction)
alertController.addAction(twoAction)
alertController.addAction(threeAction)
}
}
let downloadAction = UIAlertAction(title: "Download to fav", style: .default) { (action) in
let url = self.currentItem!.playerURL
self.downloadDelegate?.download(url: url!, path: "download") { url in
self.showAlert(title: url.lastPathComponent, message: "ready")
}
}
let url = self.currentItem!.playerURL
if url!.pathExtension != "m3u8" {
alertController.addAction(downloadAction)
}
let serverAction = UIAlertAction(title: "Download to server", style: .default) { (action) in
let url = self.currentItem!.playerURL
if url!.pathExtension == "m3u8" {
self.ffmpeg = true
}
self.downloadDelegate?.downloadToServer(path: self.currentItem!.root, url: url!, result: {
(r) in
print(r)
self.showAlert(title: "download ready", message: r)
if (r == "exists") {
}
})
}
alertController.addAction(serverAction)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (action) in
}
alertController.addAction(cancelAction)
present(alertController, animated: true)
}
func save(currentSnapshot c: MediaItem, name: String) {
do {
try FileHelper.createDir(name: name)
var file = FileHelper.getDocumentsDirectory().appendingPathComponent(name).appendingPathComponent(c.name)
if file.pathExtension != "mp4" {
file = file.appendingPathExtension("mp4")
}
print (file)
var dur = player.loopEnd - loopStart
if (dur < 0) {
return
}
VideoHelper.export(item: player.avPlayer!.currentItem!, clipStart: loopStart, clipDuration: dur, file: file, progress: { p in print(p) }) { url in
self.showAlert(title: c.name, message: "saved")
}
var s : MediaModel;
if (c.type == ItemType.SNAPSHOT && c.parent != nil) {
s = c.parent!.toMediaModel()
do {
if c.thumbUrlAbsolute != "" {
let local = try Data(contentsOf: URL(string: c.thumbUrlAbsolute)!)
let tfile = FileHelper.getDocumentsDirectory().appendingPathComponent(name).appendingPathComponent(s.name).appendingPathExtension("jpg")
try local.write(to: tfile)
}
}
catch {
// ignore
}
}
else {
s = c.toMediaModel()
}
let jfile = FileHelper.getDocumentsDirectory().appendingPathComponent(name).appendingPathComponent(s.name).appendingPathExtension("json")
let jsonEncoder = JSONEncoder()
let jsonData = try! jsonEncoder.encode(s)
let json = String(data: jsonData, encoding: String.Encoding.utf8)
print(json)
try json!.write(to: jfile, atomically: true, encoding: .utf8)
} catch {
print(error)
}
}
@objc func startstop(_ sender: AnyObject) {
if player.isPlaying {
player.pause()
}
else {
if player.isPlayToTheEnd {
player.seekSmoothlyToTime(newChaseTime: 0)
player.isPlayToTheEnd = false
}
player.play()
}
print("play")
}
@objc func loop(_ sender: AnyObject) {
if edit {
loopMode = false
}
else {
loopMode = !loopMode
}
updateLoop()
}
func updateLoop() {
if loopMode {
loopButton!.title = "loop"
}
else {
loopButton!.title = "cont"
}
}
@objc func aspect(_ sender: AnyObject) {
aspect += 1
if aspect > 3 {
aspect = 1
}
switch aspect {
case 1:
player.aspectx = 1.0
player.aspecty = 1.0
case 2:
player.aspectx = 0.9
player.aspecty = 1.0
case 3:
player.aspectx = 1.0
player.aspecty = 0.9
default:
print("aspect")
}
// todo player.verticalMoved(0)
player.transformLayer()
aspectButton!.title = "\(aspect)"
}
@objc func speed(_ sender: AnyObject) {
speedOption += 1
if speedOption >= speedOptions.count {
speedOption = 0
}
let speed = Float(speedOptions[speedOption])
player.playerLayer!.player!.rate = speed
speedButton!.title = "\(speed)"
print("speed \(speed)")
}
@IBAction func back(_ sender: AnyObject) {
player.playerLayer?.pause()
player.playerLayer?.prepareToDeinit()
completionHandler!()
}
func play(_ url: URL) {
var def = [BMPlayerResourceDefinition]()
var index = 0;
var count = 0;
if allItems.isEmpty {
print("no items found")
return
}
for i in allItems {
if let a = urls {
let r = BMPlayerCompositionResourceDefinition(url: i.playerURL!, definition: i.name)
r.assets = a
def.append(r)
}
else {
let r = BMPlayerResourceDefinition(url: i.playerURL!, definition: i.name);
def.append(r)
if (LocalManager.sharedInstance.fixDocumentURL(url.absoluteString) == LocalManager.sharedInstance.fixDocumentURL(i.playerURL?.absoluteString)) {
index = count
}
count += 1
}
}
let asset = BMPlayerResource(name: "video", definitions: def)
// let asset = BMPlayerResource(url: url)
player.setVideo(resource: asset, definitionIndex: index)
player.playerLayer!.player!.automaticallyWaitsToMinimizeStalling = false
if let item = player.playerLayer?.playerItem {
item.canUseNetworkResourcesForLiveStreamingWhilePaused = true
item.preferredForwardBufferDuration = 10
}
}
func addItemButton(_ newItem: MediaItem) {
let frame = CGRect(x: 0, y: 0, width: 66.0, height: 44.0);
let button = UIButton(frame: frame)
button.showsTouchWhenHighlighted = true
button.addTarget(self, action: #selector(thumbnailClicked(_:)), for: .touchDown)
if newItem.image != nil {
let icon = newItem.image!.scaleToSize(66.0, height: 44.0)
button.setBackgroundImage(icon, for: UIControl.State());
} else {
if newItem.thumbUrl != nil {
let URL = Foundation.URL(string: newItem.thumbUrlAbsolute)!
Shared.imageCache.fetch(URL: URL).onSuccess {
i in
let icon = i.scaleToSize(66.0, height: 44.0)
button.setBackgroundImage(icon, for: UIControl.State.normal);
}
}
}
let barbutton = UIBarButtonItem(customView: button);
if navigationItem.rightBarButtonItems == nil {
navigationItem.rightBarButtonItems = []
}
navigationItem.rightBarButtonItems!.append(barbutton)
buttons[button] = newItem
}
@objc func thumbnailClicked(_ source: UIButton) {
if let currentSnapshot = buttons[source] {
if (edit) {
self.currentSnapshot = currentSnapshot
editItem()
}
else {
gotoSnapshot(currentSnapshot: currentSnapshot)
}
}
}
private func gotoSnapshot(currentSnapshot: MediaItem) {
player.forceSeekSmoothlyToTime(newChaseTime: currentSnapshot.time)
loopStart = currentSnapshot.time
player.loopEnd = loopStart + currentSnapshot.length
if loopMode && currentSnapshot.scale > 0 {
player.zoom = Float(currentSnapshot.scale)
player.xpos = currentSnapshot.offset.x
player.ypos = currentSnapshot.offset.y
player.transformLayer()
}
self.currentSnapshot = currentSnapshot
}
@objc func update() {
reviewButton!.title = currentItem!.name
if currentItem!.type == ItemType.SNAPSHOT {
player.seek(currentItem!.time)
loopStart = currentItem!.time
player.loopEnd = currentItem!.time + currentItem!.length
currentItem = currentItem!.parent
} else {
if !currentItem!.children.isEmpty {
player.seekSmoothlyToTime(newChaseTime: currentItem!.children[0].time)
}
else {
let duration = player.playerLayer!.playerItem!.duration
if !duration.isIndefinite {
print(duration)
player.seekSmoothlyToTime(newChaseTime: duration.seconds / 2.0)
}
}
}
navigationItem.rightBarButtonItems = [barbutton!]
for c in currentItem!.children {
addItemButton(c)
}
player.play()
}
func installGestures(_ moviePlayer: UIView) {
let twoFingersTwoTapsGesture = UITapGestureRecognizer(target: self, action: #selector(captureThumbnail))
twoFingersTwoTapsGesture.numberOfTapsRequired = 2
twoFingersTwoTapsGesture.numberOfTouchesRequired = 2
moviePlayer.addGestureRecognizer(twoFingersTwoTapsGesture)
let sR = UISwipeGestureRecognizer(target: self, action: #selector(swipeRight))
sR.direction = UISwipeGestureRecognizer.Direction.right
sR.numberOfTouchesRequired = 1
moviePlayer.addGestureRecognizer(sR)
let sL = UISwipeGestureRecognizer(target: self, action: #selector(swipeLeft))
sL.direction = UISwipeGestureRecognizer.Direction.left
sL.numberOfTouchesRequired = 1
moviePlayer.addGestureRecognizer(sL)
let sR2 = UISwipeGestureRecognizer(target: self, action: #selector(swipeDown))
sR2.direction = UISwipeGestureRecognizer.Direction.down
sR2.numberOfTouchesRequired = 1
moviePlayer.addGestureRecognizer(sR2)
let sR3 = UISwipeGestureRecognizer(target: self, action: #selector(swipeUp))
sR3.direction = UISwipeGestureRecognizer.Direction.up
sR3.numberOfTouchesRequired = 1
moviePlayer.addGestureRecognizer(sR3)
}
@objc func swipeUp() {
print("u")
print("Type: \(currentItem!.type) Count: \(currentItem!.children.count) Index: \(index) Current: \(currentItem!.index)")
if !edit && (currentItem!.children.isEmpty || !(index < currentItem!.children.count - 1)) {
print ("switch")
var newIndex = currentItem!.index + 1
if currentItem!.parent!.children.count <= newIndex {
newIndex = 0
}
currentItem = currentItem!.parent!.children[newIndex]
print("'Switched Type: \(currentItem!.type) Count: \(currentItem!.children.count) Index: \(index) Current: \(currentItem!.index)")
index = 0
// player.playWithURL(currentItem!.playerURL)
Timer.scheduledTimer(timeInterval: 1.2, target: self, selector: #selector(update), userInfo: nil, repeats: false)
return
}
if !(currentItem!.children.isEmpty) {
print ("switch internal")
if index < currentItem!.children.count - 1 {
index+=1;
} else {
index = 0;
}
let child = currentItem!.children[index]
// player.currentPlaybackTime = child.time!
// player.currentPlaybackRate = Float(speedOptions[speedOption])
}
}
@objc func swipeRight() {
// print("r")
// player.currentPlaybackRate = Float(0.0)
// player.currentPlaybackTime = player.currentPlaybackTime - 30.0
// Timer.scheduledTimer(timeInterval: 0.9,
// target: self,
// selector: #selector(resumePlay),
// userInfo: nil,
// repeats: false)
}
@objc func swipeDown() {
print("d")
if !edit {
var newIndex = currentItem!.index - 1
if newIndex < 0 {
newIndex = 0
}
currentItem = currentItem!.parent!.children[newIndex]
index = 0;
// player.contentURL = currentItem!.playerURL
player.play()
Timer.scheduledTimer(timeInterval: 1.2, target: self, selector: #selector(update), userInfo: nil, repeats: false)
return
}
// player.currentPlaybackTime = player.currentPlaybackTime + 10.0
Timer.scheduledTimer(timeInterval: 0.9,
target: self,
selector: #selector(resumePlay),
userInfo: nil,
repeats: false)
}
@objc func swipeLeft() {
print("l")
// player.currentPlaybackRate = Float(0.0)
// player.currentPlaybackTime = player.currentPlaybackTime + 30.0
Timer.scheduledTimer(timeInterval: 0.9,
target: self,
selector: #selector(resumePlay),
userInfo: nil,
repeats: false)
}
@objc func resumePlay() {
// player.currentPlaybackRate = Float(speedOptions[speedOption])
print("resumePlay")
}
@objc func captureThumbnail() {
if edit {
let asset = player.playerLayer!.playerItem!.asset
do {
let imgGenerator = AVAssetImageGenerator(asset: asset)
imgGenerator.appliesPreferredTrackTransform = true
let time = player.playerLayer!.playerItem!.currentTime()
let cgImage = try imgGenerator.copyCGImage(at: time, actualTime: nil)
let thumbnail = UIImage(cgImage: cgImage)
showThumbnail(thumbnail: thumbnail, time: time)
} catch let error {
print("*** Error generating thumbnail: \(error.localizedDescription)")
}
// thumbnailTime = player.currentPlaybackTime
print("tap \(thumbnailTime)")
// moviePlayer!.requestThumbnailImages(atTimes: [thumbnailTime],
// timeOption: MPMovieTimeOption.exact);
}
else {
detailDelegate!.favItem(currentItem!)
}
}
func showThumbnail(thumbnail: UIImage, time: CMTime) {
let newItem = MediaItem(name: currentItem!.name, path: currentItem!.path, root: currentItem!.root, type: ItemType.SNAPSHOT)
newItem.image = thumbnail
newItem.time = time.seconds
newItem.parent = currentItem!
newItem.local = currentItem!.local
currentItem!.children.append(newItem)
print(newItem.time)
addItemButton(newItem)
}
func moveUp() {
if !loopMode {
return
}
let t = Date().timeIntervalSince1970
if lastMove + 2 > t {
return
}
lastMove = t
if let c = currentItem?.children {
if !c.isEmpty{
if let s = currentSnapshot {
if var i = c.firstIndex { x in x===s } {
print(i)
i+=1
if i >= c.count {
i = 0
}
gotoSnapshot(currentSnapshot: c[i])
}
}
else {
gotoSnapshot(currentSnapshot: c[0])
}
}
}
}
var lastMove = 0.0
func moveDown() {
if !loopMode {
return
}
let t = Date().timeIntervalSince1970
if lastMove + 2 > t {
return
}
lastMove = t
if let c = currentItem?.children {
if !c.isEmpty{
if let s = currentSnapshot {
if var i = c.firstIndex { x in x===s } {
print(i)
i-=1
if i < 0 {
i = c.count-1
}
gotoSnapshot(currentSnapshot: c[i])
}
}
else {
gotoSnapshot(currentSnapshot: c[0])
}
}
}
}
func bmPlayer(player: BMPlayer) {
let speed = Float(speedOptions[speedOption])
if let pl = player.playerLayer!.player {
pl.rate = speed
}
}
func bmPlayer(player: BMPlayer, playerStateDidChange state: BMPlayerState) {
print("state")
}
func bmPlayer(player: BMPlayer, loadedTimeDidChange loadedDuration: TimeInterval, totalDuration: TimeInterval) {
// print("load")
}
func bmPlayer(player: BMPlayer, playTimeDidChange currentTime: TimeInterval, totalTime: TimeInterval) {
if loopMode {
if currentTime > player.loopEnd && loopStart < player.loopEnd {
player.forceSeekSmoothlyToTime(newChaseTime: loopStart)
}
}
if let b = backButton {
b.title = BMPlayer.formatSecondsToString(currentTime)
}
}
func bmPlayer(player: BMPlayer, playerIsPlaying playing: Bool) {
print("playing")
}
func bmPlayer(player: BMPlayer, playerOrientChanged isFullscreen: Bool) {
print("orient")
}
}

3
kplayer/master/KSettingsView.swift

@ -27,9 +27,6 @@ struct KSettingsView: View {
Toggle(isOn: $kSettings.edit, label: {
Text("Edit")
})
Toggle(isOn: $kSettings.newPlayer, label: {
Text("New Player")
})
}
}
Button(action: {

23
kplayer/master/MasterViewController.swift

@ -103,13 +103,22 @@ class MasterViewController: UITableViewController, UISearchResultsUpdating, UITa
let submitAction = UIAlertAction(title: "Submit", style: .default) { [unowned ac] _ in
let answer = ac.textFields![0].text!
do {
try FileHelper.createDir(name: answer)
item.name = answer
item.path = answer
self.tableView.reloadData()
} catch {
print(error)
if (item.root == "/tags") {
DatabaseManager.sharedInstance.createTag(answer)
let m = MediaItem(name: answer, path: answer, root: "tags", type: ItemType.TAG)
m.local = true
item.children.append(m)
m.parent = item
}
else {
do {
try FileHelper.createDir(name: answer)
item.name = answer
item.path = answer
self.tableView.reloadData()
} catch {
print(error)
}
}
}

7
kplayer/master/NetworkDelegate.swift

@ -52,6 +52,13 @@ class NetworkDelegate: MasterDelegate, DetailDelegate {
return
}
if selectedItem.type == ItemType.TAGROOT {
DatabaseManager.sharedInstance.loadTags(completionHandler: weiter)
return
}
if (NetworkManager.sharedInstance.offline) {
completionHandler(selectedItem)
}

16
kplayer/photo/PhotoController.swift

@ -225,22 +225,6 @@ class MediaPhotoController: NIToolbarPhotoViewController, NIPhotoAlbumScrollView
}
return
}
let controller = VideoController()
controller.edit = false
controller.allowEdit = false
controller.currentItem = currentItem
controller.setItems(items: [currentItem])
controller.navigationItem.leftItemsSupplementBackButton = true
navigationController!.navigationBar.barTintColor = UIColor.black
navigationController!.pushViewController(controller, animated: true)
controller.completionHandler = {
() in
// NetworkManager.sharedInstance.saveItem(self.currentItem!)
self.dismiss(animated: true, completion: nil);
}
}
override func loadView() {

820
kplayer/video/BMPlayer.swift

@ -1,820 +0,0 @@
//
// BMPlayer.swift
// Pods
//
// Created by BrikerMan on 16/4/28.
//
//
import UIKit
import SnapKit
import MediaPlayer
/// BMPlayerDelegate to obserbe player state
public protocol BMPlayerDelegate : class {
func bmPlayer(player: BMPlayer)
func bmPlayer(player: BMPlayer, playerStateDidChange state: BMPlayerState)
func bmPlayer(player: BMPlayer, loadedTimeDidChange loadedDuration: TimeInterval, totalDuration: TimeInterval)
func bmPlayer(player: BMPlayer, playTimeDidChange currentTime : TimeInterval, totalTime: TimeInterval)
func bmPlayer(player: BMPlayer, playerIsPlaying playing: Bool)
func bmPlayer(player: BMPlayer, playerOrientChanged isFullscreen: Bool)
func moveUp()
func moveDown()
}
/**
internal enum to check the pan direction
- horizontal: horizontal
- vertical: vertical
*/
enum BMPanDirection: Int {
case horizontal = 0
case vertical = 1
}
open class BMPlayer: UIView, UIGestureRecognizerDelegate {
open var zoom = Float(1.0)
open var aspectx = Float(1.0)
open var aspecty = Float(1.0)
var xpos = 0.0
var ypos = 0.0
var loopEnd = 0.0
open var pinchGesture: UIPinchGestureRecognizer!
open var moveGesture: UIPanGestureRecognizer!
open weak var delegate: BMPlayerDelegate?
open var backBlock:((Bool) -> Void)?
/// Gesture to change volume / brightness
open var panGesture: UIPanGestureRecognizer!
private func initGesture() {
pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(self.pinched(_:)))
self.addGestureRecognizer(pinchGesture)
// moveGesture = UIPanGestureRecognizer(target: self, action: #selector(self.moved(_:)))
// moveGesture.minimumNumberOfTouches = 2
// moveGesture.maximumNumberOfTouches = 2
// self.addGestureRecognizer(moveGesture)
}
func panDir(_ pan: UIPanGestureRecognizer) {
let velocityPoint = pan.velocity(in: self)
xpos += (Double(velocityPoint.x) / 50.0)
ypos += (Double(velocityPoint.y) / 50.0)
transformLayer()
}
@objc fileprivate func moved(_ gestureRecognizer: UIPanGestureRecognizer) {
}
@objc fileprivate func pinched(_ gestureRecognizer: UIPinchGestureRecognizer) {
if gestureRecognizer.state == .began || gestureRecognizer.state == .changed {
zoom *= Float(gestureRecognizer.scale)
if zoom < 0.1 {
zoom = 1.0
xpos = 0
ypos = 0
}
gestureRecognizer.scale = 1.0
transformLayer()
}
}
func transformLayer() {
let t = CATransform3DMakeTranslation(CGFloat(xpos), CGFloat(ypos), 0.0)
playerLayer!.layer.transform = CATransform3DScale(t, CGFloat(zoom * aspectx), CGFloat(zoom * aspecty), 1.0)
}
/// AVLayerVideoGravityType
open var videoGravity = AVLayerVideoGravity.resizeAspect {
didSet {
self.playerLayer?.videoGravity = videoGravity
}
}
open var isPlaying: Bool {
get {
return playerLayer?.isPlaying ?? false
}
}
//Closure fired when play time changed
open var playTimeDidChange:((TimeInterval, TimeInterval) -> Void)?
//Closure fired when play state chaged
@available(*, deprecated, message: "Use newer `isPlayingStateChanged`")
open var playStateDidChange:((Bool) -> Void)?
open var playOrientChanged:((Bool) -> Void)?
open var isPlayingStateChanged:((Bool) -> Void)?
open var playStateChanged:((BMPlayerState) -> Void)?
open var avPlayer: AVPlayer? {
return playerLayer?.player
}
open var playerLayer: BMPlayerLayerView?
fileprivate var resource: BMPlayerResource!
fileprivate var currentDefinition = 0
var controlView: BMPlayerControlView!
fileprivate var customControlView: BMPlayerControlView?
fileprivate var isFullScreen:Bool {
get {
return UIApplication.shared.statusBarOrientation.isLandscape
}
}
///
fileprivate var panDirection = BMPanDirection.horizontal
/// 竿
fileprivate var volumeViewSlider: UISlider!
fileprivate let BMPlayerAnimationTimeInterval: Double = 4.0
fileprivate let BMPlayerControlBarAutoFadeOutTimeInterval: Double = 0.5
///
fileprivate var sumTime : TimeInterval = 0
var totalDuration : TimeInterval = 0
fileprivate var currentPosition : TimeInterval = 0
fileprivate var shouldSeekTo : TimeInterval = 0
fileprivate var isURLSet = false
fileprivate var isSliderSliding = false
fileprivate var isPauseByUser = false
fileprivate var isVolume = false
fileprivate var isSkip = false
fileprivate var skipAmount = 0.0
fileprivate var isMaskShowing = false
fileprivate var isSlowed = false
fileprivate var isMirrored = false
var isPlayToTheEnd = false
//
fileprivate var aspectRatio: BMPlayerAspectRatio = .default
//Cache is playing result to improve callback performance
fileprivate var isPlayingCache: Bool? = nil
var isScrub = false
var isSeekInProgress = false
var chaseTime = CMTime.zero
var continueTime = 0.0
// MARK: - Public functions
/**
Play
- parameter resource: media resource
- parameter definitionIndex: starting definition index, default start with the first definition
*/
open func setVideo(resource: BMPlayerResource, definitionIndex: Int = 0) {
isURLSet = false
self.resource = resource
currentDefinition = definitionIndex
controlView.prepareUI(for: resource, selectedIndex: definitionIndex)
if BMPlayerConf.shouldAutoPlay {
isURLSet = true
let asset = resource.definitions[definitionIndex]
let size = asset.avURLAsset.videoSize()
if (size.height < 1500) {
zoom = LocalManager.sharedInstance.settings.scale
if (zoom < 1.0) {
xpos = -100
}
transformLayer()
}
playerLayer?.playAsset(asset: asset.avURLAsset)
} else {
controlView.showCover(url: resource.cover)
controlView.hideLoader()
}
}
/**
auto start playing, call at viewWillAppear, See more at pause
*/
open func autoPlay() {
if !isPauseByUser && isURLSet && !isPlayToTheEnd {
play()
}
}
func isFile() -> Bool {
guard resource != nil else { return false }
let asset = resource.definitions[currentDefinition]
return asset.url.isFileURL
}
/**
Play
*/
open func play() {
guard resource != nil else { return }
if !isURLSet {
let asset = resource.definitions[currentDefinition]
playerLayer?.playAsset(asset: asset.avURLAsset)
controlView.hideCoverImageView()
isURLSet = true
}
panGesture.isEnabled = true
playerLayer?.play()
isPauseByUser = false
if let d = delegate { d.bmPlayer(player: self) }
}
/**
Pause
- parameter allow: should allow to response `autoPlay` function
*/
open func pause(allowAutoPlay allow: Bool = false) {
playerLayer?.pause()
isPauseByUser = !allow
}
/**
seek
- parameter to: target time
*/
open func seek(_ to:TimeInterval, completion: (()->Void)? = nil) {
chaseTime = CMTime(value: Int64(to * 10000), timescale: CMTimeScale(10000))
playerLayer?.seek(to: to, completion: completion)
}
/**
update UI to fullScreen
*/
open func updateUI(_ isFullScreen: Bool) {
controlView.updateUI(isFullScreen)
}
/**
increade volume with step, default step 0.1
- parameter step: step
*/
open func addVolume(step: Float = 0.1) {
self.volumeViewSlider.value += step
}
/**
decreace volume with step, default step 0.1
- parameter step: step
*/
open func reduceVolume(step: Float = 0.1) {
self.volumeViewSlider.value -= step
}
/**
prepare to dealloc player, call at View or Controllers deinit funciton.
*/
open func prepareToDealloc() {
playerLayer?.prepareToDeinit()
controlView.prepareToDealloc()
}
/**
If you want to create BMPlayer with custom control in storyboard.
create a subclass and override this method.
- return: costom control which you want to use
*/
open func storyBoardCustomControl() -> BMPlayerControlView? {
return nil
}
// https://gist.github.com/shaps80/ac16b906938ad256e1f47b52b4809512
public func forceSeekSmoothlyToTime(newChaseTime: Double) {
chaseTime = CMTime.zero
seekSmoothlyToTime(newChaseTime: CMTime(value: Int64(newChaseTime * 10000), timescale: CMTimeScale(10000)))
}
public func seekSmoothlyToTime(newChaseTime: Double) {
seekSmoothlyToTime(newChaseTime: CMTime(value: Int64(newChaseTime * 10000), timescale: CMTimeScale(10000)))
}
public func seekSmoothlyToTime(newChaseTime: CMTime) {
if CMTimeCompare(newChaseTime, chaseTime) != 0 {
chaseTime = newChaseTime
if !isSeekInProgress {
trySeekToChaseTime()
}
}
}
private func trySeekToChaseTime() {
guard playerLayer!.player?.status == .readyToPlay else { return }
actuallySeekToTime()
}
private func actuallySeekToTime() {
isSeekInProgress = true
let seekTimeInProgress = chaseTime
playerLayer!.player?.seek(to: seekTimeInProgress, toleranceBefore: CMTime.zero, toleranceAfter: CMTime.zero) { [weak self] _ in
guard let `self` = self else { return }
if CMTimeCompare(seekTimeInProgress, self.chaseTime) == 0 {
self.isSeekInProgress = false
} else {
self.trySeekToChaseTime()
}
}
}
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if touch.view?.isDescendant(of: controlView.topWrapperView) == true {
return true
}
else {
return true
}
}
// MARK: - Action Response
@objc open func panDirection(_ pan: UIPanGestureRecognizer) {
let resolution = 10000.0
if pan.numberOfTouches <= 1 {
// viewPan
let locationPoint = pan.location(in: self)
//
// point
let velocityPoint = pan.velocity(in: self)
//
switch pan.state {
case UIGestureRecognizer.State.began:
// 使
let x = abs(velocityPoint.x)
let y = abs(velocityPoint.y)
if (locationPoint.y < self.bounds.size.height * 1 / 7) {
self.isSkip = (zoom <= 1.0)
isScrub = false
if (!isPlaying) {
isScrub = true
let currentTime = CMTimeGetSeconds(playerLayer!.player!.currentTime())
continueTime = currentTime
let vel = velocityPoint.x + velocityPoint.y
var time = CMTimeMake(value: Int64((currentTime + (Float64(vel) / resolution))*10000), timescale: 10000)
seekSmoothlyToTime(newChaseTime: time)
}
} else {
isScrub = false
self.isSkip = true
self.skipAmount = 0.0
}
if x > y {
if BMPlayerConf.enablePlaytimeGestures {
self.panDirection = BMPanDirection.horizontal
// sumTime
if let player = playerLayer?.player {
let time = player.currentTime()
self.sumTime = TimeInterval(time.value) / TimeInterval(time.timescale)
}
}
} else {
self.panDirection = BMPanDirection.vertical
if locationPoint.x > self.bounds.size.width / 2 {
self.isVolume = false
} else {
self.isVolume = false
}
}
case UIGestureRecognizer.State.changed:
if !self.isSkip && isPlaying {
xpos += (Double(velocityPoint.x) * 0.1);
ypos += (Double(velocityPoint.y) * 0.1);
transformLayer()
}
if (isScrub) {
let vel = velocityPoint.x + velocityPoint.y
var time = CMTimeMake(value: Int64((Float64(vel) / resolution)*10000), timescale: 10000)
seekSmoothlyToTime(newChaseTime: CMTimeAdd(chaseTime, time))
}
switch self.panDirection {
case BMPanDirection.horizontal:
self.horizontalMoved(velocityPoint.x)
case BMPanDirection.vertical:
self.verticalMoved(velocityPoint.y)
}
case UIGestureRecognizer.State.ended:
//
// bug
switch (self.panDirection) {
case BMPanDirection.horizontal:
controlView.hideSeekToView()
isSliderSliding = false
if isSkip && !isScrub {
print(skipAmount)
if (locationPoint.y < self.bounds.size.height * 1 / 8) {
if skipAmount > 0 {
self.sumTime += TimeInterval(5)
} else if skipAmount < -0 {
self.sumTime -= TimeInterval(5)
}
}
else {
if (skipAmount > 1500) {
self.sumTime += TimeInterval(30)
if sumTime > loopEnd {
loopEnd = 1000000;
}
} else if skipAmount > 0 {
self.sumTime += TimeInterval(10)
} else if skipAmount > -1500 {
self.sumTime -= TimeInterval(10)
} else {
self.sumTime -= TimeInterval(30)
}
}
controlView.controlViewAnimation(isShow: true)
if isPlayToTheEnd {
isPlayToTheEnd = false
seek(self.sumTime, completion: { [weak self] in
self?.play()
})
} else {
seek(self.sumTime, completion: { [weak self] in
self?.autoPlay()
})
}
}
// sumTime
self.sumTime = 0.0
if (isScrub && !isFile()) {
seek(continueTime)
}
isScrub = false
case BMPanDirection.vertical:
self.isVolume = false
}
default:
break
}
}
else {
panDir(pan)
}
}
open func verticalMoved(_ value: CGFloat) {
print(value)
if (value < -100) {
delegate?.moveDown()
// controlView(controlView: controlView, didChooseDefinition: currentDefinition+1);
}
if (value > 100) {
delegate?.moveUp()
// controlView(controlView: controlView, didChooseDefinition: currentDefinition+1);
}
if !isSkip {
}
}
open func horizontalMoved(_ value: CGFloat) {
guard BMPlayerConf.enablePlaytimeGestures else { return }
isSliderSliding = true
if !isSkip {
}
else {
skipAmount += Double(value)
}
}
@objc open func onOrientationChanged() {
self.updateUI(isFullScreen)
delegate?.bmPlayer(player: self, playerOrientChanged: isFullScreen)
playOrientChanged?(isFullScreen)
}
@objc fileprivate func fullScreenButtonPressed() {
controlView.updateUI(!self.isFullScreen)
if isFullScreen {
UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
UIApplication.shared.setStatusBarHidden(false, with: .fade)
UIApplication.shared.statusBarOrientation = .portrait
} else {
UIDevice.current.setValue(UIInterfaceOrientation.landscapeRight.rawValue, forKey: "orientation")
UIApplication.shared.setStatusBarHidden(false, with: .fade)
UIApplication.shared.statusBarOrientation = .landscapeRight
}
}
// MARK: -
deinit {
playerLayer?.pause()
playerLayer?.prepareToDeinit()
NotificationCenter.default.removeObserver(self, name: UIApplication.didChangeStatusBarOrientationNotification, object: nil)
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
if let customControlView = storyBoardCustomControl() {
self.customControlView = customControlView
}
initUI()
initUIData()
configureVolume()
preparePlayer()
initGesture()
}
// @available(*, deprecated:3.0, message:"Use newer init(customControlView:_)")
// public convenience init(customControllView: BMPlayerControlView?) {
// self.init(customControlView: customControllView)
// }
@objc public init(customControlView: BMPlayerControlView?) {
super.init(frame:CGRect.zero)
self.customControlView = customControlView
initUI()
initUIData()
configureVolume()
preparePlayer()
initGesture()
}
public convenience init() {
self.init(customControlView:nil)
}
// MARK: -
fileprivate func initUI() {
self.backgroundColor = UIColor.black
if let customView = customControlView {
controlView = customView
} else {
controlView = BMPlayerControlView()
}
addSubview(controlView)
controlView.updateUI(isFullScreen)
controlView.delegate = self
controlView.player = self
controlView.snp.makeConstraints { (make) in
make.edges.equalTo(self)
}
panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.panDirection(_:)))
panGesture.delegate = self
// panGesture.minimumNumberOfTouches = 1
// panGesture.maximumNumberOfTouches = 1
self.addGestureRecognizer(panGesture)
}
fileprivate func initUIData() {
NotificationCenter.default.addObserver(self, selector: #selector(self.onOrientationChanged), name: UIApplication.didChangeStatusBarOrientationNotification, object: nil)
}
fileprivate func configureVolume() {
let volumeView = MPVolumeView()
for view in volumeView.subviews {
if let slider = view as? UISlider {
self.volumeViewSlider = slider
}
}
}
fileprivate func preparePlayer() {
playerLayer = BMPlayerLayerView()
playerLayer!.videoGravity = videoGravity
insertSubview(playerLayer!, at: 0)
playerLayer!.snp.makeConstraints { [weak self](make) in
guard let `self` = self else { return }
make.edges.equalTo(self)
}
playerLayer!.delegate = self
controlView.showLoader()
self.layoutIfNeeded()
}
}
extension BMPlayer: BMPlayerLayerViewDelegate {
public func bmPlayer(player: BMPlayerLayerView, playerIsPlaying playing: Bool) {
controlView.playStateDidChange(isPlaying: playing)
delegate?.bmPlayer(player: self, playerIsPlaying: playing)
playStateDidChange?(player.isPlaying)
isPlayingStateChanged?(player.isPlaying)
}
public func bmPlayer(player: BMPlayerLayerView, loadedTimeDidChange loadedDuration: TimeInterval, totalDuration: TimeInterval) {
BMPlayerManager.shared.log("loadedTimeDidChange - \(loadedDuration) - \(totalDuration)")
controlView.loadedTimeDidChange(loadedDuration: loadedDuration, totalDuration: totalDuration)
delegate?.bmPlayer(player: self, loadedTimeDidChange: loadedDuration, totalDuration: totalDuration)
controlView.totalDuration = totalDuration
self.totalDuration = totalDuration
}
public func bmPlayer(player: BMPlayerLayerView, playerStateDidChange state: BMPlayerState) {
BMPlayerManager.shared.log("playerStateDidChange - \(state)")
controlView.playerStateDidChange(state: state)
switch state {
case .readyToPlay:
if !isPauseByUser {
play()
}
if shouldSeekTo != 0 {
seek(shouldSeekTo, completion: {[weak self] in
guard let `self` = self else { return }
if !self.isPauseByUser {
self.play()
} else {
self.pause()
}
})
shouldSeekTo = 0
}
case .bufferFinished:
autoPlay()
case .playedToTheEnd:
isPlayToTheEnd = true
default:
break
}
panGesture.isEnabled = state != .playedToTheEnd
delegate?.bmPlayer(player: self, playerStateDidChange: state)
playStateChanged?(state)
}
public func bmPlayer(player: BMPlayerLayerView, playTimeDidChange currentTime: TimeInterval, totalTime: TimeInterval) {
// BMPlayerManager.shared.log("playTimeDidChange - \(currentTime) - \(totalTime)")
delegate?.bmPlayer(player: self, playTimeDidChange: currentTime, totalTime: totalTime)
self.currentPosition = currentTime
totalDuration = totalTime
if isSliderSliding {
return
}
controlView.playTimeDidChange(currentTime: currentTime, totalTime: totalTime)
controlView.totalDuration = totalDuration
playTimeDidChange?(currentTime, totalTime)
}
}
extension BMPlayer: BMPlayerControlViewDelegate {
open func controlView(controlView: BMPlayerControlView,
didChooseDefinition index: Int) {
shouldSeekTo = currentPosition
playerLayer?.resetPlayer()
currentDefinition = index
playerLayer?.playAsset(asset: resource.definitions[index].avURLAsset)
}
open func controlView(controlView: BMPlayerControlView,
didPressButton button: UIButton) {
if let action = BMPlayerControlView.ButtonType(rawValue: button.tag) {
switch action {
case .back:
backBlock?(isFullScreen)
if isFullScreen {
fullScreenButtonPressed()
} else {
playerLayer?.prepareToDeinit()
}
case .play:
if button.isSelected {
pause()
} else {
if isPlayToTheEnd {
seek(0, completion: {[weak self] in
self?.play()
})
controlView.hidePlayToTheEndView()
isPlayToTheEnd = false
}
play()
}
case .replay:
isPlayToTheEnd = false
seek(0)
play()
case .fullscreen:
fullScreenButtonPressed()
default:
print("[Error] unhandled Action")
}
}
}
open func controlView(controlView: BMPlayerControlView,
slider: UISlider,
onSliderEvent event: UIControl.Event) {
switch event {
case .touchDown:
playerLayer?.onTimeSliderBegan()
isSliderSliding = true
case .valueChanged:
let target = self.totalDuration * Double(slider.value)
seekSmoothlyToTime(newChaseTime: CMTime(seconds: target, preferredTimescale: 10000))
break
case .touchUpInside :
isSliderSliding = false
let target = self.totalDuration * Double(slider.value)
if isPlayToTheEnd {
isPlayToTheEnd = false
seek(target, completion: {[weak self] in
self?.play()
})
controlView.hidePlayToTheEndView()
} else {
seek(target, completion: {[weak self] in
self?.autoPlay()
})
}
default:
break
}
}
open func controlView(controlView: BMPlayerControlView, didChangeVideoAspectRatio: BMPlayerAspectRatio) {
self.playerLayer?.aspectRatio = self.aspectRatio
}
open func controlView(controlView: BMPlayerControlView, didChangeVideoPlaybackRate rate: Float) {
self.playerLayer?.player?.rate = rate
}
}
extension AVAsset{
func videoSize()->CGSize{
let tracks = self.tracks(withMediaType: AVMediaType.video)
if (tracks.count > 0){
let videoTrack = tracks[0]
let size = videoTrack.naturalSize
let txf = videoTrack.preferredTransform
let realVidSize = size.applying(txf)
print(videoTrack)
print(txf)
print(size)
print(realVidSize)
return realVidSize
}
return CGSize(width: 0, height: 0)
}
}

29
kplayer/video/BMPlayerClearityChooseButton.swift

@ -1,29 +0,0 @@
//
// File.swift
// Pods
//
// Created by BrikerMan on 16/5/21.
//
//
import UIKit
class BMPlayerClearityChooseButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
initUI()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initUI()
}
func initUI() {
self.titleLabel?.font = UIFont.systemFont(ofSize: 12)
self.layer.cornerRadius = 2
self.layer.borderWidth = 1
self.layer.borderColor = UIColor(white: 1, alpha: 0.8).cgColor
self.setTitleColor(UIColor(white: 1, alpha: 0.9), for: .normal)
}
}

19
kplayer/video/BMPlayerCompositionResourceDefinition.swift

@ -1,19 +0,0 @@
//
// Created by Marco Schmickler on 05.05.21.
// Copyright (c) 2021 Marco Schmickler. All rights reserved.
//
import Foundation
import AVFoundation
class BMPlayerCompositionResourceDefinition : BMPlayerResourceDefinition {
var assets: [URL]?
override var avURLAsset: AVAsset {
get {
let asset = VideoHelper.combine(urls: assets!)
return asset
}
}
}

757
kplayer/video/BMPlayerControlView.swift

@ -1,757 +0,0 @@
//
// BMPlayerControlView.swift
// Pods
//
// Created by BrikerMan on 16/4/29.
//
//
import UIKit
import NVActivityIndicatorView
@objc public protocol BMPlayerControlViewDelegate: class {
/**
call when control view choose a definition
- parameter controlView: control view
- parameter index: index of definition
*/
func controlView(controlView: BMPlayerControlView, didChooseDefinition index: Int)
/**
call when control view pressed an button
- parameter controlView: control view
- parameter button: button type
*/
func controlView(controlView: BMPlayerControlView, didPressButton button: UIButton)
/**
call when slider action trigged
- parameter controlView: control view
- parameter slider: progress slider
- parameter event: action
*/
func controlView(controlView: BMPlayerControlView, slider: UISlider, onSliderEvent event: UIControl.Event)
/**
call when needs to change playback rate
- parameter controlView: control view
- parameter rate: playback rate
*/
@objc optional func controlView(controlView: BMPlayerControlView, didChangeVideoPlaybackRate rate: Float)
}
open class BMPlayerControlView: UIView {
open weak var delegate: BMPlayerControlViewDelegate?
open weak var player: BMPlayer?
// MARK: Variables
open var resource: BMPlayerResource?
open var selectedIndex = 0
open var isFullscreen = false
open var isMaskShowing = true
open var totalDuration: TimeInterval = 0
open var delayItem: DispatchWorkItem?
var playerLastState: BMPlayerState = .notSetURL
fileprivate var isSelectDefinitionViewOpened = false
// MARK: UI Components
/// main views which contains the topMaskView and bottom mask view
open var mainMaskView = UIView()
open var topMaskView = UIView()
open var bottomMaskView = UIView()
/// Image view to show video cover
open var maskImageView = UIImageView()
/// top views
open var topWrapperView = UIView()
open var backButton = UIButton(type : UIButton.ButtonType.custom)
open var titleLabel = UILabel()
open var chooseDefinitionView = UIView()
/// bottom view
open var bottomWrapperView = UIView()
open var currentTimeLabel = UILabel()
open var totalTimeLabel = UILabel()
/// Progress slider
open var timeSlider = BMTimeSlider()
/// load progress view
open var progressView = UIProgressView()
/* play button
playButton.isSelected = player.isPlaying
*/
open var playButton = UIButton(type: UIButton.ButtonType.custom)
/* fullScreen button
fullScreenButton.isSelected = player.isFullscreen
*/
open var fullscreenButton = UIButton(type: UIButton.ButtonType.custom)
open var subtitleLabel = UILabel()
open var subtitleBackView = UIView()
open var subtileAttribute: [NSAttributedString.Key : Any]?
/// Activty Indector for loading
open var loadingIndicator = NVActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
open var seekToView = UIView()
open var seekToViewImage = UIImageView()
open var seekToLabel = UILabel()
open var replayButton = UIButton(type: UIButton.ButtonType.custom)
/// Gesture used to show / hide control view
open var tapGesture: UITapGestureRecognizer!
open var doubleTapGesture: UITapGestureRecognizer!
// MARK: - handle player state change
/**
call on when play time changed, update duration here
- parameter currentTime: current play time
- parameter totalTime: total duration
*/
open func playTimeDidChange(currentTime: TimeInterval, totalTime: TimeInterval) {
currentTimeLabel.text = BMPlayer.formatSecondsToString(currentTime)
totalTimeLabel.text = BMPlayer.formatSecondsToString(totalTime)
timeSlider.value = Float(currentTime) / Float(totalTime)
showSubtile(from: resource?.subtitle, at: currentTime)
}
/**
change subtitle resource
- Parameter subtitles: new subtitle object
*/
open func update(subtitles: BMSubtitles?) {
resource?.subtitle = subtitles
}
/**
call on load duration changed, update load progressView here
- parameter loadedDuration: loaded duration
- parameter totalDuration: total duration
*/
open func loadedTimeDidChange(loadedDuration: TimeInterval, totalDuration: TimeInterval) {
progressView.setProgress(Float(loadedDuration)/Float(totalDuration), animated: true)
}
open func playerStateDidChange(state: BMPlayerState) {
switch state {
case .readyToPlay:
hideLoader()
case .buffering:
showLoader()
case .bufferFinished:
hideLoader()
case .playedToTheEnd:
playButton.isSelected = false
showPlayToTheEndView()
controlViewAnimation(isShow: true)
default:
break
}
playerLastState = state
}
/**
Call when User use the slide to seek function
- parameter toSecound: target time
- parameter totalDuration: total duration of the video
- parameter isAdd: isAdd
*/
open func showSeekToView(to toSecound: TimeInterval, total totalDuration:TimeInterval, isAdd: Bool) {
seekToView.isHidden = false
seekToLabel.text = BMPlayer.formatSecondsToString(toSecound)
let rotate = isAdd ? 0 : CGFloat(Double.pi)
seekToViewImage.transform = CGAffineTransform(rotationAngle: rotate)
let targetTime = BMPlayer.formatSecondsToString(toSecound)
timeSlider.value = Float(toSecound / totalDuration)
currentTimeLabel.text = targetTime
}
// MARK: - UI update related function
/**
Update UI details when player set with the resource
- parameter resource: video resouce
- parameter index: defualt definition's index
*/
open func prepareUI(for resource: BMPlayerResource, selectedIndex index: Int) {
self.resource = resource
self.selectedIndex = index
titleLabel.text = resource.name
prepareChooseDefinitionView()
autoFadeOutControlViewWithAnimation()
}
open func playStateDidChange(isPlaying: Bool) {
autoFadeOutControlViewWithAnimation()
playButton.isSelected = isPlaying
}
/**
auto fade out controll view with animtion
*/
open func autoFadeOutControlViewWithAnimation() {
cancelAutoFadeOutAnimation()
delayItem = DispatchWorkItem { [weak self] in
if self?.playerLastState != .playedToTheEnd {
self?.controlViewAnimation(isShow: false)
}
}
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + BMPlayerConf.animateDelayTimeInterval,
execute: delayItem!)
}
/**
cancel auto fade out controll view with animtion
*/
open func cancelAutoFadeOutAnimation() {
delayItem?.cancel()
}
/**
Implement of the control view animation, override if need's custom animation
- parameter isShow: is to show the controlview
*/
open func controlViewAnimation(isShow: Bool) {
let alpha: CGFloat = isShow ? 1.0 : 0.0
self.isMaskShowing = isShow
UIApplication.shared.setStatusBarHidden(!isShow, with: .fade)
UIView.animate(withDuration: 0.3, animations: {[weak self] in
guard let wSelf = self else { return }
wSelf.topMaskView.alpha = alpha
wSelf.bottomMaskView.alpha = alpha
wSelf.mainMaskView.backgroundColor = UIColor(white: 0, alpha: isShow ? 0.0 : 0.0) // todo marco
if isShow {
if wSelf.isFullscreen { wSelf.chooseDefinitionView.alpha = 1.0 }
} else {
wSelf.replayButton.isHidden = true
wSelf.chooseDefinitionView.snp.updateConstraints { (make) in
make.height.equalTo(35)
}
wSelf.chooseDefinitionView.alpha = 0.0
}
wSelf.layoutIfNeeded()
}) { [weak self](_) in
if isShow {
self?.autoFadeOutControlViewWithAnimation()
}
}
}
/**
Implement of the UI update when screen orient changed
- parameter isForFullScreen: is for full screen
*/
open func updateUI(_ isForFullScreen: Bool) {
isFullscreen = isForFullScreen
fullscreenButton.isSelected = isForFullScreen
chooseDefinitionView.isHidden = !BMPlayerConf.enableChooseDefinition || !isForFullScreen
if isForFullScreen {
if BMPlayerConf.topBarShowInCase.rawValue == 2 {
topMaskView.isHidden = true
} else {
topMaskView.isHidden = false
}
} else {
if BMPlayerConf.topBarShowInCase.rawValue >= 1 {
topMaskView.isHidden = true
} else {
topMaskView.isHidden = false
}
}
}
/**
Call when video play's to the end, override if you need custom UI or animation when played to the end
*/
open func showPlayToTheEndView() {
replayButton.isHidden = false
}
open func hidePlayToTheEndView() {
replayButton.isHidden = true
}
open func showLoader() {
loadingIndicator.isHidden = false
loadingIndicator.startAnimating()
}
open func hideLoader() {
loadingIndicator.isHidden = true
}
open func hideSeekToView() {
seekToView.isHidden = true
}
open func showCoverWithLink(_ cover:String) {
self.showCover(url: URL(string: cover))
}
open func showCover(url: URL?) {
if let url = url {
DispatchQueue.global(qos: .default).async { [weak self] in
let data = try? Data(contentsOf: url)
DispatchQueue.main.async(execute: { [weak self] in
guard let `self` = self else { return }
if let data = data {
self.maskImageView.image = UIImage(data: data)
} else {
self.maskImageView.image = nil
}
self.hideLoader()
});
}
}
}
open func hideCoverImageView() {
self.maskImageView.isHidden = true
}
open func prepareChooseDefinitionView() {
guard let resource = resource else {
return
}
for item in chooseDefinitionView.subviews {
item.removeFromSuperview()
}
for i in 0..<resource.definitions.count {
let button = BMPlayerClearityChooseButton()
if i == 0 {
button.tag = selectedIndex
} else if i <= selectedIndex {
button.tag = i - 1
} else {
button.tag = i
}
button.setTitle("\(resource.definitions[button.tag].definition)", for: UIControl.State())
chooseDefinitionView.addSubview(button)
button.addTarget(self, action: #selector(self.onDefinitionSelected(_:)), for: UIControl.Event.touchUpInside)
button.snp.makeConstraints({ [weak self](make) in
guard let `self` = self else { return }
make.top.equalTo(chooseDefinitionView.snp.top).offset(35 * i)
make.width.equalTo(50)
make.height.equalTo(25)
make.centerX.equalTo(chooseDefinitionView)
})
if resource.definitions.count == 1 {
button.isEnabled = false
button.isHidden = true
}
}
}
open func prepareToDealloc() {
self.delayItem = nil
}
// MARK: - Action Response
/**
Call when some action button Pressed
- parameter button: action Button
*/
@objc open func onButtonPressed(_ button: UIButton) {
autoFadeOutControlViewWithAnimation()
if let type = ButtonType(rawValue: button.tag) {
switch type {
case .play, .replay:
if playerLastState == .playedToTheEnd {
hidePlayToTheEndView()
}
default:
break
}
}
delegate?.controlView(controlView: self, didPressButton: button)
}
/**
Call when the tap gesture tapped
- parameter gesture: tap gesture
*/
@objc open func onTapGestureTapped(_ gesture: UITapGestureRecognizer) {
if playerLastState == .playedToTheEnd {
return
}
controlViewAnimation(isShow: !isMaskShowing)
}
@objc open func onDoubleTapGestureRecognized(_ gesture: UITapGestureRecognizer) {
guard let player = player else { return }
guard playerLastState == .readyToPlay || playerLastState == .buffering || playerLastState == .bufferFinished else { return }
if player.isPlaying {
player.pause()
} else {
player.play()
}
}
// MARK: - handle UI slider actions
@objc func progressSliderTouchBegan(_ sender: UISlider) {
delegate?.controlView(controlView: self, slider: sender, onSliderEvent: .touchDown)
}
@objc func progressSliderValueChanged(_ sender: UISlider) {
hidePlayToTheEndView()
cancelAutoFadeOutAnimation()
let currentTime = Double(sender.value) * totalDuration
currentTimeLabel.text = BMPlayer.formatSecondsToString(currentTime)
delegate?.controlView(controlView: self, slider: sender, onSliderEvent: .valueChanged)
}
@objc func progressSliderTouchEnded(_ sender: UISlider) {
autoFadeOutControlViewWithAnimation()
delegate?.controlView(controlView: self, slider: sender, onSliderEvent: .touchUpInside)
}
// MARK: - private functions
fileprivate func showSubtile(from subtitle: BMSubtitles?, at time: TimeInterval) {
if let subtitle = subtitle, let group = subtitle.search(for: time) {
subtitleBackView.isHidden = false
subtitleLabel.attributedText = NSAttributedString(string: group.text,
attributes: subtileAttribute)
} else {
subtitleBackView.isHidden = true
}
}
@objc fileprivate func onDefinitionSelected(_ button:UIButton) {
let height = isSelectDefinitionViewOpened ? 35 : resource!.definitions.count * 40
chooseDefinitionView.snp.updateConstraints { (make) in
make.height.equalTo(height)
}
UIView.animate(withDuration: 0.3, animations: {[weak self] in
self?.layoutIfNeeded()
})
isSelectDefinitionViewOpened = !isSelectDefinitionViewOpened
if selectedIndex != button.tag {
selectedIndex = button.tag
delegate?.controlView(controlView: self, didChooseDefinition: button.tag)
}
prepareChooseDefinitionView()
}
@objc fileprivate func onReplyButtonPressed() {
replayButton.isHidden = true
}
// MARK: - Init
override public init(frame: CGRect) {
super.init(frame: frame)
setupUIComponents()
addSnapKitConstraint()
customizeUIComponents()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupUIComponents()
addSnapKitConstraint()
customizeUIComponents()
}
/// Add Customize functions here
open func customizeUIComponents() {
}
func setupUIComponents() {
// Subtile view
subtitleLabel.numberOfLines = 0
subtitleLabel.textAlignment = .center
subtitleLabel.textColor = UIColor.white
subtitleLabel.adjustsFontSizeToFitWidth = true
subtitleLabel.minimumScaleFactor = 0.5
subtitleLabel.font = UIFont.systemFont(ofSize: 13)
subtitleBackView.layer.cornerRadius = 2
subtitleBackView.backgroundColor = UIColor.black.withAlphaComponent(0.4)
subtitleBackView.addSubview(subtitleLabel)
subtitleBackView.isHidden = true
addSubview(subtitleBackView)
// Main mask view
addSubview(mainMaskView)
mainMaskView.addSubview(topMaskView)
mainMaskView.addSubview(bottomMaskView)
mainMaskView.insertSubview(maskImageView, at: 0)
mainMaskView.clipsToBounds = true
mainMaskView.backgroundColor = UIColor(white: 0, alpha: 0.0 )
// Top views
topMaskView.addSubview(topWrapperView)
// Bottom views
bottomMaskView.addSubview(bottomWrapperView)
bottomWrapperView.addSubview(playButton)
bottomWrapperView.addSubview(currentTimeLabel)
bottomWrapperView.addSubview(totalTimeLabel)
bottomWrapperView.addSubview(progressView)
bottomWrapperView.addSubview(timeSlider)
bottomWrapperView.addSubview(fullscreenButton)
playButton.tag = BMPlayerControlView.ButtonType.play.rawValue
playButton.setImage(BMImageResourcePath("Pod_Asset_BMPlayer_play"), for: .normal)
playButton.setImage(BMImageResourcePath("Pod_Asset_BMPlayer_pause"), for: .selected)
playButton.addTarget(self, action: #selector(onButtonPressed(_:)), for: .touchUpInside)
currentTimeLabel.textColor = UIColor.white
currentTimeLabel.font = UIFont.systemFont(ofSize: 14)
currentTimeLabel.text = "00:00"
currentTimeLabel.textAlignment = NSTextAlignment.center
currentTimeLabel.backgroundColor = UIColor.black
totalTimeLabel.textColor = UIColor.white
totalTimeLabel.font = UIFont.systemFont(ofSize: 14)
totalTimeLabel.text = "00:00"
totalTimeLabel.textAlignment = NSTextAlignment.center
totalTimeLabel.backgroundColor = UIColor.black
timeSlider.maximumValue = 1.0
timeSlider.minimumValue = 0.0
timeSlider.value = 0.0
timeSlider.setThumbImage(BMImageResourcePath("Pod_Asset_BMPlayer_slider_thumb"), for: .normal)
timeSlider.maximumTrackTintColor = UIColor.clear
timeSlider.minimumTrackTintColor = BMPlayerConf.tintColor
timeSlider.addTarget(self, action: #selector(progressSliderTouchBegan(_:)),
for: UIControl.Event.touchDown)
timeSlider.addTarget(self, action: #selector(progressSliderValueChanged(_:)),
for: UIControl.Event.valueChanged)
timeSlider.addTarget(self, action: #selector(progressSliderTouchEnded(_:)),
for: [UIControl.Event.touchUpInside,UIControl.Event.touchCancel, UIControl.Event.touchUpOutside])
progressView.tintColor = UIColor ( red: 1.0, green: 1.0, blue: 1.0, alpha: 0.6 )
progressView.trackTintColor = UIColor ( red: 1.0, green: 1.0, blue: 1.0, alpha: 0.3 )
fullscreenButton.tag = BMPlayerControlView.ButtonType.fullscreen.rawValue
fullscreenButton.setImage(BMImageResourcePath("Pod_Asset_BMPlayer_fullscreen"), for: .normal)
fullscreenButton.setImage(BMImageResourcePath("Pod_Asset_BMPlayer_portialscreen"), for: .selected)
fullscreenButton.addTarget(self, action: #selector(onButtonPressed(_:)), for: .touchUpInside)
mainMaskView.addSubview(loadingIndicator)
loadingIndicator.type = BMPlayerConf.loaderType
loadingIndicator.color = BMPlayerConf.tintColor
// View to show when slide to seek
addSubview(seekToView)
seekToView.addSubview(seekToViewImage)
seekToView.addSubview(seekToLabel)
seekToLabel.font = UIFont.systemFont(ofSize: 13)
seekToLabel.textColor = UIColor ( red: 0.9098, green: 0.9098, blue: 0.9098, alpha: 1.0 )
seekToView.backgroundColor = UIColor ( red: 0.0, green: 0.0, blue: 0.0, alpha: 0.7 )
seekToView.layer.cornerRadius = 4
seekToView.layer.masksToBounds = true
seekToView.isHidden = true
seekToViewImage.image = BMImageResourcePath("Pod_Asset_BMPlayer_seek_to_image")
addSubview(replayButton)
replayButton.isHidden = true
replayButton.setImage(BMImageResourcePath("Pod_Asset_BMPlayer_replay"), for: .normal)
replayButton.addTarget(self, action: #selector(onButtonPressed(_:)), for: .touchUpInside)
replayButton.tag = ButtonType.replay.rawValue
tapGesture = UITapGestureRecognizer(target: self, action: #selector(onTapGestureTapped(_:)))
addGestureRecognizer(tapGesture)
if BMPlayerManager.shared.enablePlayControlGestures {
doubleTapGesture = UITapGestureRecognizer(target: self, action: #selector(onDoubleTapGestureRecognized(_:)))
doubleTapGesture.numberOfTapsRequired = 2
addGestureRecognizer(doubleTapGesture)
tapGesture.require(toFail: doubleTapGesture)
}
}
func addSnapKitConstraint() {
// Main mask view
mainMaskView.snp.makeConstraints { [unowned self](make) in
make.edges.equalTo(self)
}
maskImageView.snp.makeConstraints { [unowned self](make) in
make.edges.equalTo(self.mainMaskView)
}
topMaskView.snp.makeConstraints { [unowned self](make) in
make.top.left.right.equalTo(self.mainMaskView)
}
topWrapperView.snp.makeConstraints { [unowned self](make) in
make.height.equalTo(50)
if #available(iOS 11.0, *) {
make.top.left.right.equalTo(self.topMaskView.safeAreaLayoutGuide)
make.bottom.equalToSuperview()
} else {
make.top.equalToSuperview().offset(15)
make.bottom.left.right.equalToSuperview()
}
}
bottomMaskView.snp.makeConstraints { [unowned self](make) in
make.bottom.left.right.equalTo(self.mainMaskView)
}
bottomWrapperView.snp.makeConstraints { [unowned self](make) in
make.height.equalTo(50)
if #available(iOS 11.0, *) {
make.bottom.left.right.equalTo(self.bottomMaskView.safeAreaLayoutGuide)
make.top.equalToSuperview()
} else {
make.edges.equalToSuperview()
}
}
// Top views
// backButton.snp.makeConstraints { (make) in
// make.width.height.equalTo(50)
// make.left.bottom.equalToSuperview()
// }
//
// titleLabel.snp.makeConstraints { [unowned self](make) in
// make.left.equalTo(self.backButton.snp.right)
// make.centerY.equalTo(self.backButton)
// }
//
// chooseDefinitionView.snp.makeConstraints { [unowned self](make) in
// make.right.equalToSuperview().offset(-20)
// make.top.equalTo(self.titleLabel.snp.top).offset(-4)
// make.width.equalTo(60)
// make.height.equalTo(30)
// }
//
// Bottom views
playButton.snp.makeConstraints { (make) in
make.width.equalTo(50)
make.height.equalTo(50)
make.left.bottom.equalToSuperview()
}
currentTimeLabel.snp.makeConstraints { [unowned self](make) in
make.left.equalTo(self.playButton.snp.right)
make.centerY.equalTo(self.playButton)
make.width.equalTo(40)
}
timeSlider.snp.makeConstraints { [unowned self](make) in
make.centerY.equalTo(self.currentTimeLabel)
make.left.equalTo(self.currentTimeLabel.snp.right).offset(10).priority(750)
make.height.equalTo(30)
}
progressView.snp.makeConstraints { [unowned self](make) in
make.centerY.left.right.equalTo(self.timeSlider)
make.height.equalTo(2)
}
totalTimeLabel.snp.makeConstraints { [unowned self](make) in
make.centerY.equalTo(self.currentTimeLabel)
make.left.equalTo(self.timeSlider.snp.right).offset(5)
make.width.equalTo(40)
}
fullscreenButton.snp.makeConstraints { [unowned self](make) in
make.width.equalTo(50)
make.height.equalTo(50)
make.centerY.equalTo(self.currentTimeLabel)
make.left.equalTo(self.totalTimeLabel.snp.right)
make.right.equalToSuperview()
}
loadingIndicator.snp.makeConstraints { [unowned self](make) in
make.center.equalTo(self.mainMaskView)
}
// View to show when slide to seek
seekToView.snp.makeConstraints { [unowned self](make) in
make.center.equalTo(self.snp.center)
make.width.equalTo(100)
make.height.equalTo(40)
}
seekToViewImage.snp.makeConstraints { [unowned self](make) in
make.left.equalTo(self.seekToView.snp.left).offset(15)
make.centerY.equalTo(self.seekToView.snp.centerY)
make.height.equalTo(15)
make.width.equalTo(25)
}
seekToLabel.snp.makeConstraints { [unowned self](make) in
make.left.equalTo(self.seekToViewImage.snp.right).offset(10)
make.centerY.equalTo(self.seekToView.snp.centerY)
}
replayButton.snp.makeConstraints { [unowned self](make) in
make.center.equalTo(self.mainMaskView)
make.width.height.equalTo(50)
}
subtitleBackView.snp.makeConstraints { [unowned self](make) in
make.bottom.equalTo(self.snp.bottom).offset(-5)
make.centerX.equalTo(self.snp.centerX)
make.width.lessThanOrEqualTo(self.snp.width).offset(-10).priority(750)
}
subtitleLabel.snp.makeConstraints { [unowned self](make) in
make.left.equalTo(self.subtitleBackView.snp.left).offset(10)
make.right.equalTo(self.subtitleBackView.snp.right).offset(-10)
make.top.equalTo(self.subtitleBackView.snp.top).offset(2)
make.bottom.equalTo(self.subtitleBackView.snp.bottom).offset(-2)
}
}
fileprivate func BMImageResourcePath(_ fileName: String) -> UIImage? {
let bundle = Bundle(for: BMPlayer.self)
return UIImage(named: fileName, in: bundle, compatibleWith: nil)
}
}

91
kplayer/video/BMPlayerItem.swift

@ -1,91 +0,0 @@
//
// BMPlayerItem.swift
// Pods
//
// Created by BrikerMan on 16/5/21.
//
//
import Foundation
import AVFoundation
public class BMPlayerResource {
public let name: String
public let cover: URL?
public var subtitle: BMSubtitles?
public let definitions: [BMPlayerResourceDefinition]
/**
Player recource item with url, used to play single difinition video
- parameter name: video name
- parameter url: video url
- parameter cover: video cover, will show before playing, and hide when play
- parameter subtitles: video subtitles
*/
public convenience init(url: URL, name: String = "", cover: URL? = nil, subtitle: URL? = nil) {
let definition = BMPlayerResourceDefinition(url: url, definition: "")
var subtitles: BMSubtitles? = nil
if let subtitle = subtitle {
subtitles = BMSubtitles(url: subtitle)
}
self.init(name: name, definitions: [definition], cover: cover, subtitles: subtitles)
}
/**
Play resouce with multi definitions
- parameter name: video name
- parameter definitions: video definitions
- parameter cover: video cover
- parameter subtitles: video subtitles
*/
public init(name: String = "", definitions: [BMPlayerResourceDefinition], cover: URL? = nil, subtitles: BMSubtitles? = nil) {
self.name = name
self.cover = cover
self.subtitle = subtitles
self.definitions = definitions
}
}
open class BMPlayerResourceDefinition {
public let url: URL
public let definition: String
/// An instance of NSDictionary that contains keys for specifying options for the initialization of the AVURLAsset. See AVURLAssetPreferPreciseDurationAndTimingKey and AVURLAssetReferenceRestrictionsKey above.
public var options: [String : Any]?
open var avURLAsset: AVAsset {
get {
guard !url.isFileURL, url.pathExtension != "m3u8" else {
return AVURLAsset(url: url)
}
return BMPlayerManager.asset(for: self)
}
}
/**
Video recource item with defination name and specifying options
- parameter url: video url
- parameter definition: url deifination
- parameter options: specifying options for the initialization of the AVURLAsset
you can add http-header or other options which mentions in https://developer.apple.com/reference/avfoundation/avurlasset/initialization_options
to add http-header init options like this
```
let header = ["User-Agent":"BMPlayer"]
let definiton.options = ["AVURLAssetHTTPHeaderFieldsKey":header]
```
*/
public init(url: URL, definition: String, options: [String : Any]? = nil) {
self.url = url
self.definition = definition
self.options = options
}
}

506
kplayer/video/BMPlayerLayerView.swift

@ -1,506 +0,0 @@
//
// BMPlayerLayerView.swift
// Pods
//
// Created by BrikerMan on 16/4/28.
//
//
import UIKit
import AVFoundation
/**
Player status emun
- notSetURL: not set url yet
- readyToPlay: player ready to play
- buffering: player buffering
- bufferFinished: buffer finished
- playedToTheEnd: played to the End
- error: error with playing
*/
public enum BMPlayerState {
case notSetURL
case readyToPlay
case buffering
case bufferFinished
case playedToTheEnd
case error
}
/**
video aspect ratio types
- `default`: video default aspect
- sixteen2NINE: 16:9
- four2THREE: 4:3
*/
public enum BMPlayerAspectRatio : Int {
case `default` = 0
case sixteen2NINE
case four2THREE
}
public protocol BMPlayerLayerViewDelegate : class {
func bmPlayer(player: BMPlayerLayerView, playerStateDidChange state: BMPlayerState)
func bmPlayer(player: BMPlayerLayerView, loadedTimeDidChange loadedDuration: TimeInterval, totalDuration: TimeInterval)
func bmPlayer(player: BMPlayerLayerView, playTimeDidChange currentTime: TimeInterval, totalTime: TimeInterval)
func bmPlayer(player: BMPlayerLayerView, playerIsPlaying playing: Bool)
}
open class BMPlayerLayerView: UIView {
open weak var delegate: BMPlayerLayerViewDelegate?
/// 0
open var seekTime = 0
///
open var playerItem: AVPlayerItem? {
didSet {
onPlayerItemChange()
}
}
///
open lazy var player: AVPlayer? = {
if let item = self.playerItem {
let player = AVPlayer(playerItem: item)
return player
}
return nil
}()
open var videoGravity = AVLayerVideoGravity.resizeAspect {
didSet {
self.playerLayer?.videoGravity = videoGravity
}
}
open var isPlaying: Bool = false {
didSet {
if oldValue != isPlaying {
delegate?.bmPlayer(player: self, playerIsPlaying: isPlaying)
}
}
}
var aspectRatio: BMPlayerAspectRatio = .default {
didSet {
self.setNeedsLayout()
}
}
///
var timer: Timer?
fileprivate var urlAsset: AVAsset?
fileprivate var lastPlayerItem: AVPlayerItem?
/// playerLayer
fileprivate var playerLayer: AVPlayerLayer?
///
fileprivate var volumeViewSlider: UISlider!
///
fileprivate var state = BMPlayerState.notSetURL {
didSet {
if state != oldValue {
delegate?.bmPlayer(player: self, playerStateDidChange: state)
}
}
}
///
fileprivate var isFullScreen = false
///
fileprivate var isLocked = false
///
fileprivate var isVolume = false
///
fileprivate var isLocalVideo = false
/// slider
fileprivate var sliderLastValue: Float = 0
///
fileprivate var repeatToPlay = false
///
fileprivate var playDidEnd = false
// playbackBufferEmptybufferingOneSecondbufferingSomeSecond
// bufferingSomeSecond使
fileprivate var isBuffering = false
fileprivate var hasReadyToPlay = false
fileprivate var shouldSeekTo: TimeInterval = 0
// MARK: - Actions
open func playURL(url: URL) {
let asset = AVURLAsset(url: url)
playAsset(asset: asset)
}
open func playAsset(asset: AVAsset) {
urlAsset = asset
onSetVideoAsset()
play()
}
open func play() {
if let player = player {
player.play()
setupTimer()
isPlaying = true
}
}
open func pause() {
player?.pause()
isPlaying = false
timer?.fireDate = Date.distantFuture
}
deinit {
NotificationCenter.default.removeObserver(self)
}
// MARK: - layoutSubviews
override open func layoutSubviews() {
super.layoutSubviews()
switch self.aspectRatio {
case .default:
self.playerLayer?.videoGravity = AVLayerVideoGravity.resizeAspect
self.playerLayer?.frame = self.bounds
break
case .sixteen2NINE:
self.playerLayer?.videoGravity = AVLayerVideoGravity.resize
self.playerLayer?.frame = CGRect(x: 0, y: 0, width: self.bounds.width, height: self.bounds.width/(16/9))
break
case .four2THREE:
self.playerLayer?.videoGravity = AVLayerVideoGravity.resize
let _w = self.bounds.height * 4 / 3
self.playerLayer?.frame = CGRect(x: (self.bounds.width - _w )/2, y: 0, width: _w, height: self.bounds.height)
break
}
}
open func resetPlayer() {
//
self.playDidEnd = false
self.playerItem = nil
self.lastPlayerItem = nil
self.seekTime = 0
self.timer?.invalidate()
self.pause()
// layer
self.playerLayer?.removeFromSuperlayer()
// PlayerItemnil
self.player?.replaceCurrentItem(with: nil)
player?.removeObserver(self, forKeyPath: "rate")
// playernil
self.player = nil
}
open func prepareToDeinit() {
self.resetPlayer()
}
open func onTimeSliderBegan() {
if self.player?.currentItem?.status == AVPlayerItem.Status.readyToPlay {
self.timer?.fireDate = Date.distantFuture
}
}
open func seek(to secounds: TimeInterval, completion:(()->Void)?) {
if secounds.isNaN {
return
}
setupTimer()
if self.player?.currentItem?.status == AVPlayerItem.Status.readyToPlay {
let draggedTime = CMTime(value: Int64(secounds)*10000, timescale: 10000)
// self.player!.cancelPendingPrerolls()
self.playerItem!.cancelPendingSeeks()
self.player!.seek(to: draggedTime, toleranceBefore: CMTimeMake(value: 1, timescale: 1), toleranceAfter: CMTimeMake(value: 1, timescale: 1), completionHandler: { (finished) in
completion?()
})
} else {
self.shouldSeekTo = secounds
}
}
// MARK: - URL
fileprivate func onSetVideoAsset() {
repeatToPlay = false
playDidEnd = false
configPlayer()
}
fileprivate func onPlayerItemChange() {
if lastPlayerItem == playerItem {
return
}
if let item = lastPlayerItem {
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: item)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.AVPlayerItemFailedToPlayToEndTime, object: item)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.AVPlayerItemNewErrorLogEntry, object: item)
item.removeObserver(self, forKeyPath: "status")
item.removeObserver(self, forKeyPath: "loadedTimeRanges")
item.removeObserver(self, forKeyPath: "playbackBufferEmpty")
item.removeObserver(self, forKeyPath: "playbackLikelyToKeepUp")
}
lastPlayerItem = playerItem
if let item = playerItem {
NotificationCenter.default.addObserver(self, selector: #selector(moviePlayDidEnd),
name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
object: playerItem)
item.addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.new, context: nil)
item.addObserver(self, forKeyPath: "loadedTimeRanges", options: NSKeyValueObservingOptions.new, context: nil)
//
item.addObserver(self, forKeyPath: "playbackBufferEmpty", options: NSKeyValueObservingOptions.new, context: nil)
//
item.addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: NSKeyValueObservingOptions.new, context: nil)
}
}
// Getting error from Notification payload
@objc fileprivate func newErrorLogEntry(_ notification: Notification) {
guard let object = notification.object, let playerItem = object as? AVPlayerItem else {
return
}
guard let errorLog: AVPlayerItemErrorLog = playerItem.errorLog() else {
return
}
print("Error: \(errorLog)")
}
@objc fileprivate func failedToPlayToEndTime(_ notification: Notification) {
let error = notification.userInfo!["AVPlayerItemFailedToPlayToEndTimeErrorKey"]
print(error)
}
fileprivate func configPlayer(){
player?.removeObserver(self, forKeyPath: "rate")
playerItem = AVPlayerItem(asset: urlAsset!)
playerItem?.preferredForwardBufferDuration = TimeInterval(floatLiteral: 100.0)
playerItem?.canUseNetworkResourcesForLiveStreamingWhilePaused = true
player = AVPlayer(playerItem: playerItem!)
player?.automaticallyWaitsToMinimizeStalling = true
player!.addObserver(self, forKeyPath: "rate", options: NSKeyValueObservingOptions.new, context: nil)
self.connectPlayerLayer()
setNeedsLayout()
layoutIfNeeded()
NotificationCenter.default.addObserver(self, selector: #selector(self.connectPlayerLayer), name: UIApplication.willEnterForegroundNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.disconnectPlayerLayer), name: UIApplication.didEnterBackgroundNotification, object: nil)
let center = NotificationCenter.default
center.addObserver(self, selector: #selector(self.newErrorLogEntry), name: .AVPlayerItemNewErrorLogEntry, object: playerItem)
center.addObserver(self, selector:#selector(self.failedToPlayToEndTime), name: .AVPlayerItemFailedToPlayToEndTime, object: playerItem)
}
func setupTimer() {
timer?.invalidate()
timer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(playerTimerAction), userInfo: nil, repeats: true)
timer?.fireDate = Date()
}
// MARK: -
@objc fileprivate func playerTimerAction() {
guard let playerItem = playerItem else { return }
if playerItem.duration.timescale != 0 {
let currentTime = CMTimeGetSeconds(self.player!.currentTime())
let totalTime = TimeInterval(playerItem.duration.value) / TimeInterval(playerItem.duration.timescale)
delegate?.bmPlayer(player: self, playTimeDidChange: currentTime, totalTime: totalTime)
}
updateStatus(includeLoading: true)
}
// Observe If AVPlayerItem.status Changed to Fail
func obsrveValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
if let player: AVPlayer = object as? AVPlayer {
if (keyPath == #keyPath(AVPlayer.currentItem.status)) {
let newStatus: AVPlayerItem.Status
if let newStatusAsNumber = change?[NSKeyValueChangeKey.newKey] as? NSNumber {
newStatus = AVPlayerItem.Status(rawValue: newStatusAsNumber.intValue)!
} else {
newStatus = .unknown
}
if newStatus == .failed {
NSLog("Error: \(String(describing: self.player?.currentItem?.error?.localizedDescription)), error: \(String(describing: self.player?.currentItem?.error))")
}
}
}
}
fileprivate func updateStatus(includeLoading: Bool = false) {
if let player = player {
if let playerItem = playerItem, includeLoading {
if playerItem.isPlaybackLikelyToKeepUp || playerItem.isPlaybackBufferFull {
self.state = .bufferFinished
} else if playerItem.status == .failed {
self.state = .error
} else {
self.state = .buffering
}
}
if player.rate == 0.0 {
if player.error != nil {
self.state = .error
return
}
if let currentItem = player.currentItem {
if player.currentTime() >= currentItem.duration {
moviePlayDidEnd()
return
}
if currentItem.isPlaybackLikelyToKeepUp || currentItem.isPlaybackBufferFull {
}
}
}
}
}
// MARK: - Notification Event
@objc fileprivate func moviePlayDidEnd() {
if state != .playedToTheEnd {
if let playerItem = playerItem {
delegate?.bmPlayer(player: self,
playTimeDidChange: CMTimeGetSeconds(playerItem.duration),
totalTime: CMTimeGetSeconds(playerItem.duration))
}
self.state = .playedToTheEnd
self.isPlaying = false
self.playDidEnd = true
self.timer?.invalidate()
}
}
// MARK: - KVO
override open func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let item = object as? AVPlayerItem, let keyPath = keyPath {
if item == self.playerItem {
switch keyPath {
case "status":
if item.status == .failed || player?.status == AVPlayer.Status.failed {
self.state = .error
print(item.error)
} else if player?.status == AVPlayer.Status.readyToPlay {
self.state = .buffering
if shouldSeekTo > 0 {
seek(to: shouldSeekTo, completion: { [weak self] in
self?.shouldSeekTo = 0
self?.hasReadyToPlay = true
self?.state = .readyToPlay
})
} else {
self.hasReadyToPlay = true
self.state = .readyToPlay
}
}
case "loadedTimeRanges":
//
if let timeInterVarl = self.availableDuration() {
let duration = item.duration
let totalDuration = CMTimeGetSeconds(duration)
delegate?.bmPlayer(player: self, loadedTimeDidChange: timeInterVarl, totalDuration: totalDuration)
}
case "playbackBufferEmpty":
//
if self.playerItem!.isPlaybackBufferEmpty {
self.state = .buffering
self.bufferingSomeSecond()
}
case "playbackLikelyToKeepUp":
if item.isPlaybackBufferEmpty {
if state != .bufferFinished && hasReadyToPlay {
self.state = .bufferFinished
self.playDidEnd = true
}
}
default:
break
}
}
}
if keyPath == "rate" {
updateStatus()
}
}
/**
- returns:
*/
fileprivate func availableDuration() -> TimeInterval? {
if let loadedTimeRanges = player?.currentItem?.loadedTimeRanges,
let first = loadedTimeRanges.first {
let timeRange = first.timeRangeValue
let startSeconds = CMTimeGetSeconds(timeRange.start)
let durationSecound = CMTimeGetSeconds(timeRange.duration)
let result = startSeconds + durationSecound
return result
}
return nil
}
/**
*/
fileprivate func bufferingSomeSecond() {
print("buffering some second")
self.state = .buffering
// playbackBufferEmptybufferingOneSecondbufferingSomeSecond
if isBuffering {
return
}
isBuffering = true
//
player?.pause()
let popTime = DispatchTime.now() + Double(Int64( Double(NSEC_PER_SEC) * 1.0 )) / Double(NSEC_PER_SEC)
DispatchQueue.main.asyncAfter(deadline: popTime) {[weak self] in
guard let `self` = self else { return }
// play
self.isBuffering = false
if let item = self.playerItem {
if !item.isPlaybackLikelyToKeepUp {
self.bufferingSomeSecond()
} else {
//
self.state = BMPlayerState.bufferFinished
}
}
}
}
@objc fileprivate func connectPlayerLayer() {
playerLayer?.removeFromSuperlayer()
playerLayer = AVPlayerLayer(player: player)
playerLayer!.videoGravity = videoGravity
layer.addSublayer(playerLayer!)
}
@objc fileprivate func disconnectPlayerLayer() {
playerLayer?.removeFromSuperlayer()
playerLayer = nil
}
}

62
kplayer/video/BMPlayerManager.swift

@ -1,62 +0,0 @@
//
// BMPlayerManager.swift
// Pods
//
// Created by BrikerMan on 16/5/21.
//
//
import UIKit
import AVFoundation
import NVActivityIndicatorView
public let BMPlayerConf = BMPlayerManager.shared
public enum BMPlayerTopBarShowCase: Int {
case always = 0 ///
case horizantalOnly = 1 ///
case none = 2 ///
}
open class BMPlayerManager {
///
public static let shared = BMPlayerManager()
/// tint color
open var tintColor = UIColor.white
/// Loader
open var loaderType = NVActivityIndicatorType.ballRotateChase
/// should auto play
open var shouldAutoPlay = true
open var topBarShowInCase = BMPlayerTopBarShowCase.always
open var animateDelayTimeInterval = TimeInterval(100)
/// should show log
open var allowLog = false
/// use gestures to set brightness, volume and play position
open var enableBrightnessGestures = true
open var enableVolumeGestures = true
open var enablePlaytimeGestures = true
open var enablePlayControlGestures = true
open var enableChooseDefinition = true
internal static func asset(for resouce: BMPlayerResourceDefinition) -> AVURLAsset {
return AVURLAsset(url: resouce.url, options: resouce.options)
}
/**
log
- parameter info: log信息
*/
func log(_ info:String) {
if allowLog {
print(info)
}
}
}

30
kplayer/video/BMPlayerProtocols.swift

@ -1,30 +0,0 @@
//
// BMPlayerProtocols.swift
// Pods
//
// Created by BrikerMan on 16/4/30.
//
//
import UIKit
extension BMPlayerControlView {
public enum ButtonType: Int {
case play = 101
case pause = 102
case back = 103
case fullscreen = 105
case replay = 106
}
}
extension BMPlayer {
static func formatSecondsToString(_ seconds: TimeInterval) -> String {
if seconds.isNaN {
return "00:00"
}
let min = Int(seconds / 60)
let sec = Int(seconds.truncatingRemainder(dividingBy: 60))
return String(format: "%02d:%02d", min, sec)
}
}

122
kplayer/video/BMSubtitles.swift

@ -1,122 +0,0 @@
//
// BMSubtitles.swift
// Pods
//
// Created by BrikerMan on 2017/4/2.
//
//
import Foundation
public class BMSubtitles {
public var groups: [Group] = []
/// subtitles delay, positive:fast, negative:forward
public var delay: TimeInterval = 0
public struct Group: CustomStringConvertible {
var index: Int
var start: TimeInterval
var end : TimeInterval
var text : String
init(_ index: Int, _ start: NSString, _ end: NSString, _ text: NSString) {
self.index = index
self.start = Group.parseDuration(start as String)
self.end = Group.parseDuration(end as String)
self.text = text as String
}
static func parseDuration(_ fromStr:String) -> TimeInterval {
var h: TimeInterval = 0.0, m: TimeInterval = 0.0, s: TimeInterval = 0.0, c: TimeInterval = 0.0
let scanner = Scanner(string: fromStr)
scanner.scanDouble(&h)
scanner.scanString(":", into: nil)
scanner.scanDouble(&m)
scanner.scanString(":", into: nil)
scanner.scanDouble(&s)
scanner.scanString(",", into: nil)
scanner.scanDouble(&c)
return (h * 3600.0) + (m * 60.0) + s + (c / 1000.0)
}
public var description: String {
return "Subtile Group ==========\nindex : \(index),\nstart : \(start)\nend :\(end)\ntext :\(text)"
}
}
public init(url: URL, encoding: String.Encoding? = nil) {
DispatchQueue.global(qos: .background).async {[weak self] in
do {
let string: String
if let encoding = encoding {
string = try String(contentsOf: url, encoding: encoding)
} else {
string = try String(contentsOf: url)
}
self?.groups = BMSubtitles.parseSubRip(string) ?? []
} catch {
print("| BMPlayer | [Error] failed to load \(url.absoluteString) \(error.localizedDescription)")
}
}
}
/**
Search for target group for time
- parameter time: target time
- returns: result group or nil
*/
public func search(for time: TimeInterval) -> Group? {
let result = groups.first(where: { group -> Bool in
if group.start - delay <= time && group.end - delay >= time {
return true
}
return false
})
return result
}
/**
Parse str string into Group Array
- parameter payload: target string
- returns: result group
*/
fileprivate static func parseSubRip(_ payload: String) -> [Group]? {
var groups: [Group] = []
let scanner = Scanner(string: payload)
while !scanner.isAtEnd {
var indexString: NSString?
scanner.scanUpToCharacters(from: .newlines, into: &indexString)
var startString: NSString?
scanner.scanUpTo(" --> ", into: &startString)
// skip spaces and newlines by default.
scanner.scanString("-->", into: nil)
var endString: NSString?
scanner.scanUpToCharacters(from: .newlines, into: &endString)
var textString: NSString?
scanner.scanUpTo("\r\n\r\n", into: &textString)
if let text = textString {
textString = text.trimmingCharacters(in: .whitespaces) as NSString
textString = text.replacingOccurrences(of: "\r", with: "") as NSString
}
if let indexString = indexString,
let index = Int(indexString as String),
let start = startString,
let end = endString,
let text = textString {
let group = Group(index, start, end, text)
groups.append(group)
}
}
return groups
}
}

26
kplayer/video/BMTimeSlider.swift

@ -1,26 +0,0 @@
//
// BMTimeSlider.swift
// Pods
//
// Created by BrikerMan on 2017/4/2.
//
//
import UIKit
public class BMTimeSlider: UISlider {
override open func trackRect(forBounds bounds: CGRect) -> CGRect {
let trackHeight: CGFloat = 2
let position = CGPoint(x: 0, y: 14)
let customBounds = CGRect(origin: position, size: CGSize(width: bounds.size.width, height: trackHeight))
super.trackRect(forBounds: customBounds)
return customBounds
}
override open func thumbRect(forBounds bounds: CGRect, trackRect rect: CGRect, value: Float) -> CGRect {
let rect = super.thumbRect(forBounds: bounds, trackRect: rect, value: value)
let newx = rect.origin.x - 10
let newRect = CGRect(x: newx, y: 0, width: 30, height: 30)
return newRect
}
}

30
kplayer/video/KVideoPlayer.swift

@ -1,30 +0,0 @@
//
// Created by Marco Schmickler on 14.11.21.
// Copyright (c) 2021 Marco Schmickler. All rights reserved.
//
import SwiftUI
import AVKit
struct KVideoPlayer: View {
private let player = AVPlayer(url: URL(string: "https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8")!)
var body: some View {
VideoPlayer(player: player)
.onAppear() {
// Start the player going, otherwise controls don't appear
player.play()
}
.onDisappear() {
// Stop the player when the view disappears
player.pause()
}
}
}
struct KVideoPlayer_Previews: PreviewProvider {
static var previews: some View {
KVideoPlayer()
}
}
Loading…
Cancel
Save