Browse Source

Slideshow

master
marcoschmickler 5 years ago
parent
commit
349df3e84c
  1. 30
      Podfile
  2. 53
      kplayer.xcodeproj/project.pbxproj
  3. 5
      kplayer/core/MediaItem.swift
  4. 29
      kplayer/core/NetworkManager.swift
  5. 41
      kplayer/detail/AVPlayerController.swift
  6. 4
      kplayer/detail/BrowserController.swift
  7. 58
      kplayer/detail/DetailViewController.swift
  8. 396
      kplayer/detail/VideoPlayerController.swift
  9. 2
      kplayer/master/MasterViewController.swift
  10. 34
      kplayer/photo/MediaPhotoController.swift
  11. 2
      kplayer/util/ImageLoadOperation.swift
  12. 35
      kplayer/util/UploadOperation.swift
  13. 631
      kplayer/video/BMPlayer.swift
  14. 29
      kplayer/video/BMPlayerClearityChooseButton.swift
  15. 769
      kplayer/video/BMPlayerControlView.swift
  16. 91
      kplayer/video/BMPlayerItem.swift
  17. 507
      kplayer/video/BMPlayerLayerView.swift
  18. 62
      kplayer/video/BMPlayerManager.swift
  19. 30
      kplayer/video/BMPlayerProtocols.swift
  20. 122
      kplayer/video/BMSubtitles.swift
  21. 26
      kplayer/video/BMTimeSlider.swift
  22. 88
      kplayer/video/KBMPlayer.swift

30
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

53
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 = "<group>"; };
1C7360744ABACC3557D05760 /* HanekeFetchOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HanekeFetchOperation.swift; sourceTree = "<group>"; };
1C7360A94DBECA685ED8602F /* ImageLoadOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageLoadOperation.swift; sourceTree = "<group>"; };
1C7360AE55EB115762C42EB9 /* BMTimeSlider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMTimeSlider.swift; sourceTree = "<group>"; };
1C7360D6580FB5D09C2BBCCB /* BMPlayerManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMPlayerManager.swift; sourceTree = "<group>"; };
1C73611D226B48C24DB37535 /* MasterViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterViewController.swift; sourceTree = "<group>"; };
1C73620D01687FB4F1811C5C /* NetworkHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkHelper.swift; sourceTree = "<group>"; };
1C736253AB7A95EA41B605B7 /* ItemModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemModel.swift; sourceTree = "<group>"; };
1C736260E748CF136FF37EA7 /* UploadOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UploadOperation.swift; sourceTree = "<group>"; };
1C73648CEC974A2500172064 /* ViewControllerExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewControllerExtensions.swift; sourceTree = "<group>"; };
1C7364F924BD979294C3EE4A /* BMPlayerItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMPlayerItem.swift; sourceTree = "<group>"; };
1C7365B06FA66294E99AC2D3 /* NetworkManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkManager.swift; sourceTree = "<group>"; };
1C7367379DEE94EBF3FAFA78 /* VideoPlayerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoPlayerController.swift; sourceTree = "<group>"; };
1C7365F45D765A218FFC100F /* BMPlayerProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMPlayerProtocols.swift; sourceTree = "<group>"; };
1C7366AAB82A46086690E164 /* BMSubtitles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMSubtitles.swift; sourceTree = "<group>"; };
1C7366D766CDE0C9872E86F5 /* BMPlayerLayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMPlayerLayerView.swift; sourceTree = "<group>"; };
1C73673DC671535E3A049F54 /* MediaPhotoController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaPhotoController.swift; sourceTree = "<group>"; };
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 = "<group>"; };
1C73688DAB88F9360FB62A49 /* MediaItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaItem.swift; sourceTree = "<group>"; };
1C736927EA28AFBEB25D7487 /* BMPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMPlayer.swift; sourceTree = "<group>"; };
1C7369EC16B19B32B515169E /* NetData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetData.swift; sourceTree = "<group>"; };
1C7369F53095B7A4D65679C2 /* DetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = "<group>"; };
1C736B794396F2E50387B8F2 /* stringutil.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = stringutil.swift; sourceTree = "<group>"; };
@ -77,6 +91,8 @@
1C736D981F8315FFD7D40695 /* KBMPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KBMPlayer.swift; sourceTree = "<group>"; };
1C736D9BB5498E7E8F11C754 /* HeaderCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderCell.swift; sourceTree = "<group>"; };
1C736DCCE3AA9993E15F7652 /* UIImageExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIImageExtension.swift; sourceTree = "<group>"; };
1C736DFBD072763248412F74 /* BMPlayerClearityChooseButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMPlayerClearityChooseButton.swift; sourceTree = "<group>"; };
1C736E51F1A03E3A1200BDB6 /* BMPlayerControlView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BMPlayerControlView.swift; sourceTree = "<group>"; };
1C736EA15A11AF7D57F85824 /* ThumbnailCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThumbnailCache.swift; sourceTree = "<group>"; };
1C736F9338CE36708244D42A /* DataLoadOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataLoadOperation.swift; sourceTree = "<group>"; };
6D522F61736592330F481B4F /* Pods-kplayer.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-kplayer.debug.xcconfig"; path = "Pods/Target Support Files/Pods-kplayer/Pods-kplayer.debug.xcconfig"; sourceTree = "<group>"; };
@ -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 = "<group>";
@ -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;
};

5
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)

29
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<String, MediaItem>()
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<String, String>()
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<String>()
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] {

41
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
}
}

4
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) {
}

58
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

396
kplayer/detail/VideoPlayerController.swift

@ -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<UIButton, MediaItem>()
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!)
}
}
}

2
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

34
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() {

2
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

35
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)")

631
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) {
// viewPan
let locationPoint = pan.location(in: self)
//
// point
let velocityPoint = pan.velocity(in: self)
//
switch pan.state {
case UIGestureRecognizer.State.began:
// 使
let x = abs(velocityPoint.x)
let y = abs(velocityPoint.y)
if 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
}
}

29
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)
}
}

769
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..<resource.definitions.count {
let button = BMPlayerClearityChooseButton()
if i == 0 {
button.tag = selectedIndex
} else if i <= selectedIndex {
button.tag = i - 1
} else {
button.tag = i
}
button.setTitle("\(resource.definitions[button.tag].definition)", for: UIControl.State())
chooseDefinitionView.addSubview(button)
button.addTarget(self, action: #selector(self.onDefinitionSelected(_:)), for: UIControl.Event.touchUpInside)
button.snp.makeConstraints({ [weak self](make) in
guard let `self` = self else { return }
make.top.equalTo(chooseDefinitionView.snp.top).offset(35 * i)
make.width.equalTo(50)
make.height.equalTo(25)
make.centerX.equalTo(chooseDefinitionView)
})
if resource.definitions.count == 1 {
button.isEnabled = false
button.isHidden = true
}
}
}
open func prepareToDealloc() {
self.delayItem = nil
}
// MARK: - Action Response
/**
Call when some action button Pressed
- parameter button: action Button
*/
@objc open func onButtonPressed(_ button: UIButton) {
autoFadeOutControlViewWithAnimation()
if let type = ButtonType(rawValue: button.tag) {
switch type {
case .play, .replay:
if playerLastState == .playedToTheEnd {
hidePlayToTheEndView()
}
default:
break
}
}
delegate?.controlView(controlView: self, didPressButton: button)
}
/**
Call when the tap gesture tapped
- parameter gesture: tap gesture
*/
@objc open func onTapGestureTapped(_ gesture: UITapGestureRecognizer) {
if playerLastState == .playedToTheEnd {
return
}
controlViewAnimation(isShow: !isMaskShowing)
}
@objc open func onDoubleTapGestureRecognized(_ gesture: UITapGestureRecognizer) {
guard let player = player else { return }
guard playerLastState == .readyToPlay || playerLastState == .buffering || playerLastState == .bufferFinished else { return }
if player.isPlaying {
player.pause()
} else {
player.play()
}
}
// MARK: - handle UI slider actions
@objc func progressSliderTouchBegan(_ sender: UISlider) {
delegate?.controlView(controlView: self, slider: sender, onSliderEvent: .touchDown)
}
@objc func progressSliderValueChanged(_ sender: UISlider) {
hidePlayToTheEndView()
cancelAutoFadeOutAnimation()
let currentTime = Double(sender.value) * totalDuration
currentTimeLabel.text = BMPlayer.formatSecondsToString(currentTime)
delegate?.controlView(controlView: self, slider: sender, onSliderEvent: .valueChanged)
}
@objc func progressSliderTouchEnded(_ sender: UISlider) {
autoFadeOutControlViewWithAnimation()
delegate?.controlView(controlView: self, slider: sender, onSliderEvent: .touchUpInside)
}
// MARK: - private functions
fileprivate func showSubtile(from subtitle: BMSubtitles?, at time: TimeInterval) {
if let subtitle = subtitle, let group = subtitle.search(for: time) {
subtitleBackView.isHidden = false
subtitleLabel.attributedText = NSAttributedString(string: group.text,
attributes: subtileAttribute)
} else {
subtitleBackView.isHidden = true
}
}
@objc fileprivate func onDefinitionSelected(_ button:UIButton) {
let height = isSelectDefinitionViewOpened ? 35 : resource!.definitions.count * 40
chooseDefinitionView.snp.updateConstraints { (make) in
make.height.equalTo(height)
}
UIView.animate(withDuration: 0.3, animations: {[weak self] in
self?.layoutIfNeeded()
})
isSelectDefinitionViewOpened = !isSelectDefinitionViewOpened
if selectedIndex != button.tag {
selectedIndex = button.tag
delegate?.controlView(controlView: self, didChooseDefinition: button.tag)
}
prepareChooseDefinitionView()
}
@objc fileprivate func onReplyButtonPressed() {
replayButton.isHidden = true
}
// MARK: - Init
override public init(frame: CGRect) {
super.init(frame: frame)
setupUIComponents()
addSnapKitConstraint()
customizeUIComponents()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupUIComponents()
addSnapKitConstraint()
customizeUIComponents()
}
/// Add Customize functions here
open func customizeUIComponents() {
}
func setupUIComponents() {
// Subtile view
subtitleLabel.numberOfLines = 0
subtitleLabel.textAlignment = .center
subtitleLabel.textColor = UIColor.white
subtitleLabel.adjustsFontSizeToFitWidth = true
subtitleLabel.minimumScaleFactor = 0.5
subtitleLabel.font = UIFont.systemFont(ofSize: 13)
subtitleBackView.layer.cornerRadius = 2
subtitleBackView.backgroundColor = UIColor.black.withAlphaComponent(0.4)
subtitleBackView.addSubview(subtitleLabel)
subtitleBackView.isHidden = true
addSubview(subtitleBackView)
// Main mask view
addSubview(mainMaskView)
mainMaskView.addSubview(topMaskView)
mainMaskView.addSubview(bottomMaskView)
mainMaskView.insertSubview(maskImageView, at: 0)
mainMaskView.clipsToBounds = true
mainMaskView.backgroundColor = UIColor(white: 0, alpha: 0.4 )
// Top views
topMaskView.addSubview(topWrapperView)
topWrapperView.addSubview(backButton)
topWrapperView.addSubview(titleLabel)
topWrapperView.addSubview(chooseDefinitionView)
backButton.tag = BMPlayerControlView.ButtonType.back.rawValue
backButton.setImage(BMImageResourcePath("Pod_Asset_BMPlayer_back"), for: .normal)
backButton.addTarget(self, action: #selector(onButtonPressed(_:)), for: .touchUpInside)
titleLabel.textColor = UIColor.white
titleLabel.text = ""
titleLabel.font = UIFont.systemFont(ofSize: 16)
chooseDefinitionView.clipsToBounds = true
// Bottom views
bottomMaskView.addSubview(bottomWrapperView)
bottomWrapperView.addSubview(playButton)
bottomWrapperView.addSubview(currentTimeLabel)
bottomWrapperView.addSubview(totalTimeLabel)
bottomWrapperView.addSubview(progressView)
bottomWrapperView.addSubview(timeSlider)
bottomWrapperView.addSubview(fullscreenButton)
playButton.tag = BMPlayerControlView.ButtonType.play.rawValue
playButton.setImage(BMImageResourcePath("Pod_Asset_BMPlayer_play"), for: .normal)
playButton.setImage(BMImageResourcePath("Pod_Asset_BMPlayer_pause"), for: .selected)
playButton.addTarget(self, action: #selector(onButtonPressed(_:)), for: .touchUpInside)
currentTimeLabel.textColor = UIColor.white
currentTimeLabel.font = UIFont.systemFont(ofSize: 12)
currentTimeLabel.text = "00:00"
currentTimeLabel.textAlignment = NSTextAlignment.center
totalTimeLabel.textColor = UIColor.white
totalTimeLabel.font = UIFont.systemFont(ofSize: 12)
totalTimeLabel.text = "00:00"
totalTimeLabel.textAlignment = NSTextAlignment.center
timeSlider.maximumValue = 1.0
timeSlider.minimumValue = 0.0
timeSlider.value = 0.0
timeSlider.setThumbImage(BMImageResourcePath("Pod_Asset_BMPlayer_slider_thumb"), for: .normal)
timeSlider.maximumTrackTintColor = UIColor.clear
timeSlider.minimumTrackTintColor = BMPlayerConf.tintColor
timeSlider.addTarget(self, action: #selector(progressSliderTouchBegan(_:)),
for: UIControl.Event.touchDown)
timeSlider.addTarget(self, action: #selector(progressSliderValueChanged(_:)),
for: UIControl.Event.valueChanged)
timeSlider.addTarget(self, action: #selector(progressSliderTouchEnded(_:)),
for: [UIControl.Event.touchUpInside,UIControl.Event.touchCancel, UIControl.Event.touchUpOutside])
progressView.tintColor = UIColor ( red: 1.0, green: 1.0, blue: 1.0, alpha: 0.6 )
progressView.trackTintColor = UIColor ( red: 1.0, green: 1.0, blue: 1.0, alpha: 0.3 )
fullscreenButton.tag = BMPlayerControlView.ButtonType.fullscreen.rawValue
fullscreenButton.setImage(BMImageResourcePath("Pod_Asset_BMPlayer_fullscreen"), for: .normal)
fullscreenButton.setImage(BMImageResourcePath("Pod_Asset_BMPlayer_portialscreen"), for: .selected)
fullscreenButton.addTarget(self, action: #selector(onButtonPressed(_:)), for: .touchUpInside)
mainMaskView.addSubview(loadingIndicator)
loadingIndicator.type = BMPlayerConf.loaderType
loadingIndicator.color = BMPlayerConf.tintColor
// View to show when slide to seek
addSubview(seekToView)
seekToView.addSubview(seekToViewImage)
seekToView.addSubview(seekToLabel)
seekToLabel.font = UIFont.systemFont(ofSize: 13)
seekToLabel.textColor = UIColor ( red: 0.9098, green: 0.9098, blue: 0.9098, alpha: 1.0 )
seekToView.backgroundColor = UIColor ( red: 0.0, green: 0.0, blue: 0.0, alpha: 0.7 )
seekToView.layer.cornerRadius = 4
seekToView.layer.masksToBounds = true
seekToView.isHidden = true
seekToViewImage.image = BMImageResourcePath("Pod_Asset_BMPlayer_seek_to_image")
addSubview(replayButton)
replayButton.isHidden = true
replayButton.setImage(BMImageResourcePath("Pod_Asset_BMPlayer_replay"), for: .normal)
replayButton.addTarget(self, action: #selector(onButtonPressed(_:)), for: .touchUpInside)
replayButton.tag = ButtonType.replay.rawValue
tapGesture = UITapGestureRecognizer(target: self, action: #selector(onTapGestureTapped(_:)))
addGestureRecognizer(tapGesture)
if BMPlayerManager.shared.enablePlayControlGestures {
doubleTapGesture = UITapGestureRecognizer(target: self, action: #selector(onDoubleTapGestureRecognized(_:)))
doubleTapGesture.numberOfTapsRequired = 2
addGestureRecognizer(doubleTapGesture)
tapGesture.require(toFail: doubleTapGesture)
}
}
func addSnapKitConstraint() {
// Main mask view
mainMaskView.snp.makeConstraints { [unowned self](make) in
make.edges.equalTo(self)
}
maskImageView.snp.makeConstraints { [unowned self](make) in
make.edges.equalTo(self.mainMaskView)
}
topMaskView.snp.makeConstraints { [unowned self](make) in
make.top.left.right.equalTo(self.mainMaskView)
}
topWrapperView.snp.makeConstraints { [unowned self](make) in
make.height.equalTo(50)
if #available(iOS 11.0, *) {
make.top.left.right.equalTo(self.topMaskView.safeAreaLayoutGuide)
make.bottom.equalToSuperview()
} else {
make.top.equalToSuperview().offset(15)
make.bottom.left.right.equalToSuperview()
}
}
bottomMaskView.snp.makeConstraints { [unowned self](make) in
make.bottom.left.right.equalTo(self.mainMaskView)
}
bottomWrapperView.snp.makeConstraints { [unowned self](make) in
make.height.equalTo(50)
if #available(iOS 11.0, *) {
make.bottom.left.right.equalTo(self.bottomMaskView.safeAreaLayoutGuide)
make.top.equalToSuperview()
} else {
make.edges.equalToSuperview()
}
}
// Top views
backButton.snp.makeConstraints { (make) in
make.width.height.equalTo(50)
make.left.bottom.equalToSuperview()
}
titleLabel.snp.makeConstraints { [unowned self](make) in
make.left.equalTo(self.backButton.snp.right)
make.centerY.equalTo(self.backButton)
}
chooseDefinitionView.snp.makeConstraints { [unowned self](make) in
make.right.equalToSuperview().offset(-20)
make.top.equalTo(self.titleLabel.snp.top).offset(-4)
make.width.equalTo(60)
make.height.equalTo(30)
}
// Bottom views
playButton.snp.makeConstraints { (make) in
make.width.equalTo(50)
make.height.equalTo(50)
make.left.bottom.equalToSuperview()
}
currentTimeLabel.snp.makeConstraints { [unowned self](make) in
make.left.equalTo(self.playButton.snp.right)
make.centerY.equalTo(self.playButton)
make.width.equalTo(40)
}
timeSlider.snp.makeConstraints { [unowned self](make) in
make.centerY.equalTo(self.currentTimeLabel)
make.left.equalTo(self.currentTimeLabel.snp.right).offset(10).priority(750)
make.height.equalTo(30)
}
progressView.snp.makeConstraints { [unowned self](make) in
make.centerY.left.right.equalTo(self.timeSlider)
make.height.equalTo(2)
}
totalTimeLabel.snp.makeConstraints { [unowned self](make) in
make.centerY.equalTo(self.currentTimeLabel)
make.left.equalTo(self.timeSlider.snp.right).offset(5)
make.width.equalTo(40)
}
fullscreenButton.snp.makeConstraints { [unowned self](make) in
make.width.equalTo(50)
make.height.equalTo(50)
make.centerY.equalTo(self.currentTimeLabel)
make.left.equalTo(self.totalTimeLabel.snp.right)
make.right.equalToSuperview()
}
loadingIndicator.snp.makeConstraints { [unowned self](make) in
make.center.equalTo(self.mainMaskView)
}
// View to show when slide to seek
seekToView.snp.makeConstraints { [unowned self](make) in
make.center.equalTo(self.snp.center)
make.width.equalTo(100)
make.height.equalTo(40)
}
seekToViewImage.snp.makeConstraints { [unowned self](make) in
make.left.equalTo(self.seekToView.snp.left).offset(15)
make.centerY.equalTo(self.seekToView.snp.centerY)
make.height.equalTo(15)
make.width.equalTo(25)
}
seekToLabel.snp.makeConstraints { [unowned self](make) in
make.left.equalTo(self.seekToViewImage.snp.right).offset(10)
make.centerY.equalTo(self.seekToView.snp.centerY)
}
replayButton.snp.makeConstraints { [unowned self](make) in
make.center.equalTo(self.mainMaskView)
make.width.height.equalTo(50)
}
subtitleBackView.snp.makeConstraints { [unowned self](make) in
make.bottom.equalTo(self.snp.bottom).offset(-5)
make.centerX.equalTo(self.snp.centerX)
make.width.lessThanOrEqualTo(self.snp.width).offset(-10).priority(750)
}
subtitleLabel.snp.makeConstraints { [unowned self](make) in
make.left.equalTo(self.subtitleBackView.snp.left).offset(10)
make.right.equalTo(self.subtitleBackView.snp.right).offset(-10)
make.top.equalTo(self.subtitleBackView.snp.top).offset(2)
make.bottom.equalTo(self.subtitleBackView.snp.bottom).offset(-2)
}
}
fileprivate func BMImageResourcePath(_ fileName: String) -> UIImage? {
let bundle = Bundle(for: BMPlayer.self)
return UIImage(named: fileName, in: bundle, compatibleWith: nil)
}
}

91
kplayer/video/BMPlayerItem.swift

@ -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
}
}

507
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
// playbackBufferEmptybufferingOneSecondbufferingSomeSecond
// bufferingSomeSecond使
fileprivate var isBuffering = false
fileprivate var hasReadyToPlay = false
fileprivate var shouldSeekTo: TimeInterval = 0
// MARK: - Actions
open func playURL(url: URL) {
let asset = AVURLAsset(url: url)
playAsset(asset: asset)
}
open func playAsset(asset: 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()
// PlayerItemnil
self.player?.replaceCurrentItem(with: nil)
player?.removeObserver(self, forKeyPath: "rate")
// playernil
self.player = nil
}
open func prepareToDeinit() {
self.resetPlayer()
}
open func onTimeSliderBegan() {
if self.player?.currentItem?.status == AVPlayerItem.Status.readyToPlay {
self.timer?.fireDate = Date.distantFuture
}
}
open func seek(to secounds: TimeInterval, completion:(()->Void)?) {
if secounds.isNaN {
return
}
setupTimer()
if self.player?.currentItem?.status == AVPlayerItem.Status.readyToPlay {
let draggedTime = CMTime(value: Int64(secounds), 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
// playbackBufferEmptybufferingOneSecondbufferingSomeSecond
if isBuffering {
return
}
isBuffering = true
//
player?.pause()
let popTime = DispatchTime.now() + Double(Int64( Double(NSEC_PER_SEC) * 1.0 )) / Double(NSEC_PER_SEC)
DispatchQueue.main.asyncAfter(deadline: popTime) {[weak self] in
guard let `self` = self else { return }
// play
self.isBuffering = false
if let item = self.playerItem {
if !item.isPlaybackLikelyToKeepUp {
self.bufferingSomeSecond()
} else {
//
self.state = BMPlayerState.bufferFinished
}
}
}
}
@objc fileprivate func connectPlayerLayer() {
playerLayer?.removeFromSuperlayer()
playerLayer = AVPlayerLayer(player: player)
playerLayer!.videoGravity = videoGravity
layer.addSublayer(playerLayer!)
}
@objc fileprivate func disconnectPlayerLayer() {
playerLayer?.removeFromSuperlayer()
playerLayer = nil
}
}

62
kplayer/video/BMPlayerManager.swift

@ -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)
}
}
}

30
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)
}
}

122
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
}
}

26
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
}
}

88
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();
// }
}
Loading…
Cancel
Save