From 349df3e84c64fc5988fbb19063b9f25c96fe5fff Mon Sep 17 00:00:00 2001 From: marcoschmickler Date: Tue, 27 Apr 2021 19:42:02 +0200 Subject: [PATCH] Slideshow --- Podfile | 30 +- kplayer.xcodeproj/project.pbxproj | 53 +- kplayer/core/MediaItem.swift | 5 +- kplayer/core/NetworkManager.swift | 29 +- kplayer/detail/AVPlayerController.swift | 41 +- kplayer/detail/BrowserController.swift | 4 + kplayer/detail/DetailViewController.swift | 58 +- kplayer/detail/VideoPlayerController.swift | 396 --------- kplayer/master/MasterViewController.swift | 2 +- kplayer/photo/MediaPhotoController.swift | 34 +- kplayer/util/ImageLoadOperation.swift | 2 +- kplayer/util/UploadOperation.swift | 35 +- kplayer/video/BMPlayer.swift | 631 ++++++++++++++ .../video/BMPlayerClearityChooseButton.swift | 29 + kplayer/video/BMPlayerControlView.swift | 769 ++++++++++++++++++ kplayer/video/BMPlayerItem.swift | 91 +++ kplayer/video/BMPlayerLayerView.swift | 507 ++++++++++++ kplayer/video/BMPlayerManager.swift | 62 ++ kplayer/video/BMPlayerProtocols.swift | 30 + kplayer/video/BMSubtitles.swift | 122 +++ kplayer/video/BMTimeSlider.swift | 26 + kplayer/video/KBMPlayer.swift | 88 +- 22 files changed, 2523 insertions(+), 521 deletions(-) delete mode 100644 kplayer/detail/VideoPlayerController.swift create mode 100644 kplayer/video/BMPlayer.swift create mode 100644 kplayer/video/BMPlayerClearityChooseButton.swift create mode 100644 kplayer/video/BMPlayerControlView.swift create mode 100644 kplayer/video/BMPlayerItem.swift create mode 100644 kplayer/video/BMPlayerLayerView.swift create mode 100644 kplayer/video/BMPlayerManager.swift create mode 100644 kplayer/video/BMPlayerProtocols.swift create mode 100644 kplayer/video/BMSubtitles.swift create mode 100644 kplayer/video/BMTimeSlider.swift diff --git a/Podfile b/Podfile index a697a3b..126f991 100644 --- a/Podfile +++ b/Podfile @@ -1,4 +1,4 @@ -platform :ios, '10.2' +platform :ios, '11.0' source 'https://github.com/CocoaPods/Specs.git' use_frameworks! @@ -7,26 +7,30 @@ target 'kplayer' do # pod 'Player' #pod 'Alamofire', :git => 'https://github.com/Alamofire/Alamofire.git', :branch => 'swift-2.0' #pod 'Alamofire', '~> 1.2' - pod 'Alamofire', '4.7' - pod 'ALMoviePlayerController', '0.3.0' + pod 'Alamofire' #, '4.7' + #pod 'ALMoviePlayerController', '0.3.0' #pod 'DZVideoPlayerViewController' #pod 'SwiftyJSON' - #pod 'HanekeSwift' - pod 'HanekeSwift', :git => 'https://github.com/jasonnoahchoi/HanekeSwift', :branch => 'swift3' + #pod 'HanekeSwift' #, '1.2' + pod 'HanekeSwift', :git => 'https://github.com/Haneke/HanekeSwift.git' + #pod '', :git => 'https://github.com/jasonnoahchoi/HanekeSwift', :branch => 'swift3' pod 'Nimbus/Photos' pod 'Nimbus/PagingScrollView' - pod 'BMPlayer', '0.8.6' + # pod 'BMPlayer' #, '0.8.6'# + # pod 'BMPlayer/CacheSupport', :git => 'https://github.com/BrikerMan/BMPlayer.git' + pod 'NVActivityIndicatorView' + pod 'SnapKit' pod 'WebBrowser' #pod 'AFNetworking', '~>2.1' #pod 'Dollar', '6.1.0' #pod 'Cent', '6.0.3' #, '1.2.0' - post_install do |installer| - installer.pods_project.targets.each do |target| - target.build_configurations.each do |config| - config.build_settings['SWIFT_VERSION'] = '3.0' - end - end - end + # post_install do |installer| + # installer.pods_project.targets.each do |target| + # target.build_configurations.each do |config| + # config.build_settings['SWIFT_VERSION'] = '3.0' + # end + # end + # end end diff --git a/kplayer.xcodeproj/project.pbxproj b/kplayer.xcodeproj/project.pbxproj index 635bc72..5be3ace 100644 --- a/kplayer.xcodeproj/project.pbxproj +++ b/kplayer.xcodeproj/project.pbxproj @@ -16,11 +16,13 @@ 1C736503B656C999E5E12081 /* NetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7365B06FA66294E99AC2D3 /* NetworkManager.swift */; }; 1C73654C9EA6D255CFC039C5 /* NetworkHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73620D01687FB4F1811C5C /* NetworkHelper.swift */; }; 1C7365885FAF292F2221ED44 /* MediaPhotoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73673DC671535E3A049F54 /* MediaPhotoController.swift */; }; + 1C7366DAC06047DE335EFC37 /* BMPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736927EA28AFBEB25D7487 /* BMPlayer.swift */; }; 1C73671FC2CCCACAA2FFC153 /* ThumbnailCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736EA15A11AF7D57F85824 /* ThumbnailCache.swift */; }; + 1C73673F39A34C3275D0230A /* BMPlayerClearityChooseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736DFBD072763248412F74 /* BMPlayerClearityChooseButton.swift */; }; 1C73675C34BE0990D44570BE /* ItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736253AB7A95EA41B605B7 /* ItemModel.swift */; }; 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 */; }; - 1C7368364397315E12E90F05 /* VideoPlayerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7367379DEE94EBF3FAFA78 /* VideoPlayerController.swift */; }; 1C73688D13E5A804880C8768 /* UIImageExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736DCCE3AA9993E15F7652 /* UIImageExtension.swift */; }; 1C73691A9C7174E0C6B57267 /* stringutil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736B794396F2E50387B8F2 /* stringutil.swift */; }; 1C73693A1334A7792856FC58 /* MasterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73611D226B48C24DB37535 /* MasterViewController.swift */; }; @@ -28,9 +30,15 @@ 1C7369763AB6C73472E11B55 /* KBMPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736D981F8315FFD7D40695 /* KBMPlayer.swift */; }; 1C7369ABC44CFB530EA71FB6 /* HeaderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736D9BB5498E7E8F11C754 /* HeaderCell.swift */; }; 1C736A5FA5BA53B2597F2ED7 /* Kirschkeks-256x256.png in Resources */ = {isa = PBXBuildFile; fileRef = 1C736059262A57AADE6AB761 /* Kirschkeks-256x256.png */; }; + 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 */; }; + 1C736DB41BD06D359E6A0DEE /* BMSubtitles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7366AAB82A46086690E164 /* BMSubtitles.swift */; }; + 1C736F278DDC77F40C8CB1D4 /* BMPlayerControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736E51F1A03E3A1200BDB6 /* BMPlayerControlView.swift */; }; + 1C736F3570EADA086682E6BC /* BMPlayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7360D6580FB5D09C2BBCCB /* BMPlayerManager.swift */; }; 1C736F6A223D4ADB2E1BA733 /* ItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736069C214E9522BB1BD97 /* ItemCell.swift */; }; 1C736FB92B19FE17E4357C85 /* MediaItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73688DAB88F9360FB62A49 /* MediaItem.swift */; }; AA74B07A01F0E99E6DEC7D1B /* Pods_kplayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B75159FFCD5A882E6F167FE /* Pods_kplayer.framework */; }; @@ -58,17 +66,23 @@ 1C736069C214E9522BB1BD97 /* ItemCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemCell.swift; sourceTree = ""; }; 1C7360744ABACC3557D05760 /* HanekeFetchOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HanekeFetchOperation.swift; sourceTree = ""; }; 1C7360A94DBECA685ED8602F /* ImageLoadOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageLoadOperation.swift; sourceTree = ""; }; + 1C7360AE55EB115762C42EB9 /* BMTimeSlider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMTimeSlider.swift; sourceTree = ""; }; + 1C7360D6580FB5D09C2BBCCB /* BMPlayerManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMPlayerManager.swift; sourceTree = ""; }; 1C73611D226B48C24DB37535 /* MasterViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterViewController.swift; sourceTree = ""; }; 1C73620D01687FB4F1811C5C /* NetworkHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkHelper.swift; sourceTree = ""; }; 1C736253AB7A95EA41B605B7 /* ItemModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemModel.swift; sourceTree = ""; }; 1C736260E748CF136FF37EA7 /* UploadOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UploadOperation.swift; sourceTree = ""; }; 1C73648CEC974A2500172064 /* ViewControllerExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewControllerExtensions.swift; sourceTree = ""; }; + 1C7364F924BD979294C3EE4A /* BMPlayerItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMPlayerItem.swift; sourceTree = ""; }; 1C7365B06FA66294E99AC2D3 /* NetworkManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkManager.swift; sourceTree = ""; }; - 1C7367379DEE94EBF3FAFA78 /* VideoPlayerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoPlayerController.swift; sourceTree = ""; }; + 1C7365F45D765A218FFC100F /* BMPlayerProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMPlayerProtocols.swift; sourceTree = ""; }; + 1C7366AAB82A46086690E164 /* BMSubtitles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMSubtitles.swift; sourceTree = ""; }; + 1C7366D766CDE0C9872E86F5 /* BMPlayerLayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMPlayerLayerView.swift; sourceTree = ""; }; 1C73673DC671535E3A049F54 /* MediaPhotoController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaPhotoController.swift; sourceTree = ""; }; 1C736777456388CA571DA17B /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = System/Library/Frameworks/MediaPlayer.framework; sourceTree = SDKROOT; }; 1C736871C9B012CB704AB803 /* readme.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = readme.md; sourceTree = ""; }; 1C73688DAB88F9360FB62A49 /* MediaItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaItem.swift; sourceTree = ""; }; + 1C736927EA28AFBEB25D7487 /* BMPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMPlayer.swift; sourceTree = ""; }; 1C7369EC16B19B32B515169E /* NetData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetData.swift; sourceTree = ""; }; 1C7369F53095B7A4D65679C2 /* DetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = ""; }; 1C736B794396F2E50387B8F2 /* stringutil.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = stringutil.swift; sourceTree = ""; }; @@ -77,6 +91,8 @@ 1C736D981F8315FFD7D40695 /* KBMPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KBMPlayer.swift; sourceTree = ""; }; 1C736D9BB5498E7E8F11C754 /* HeaderCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderCell.swift; sourceTree = ""; }; 1C736DCCE3AA9993E15F7652 /* UIImageExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIImageExtension.swift; sourceTree = ""; }; + 1C736DFBD072763248412F74 /* BMPlayerClearityChooseButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMPlayerClearityChooseButton.swift; sourceTree = ""; }; + 1C736E51F1A03E3A1200BDB6 /* BMPlayerControlView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMPlayerControlView.swift; sourceTree = ""; }; 1C736EA15A11AF7D57F85824 /* ThumbnailCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThumbnailCache.swift; sourceTree = ""; }; 1C736F9338CE36708244D42A /* DataLoadOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataLoadOperation.swift; sourceTree = ""; }; 6D522F61736592330F481B4F /* Pods-kplayer.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-kplayer.debug.xcconfig"; path = "Pods/Target Support Files/Pods-kplayer/Pods-kplayer.debug.xcconfig"; sourceTree = ""; }; @@ -117,7 +133,6 @@ 1C73615846EE8B07DAAFD230 /* detail */ = { isa = PBXGroup; children = ( - 1C7367379DEE94EBF3FAFA78 /* VideoPlayerController.swift */, 1C736069C214E9522BB1BD97 /* ItemCell.swift */, 1C736D9BB5498E7E8F11C754 /* HeaderCell.swift */, 1C7369F53095B7A4D65679C2 /* DetailViewController.swift */, @@ -175,6 +190,15 @@ isa = PBXGroup; children = ( 1C736D981F8315FFD7D40695 /* KBMPlayer.swift */, + 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 */, ); path = video; sourceTree = ""; @@ -332,6 +356,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, Base, ); @@ -374,10 +399,8 @@ files = ( ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-kplayer/Pods-kplayer-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/ALMoviePlayerController/ALMoviePlayerController.framework", + "${PODS_ROOT}/Target Support Files/Pods-kplayer/Pods-kplayer-frameworks.sh", "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", - "${BUILT_PRODUCTS_DIR}/BMPlayer/BMPlayer.framework", "${BUILT_PRODUCTS_DIR}/HanekeSwift/Haneke.framework", "${BUILT_PRODUCTS_DIR}/NVActivityIndicatorView/NVActivityIndicatorView.framework", "${BUILT_PRODUCTS_DIR}/Nimbus/Nimbus.framework", @@ -386,9 +409,7 @@ ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ALMoviePlayerController.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/BMPlayer.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Haneke.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NVActivityIndicatorView.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Nimbus.framework", @@ -397,7 +418,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-kplayer/Pods-kplayer-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-kplayer/Pods-kplayer-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 5BE7FB291B76EEE317F0B068 /* [CP] Check Pods Manifest.lock */ = { @@ -430,7 +451,6 @@ 1C736503B656C999E5E12081 /* NetworkManager.swift in Sources */, 1C736FB92B19FE17E4357C85 /* MediaItem.swift in Sources */, 1C73688D13E5A804880C8768 /* UIImageExtension.swift in Sources */, - 1C7368364397315E12E90F05 /* VideoPlayerController.swift in Sources */, 1C736F6A223D4ADB2E1BA733 /* ItemCell.swift in Sources */, 1C7369ABC44CFB530EA71FB6 /* HeaderCell.swift in Sources */, 1C736D24B49451141CD4B64D /* DetailViewController.swift in Sources */, @@ -451,6 +471,15 @@ 1C736953BDBBAFC40884132A /* BrowserController.swift in Sources */, 1C7362A6FA1C5DA0B0677F1E /* readme.md in Sources */, 1C73671FC2CCCACAA2FFC153 /* ThumbnailCache.swift in Sources */, + 1C7366DAC06047DE335EFC37 /* BMPlayer.swift in Sources */, + 1C73673F39A34C3275D0230A /* BMPlayerClearityChooseButton.swift in Sources */, + 1C736F278DDC77F40C8CB1D4 /* BMPlayerControlView.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 */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -613,7 +642,7 @@ PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_SWIFT3_OBJC_INFERENCE = On; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -633,7 +662,7 @@ PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_SWIFT3_OBJC_INFERENCE = On; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; }; name = Release; }; diff --git a/kplayer/core/MediaItem.swift b/kplayer/core/MediaItem.swift index 92437a2..0fc4570 100644 --- a/kplayer/core/MediaItem.swift +++ b/kplayer/core/MediaItem.swift @@ -152,12 +152,13 @@ class MediaItem: CustomDebugStringConvertible { imageUrl = imageUrl.replacingOccurrences(of: "?preview=true", with: "") imageUrl = imageUrl.addingPercentEncoding(withAllowedCharacters: NSCharacterSet.urlQueryAllowed)! - return NetworkManager.sharedInstance.baseurl + "/service/download" + imageUrl + return NetworkManager.sharedInstance.vidurl + imageUrl.substringStartingFrom(10) } var playerURL: URL? { let enc = name.replacingOccurrences(of: " ", with: "%20") - let s = NetworkManager.sharedInstance.baseurl + "/service/stream" + encodedDir! + let index = encodedDir!.index(encodedDir!.startIndex, offsetBy: 10) + let s = NetworkManager.sharedInstance.vidurl + encodedDir!.substring(from:index) if s.endsWith("/") { return URL(string: s + enc) diff --git a/kplayer/core/NetworkManager.swift b/kplayer/core/NetworkManager.swift index f78e992..c18965d 100644 --- a/kplayer/core/NetworkManager.swift +++ b/kplayer/core/NetworkManager.swift @@ -11,6 +11,7 @@ class NetworkManager { static let sharedInstance = NetworkManager() let baseurl = "http://linkstation:8080/tomcat/media" + let vidurl = "http://linkstation:8089" var authenticated = false @@ -39,10 +40,10 @@ class NetworkManager { var res = [MediaItem]() let url = (url1 as NSString).replacingOccurrences(of: " ", with: "+") print(url) - Alamofire.request(url).responseJSON { + AF.request(url).responseJSON { (response) in - if let json = response.result.value as? [String] { + if let json = response.value as? [String] { print("Empfange \(json.count) Ergebnisse") for s in json { @@ -84,10 +85,10 @@ class NetworkManager { let url = (url1 as NSString).replacingOccurrences(of: " ", with: "+") print(url) - Alamofire.request(url).responseJSON { + AF.request(url).responseJSON { (response) in - if let json = response.result.value { + if let json = response.value { var items = Dictionary() let result = json as! [String] @@ -145,10 +146,10 @@ class NetworkManager { let ux = baseurl + "/service/listdirs" + url print(ux) - Alamofire.request(ux).responseJSON { + AF.request(ux).responseJSON { (response) in - if let json = response.result.value { + if let json = response.value { var res = [MediaItem]() var leaf = false var hasPics = false @@ -228,10 +229,10 @@ class NetworkManager { print(url) - Alamofire.request(url).responseJSON { + AF.request(url).responseJSON { (response) in - if let json = response.result.value { + if let json = response.value { var hashes = Dictionary() for b in json as! [String] { @@ -276,13 +277,13 @@ class NetworkManager { } func deleteThumb(_ path: String) { - Alamofire.request(NetworkManager.sharedInstance.baseurl + "/service/deletethumb\(path)") + AF.request(NetworkManager.sharedInstance.baseurl + "/service/deletethumb\(path)") } func favItem(_ item: MediaItem) { let url = baseurl + "/service/linkfav" + item.fullPath - Alamofire.request(url) + AF.request(url) } func saveItem(_ item: MediaItem) { @@ -297,12 +298,12 @@ class NetworkManager { print(url) - Alamofire.request(url).responseJSON { + AF.request(url).responseJSON { (response) in var hashes = Set() - if let json = response.result.value { + if let json = response.value { for b in json as! [String] { hashes.insert(b) print(b) @@ -345,10 +346,10 @@ class NetworkManager { print(items) print(url) - Alamofire.request(url).responseJSON { + AF.request(url).responseJSON { (response) in - if let json = response.result.value { + if let json = response.value { var im = [MediaItem]() for s in json as! [String] { diff --git a/kplayer/detail/AVPlayerController.swift b/kplayer/detail/AVPlayerController.swift index 4538b59..125b0f7 100644 --- a/kplayer/detail/AVPlayerController.swift +++ b/kplayer/detail/AVPlayerController.swift @@ -6,17 +6,18 @@ import Foundation import UIKit import Haneke -import BMPlayer import AVFoundation protocol ItemController { func setCurrentItem(item: MediaItem) + func setItems(items: [MediaItem]) func setCompletionHandler(handler: @escaping (() -> Void)) } class AVPlayerController: UIViewController, ItemController { var player = KBMPlayer() var currentItem: MediaItem? + var allItems = [MediaItem]() var completionHandler: (() -> Void)? @@ -30,13 +31,13 @@ class AVPlayerController: UIViewController, ItemController { var backButton: UIBarButtonItem? var reviewButton: UIBarButtonItem? - let speedOptions = [ 0.25, 0.5, 0.75, 1.0, 2.0 ] - var speedOption = 3 + let speedOptions = [ 0.25, 1 ] + var speedOption = 1 var aspect = 1 - let zoomOptions = [ 1, 1.25, 1.5, 2.0 ] - var zoomOption = 1 + let zoomOptions = [ 1, 2.0 ] + var zoomOption = 0 var thumbnailTime: TimeInterval = 0.0 @@ -79,6 +80,10 @@ class AVPlayerController: UIViewController, ItemController { } } + func setItems(items: [MediaItem]) { + allItems = items + } + func setCurrentItem(item: MediaItem) { currentItem = item } @@ -130,9 +135,9 @@ print("play") zoomOption = 0 } let zoom = Float(zoomOptions[zoomOption]) - player.playerLayer!.layer.transform = CATransform3DMakeScale(CGFloat(zoom), CGFloat(zoom), 1.0) - - player.zoom = zoom +// player.playerLayer!.layer.transform = CATransform3DMakeScale(CGFloat(zoom), CGFloat(zoom), 1.0) +// +// player.zoom = zoom zoomButton!.title = "\(zoom)" print("zoom \(zoom)") @@ -156,7 +161,7 @@ print("play") default: print("aspect") } - player.verticalMoved(0) + // todo player.verticalMoved(0) aspectButton!.title = "\(aspect)" } @@ -181,13 +186,25 @@ print("play") } func play(_ url: URL) { - let asset = BMPlayerResource(url: url) + var def = [BMPlayerResourceDefinition]() + var index = 0; + var count = 0; + for i in allItems { + let r = BMPlayerResourceDefinition(url: i.playerURL!, definition: i.name); + def.append(r) + if (url == i.playerURL) { + index = count + } + count += 1 + } + let asset = BMPlayerResource(name: "video", definitions: def) + // let asset = BMPlayerResource(url: url) - player.setVideo(resource: asset) + player.setVideo(resource: asset, definitionIndex: index) player.playerLayer!.player!.automaticallyWaitsToMinimizeStalling = false if let item = player.playerLayer?.playerItem { item.canUseNetworkResourcesForLiveStreamingWhilePaused = true - item.preferredForwardBufferDuration = 1 + item.preferredForwardBufferDuration = 10 } } diff --git a/kplayer/detail/BrowserController.swift b/kplayer/detail/BrowserController.swift index 707140b..7532aa3 100644 --- a/kplayer/detail/BrowserController.swift +++ b/kplayer/detail/BrowserController.swift @@ -11,6 +11,10 @@ import WebKit class BrowserController : UIViewController, ItemController, WebBrowserDelegate, UINavigationControllerDelegate { var completionHandler: (() -> Void)? + func setItems(items: [MediaItem]) { + + } + func setCurrentItem(item: MediaItem) { } diff --git a/kplayer/detail/DetailViewController.swift b/kplayer/detail/DetailViewController.swift index 55937ae..93825ba 100644 --- a/kplayer/detail/DetailViewController.swift +++ b/kplayer/detail/DetailViewController.swift @@ -68,7 +68,7 @@ class DetailViewController: UIViewController, UICollectionViewDelegateFlowLayout // attach long press gesture to collectionView let lpgr = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(_:))) - lpgr.minimumPressDuration = 3; + lpgr.minimumPressDuration = 1; //seconds lpgr.delaysTouchesBegan = true self.collectionView.addGestureRecognizer(lpgr); @@ -175,25 +175,37 @@ class DetailViewController: UIViewController, UICollectionViewDelegateFlowLayout print("couldn't find index path"); } else { if let detail: MediaItem = self.detailItem { - let items = detail.children[indexPath!.section] - if (items.loaded) { - if items.children.count == 0 { - } else { - if indexPath!.item >= items.children.count { + let refreshAlert = UIAlertController(title: "Delete", message: "All data will be lost.", preferredStyle: UIAlertController.Style.alert) + + refreshAlert.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (action: UIAlertAction!) in + + let items = detail.children[indexPath!.section] + if (items.loaded) { + if items.children.count == 0 { } else { - let c = items.children.remove(at: indexPath!.item) - if let t = c.time { - let ms = Int(t * 1000) - let p = c.snapshotDirPathForVideo + "\(ms).jpg" - let pt = c.snapshotDirPathForVideo + "\(ms)_thumb.jpg" - - NetworkManager.sharedInstance.deleteThumb(p) - NetworkManager.sharedInstance.deleteThumb(pt) + if indexPath!.item >= items.children.count { + } else { + let c = items.children.remove(at: indexPath!.item) + if let t = c.time { + let ms = Int(t * 1000) + let p = c.snapshotDirPathForVideo + "\(ms).jpg" + let pt = c.snapshotDirPathForVideo + "\(ms)_thumb.jpg" + + NetworkManager.sharedInstance.deleteThumb(p) + NetworkManager.sharedInstance.deleteThumb(pt) + } + self.collectionView.reloadData() } - self.collectionView.reloadData() } } - } + })) + + refreshAlert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (action: UIAlertAction!) in + print("Handle Cancel Logic here") + })) + + present(refreshAlert, animated: true, completion: nil) + } } } @@ -294,6 +306,7 @@ class DetailViewController: UIViewController, UICollectionViewDelegateFlowLayout self.dismiss(animated: true, completion: nil); } let navController = UINavigationController(rootViewController: pc) // Creating a navigation controller with pc at the root of the navigation stack. + navController.modalPresentationStyle = .fullScreen self.present(navController, animated: false, completion: nil) } @@ -305,6 +318,7 @@ class DetailViewController: UIViewController, UICollectionViewDelegateFlowLayout self.dismiss(animated: true, completion: nil); } let navController = UINavigationController(rootViewController: pc) // Creating a navigation controller with pc at the root of the navigation stack. + navController.modalPresentationStyle = .fullScreen self.present(navController, animated: false, completion: nil) } @@ -312,15 +326,16 @@ class DetailViewController: UIViewController, UICollectionViewDelegateFlowLayout func showVideo() { var pc: ItemController? - if videoplayer { + // if videoplayer { pc = AVPlayerController() - } - else { +// } +// else { // pc = BrowserController() - pc = VideoPlayerController() - } +// pc = VideoPlayerController() +// } pc!.setCurrentItem(item: self.currentItem!) + pc!.setItems(items: detailItem!.children) pc!.setCompletionHandler(handler: { self.collectionView.reloadData() self.collectionView.collectionViewLayout.invalidateLayout() @@ -336,6 +351,7 @@ class DetailViewController: UIViewController, UICollectionViewDelegateFlowLayout self.dismiss(animated: true, completion: nil); }) let navController = UINavigationController(rootViewController: (pc! as! UIViewController)) + navController.modalPresentationStyle = .fullScreen navController.navigationBar.barTintColor = UIColor.black (pc! as! UIViewController).navigationItem.leftItemsSupplementBackButton = true diff --git a/kplayer/detail/VideoPlayerController.swift b/kplayer/detail/VideoPlayerController.swift deleted file mode 100644 index 1bf5f03..0000000 --- a/kplayer/detail/VideoPlayerController.swift +++ /dev/null @@ -1,396 +0,0 @@ -// -// Created by Marco Schmickler on 26.05.15. -// Copyright (c) 2015 Marco Schmickler. All rights reserved. -// - -import Foundation -import UIKit -import MediaPlayer -import ALMoviePlayerController -import Haneke - -class VideoPlayerController: UIViewController, ItemController { - var moviePlayer: ALMoviePlayerController? - var currentItem: MediaItem? - - var completionHandler: (() -> Void)? - - var buttons = Dictionary() - - var barbutton: UIBarButtonItem? - var speedButton: UIBarButtonItem? - var playButton: UIBarButtonItem? - var backButton: UIBarButtonItem? - var reviewButton: UIBarButtonItem? - - let speedOptions = [ 0.25, 0.5, 0.75, 1.0, 2.0 ] - var speedOption = 3 - - var thumbnailTime: TimeInterval = 0.0 - - var edit = true - var allowEdit = true - var index = 0 - - func setCompletionHandler(handler: @escaping (() -> Void)) { - completionHandler = handler - } - - override func viewDidLoad() { - super.viewDidLoad() - - barbutton = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(VideoPlayerController.twoFingersTwoTaps)); - navigationItem.rightBarButtonItems = [barbutton!] - - backButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(VideoPlayerController.back(_:))) - speedButton = UIBarButtonItem(title:"1.0", style:UIBarButtonItem.Style.plain, target: self, action: #selector(VideoPlayerController.speed(_:))) - playButton = UIBarButtonItem(barButtonSystemItem: .play, target: self, action: #selector(VideoPlayerController.startstop(_:))) - reviewButton = UIBarButtonItem(title:"Edit ", style:UIBarButtonItem.Style.plain, target: self, action: #selector(VideoPlayerController.doEdit(_:))) - - navigationItem.leftBarButtonItems = [backButton!, playButton!, speedButton!, reviewButton!] - - if let c = currentItem, let url = c.playerURL { - print(url) - play(url as URL) - } - } - - func setCurrentItem(item: MediaItem) { - currentItem = item - } - - @objc func doEdit(_ sender: AnyObject) { - if (!allowEdit) { - return - } - - if (edit) { - edit = false - reviewButton!.tintColor = UIColor.blue - } - else { - edit = true - reviewButton!.tintColor = UIColor.yellow - } - } - - @objc func startstop(_ sender: AnyObject) { - if moviePlayer!.playbackState == MPMoviePlaybackState.playing { - moviePlayer!.pause() - } - else { - moviePlayer!.play() - - Timer.scheduledTimer(timeInterval: 0.5, - target: self, - selector: #selector(resumePlay), - userInfo: nil, - repeats: false) - } -print("play") - } - - @objc func speed(_ sender: AnyObject) { - speedOption += 1 - if speedOption >= speedOptions.count { - speedOption = 0 - } - - moviePlayer!.currentPlaybackRate = Float(speedOptions[speedOption]) - - speedButton!.title = "\(moviePlayer!.currentPlaybackRate)" - print("speed \(moviePlayer!.currentPlaybackRate)") - } - - @IBAction func back(_ sender: AnyObject) { - if moviePlayer!.playbackState == MPMoviePlaybackState.playing { - moviePlayer!.pause() - } - completionHandler!() - } - - func play(_ url: URL) { - self.moviePlayer = ALMoviePlayerController(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height)) - if let player = self.moviePlayer { - - let movieControls = ALMoviePlayerControls(moviePlayer: player, style: ALMoviePlayerControlsStyleDefault)!; - movieControls.fadeDelay = 60 - player.controls = movieControls - movieControls.style = ALMoviePlayerControlsStyleEmbedded - - NotificationCenter.default.addObserver(self, selector: #selector(VideoPlayerController.exitedFullscreen), name: NSNotification.Name.MPMoviePlayerDidExitFullscreen, object: nil); - NotificationCenter.default.addObserver(self, selector: #selector(VideoPlayerController.enteredFullscreen), name: NSNotification.Name.MPMoviePlayerDidEnterFullscreen, object: nil); - NotificationCenter.default.addObserver(self, selector: #selector(VideoPlayerController.showThumbnail(_:)), name: NSNotification.Name.MPMoviePlayerThumbnailImageRequestDidFinish, object: nil); - NotificationCenter.default.addObserver(self, selector: #selector(VideoPlayerController.playerItemDidReachEnd(_:)), name: NSNotification.Name.MPMoviePlayerLoadStateDidChange, object: nil); - - player.view.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: self.view.frame.size.height) - player.view.sizeToFit() - player.scalingMode = MPMovieScalingMode.aspectFit -// player.controlStyle = MPMovieControlStyle.Embedded - player.movieSourceType = MPMovieSourceType.streaming - player.repeatMode = MPMovieRepeatMode.one - player.contentURL = url - - self.view.addSubview(player.view) - installGestures(player.view) - update() - } - } - - @objc func playerItemDidReachEnd(_ note: Notification) { -print("finish") - // Timer.scheduledTimer(timeInterval: 0.6, target: self, selector: #selector(update), userInfo: nil, repeats: false) - } - - @objc func showThumbnail(_ note: Notification) { - let userInfo = note.userInfo! as NSDictionary - let thumbnail = userInfo.object(forKey: MPMoviePlayerThumbnailImageKey) as! UIImage - let time = userInfo.object(forKey: MPMoviePlayerThumbnailTimeKey) as! TimeInterval - - let newItem = MediaItem(name: currentItem!.name, path: currentItem!.path, root: currentItem!.root, type: ItemType.SNAPSHOT) - newItem.image = thumbnail - newItem.time = time - newItem.parent = currentItem! - currentItem!.children.append(newItem) - - print(newItem.time) - - addItemButton(newItem) - } - - 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) { - - moviePlayer!.currentPlaybackTime = buttons[source]!.time! - moviePlayer!.currentPlaybackRate = Float(speedOptions[speedOption]) - - print("goto \(buttons[source]!.time!) is \(moviePlayer!.currentPlaybackTime)") - } - - @objc func update() { - if let player = self.moviePlayer { - -// if !(player.duration > 0.0) { -// print("again") -// Timer.scheduledTimer(timeInterval: 0.3, target: self, selector: #selector(update), userInfo: nil, repeats: false) -// return -// } - - reviewButton!.title = currentItem!.name - - if currentItem!.type == ItemType.SNAPSHOT { - player.currentPlaybackTime = currentItem!.time! - currentItem = currentItem!.parent - } else { - if !currentItem!.children.isEmpty { - player.currentPlaybackTime = currentItem!.children[0].time! - } - else { - print(player.duration) - player.currentPlaybackTime = player.duration / 2 - } - } - - navigationItem.rightBarButtonItems = [barbutton!] - - for c in currentItem!.children { - addItemButton(c) - } - - player.play() - } - } - - @objc func enteredFullscreen() { - let mp = UIApplication.shared.keyWindow; - if let moviePlayerContainer = mp!.recursiveSearchForViewWithName("MPVideoContainerView") { - if (moviePlayerContainer.gestureRecognizers != nil) { - return; - } - installGestures(moviePlayerContainer) - } - } - - @objc func exitedFullscreen() { - moviePlayer!.view.removeFromSuperview(); - moviePlayer = nil; - NotificationCenter.default.removeObserver(self); - - } - - func installGestures(_ moviePlayer: UIView) { - let twoFingersTwoTapsGesture = UITapGestureRecognizer(target: self, action: #selector(twoFingersTwoTaps)) - 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") - if let player = self.moviePlayer { - 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.contentURL = currentItem!.playerURL - player.play() - 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! - moviePlayer!.currentPlaybackRate = Float(speedOptions[speedOption]) - } - } - } - - @objc func swipeRight() { - print("r") - if let player = self.moviePlayer { - moviePlayer!.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 let player = self.moviePlayer { - 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") - if let player = self.moviePlayer { - moviePlayer!.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() { - moviePlayer!.currentPlaybackRate = Float(speedOptions[speedOption]) - print("resumePlay") - } - - @objc func twoFingersTwoTaps() { - if edit { - thumbnailTime = moviePlayer!.currentPlaybackTime - print("tap \(thumbnailTime)") - moviePlayer!.requestThumbnailImages(atTimes: [thumbnailTime], - timeOption: MPMovieTimeOption.exact); - } - else { - NetworkManager.sharedInstance.favItem(currentItem!) - } - } - - -} diff --git a/kplayer/master/MasterViewController.swift b/kplayer/master/MasterViewController.swift index 68a2917..eead07e 100644 --- a/kplayer/master/MasterViewController.swift +++ b/kplayer/master/MasterViewController.swift @@ -294,7 +294,7 @@ class MasterViewController: UITableViewController, UISearchResultsUpdating { func configureCell(_ cell: UITableViewCell, atIndexPath indexPath: IndexPath) { let object = model.items[indexPath.row] - if (object.path.characters.count == 0) { + if (object.path.count == 0) { cell.textLabel!.text = "'" + object.name } else { cell.textLabel!.text = object.path diff --git a/kplayer/photo/MediaPhotoController.swift b/kplayer/photo/MediaPhotoController.swift index be33d3c..b26a912 100644 --- a/kplayer/photo/MediaPhotoController.swift +++ b/kplayer/photo/MediaPhotoController.swift @@ -88,7 +88,7 @@ class MediaPhotoController: NIToolbarPhotoViewController, NIPhotoAlbumScrollView let url = NetworkManager.sharedInstance.baseurl + "/service/linkfavpic" + imageUrl print(url) - Alamofire.request(url).responseString { + AF.request(url).responseString { (result) in print("ok") } @@ -142,12 +142,12 @@ class MediaPhotoController: NIToolbarPhotoViewController, NIPhotoAlbumScrollView print(items) print(url) - Alamofire.request(url).responseJSON { + AF.request(url).responseJSON { (response) in var im = [MediaItem]() - if let json = response.result.value { + if let json = response.value { for s in json as! [String] { if s.hasSuffix(".jpg") { @@ -194,20 +194,20 @@ class MediaPhotoController: NIToolbarPhotoViewController, NIPhotoAlbumScrollView return } - let controller = VideoPlayerController() - controller.edit = false - controller.allowEdit = false - - controller.currentItem = 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); - } +// let controller = VideoPlayerController() +// controller.edit = false +// controller.allowEdit = false +// +// controller.currentItem = 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() { diff --git a/kplayer/util/ImageLoadOperation.swift b/kplayer/util/ImageLoadOperation.swift index c9e3896..65e5b20 100644 --- a/kplayer/util/ImageLoadOperation.swift +++ b/kplayer/util/ImageLoadOperation.swift @@ -15,7 +15,7 @@ public class ImageLoadOperation: Operation { let index: Int var request: DataRequest? - var manager: SessionManager? + var manager: Session? public init(imageURL: URL, succeeder: @escaping Succeeder, index: Int) { self.imageURL = imageURL diff --git a/kplayer/util/UploadOperation.swift b/kplayer/util/UploadOperation.swift index 0ef4e36..5644c2b 100644 --- a/kplayer/util/UploadOperation.swift +++ b/kplayer/util/UploadOperation.swift @@ -25,20 +25,27 @@ class UploadOperation: Operation { print("PATH: \(path)") - Alamofire.upload(multipartFormData: { multipartFormData in - multipartFormData.append(self.data, withName: "file", fileName: self.path, mimeType: MimeType.Json.rawValue) - multipartFormData.append(self.path.data(using: String.Encoding.utf8)!, withName: "name") - }, to: baseUrl, - encodingCompletion: { encodingResult in - switch encodingResult { - case .success(let upload, _, _): - upload.responseJSON { response in - print(response.result.value) - } - case .failure(let error): - print(error) - } - }) + do { + try AF.upload(multipartFormData: { multipartFormData in + multipartFormData.append(self.data, withName: "file", fileName: self.path, mimeType: MimeType.Json.rawValue) + multipartFormData.append(self.path.data(using: String.Encoding.utf8)!, withName: "name") + }, with: URLRequest(url: URL(string: baseUrl)!, method: HTTPMethod.post)) + .response(completionHandler: { data in + print(data.data!) + }) + } catch { + print("Upload Error") + }; +// encodingCompletion: { encodingResult in +// switch encodingResult { +// case .success(let upload, _, _): +// upload.responseJSON { response in +// print(response.result.value) +// } +// case .failure(let error): +// print(error) +// } +// }) // .progress { // (bytesWritten, totalBytesWritten, totalBytesExpectedToWrite) in // print("progress : \(totalBytesWritten) / \(totalBytesExpectedToWrite)") diff --git a/kplayer/video/BMPlayer.swift b/kplayer/video/BMPlayer.swift new file mode 100644 index 0000000..d51f058 --- /dev/null +++ b/kplayer/video/BMPlayer.swift @@ -0,0 +1,631 @@ +// +// 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, 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) +} + +/** + internal enum to check the pan direction + + - horizontal: horizontal + - vertical: vertical + +enum BMPanDirection: Int { + case horizontal = 0 + case vertical = 1 +} + */ +open class BMPlayer: UIView { + + open weak var delegate: BMPlayerDelegate? + + open var backBlock:((Bool) -> Void)? + + /// Gesture to change volume / brightness + open var panGesture: UIPanGestureRecognizer! + + /// 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 + + fileprivate 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 + fileprivate 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 + fileprivate var isPlayToTheEnd = false + //视频画面比例 + fileprivate var aspectRatio: BMPlayerAspectRatio = .default + + //Cache is playing result to improve callback performance + fileprivate var isPlayingCache: Bool? = nil + + // 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] + 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() + } + } + + /** + 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 + } + + /** + 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) { + 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 + } + + // MARK: - Action Response + + @objc open func panDirection(_ pan: UIPanGestureRecognizer) { + // 根据在view上Pan的位置,确定是调音量还是亮度 + 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 x > y { + if BMPlayerConf.enablePlaytimeGestures { + self.panDirection = BMPanDirection.horizontal + + if locationPoint.y > self.bounds.size.height * 2 / 3 { + self.isSkip = false + } else { + self.isSkip = true + self.skipAmount = 0.0 + } + + // 给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: + 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 { + print( skipAmount) + if (skipAmount > 1000) { + self.sumTime += TimeInterval(30) + } + else if skipAmount > 0 { + self.sumTime += TimeInterval(10) + } + else if skipAmount > -1000 { + self.sumTime -= TimeInterval(10) + } + else { + self.sumTime -= TimeInterval(30) + } + } + 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 + + case BMPanDirection.vertical: + self.isVolume = false + } + default: + break + } + } + + open func verticalMoved(_ value: CGFloat) { +print(value) + if (value < -100) { + // controlView(controlView: controlView, didChooseDefinition: currentDefinition+1); + } +// if BMPlayerConf.enableVolumeGestures && self.isVolume{ +// self.volumeViewSlider.value -= Float(value / 10000) +// } +// else if BMPlayerConf.enableBrightnessGestures && !self.isVolume{ +// UIScreen.main.brightness -= value / 10000 +// } + } + + open func horizontalMoved(_ value: CGFloat) { + guard BMPlayerConf.enablePlaytimeGestures else { return } + + isSliderSliding = true + + if !isSkip { + if let playerItem = playerLayer?.playerItem { + // 每次滑动需要叠加时间,通过一定的比例,使滑动一直处于统一水平 + self.sumTime = self.sumTime + TimeInterval(value) / 100.0 * (TimeInterval(self.totalDuration) / 400) + + let totalTime = playerItem.duration + + // 防止出现NAN + if totalTime.timescale == 0 { + return + } + + let totalDuration = TimeInterval(totalTime.value) / TimeInterval(totalTime.timescale) + if (self.sumTime >= totalDuration) { + self.sumTime = totalDuration + } + if (self.sumTime <= 0) { + self.sumTime = 0 + } + + controlView.showSeekToView(to: sumTime, total: totalDuration, isAdd: value > 0) + } + } + 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() + } + + @available(*, deprecated:3.0, message:"Use newer init(customControlView:_)") + public convenience init(customControllView: BMPlayerControlView?) { + self.init(customControlView: customControllView) + } + + public init(customControlView: BMPlayerControlView?) { + super.init(frame:CGRect.zero) + self.customControlView = customControlView + initUI() + initUIData() + configureVolume() + preparePlayer() + } + + 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.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 .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 + } +} diff --git a/kplayer/video/BMPlayerClearityChooseButton.swift b/kplayer/video/BMPlayerClearityChooseButton.swift new file mode 100644 index 0000000..22c59db --- /dev/null +++ b/kplayer/video/BMPlayerClearityChooseButton.swift @@ -0,0 +1,29 @@ +// +// 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) + } +} diff --git a/kplayer/video/BMPlayerControlView.swift b/kplayer/video/BMPlayerControlView.swift new file mode 100644 index 0000000..354c1e1 --- /dev/null +++ b/kplayer/video/BMPlayerControlView.swift @@ -0,0 +1,769 @@ +// +// 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.. UIImage? { + let bundle = Bundle(for: BMPlayer.self) + return UIImage(named: fileName, in: bundle, compatibleWith: nil) + } +} + diff --git a/kplayer/video/BMPlayerItem.swift b/kplayer/video/BMPlayerItem.swift new file mode 100644 index 0000000..86b897d --- /dev/null +++ b/kplayer/video/BMPlayerItem.swift @@ -0,0 +1,91 @@ +// +// 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: AVURLAsset { + 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 + } +} diff --git a/kplayer/video/BMPlayerLayerView.swift b/kplayer/video/BMPlayerLayerView.swift new file mode 100644 index 0000000..75b9af5 --- /dev/null +++ b/kplayer/video/BMPlayerLayerView.swift @@ -0,0 +1,507 @@ +// +// 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: AVURLAsset? + + 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 + // playbackBufferEmpty会反复进入,因此在bufferingOneSecond延时播放执行完之前再调用bufferingSomeSecond都忽略 + // 仅在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: AVURLAsset) { + 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() + // 替换PlayerItem为nil + self.player?.replaceCurrentItem(with: nil) + player?.removeObserver(self, forKeyPath: "rate") + + // 把player置为nil + 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), timescale: 1) + // self.player!.cancelPendingPrerolls() + self.playerItem!.cancelPendingSeeks() + self.player!.seek(to: draggedTime, toleranceBefore: CMTimeMake(value: 10, timescale: 1), toleranceAfter: CMTimeMake(value: 10, 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 + // playbackBufferEmpty会反复进入,因此在bufferingOneSecond延时播放执行完之前再调用bufferingSomeSecond都忽略 + + 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 + } +} + diff --git a/kplayer/video/BMPlayerManager.swift b/kplayer/video/BMPlayerManager.swift new file mode 100644 index 0000000..00ed18c --- /dev/null +++ b/kplayer/video/BMPlayerManager.swift @@ -0,0 +1,62 @@ +// +// 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(5) + + /// 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) + } + } +} \ No newline at end of file diff --git a/kplayer/video/BMPlayerProtocols.swift b/kplayer/video/BMPlayerProtocols.swift new file mode 100644 index 0000000..3a07da6 --- /dev/null +++ b/kplayer/video/BMPlayerProtocols.swift @@ -0,0 +1,30 @@ +// +// 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) + } +} diff --git a/kplayer/video/BMSubtitles.swift b/kplayer/video/BMSubtitles.swift new file mode 100644 index 0000000..26b0ffc --- /dev/null +++ b/kplayer/video/BMSubtitles.swift @@ -0,0 +1,122 @@ +// +// 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 + } +} diff --git a/kplayer/video/BMTimeSlider.swift b/kplayer/video/BMTimeSlider.swift new file mode 100644 index 0000000..1920fe9 --- /dev/null +++ b/kplayer/video/BMTimeSlider.swift @@ -0,0 +1,26 @@ +// +// 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 + } +} diff --git a/kplayer/video/KBMPlayer.swift b/kplayer/video/KBMPlayer.swift index e2744c7..07204bd 100644 --- a/kplayer/video/KBMPlayer.swift +++ b/kplayer/video/KBMPlayer.swift @@ -7,9 +7,8 @@ // import UIKit -import SnapKit +//import SnapKit import MediaPlayer -import BMPlayer /** internal enum to check the pan direction @@ -29,31 +28,84 @@ open class KBMPlayer: BMPlayer { var xpos = 0.0 var ypos = 0.0 - open override func horizontalMoved(_ value: CGFloat) { - if zoom == 1.0 { - super.horizontalMoved(value) - xpos = 0.0 - ypos = 0.0 + open var pinchGesture: UIPinchGestureRecognizer! + open var moveGesture: UIPanGestureRecognizer! + + public override init(customControlView: BMPlayerControlView?) { + super.init(customControlView: customControlView) + initGesture() + + } + + 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) + } + + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + initGesture() + } + @objc open override func panDirection(_ pan: UIPanGestureRecognizer) { + if pan.numberOfTouches <= 1 { + super.panDirection(_: pan) } else { - xpos += (Double(value) / 50.0) + let velocityPoint = pan.velocity(in: self) + + xpos += (Double(velocityPoint.x) / 50.0) + ypos += (Double(velocityPoint.y) / 50.0) + + transformLayer() } - let t = CATransform3DMakeTranslation(CGFloat(xpos), CGFloat(ypos), 0.0) - playerLayer!.layer.transform = CATransform3DScale(t, CGFloat(zoom * aspectx), CGFloat(zoom * aspecty), 1.0) } + @objc fileprivate func moved(_ gestureRecognizer: UIPanGestureRecognizer) { + } - open override func verticalMoved(_ value: CGFloat) { - if zoom == 1.0 { - // super.verticalMoved(value) - xpos = 0.0 - ypos = 0.0 - } - else { - ypos += (Double(value) / 50.0) + @objc fileprivate func pinched(_ gestureRecognizer: UIPinchGestureRecognizer) { + if gestureRecognizer.state == .began || gestureRecognizer.state == .changed { + zoom *= Float(gestureRecognizer.scale) + + gestureRecognizer.scale = 1.0 + + transformLayer() } + + } + +// open override func horizontalMoved(_ value: CGFloat) { +// if zoom <= -1.0 { +// super.horizontalMoved(value) +// xpos = 0.0 +// ypos = 0.0 +// } +// else { +// xpos += (Double(value) / 50.0) +// } +// transformLayer() +// } + + private func transformLayer() { let t = CATransform3DMakeTranslation(CGFloat(xpos), CGFloat(ypos), 0.0) playerLayer!.layer.transform = CATransform3DScale(t, CGFloat(zoom * aspectx), CGFloat(zoom * aspecty), 1.0) } + +// open override func verticalMoved(_ value: CGFloat) { +// if zoom <= -1.0 { +// // super.verticalMoved(value) +// xpos = 0.0 +// ypos = 0.0 +// } +// else { +// ypos += (Double(value) / 50.0) +// } +// transformLayer(); +// } + }