diff --git a/Podfile b/Podfile index 126f991..d564137 100644 --- a/Podfile +++ b/Podfile @@ -4,33 +4,14 @@ source 'https://github.com/CocoaPods/Specs.git' use_frameworks! 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 'DZVideoPlayerViewController' - #pod 'SwiftyJSON' - #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/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 + pod 'FileBrowser' end diff --git a/kplayer.xcodeproj/project.pbxproj b/kplayer.xcodeproj/project.pbxproj index c674ca2..51eea10 100644 --- a/kplayer.xcodeproj/project.pbxproj +++ b/kplayer.xcodeproj/project.pbxproj @@ -417,6 +417,7 @@ inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-kplayer/Pods-kplayer-frameworks.sh", "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", + "${BUILT_PRODUCTS_DIR}/FileBrowser/FileBrowser.framework", "${BUILT_PRODUCTS_DIR}/HanekeSwift/Haneke.framework", "${BUILT_PRODUCTS_DIR}/NVActivityIndicatorView/NVActivityIndicatorView.framework", "${BUILT_PRODUCTS_DIR}/Nimbus/Nimbus.framework", @@ -426,6 +427,7 @@ name = "[CP] Embed Pods Frameworks"; outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FileBrowser.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", diff --git a/kplayer/AppDelegate.swift b/kplayer/AppDelegate.swift index 0207c7e..231411f 100644 --- a/kplayer/AppDelegate.swift +++ b/kplayer/AppDelegate.swift @@ -80,6 +80,7 @@ google } func applicationDidBecomeActive(_ application: UIApplication) { + NetworkManager.sharedInstance.alive() // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } diff --git a/kplayer/core/ItemModel.swift b/kplayer/core/ItemModel.swift index 02ddaa1..f0dfa43 100644 --- a/kplayer/core/ItemModel.swift +++ b/kplayer/core/ItemModel.swift @@ -30,6 +30,10 @@ class ItemModel { item.index = folder!.children.count folder!.children.append(item) item.parent = folder + + if (item.isVideo()) { + item.parent?.type = ItemType.VIDEO + } } func removeAll() { diff --git a/kplayer/core/NetworkManager.swift b/kplayer/core/NetworkManager.swift index 05a751e..c5755cb 100644 --- a/kplayer/core/NetworkManager.swift +++ b/kplayer/core/NetworkManager.swift @@ -220,6 +220,17 @@ class NetworkManager { } } + func deleteLocal(selectedItem: MediaItem) { + if (selectedItem.local) { + print(selectedItem.playerURL?.absoluteString) + do { + try FileManager.default.removeItem(at: selectedItem.playerURL!) + } catch { + print(error) + } + } + } + func listDirs(_ root: String, completionHandler: @escaping ([MediaItem], Bool) -> Void) -> Void { let len = root.count let url = (root as NSString).replacingOccurrences(of: " ", with: "+") diff --git a/kplayer/detail/DetailViewController.swift b/kplayer/detail/DetailViewController.swift index 5748bc9..7f2b7f9 100644 --- a/kplayer/detail/DetailViewController.swift +++ b/kplayer/detail/DetailViewController.swift @@ -8,10 +8,12 @@ import UIKit import Alamofire +import FileBrowser protocol DetailDelegate { func loadDetails(selectedItem: MediaItem, completionHandler: @escaping () -> Void) func deleteThumb(selectedItem: MediaItem) + func deleteLocal(selectedItem: MediaItem) func saveItem(selectedItem: MediaItem) } @@ -83,12 +85,21 @@ class DetailViewController: UIViewController, UICollectionViewDelegateFlowLayout let overviewButton = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(overview)); let favButton = UIBarButtonItem(barButtonSystemItem: .bookmarks, target: self, action: #selector(favorites)); let vidButton = UIBarButtonItem(barButtonSystemItem: .play, target: self, action: #selector(vplayer)); - navigationItem.rightBarButtonItems = [vidButton, favButton, overviewButton] + let browserButton = UIBarButtonItem(barButtonSystemItem: .organize, target: self, action: #selector(fileBrowser)); + navigationItem.rightBarButtonItems = [vidButton, favButton, overviewButton,browserButton] if detailItem != nil { print("Details \(detailItem!.children)") } } + // https://github.com/marmelroy/FileBrowser + @objc func fileBrowser() { + let d = FileHelper.getDocumentsDirectory() + let fileBrowser = FileBrowser(initialPath: d, allowEditing: true) + + present(fileBrowser, animated: true, completion: nil) + } + @objc func vplayer() { videoplayer = !videoplayer } @@ -200,6 +211,9 @@ class DetailViewController: UIViewController, UICollectionViewDelegateFlowLayout } } } + else if items.local { + self.delegate!.deleteLocal(selectedItem: items) + } })) refreshAlert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (action: UIAlertAction!) in @@ -277,7 +291,7 @@ class DetailViewController: UIViewController, UICollectionViewDelegateFlowLayout func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { if let detail: MediaItem = self.detailItem { print(detail.toJSON()) - NetworkManager.sharedInstance.saveFavDir(name: "fav.json", item: detail) + // NetworkManager.sharedInstance.saveFavDir(name: "fav.json", item: detail) var sectionItem = detail.children[indexPath.section] currentItem = sectionItem @@ -334,21 +348,43 @@ class DetailViewController: UIViewController, UICollectionViewDelegateFlowLayout } func showVideo(selectedItem: MediaItem) { + var se = selectedItem + var children = detailItem!.children + if videoplayer { + if selectedItem.local { + let jfile = selectedItem.playerURL!.appendingPathExtension("json") + do { + let jsonData = try Data(contentsOf: jfile) + + let items = try JSONDecoder().decode(MediaModel.self, from: jsonData) + se = MediaItem(model: items) + children = se.children + + print (String(data: jsonData, encoding: .utf8)) + } catch { + print(error) + } + } + } + var pc: ItemController? pc = VideoController() - pc!.setCurrentItem(item: selectedItem) - pc!.setItems(items: detailItem!.children) + pc!.setCurrentItem(item: se) + pc!.setItems(items: children) pc!.setCompletionHandler(handler: { self.collectionView.reloadData() self.collectionView.collectionViewLayout.invalidateLayout() - self.delegate!.saveItem(selectedItem: self.currentItem!) + if !se.local { + self.delegate!.saveItem(selectedItem: se) + } self.dismiss(animated: true, completion: nil); }) let navController = UINavigationController(rootViewController: (pc! as! UIViewController)) navController.modalPresentationStyle = .fullScreen + navController.modalPresentationCapturesStatusBarAppearance = true navController.navigationBar.barTintColor = UIColor.black (pc! as! UIViewController).navigationItem.leftItemsSupplementBackButton = true diff --git a/kplayer/detail/VideoController.swift b/kplayer/detail/VideoController.swift index 9f942b0..4b3e6e3 100644 --- a/kplayer/detail/VideoController.swift +++ b/kplayer/detail/VideoController.swift @@ -67,10 +67,11 @@ class VideoController: UIViewController, ItemController, BMPlayerDelegate { view.addSubview(player) player.snp.makeConstraints { (make) in - make.top.equalTo(self.view).offset(100) + // make.top.equalTo(self.view).offset(100) make.left.right.equalTo(self.view) + make.height.equalTo(self.view) // Note here, the aspect ratio 16:9 priority is lower than 1000 on the line, because the 4S iPhone aspect ratio is not 16:9 - make.height.equalTo(player.snp.width).multipliedBy(9.0/16.0).priority(750) + // make.height.equalTo(player.snp.width).multipliedBy(9.0/16.0).priority(750) } player.delegate = self @@ -82,12 +83,17 @@ class VideoController: UIViewController, ItemController, BMPlayerDelegate { if let c = currentItem, let url = c.playerURL { print(url) play(url as URL) + player.playerLayer!.player!.volume = 0.0 update() } updateLoop() } + override var prefersStatusBarHidden: Bool { + return true + } + func setItems(items: [MediaItem]) { allItems = items } @@ -115,23 +121,79 @@ class VideoController: UIViewController, ItemController, BMPlayerDelegate { } } + func showAlert(title:String, message:String ) { + + let alertVC = UIAlertController(title: title, message: message, preferredStyle: .alert) + + let okAction = UIAlertAction(title: "Ok", style: .default, handler: nil) + alertVC.addAction(okAction) + + DispatchQueue.main.async() { () -> Void in + self.present(alertVC, animated: true, completion: nil) + } + } + @objc func favorite(_ sender: AnyObject) { print("favorite") if let c = currentSnapshot { - do { - let file = FileHelper.getDocumentsDirectory().appendingPathComponent(c.name) - VideoHelper.export(item: player.avPlayer!.currentItem!, clipStart: c.time!, clipDuration: c.length!, file: file) { url in - print(url) + if (c.loop) { + let alertController = UIAlertController(title: "Title", message: "Message", preferredStyle: .alert) + + let oneAction = UIAlertAction(title: "One", style: .default) { (action) in + self.save(currentSnapshot: c, name: "1") + } + let twoAction = UIAlertAction(title: "Two", style: .default) { (action) in + self.save(currentSnapshot: c, name: "2") + } + let threeAction = UIAlertAction(title: "Three", style: .default) { (action) in + self.save(currentSnapshot: c, name: "3") + } + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (action) in + } + + alertController.addAction(oneAction) + alertController.addAction(twoAction) + alertController.addAction(threeAction) + alertController.addAction(cancelAction) + + self.present(alertController, animated: true) { + } - } catch { -print(error) } // NetworkManager.sharedInstance.currentFav.children.append(currentItem!) } } + func save(currentSnapshot c: MediaItem, name: String) { + do { + try FileHelper.createDir(name: name) + + let file = FileHelper.getDocumentsDirectory().appendingPathComponent(name).appendingPathComponent(c.name) + var dur = loopEnd - loopStart + if (dur < 0) { + return + } + + VideoHelper.export(item: player.avPlayer!.currentItem!, clipStart: loopStart, clipDuration: dur, file: file) { url in + self.showAlert(title: c.name, message: "saved") + } + var s = c; + if (c.type == ItemType.SNAPSHOT && c.parent != nil) { + s = c.parent! + } + + let jfile = FileHelper.getDocumentsDirectory().appendingPathComponent(name).appendingPathComponent(c.name).appendingPathExtension("json") + + let json = s.toJSON() + print(json) + try json.write(to: jfile, atomically: true, encoding: .utf8) + } catch { + print(error) + } + } + @objc func startstop(_ sender: AnyObject) { if player.isPlaying { player.pause() @@ -169,7 +231,15 @@ print("play") func updateLoop() { if loopOption == 1 { - loopButton!.title = "cont" + var dur = loopEnd - loopStart + + if dur > 0 { + loopButton!.title = "(\(dur))" + } + else { + loopButton!.title = "cont" + } + } else { loopButton!.title = "loop" @@ -187,11 +257,11 @@ print("play") player.aspectx = 1.0 player.aspecty = 1.0 case 2: - player.aspectx = 0.85 + player.aspectx = 0.9 player.aspecty = 1.0 case 3: player.aspectx = 1.0 - player.aspecty = 0.85 + player.aspecty = 0.9 default: print("aspect") } @@ -316,9 +386,11 @@ print("finish") player.seek(currentItem!.children[0].time!) } else { - let duration = player.playerLayer!.playerItem!.duration.seconds - print(duration) - player.seek(duration / 2.0) + let duration = player.playerLayer!.playerItem!.duration + if !duration.isIndefinite { + print(duration) + player.seek(duration.seconds / 2.0) + } } } diff --git a/kplayer/master/MasterViewController.swift b/kplayer/master/MasterViewController.swift index 282e490..91667c9 100644 --- a/kplayer/master/MasterViewController.swift +++ b/kplayer/master/MasterViewController.swift @@ -62,22 +62,6 @@ class MasterViewController: UITableViewController, UISearchResultsUpdating { } } - func selectLoadedItem(_ selectedItem: MediaItem) { - switch selectedItem.type { - case ItemType.DETAILS: - performSegue(withIdentifier: "showDetail", sender: self) - - case ItemType.FOLDER: - if selectedItem.children.first?.type == ItemType.VIDEO { - performSegue(withIdentifier: "showDetail", sender: self) - } else { - gotoNextFolder(selectedItem) - } - default: - print(selectedItem.type) - } - } - func doAuthenticate() { let authenticationContext = LAContext() var error : NSError? diff --git a/kplayer/master/NetworkDelegate.swift b/kplayer/master/NetworkDelegate.swift index 4937cee..b212a4f 100644 --- a/kplayer/master/NetworkDelegate.swift +++ b/kplayer/master/NetworkDelegate.swift @@ -143,4 +143,8 @@ class NetworkDelegate: MasterDelegate, DetailDelegate { func saveItem(selectedItem: MediaItem) { NetworkManager.sharedInstance.saveItem(selectedItem) } + + func deleteLocal(selectedItem: MediaItem) { + NetworkManager.sharedInstance.deleteLocal(selectedItem: selectedItem) + } } diff --git a/kplayer/video/BMPlayer.swift b/kplayer/video/BMPlayer.swift index 01e698c..c7aba0c 100644 --- a/kplayer/video/BMPlayer.swift +++ b/kplayer/video/BMPlayer.swift @@ -163,7 +163,12 @@ open class BMPlayer: UIView { //Cache is playing result to improve callback performance fileprivate var isPlayingCache: Bool? = nil - + + var isScrub = false + var isSeekInProgress = false + var chaseTime = CMTime.zero + var continueTime = 0.0 + // MARK: - Public functions /** @@ -197,6 +202,14 @@ open class BMPlayer: UIView { play() } } + + func isFile() -> Bool { + guard resource != nil else { return false } + + let asset = resource.definitions[currentDefinition] + + return asset.url.isFileURL + } /** Play @@ -279,10 +292,43 @@ open class BMPlayer: UIView { open func storyBoardCustomControl() -> BMPlayerControlView? { return nil } - + + // https://gist.github.com/shaps80/ac16b906938ad256e1f47b52b4809512 + private func seekSmoothlyToTime(newChaseTime: CMTime) { + if CMTimeCompare(newChaseTime, chaseTime) != 0 { + chaseTime = newChaseTime + + if !isSeekInProgress { + trySeekToChaseTime() + } + } + } + + private func trySeekToChaseTime() { + guard playerLayer!.player?.status == .readyToPlay else { return } + actuallySeekToTime() + } + + private func actuallySeekToTime() { + isSeekInProgress = true + let seekTimeInProgress = chaseTime + + playerLayer!.player?.seek(to: seekTimeInProgress, toleranceBefore: CMTime.zero, toleranceAfter: CMTime.zero) { [weak self] _ in + guard let `self` = self else { return } + + if CMTimeCompare(seekTimeInProgress, self.chaseTime) == 0 { + self.isSeekInProgress = false + } else { + self.trySeekToChaseTime() + } + } + } + // MARK: - Action Response @objc open func panDirection(_ pan: UIPanGestureRecognizer) { + let resolution = 10000.0 + if pan.numberOfTouches <= 1 { // 根据在view上Pan的位置,确定是调音量还是亮度 let locationPoint = pan.location(in: self) @@ -298,9 +344,21 @@ open class BMPlayer: UIView { let x = abs(velocityPoint.x) let y = abs(velocityPoint.y) - if (zoom > 1.0) && (locationPoint.y < self.bounds.size.height * 1 / 8) { - self.isSkip = false + if (locationPoint.y < self.bounds.size.height * 1 / 8) { + self.isSkip = (zoom <= 1.0) + isScrub = false + + if (!isPlaying) { + isScrub = true + let currentTime = CMTimeGetSeconds(playerLayer!.player!.currentTime()) + continueTime = currentTime + let vel = velocityPoint.x + velocityPoint.y + var time = CMTimeMake(value: Int64((currentTime + (Float64(vel) / resolution))*10000), timescale: 10000) + seekSmoothlyToTime(newChaseTime: time) + } + } else { + isScrub = false self.isSkip = true self.skipAmount = 0.0 } @@ -326,11 +384,16 @@ open class BMPlayer: UIView { } case UIGestureRecognizer.State.changed: - if !self.isSkip { + if !self.isSkip && isPlaying { xpos += (Double(velocityPoint.x) * 0.1); ypos += (Double(velocityPoint.y) * 0.1); transformLayer() } + if (isScrub) { + let vel = velocityPoint.x + velocityPoint.y + var time = CMTimeMake(value: Int64((Float64(vel) / resolution)*10000), timescale: 10000) + seekSmoothlyToTime(newChaseTime: CMTimeAdd(chaseTime, time)) + } switch self.panDirection { case BMPanDirection.horizontal: self.horizontalMoved(velocityPoint.x) @@ -346,7 +409,7 @@ open class BMPlayer: UIView { controlView.hideSeekToView() isSliderSliding = false - if isSkip { + if isSkip && !isScrub { print(skipAmount) if (skipAmount > 1500) { self.sumTime += TimeInterval(30) @@ -357,20 +420,28 @@ open class BMPlayer: UIView { } 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() - }) + controlView.controlViewAnimation(isShow: true) + + if isPlayToTheEnd { + isPlayToTheEnd = false + seek(self.sumTime, completion: { [weak self] in + self?.play() + }) + } else { + seek(self.sumTime, completion: { [weak self] in + self?.autoPlay() + }) + } } // 把sumTime滞空,不然会越加越多 self.sumTime = 0.0 + if (isScrub && !isFile()) { + seek(continueTime) + } + + isScrub = false + case BMPanDirection.vertical: self.isVolume = false } diff --git a/kplayer/video/BMPlayerControlView.swift b/kplayer/video/BMPlayerControlView.swift index 354c1e1..e402117 100644 --- a/kplayer/video/BMPlayerControlView.swift +++ b/kplayer/video/BMPlayerControlView.swift @@ -519,7 +519,7 @@ open class BMPlayerControlView: UIView { mainMaskView.addSubview(bottomMaskView) mainMaskView.insertSubview(maskImageView, at: 0) mainMaskView.clipsToBounds = true - mainMaskView.backgroundColor = UIColor(white: 0, alpha: 0.4 ) + mainMaskView.backgroundColor = UIColor(white: 0, alpha: 0.0 ) // Top views topMaskView.addSubview(topWrapperView) @@ -552,15 +552,16 @@ open class BMPlayerControlView: UIView { playButton.addTarget(self, action: #selector(onButtonPressed(_:)), for: .touchUpInside) currentTimeLabel.textColor = UIColor.white - currentTimeLabel.font = UIFont.systemFont(ofSize: 12) + currentTimeLabel.font = UIFont.systemFont(ofSize: 14) currentTimeLabel.text = "00:00" currentTimeLabel.textAlignment = NSTextAlignment.center + currentTimeLabel.backgroundColor = UIColor.black totalTimeLabel.textColor = UIColor.white - totalTimeLabel.font = UIFont.systemFont(ofSize: 12) + totalTimeLabel.font = UIFont.systemFont(ofSize: 14) totalTimeLabel.text = "00:00" totalTimeLabel.textAlignment = NSTextAlignment.center - + totalTimeLabel.backgroundColor = UIColor.black timeSlider.maximumValue = 1.0 timeSlider.minimumValue = 0.0 diff --git a/kplayer/video/BMPlayerItem.swift b/kplayer/video/BMPlayerItem.swift index 86b897d..5d6178b 100644 --- a/kplayer/video/BMPlayerItem.swift +++ b/kplayer/video/BMPlayerItem.swift @@ -61,10 +61,10 @@ open class BMPlayerResourceDefinition { open var avURLAsset: AVURLAsset { get { - // guard !url.isFileURL, url.pathExtension != "m3u8" else { + guard !url.isFileURL, url.pathExtension != "m3u8" else { return AVURLAsset(url: url) - // } - // return BMPlayerManager.asset(for: self) + } + return BMPlayerManager.asset(for: self) } } diff --git a/kplayer/video/BMPlayerLayerView.swift b/kplayer/video/BMPlayerLayerView.swift index 75b9af5..7d7a502 100644 --- a/kplayer/video/BMPlayerLayerView.swift +++ b/kplayer/video/BMPlayerLayerView.swift @@ -220,7 +220,7 @@ open class BMPlayerLayerView: UIView { } setupTimer() if self.player?.currentItem?.status == AVPlayerItem.Status.readyToPlay { - let draggedTime = CMTime(value: Int64(secounds), timescale: 1) + let draggedTime = CMTime(value: Int64(secounds)*10000, timescale: 10000) // 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 @@ -230,8 +230,7 @@ open class BMPlayerLayerView: UIView { self.shouldSeekTo = secounds } } - - + // MARK: - 设置视频URL fileprivate func onSetVideoAsset() { repeatToPlay = false @@ -398,7 +397,7 @@ open class BMPlayerLayerView: UIView { print(item.error) } else if player?.status == AVPlayer.Status.readyToPlay { self.state = .buffering - if shouldSeekTo != 0 { + if shouldSeekTo > 0 { seek(to: shouldSeekTo, completion: { [weak self] in self?.shouldSeekTo = 0 self?.hasReadyToPlay = true diff --git a/kplayer/video/BMPlayerManager.swift b/kplayer/video/BMPlayerManager.swift index 00ed18c..9d31aba 100644 --- a/kplayer/video/BMPlayerManager.swift +++ b/kplayer/video/BMPlayerManager.swift @@ -30,7 +30,7 @@ open class BMPlayerManager { /// should auto play open var shouldAutoPlay = true - open var topBarShowInCase = BMPlayerTopBarShowCase.always + open var topBarShowInCase = BMPlayerTopBarShowCase.none open var animateDelayTimeInterval = TimeInterval(5)