diff --git a/Podfile b/Podfile index d1af914..1fdd82f 100644 --- a/Podfile +++ b/Podfile @@ -5,3 +5,6 @@ use_frameworks! # pod 'Player' pod 'Alamofire', '~> 1.2' +pod 'ALMoviePlayerController', '~>0.3.0' +pod 'SwiftyJSON' +pod 'HanekeSwift' diff --git a/kplayer.xcodeproj/project.pbxproj b/kplayer.xcodeproj/project.pbxproj index 438cd7d..dad3852 100644 --- a/kplayer.xcodeproj/project.pbxproj +++ b/kplayer.xcodeproj/project.pbxproj @@ -12,6 +12,12 @@ 1C7363D9DC8F9D1F866DE935 /* Kirschkeks-256x256.png in Resources */ = {isa = PBXBuildFile; fileRef = 1C7368DC7EF11A553145E169 /* Kirschkeks-256x256.png */; }; 1C73641627BE29D9FA819F3C /* LayoutTools.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73649CEA7BDD2AC1496F76 /* LayoutTools.swift */; }; 1C736503B656C999E5E12081 /* NetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7365B06FA66294E99AC2D3 /* NetworkManager.swift */; }; + 1C73664F657BE51633B69851 /* alamojson.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736683E76027D72412D440 /* alamojson.swift */; }; + 1C73670791CDD5C9BB6B1DDC /* NetData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7364950C3B8C5DFA243D62 /* NetData.swift */; }; + 1C7367A160B25EEC5E99A517 /* ImageLoadOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736E98DC8F46C7FF9CD540 /* ImageLoadOperation.swift */; }; + 1C73688D13E5A804880C8768 /* UIImageExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C736DCCE3AA9993E15F7652 /* UIImageExtension.swift */; }; + 1C736AD52D967F2F4000F997 /* UploadOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7362F9C26405D4F3FA15A1 /* UploadOperation.swift */; }; + 1C736BF3C4F2D3BE570A89C7 /* NetworkHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7368F1190278747E95C12F /* NetworkHelper.swift */; }; 1C736C90DB50C4FDED266C3D /* ItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7362C647AEBF03F5FD9FEB /* ItemCell.swift */; }; 1C736FB92B19FE17E4357C85 /* MediaItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C73688DAB88F9360FB62A49 /* MediaItem.swift */; }; A5D637AE4588AAB5DC1CBC6B /* Pods.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 127AC1F28342F9AAE3CEC5C2 /* Pods.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; @@ -38,12 +44,18 @@ /* Begin PBXFileReference section */ 127AC1F28342F9AAE3CEC5C2 /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1C7362C647AEBF03F5FD9FEB /* ItemCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemCell.swift; sourceTree = ""; }; + 1C7362F9C26405D4F3FA15A1 /* UploadOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UploadOperation.swift; sourceTree = ""; }; + 1C7364950C3B8C5DFA243D62 /* NetData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetData.swift; sourceTree = ""; }; 1C73649CEA7BDD2AC1496F76 /* LayoutTools.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LayoutTools.swift; sourceTree = ""; }; 1C7365B06FA66294E99AC2D3 /* NetworkManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkManager.swift; sourceTree = ""; }; + 1C736683E76027D72412D440 /* alamojson.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = alamojson.swift; sourceTree = ""; }; 1C7366EF59D75216EBC0D3F0 /* VideoPlayerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoPlayerController.swift; sourceTree = ""; }; 1C736777456388CA571DA17B /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = System/Library/Frameworks/MediaPlayer.framework; sourceTree = SDKROOT; }; 1C73688DAB88F9360FB62A49 /* MediaItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaItem.swift; sourceTree = ""; }; 1C7368DC7EF11A553145E169 /* Kirschkeks-256x256.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Kirschkeks-256x256.png"; sourceTree = ""; }; + 1C7368F1190278747E95C12F /* NetworkHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkHelper.swift; sourceTree = ""; }; + 1C736DCCE3AA9993E15F7652 /* UIImageExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIImageExtension.swift; sourceTree = ""; }; + 1C736E98DC8F46C7FF9CD540 /* ImageLoadOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageLoadOperation.swift; sourceTree = ""; }; 5C6CBA548F885BF342F594EA /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; A170BFB886D61D57F7009BFC /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; C98AF5CF1B124D6A00D196CC /* kplayer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = kplayer.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -80,11 +92,24 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 1C7365603CAE04E39B73D843 /* util */ = { + isa = PBXGroup; + children = ( + 1C736DCCE3AA9993E15F7652 /* UIImageExtension.swift */, + ); + path = util; + sourceTree = ""; + }; 1C736DC8C3AFB991541A2079 /* core */ = { isa = PBXGroup; children = ( 1C7365B06FA66294E99AC2D3 /* NetworkManager.swift */, 1C73688DAB88F9360FB62A49 /* MediaItem.swift */, + 1C7362F9C26405D4F3FA15A1 /* UploadOperation.swift */, + 1C7368F1190278747E95C12F /* NetworkHelper.swift */, + 1C7364950C3B8C5DFA243D62 /* NetData.swift */, + 1C736683E76027D72412D440 /* alamojson.swift */, + 1C736E98DC8F46C7FF9CD540 /* ImageLoadOperation.swift */, ); path = core; sourceTree = ""; @@ -143,6 +168,7 @@ 1C73649CEA7BDD2AC1496F76 /* LayoutTools.swift */, 1C7368DC7EF11A553145E169 /* Kirschkeks-256x256.png */, 1C7366EF59D75216EBC0D3F0 /* VideoPlayerController.swift */, + 1C7365603CAE04E39B73D843 /* util */, ); path = kplayer; sourceTree = ""; @@ -333,6 +359,12 @@ 1C736C90DB50C4FDED266C3D /* ItemCell.swift in Sources */, 1C73641627BE29D9FA819F3C /* LayoutTools.swift in Sources */, 1C736261CBA1D13D16DCBAFB /* VideoPlayerController.swift in Sources */, + 1C736AD52D967F2F4000F997 /* UploadOperation.swift in Sources */, + 1C736BF3C4F2D3BE570A89C7 /* NetworkHelper.swift in Sources */, + 1C73670791CDD5C9BB6B1DDC /* NetData.swift in Sources */, + 1C73664F657BE51633B69851 /* alamojson.swift in Sources */, + 1C7367A160B25EEC5E99A517 /* ImageLoadOperation.swift in Sources */, + 1C73688D13E5A804880C8768 /* UIImageExtension.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/kplayer/Base.lproj/Main.storyboard b/kplayer/Base.lproj/Main.storyboard index 91a3dfb..a281f81 100644 --- a/kplayer/Base.lproj/Main.storyboard +++ b/kplayer/Base.lproj/Main.storyboard @@ -50,12 +50,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -130,6 +186,6 @@ - + diff --git a/kplayer/DetailViewController.swift b/kplayer/DetailViewController.swift index cbdbcd0..b0d2b13 100644 --- a/kplayer/DetailViewController.swift +++ b/kplayer/DetailViewController.swift @@ -7,16 +7,16 @@ // import UIKit -import MediaPlayer class DetailViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource { @IBOutlet weak var detailDescriptionLabel: UILabel! - var moviePlayer: MPMoviePlayerController? var collectionView: UICollectionView! + var currentItem: MediaItem? + var detailItem: MediaItem? { didSet { println(detailItem!.children) @@ -28,8 +28,6 @@ class DetailViewController: UIViewController, UICollectionViewDelegateFlowLayout } } - var currentItem: MediaItem? - func configureView() { // Update the user interface for the detail item. println(detailItem) @@ -48,92 +46,18 @@ class DetailViewController: UIViewController, UICollectionViewDelegateFlowLayout println(url) // if moviePlayer == nil { - play(url) +// play(url) // } } } } - func play(url: String) { - self.moviePlayer = MPMoviePlayerController() - if let player = self.moviePlayer { - NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("exitedFullscreen"), name: MPMoviePlayerDidExitFullscreenNotification, object: nil); - NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("enteredFullscreen"), name: MPMoviePlayerDidEnterFullscreenNotification, object: nil); - NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("showThumbnail:"), name: MPMoviePlayerThumbnailImageRequestDidFinishNotification, 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 = NSURL(string: url) - player.play() - - var timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: Selector("update"), userInfo: nil, repeats: false) - - - self.view.addSubview(player.view) - } - } - - func showThumbnail(note: NSNotification) { - let userInfo = note.userInfo! as NSDictionary - let thumbnail = userInfo.objectForKey(MPMoviePlayerThumbnailImageKey) as! UIImage - let time = userInfo.objectForKey(MPMoviePlayerThumbnailTimeKey) as! NSTimeInterval - - 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) - - println (newItem.time) - - } - - func update() { - if let player = self.moviePlayer { - if let t = currentItem!.time { - player.currentPlaybackTime = t - } - else { - println(player.duration) - player.currentPlaybackTime = player.duration / 2 - } - - if currentItem!.type == ItemType.SNAPSHOT { - currentItem = currentItem!.parent - } - - player.fullscreen = true - } - } - - func enteredFullscreen() { - let mp = UIApplication.sharedApplication().keyWindow; - if let moviePlayerContainer = recursiveSearchForViewWithName(mp!, classname: "MPVideoContainerView") { - if (moviePlayerContainer.gestureRecognizers != nil) { - return; - } - installGestures(moviePlayerContainer) - } - } - - func exitedFullscreen() { - moviePlayer!.view.removeFromSuperview(); - moviePlayer = nil; - NSNotificationCenter.defaultCenter().removeObserver(self); - - collectionView.reloadData() - collectionView.collectionViewLayout.invalidateLayout() - } override func loadView() { super.loadView() - + NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("refreshItems:"), name: "loadedItems", object: nil) } override func viewDidLoad() { @@ -142,7 +66,7 @@ class DetailViewController: UIViewController, UICollectionViewDelegateFlowLayout let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout() layout.sectionInset = UIEdgeInsets(top: 2, left: 2, bottom: 2, right: 2) - layout.itemSize = CGSize(width: 14 * 16 , height: 14 * 10) + layout.itemSize = CGSize(width: 14 * 16, height: 14 * 10) layout.headerReferenceSize = CGSize(width: 50, height: 50) collectionView = UICollectionView(frame: view.frame, collectionViewLayout: layout) @@ -153,11 +77,67 @@ class DetailViewController: UIViewController, UICollectionViewDelegateFlowLayout collectionView.registerClass(ItemCell.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "HeaderView"); view.addSubview(collectionView) -// view.autoresizesSubviews = true -// collectionView.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight + view.autoresizesSubviews = true + collectionView.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight // Do any additional setup after loading the view, typically from a nib. collectionView.reloadData() + + // attach long press gesture to collectionView + let lpgr = UILongPressGestureRecognizer(target: self, action: Selector("handleLongPress:")) + lpgr.minimumPressDuration = 3; //seconds + lpgr.delaysTouchesBegan = true + self.collectionView.addGestureRecognizer(lpgr); + + } + + func refreshItems(notification: NSNotification) { + let index = notification.object as! Int + + if let detail: MediaItem = self.detailItem { + collectionView.performBatchUpdates( { + let path = NSIndexPath(forItem: 0, inSection: index) + self.collectionView.reloadItemsAtIndexPaths([path]) + + var newItems = [NSIndexPath]() + var j = 0 + for i in detail.children[index].children { + if j >= 1 { + newItems.append(NSIndexPath(forItem: j, inSection: index)) + } + j++ + } + self.collectionView.insertItemsAtIndexPaths(newItems) + return + }, completion: nil) + } + + } + + func handleLongPress(gestureRecognizer: UILongPressGestureRecognizer) { + if (gestureRecognizer.state != UIGestureRecognizerState.Ended) { + return; + } + let p = gestureRecognizer.locationInView(self.collectionView); + + let indexPath = self.collectionView.indexPathForItemAtPoint(p); + if (indexPath == nil) { + println("couldn't find index path"); + } else { + if let detail: MediaItem = self.detailItem { + if (detail.loaded) { + let items = detail.children[indexPath!.section] + if count(items.children) == 0 { + } else { + if indexPath!.item >= count(items.children) { + } else { + items.children.removeAtIndex(indexPath!.item) + self.collectionView.reloadData() + } + } + } + } + } } override func viewDidAppear(animated: Bool) { @@ -183,7 +163,13 @@ class DetailViewController: UIViewController, UICollectionViewDelegateFlowLayout func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { if let detail: MediaItem = self.detailItem { - return count(detail.children[section].children) + 1 + let n = count(detail.children[section].children) + + if n == 0 { + return 1 + } + println(n) + return n } return 0 } @@ -193,11 +179,15 @@ class DetailViewController: UIViewController, UICollectionViewDelegateFlowLayout if let detail: MediaItem = self.detailItem { let items = detail.children[indexPath.section] - if indexPath.item >= count(items.children) { + if count(items.children) == 0 { cell.setItem(items) } else { - let item = items.children[indexPath.item] - cell.setItem(item) + if indexPath.item >= count(items.children) { + cell.setItem(items) + } else { + let item = items.children[indexPath.item] + cell.setItem(item) + } } } cell.backgroundColor = UIColor.blueColor() @@ -216,8 +206,6 @@ class DetailViewController: UIViewController, UICollectionViewDelegateFlowLayout as! ItemCell let items = detailItem!.children[indexPath.section] - println("sec \(indexPath.section) " + items.name) - headerView.label.text = items.name headerView.backgroundColor = UIColor.yellowColor() return headerView @@ -228,90 +216,45 @@ class DetailViewController: UIViewController, UICollectionViewDelegateFlowLayout func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) { if let detail: MediaItem = self.detailItem { - var items = detail.children[indexPath.section] - if indexPath.item >= count(items.children) { - println(items.name) - } else { - items = items.children[indexPath.item] - println(items.name) + if (detail.loaded) { + var items = detail.children[indexPath.section] + if indexPath.item >= count(items.children) { + println(items.name) + } else { + items = items.children[indexPath.item] + println(items.name) + } + + self.currentItem = items + performSegueWithIdentifier("showVideo", sender: self) } - - currentItem = items - - var url = NetworkManager.sharedInstance.playerURL(items) - - println(url) - - play(url) - - } - } - - func installGestures(moviePlayer: UIView) { - let twoFingersTwoTaps = UITapGestureRecognizer(target: self, action: "twoFingersTwoTaps") - twoFingersTwoTaps.numberOfTapsRequired = 2 - twoFingersTwoTaps.numberOfTouchesRequired = 2 - moviePlayer.addGestureRecognizer(twoFingersTwoTaps) - - let sR = UISwipeGestureRecognizer(target: self, action: "swipeRight") - sR.direction = UISwipeGestureRecognizerDirection.Right - sR.numberOfTouchesRequired = 1 - moviePlayer.addGestureRecognizer(sR) - - let sL = UISwipeGestureRecognizer(target: self, action: "swipeLeft") - sL.direction = UISwipeGestureRecognizerDirection.Left - sL.numberOfTouchesRequired = 1 - moviePlayer.addGestureRecognizer(sL) - - let sR2 = UISwipeGestureRecognizer(target: self, action: "swipeDown") - sR2.direction = UISwipeGestureRecognizerDirection.Down - sR2.numberOfTouchesRequired = 1 - moviePlayer.addGestureRecognizer(sR2) - - } - - func swipeRight() { - println("r") - if let player = self.moviePlayer { - player.currentPlaybackTime = player.currentPlaybackTime + 30.0 } } - func swipeDown() { - println("r") - if let player = self.moviePlayer { - player.currentPlaybackTime = player.currentPlaybackTime + 10.0 - } - } - - func swipeLeft() { - println("l") - if let player = self.moviePlayer { - player.currentPlaybackTime = player.currentPlaybackTime - 30.0 - } - } - - func twoFingersTwoTaps() { - println("tap") - moviePlayer!.requestThumbnailImagesAtTimes([moviePlayer!.currentPlaybackTime], - timeOption:MPMovieTimeOption.NearestKeyFrame); - } - - - func recursiveSearchForViewWithName(view: UIView, classname: String) -> UIView? { - for v in view.subviews { - var result = recursiveSearchForViewWithName(v as! UIView, classname: classname) - let cn = toString(v.dynamicType) - - if cn == classname { - return v as! UIView - } - - if result != nil { - return result + // MARK: - Segues + + override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { + if segue.identifier == "showVideo" { + let nc = segue.destinationViewController as! UINavigationController + let controller = nc.topViewController as! VideoPlayerController + controller.currentItem = self.currentItem +// controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem() + controller.navigationItem.leftItemsSupplementBackButton = true + nc.navigationBar.barTintColor = UIColor.blackColor() + + controller.completionHandler = { + () in + self.collectionView.reloadData() + self.collectionView.collectionViewLayout.invalidateLayout() + + if self.currentItem!.type == ItemType.SNAPSHOT { + self.currentItem = self.currentItem!.parent + } + NetworkManager.sharedInstance.saveItem(self.currentItem!) + self.dismissViewControllerAnimated(true, completion: nil); } } - return nil } + } diff --git a/kplayer/ItemCell.swift b/kplayer/ItemCell.swift index e38b99d..0a1a6f4 100644 --- a/kplayer/ItemCell.swift +++ b/kplayer/ItemCell.swift @@ -5,6 +5,7 @@ import Foundation import UIKit +import Haneke class ItemCell: UICollectionViewCell { var item: MediaItem? @@ -25,7 +26,7 @@ class ItemCell: UICollectionViewCell { label = UILabel(frame: frame) - image = UIImageView() + image = UIImageView(frame: frame) image.layer.borderWidth = 2.0; autolayout(["label": label, "imag": image], @@ -38,15 +39,25 @@ class ItemCell: UICollectionViewCell { func setItem(item: MediaItem) { self.item = item - label.text = item.name + if (item.type == ItemType.SNAPSHOT) { + label.hidden = true + } + else { + label.text = item.name + label.hidden = false + } - if let i = item.image { - println (i) - image.image = i - image.sizeToFit() + if let url = item.thumbUrl { + image.hnk_setImageFromURL(NSURL(string: NetworkManager.sharedInstance.baseurl + "/service/download" + url)!) } else { - image.image = UIImage(named: "Kirschkeks-256x256.png") + if let i = item.image { + println(i) + image.image = i + image.sizeToFit() + } else { + image.image = UIImage(named: "Kirschkeks-256x256.png") + } } } diff --git a/kplayer/MasterViewController.swift b/kplayer/MasterViewController.swift index 6192f33..0ec49ab 100644 --- a/kplayer/MasterViewController.swift +++ b/kplayer/MasterViewController.swift @@ -74,6 +74,7 @@ class MasterViewController: UITableViewController { let object = items[indexPath.row] let controller = (segue.destinationViewController as! UINavigationController).topViewController as! DetailViewController controller.detailItem = object + NetworkManager.sharedInstance.loadItems(object) controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem() controller.navigationItem.leftItemsSupplementBackButton = true } diff --git a/kplayer/VideoPlayerController.swift b/kplayer/VideoPlayerController.swift index 5cd3e88..5838e1e 100644 --- a/kplayer/VideoPlayerController.swift +++ b/kplayer/VideoPlayerController.swift @@ -4,6 +4,235 @@ // import Foundation +import UIKit +import MediaPlayer +import ALMoviePlayerController -class VideoPlayerController { +class VideoPlayerController: UIViewController { + var moviePlayer: ALMoviePlayerController? + var currentItem: MediaItem? + + var completionHandler: ((Void) -> Void)? + + var buttons = Dictionary() + + var index = 0 + + override func viewDidLoad() { + super.viewDidLoad() + + var url = NetworkManager.sharedInstance.playerURL(currentItem!) + + println(url) + + play(url) + } + + @IBAction func back(sender: AnyObject) { + if let player = self.moviePlayer { + player.stop() + } + completionHandler!() + } + + func play(url: String) { + self.moviePlayer = ALMoviePlayerController(frame: CGRectMake(0, 0, 1024, 680)) + if let player = self.moviePlayer { + + let movieControls = ALMoviePlayerControls(moviePlayer: player, style: ALMoviePlayerControlsStyleDefault); + player.controls = movieControls + movieControls.style = ALMoviePlayerControlsStyleEmbedded + + NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("exitedFullscreen"), name: MPMoviePlayerDidExitFullscreenNotification, object: nil); + NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("enteredFullscreen"), name: MPMoviePlayerDidEnterFullscreenNotification, object: nil); + NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("showThumbnail:"), name: MPMoviePlayerThumbnailImageRequestDidFinishNotification, 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.AspectFill +// player.controlStyle = MPMovieControlStyle.Embedded + player.movieSourceType = MPMovieSourceType.Streaming + player.repeatMode = MPMovieRepeatMode.One + player.contentURL = NSURL(string: url) + player.play() + + var timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: Selector("update"), userInfo: nil, repeats: false) + +// view.autolayout(["controls": movieControls, "player": player.view], +// constraints: +// "H:|-[player]-|", +// "H:|-[controls]-|", +// "V:|-[player]-[controls(<=50)]-|" ) + self.view.addSubview(player.view) +// view.addSubview(movieControls) + // player.setFrame(view.frame) + } + } + + func showThumbnail(note: NSNotification) { + let userInfo = note.userInfo! as NSDictionary + let thumbnail = userInfo.objectForKey(MPMoviePlayerThumbnailImageKey) as! UIImage + let time = userInfo.objectForKey(MPMoviePlayerThumbnailTimeKey) as! NSTimeInterval + + 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) + + println(newItem.time) + + addItemButton(newItem) + } + + func addItemButton(newItem: MediaItem) { + let icon = newItem.image!.scaleToSize(66.0, height: 44.0) + let frame = CGRectMake(0, 0, 66.0, 44.0); + + let button = UIButton(frame: frame) + button.setBackgroundImage(icon, forState: UIControlState.Normal); + button.showsTouchWhenHighlighted = true + button.addTarget(self, action: "thumbnailClicked:", forControlEvents: .TouchDown) + + let barbutton = UIBarButtonItem(customView: button); + + if navigationItem.rightBarButtonItems == nil { + navigationItem.rightBarButtonItems = [] + } + navigationItem.rightBarButtonItems!.append(barbutton) + + buttons[button] = newItem + } + + func thumbnailClicked(source: UIButton) { + println(source) + + moviePlayer!.currentPlaybackTime = buttons[source]!.time! + } + + func update() { + if let player = self.moviePlayer { + installGestures(player.view) + + if let t = currentItem!.time { + player.currentPlaybackTime = t + } else { + println(player.duration) + player.currentPlaybackTime = player.duration / 2 + } + + if currentItem!.type == ItemType.SNAPSHOT { + currentItem = currentItem!.parent + } + + for c in currentItem!.children { + addItemButton(c) + } + + // player.fullscreen = true + } + } + + func enteredFullscreen() { + let mp = UIApplication.sharedApplication().keyWindow; + if let moviePlayerContainer = recursiveSearchForViewWithName(mp!, classname: "MPVideoContainerView") { + if (moviePlayerContainer.gestureRecognizers != nil) { + return; + } + installGestures(moviePlayerContainer) + } + } + + func exitedFullscreen() { + moviePlayer!.view.removeFromSuperview(); + moviePlayer = nil; + NSNotificationCenter.defaultCenter().removeObserver(self); + + } + + func installGestures(moviePlayer: UIView) { + let twoFingersTwoTaps = UITapGestureRecognizer(target: self, action: "twoFingersTwoTaps") + twoFingersTwoTaps.numberOfTapsRequired = 2 + twoFingersTwoTaps.numberOfTouchesRequired = 2 + moviePlayer.addGestureRecognizer(twoFingersTwoTaps) + + let sR = UISwipeGestureRecognizer(target: self, action: "swipeRight") + sR.direction = UISwipeGestureRecognizerDirection.Right + sR.numberOfTouchesRequired = 1 + moviePlayer.addGestureRecognizer(sR) + + let sL = UISwipeGestureRecognizer(target: self, action: "swipeLeft") + sL.direction = UISwipeGestureRecognizerDirection.Left + sL.numberOfTouchesRequired = 1 + moviePlayer.addGestureRecognizer(sL) + + let sR2 = UISwipeGestureRecognizer(target: self, action: "swipeDown") + sR2.direction = UISwipeGestureRecognizerDirection.Down + sR2.numberOfTouchesRequired = 1 + moviePlayer.addGestureRecognizer(sR2) + + let sR3 = UISwipeGestureRecognizer(target: self, action: "swipeUp") + sR3.direction = UISwipeGestureRecognizerDirection.Up + sR3.numberOfTouchesRequired = 1 + moviePlayer.addGestureRecognizer(sR3) + + } + + func swipeUp() { + println("u") + if let player = self.moviePlayer { + if index < count(currentItem!.children) - 1 { + index++; + } + else { + index = 0; + } + let child = currentItem!.children[index] + player.currentPlaybackTime = child.time! + } + } + + func swipeRight() { + println("r") + if let player = self.moviePlayer { + player.currentPlaybackTime = player.currentPlaybackTime + 30.0 + } + } + + func swipeDown() { + println("r") + if let player = self.moviePlayer { + player.currentPlaybackTime = player.currentPlaybackTime + 10.0 + } + } + + func swipeLeft() { + println("l") + if let player = self.moviePlayer { + player.currentPlaybackTime = player.currentPlaybackTime - 30.0 + } + } + + func twoFingersTwoTaps() { + println("tap") + moviePlayer!.requestThumbnailImagesAtTimes([moviePlayer!.currentPlaybackTime], + timeOption: MPMovieTimeOption.NearestKeyFrame); + } + + + func recursiveSearchForViewWithName(view: UIView, classname: String) -> UIView? { + for v in view.subviews { + var result = recursiveSearchForViewWithName(v as! UIView, classname: classname) + let cn = toString(v.dynamicType) + + if cn == classname { + return v as? UIView + } + + if result != nil { + return result + } + } + return nil + } } diff --git a/kplayer/core/ImageLoadOperation.swift b/kplayer/core/ImageLoadOperation.swift new file mode 100644 index 0000000..1c536d9 --- /dev/null +++ b/kplayer/core/ImageLoadOperation.swift @@ -0,0 +1,59 @@ +// +// Created by Marco Schmickler on 21.03.15. +// Copyright (c) 2015 Marco Schmickler. All rights reserved. +// + +import Foundation +import Alamofire +import SwiftyJSON + +class ImageLoadOperation: NSOperation { + let baseUrl: String + let item: MediaItem + + init(baseUrl: String, item: MediaItem) { + self.baseUrl = baseUrl + self.item = item + } + + override func main() { + if self.cancelled { + return + } + + let path = baseUrl + "/service/download" + item.thumbUrl! + println(path) + + Alamofire.request(.GET, path).validate().responseImage( { + (_, _, image, error) in + + if error == nil && image != nil { + self.item.image = image + } + else { + println(error) + } + }) + + } +} + +extension Alamofire.Request { + class func imageResponseSerializer() -> Serializer { + return { request, response, data in + if data == nil { + return (nil, nil) + } + + let image = UIImage(data: data!) //, scale: UIScreen.mainScreen().scale) + + return (image, nil) + } + } + + func responseImage(completionHandler: (NSURLRequest, NSHTTPURLResponse?, UIImage?, NSError?) -> Void) -> Self { + return response(serializer: Request.imageResponseSerializer(), completionHandler: { (request, response, image, error) in + completionHandler(request, response, image as? UIImage, error) + }) + } +} \ No newline at end of file diff --git a/kplayer/core/MediaItem.swift b/kplayer/core/MediaItem.swift index d85044d..58960bc 100644 --- a/kplayer/core/MediaItem.swift +++ b/kplayer/core/MediaItem.swift @@ -19,11 +19,14 @@ class MediaItem : DebugPrintable { var image: UIImage? var time: NSTimeInterval? + var thumbUrl: String? var children: [MediaItem] var parent: MediaItem? var type: ItemType + var loaded = false + init(name: String, path: String, root: String, type: ItemType) { self.name = name self.path = path @@ -32,6 +35,19 @@ class MediaItem : DebugPrintable { children = [MediaItem]() } + var thumbPath: String { + let len = count("/srv/samba/ren") + let tpath = "/srv/samba/ren/thumb" + (root as NSString).substringFromIndex(len) + "/" + path + "/" + name + + return tpath + "/" + } + + var fullPath: String { + let fpath = root + "/" + path + "/" + name + + return fpath + } + var debugDescription: String { return root + " " + path + " " + name } diff --git a/kplayer/core/NetData.swift b/kplayer/core/NetData.swift new file mode 100644 index 0000000..ead4f24 --- /dev/null +++ b/kplayer/core/NetData.swift @@ -0,0 +1,54 @@ +// +// NetData.swift +// Net +// +// Created by Le Van Nghia on 8/3/14. +// Copyright (c) 2014 Le Van Nghia. All rights reserved. +// +import Foundation +import UIKit +enum MimeType: String { + case ImageJpeg = "image/jpeg" + case ImagePng = "image/png" + case ImageGif = "image/gif" + case Json = "application/json" + case Unknown = "" + func getString() -> String? { + switch self { + case .ImagePng: + fallthrough + case .ImageJpeg: + fallthrough + case .ImageGif: + fallthrough + case .Json: + return self.rawValue + case .Unknown: + fallthrough + default: + return nil + } + } +} + +class NetData +{ + let data: NSData + let mimeType: MimeType + let filename: String + init(data: NSData, mimeType: MimeType, filename: String) { + self.data = data + self.mimeType = mimeType + self.filename = filename + } + init(pngImage: UIImage, filename: String) { + data = UIImagePNGRepresentation(pngImage) + self.mimeType = MimeType.ImagePng + self.filename = filename + } + init(jpegImage: UIImage, compressionQuanlity: CGFloat, filename: String) { + data = UIImageJPEGRepresentation(jpegImage, compressionQuanlity) + self.mimeType = MimeType.ImageJpeg + self.filename = filename + } +} diff --git a/kplayer/core/NetworkHelper.swift b/kplayer/core/NetworkHelper.swift new file mode 100644 index 0000000..53b4ca3 --- /dev/null +++ b/kplayer/core/NetworkHelper.swift @@ -0,0 +1,59 @@ +// +// Created by Marco Schmickler on 22.03.15. +// Copyright (c) 2015 Marco Schmickler. All rights reserved. +// + +import Foundation +import Alamofire + +func urlRequestWithComponents(urlString: String, parameters: NSDictionary) -> (URLRequestConvertible, NSData) { + + // create url request to send + var mutableURLRequest = NSMutableURLRequest(URL: NSURL(string: urlString)!) + mutableURLRequest.HTTPMethod = Alamofire.Method.POST.rawValue + //let boundaryConstant = "myRandomBoundary12345" + let boundaryConstant = "NET-POST-boundary-\(arc4random())-\(arc4random())" + let contentType = "multipart/form-data;boundary=" + boundaryConstant + mutableURLRequest.setValue(contentType, forHTTPHeaderField: "Content-Type") + + + // create upload data to send + let uploadData = NSMutableData() + + // add parameters + for (key, value) in parameters { + + uploadData.appendData("\r\n--\(boundaryConstant)\r\n".dataUsingEncoding(NSUTF8StringEncoding)!) + + if value is NetData { + // add image + var postData = value as! NetData + + //uploadData.appendData("Content-Disposition: form-data; name=\"\(key)\"; filename=\"\(postData.filename)\"\r\n".dataUsingEncoding(NSUTF8StringEncoding)!) + + // append content disposition + var filenameClause = " filename=\"\(postData.filename)\"" + let contentDispositionString = "Content-Disposition: form-data; name=\"\(key)\";\(filenameClause)\r\n" + let contentDispositionData = contentDispositionString.dataUsingEncoding(NSUTF8StringEncoding) + uploadData.appendData(contentDispositionData!) + + + // append content type + //uploadData.appendData("Content-Type: image/png\r\n\r\n".dataUsingEncoding(NSUTF8StringEncoding)!) // mark this. + let contentTypeString = "Content-Type: \(postData.mimeType.getString())\r\n\r\n" + let contentTypeData = contentTypeString.dataUsingEncoding(NSUTF8StringEncoding) + uploadData.appendData(contentTypeData!) + uploadData.appendData(postData.data) + + } else { + uploadData.appendData("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n\(value)".dataUsingEncoding(NSUTF8StringEncoding)!) + } + } + uploadData.appendData("\r\n--\(boundaryConstant)--\r\n".dataUsingEncoding(NSUTF8StringEncoding)!) + + // return URLRequestConvertible and NSData + return (Alamofire.ParameterEncoding.URL.encode(mutableURLRequest, parameters: nil).0, uploadData) +} + +class NetworkHelper { +} diff --git a/kplayer/core/NetworkManager.swift b/kplayer/core/NetworkManager.swift index 477a910..2d260ce 100644 --- a/kplayer/core/NetworkManager.swift +++ b/kplayer/core/NetworkManager.swift @@ -5,12 +5,20 @@ import Foundation import Alamofire +import SwiftyJSON class NetworkManager { static let sharedInstance = NetworkManager() let baseurl = "http://linkstation/tomcat/media" + lazy var operationQueue: NSOperationQueue = { + var queue = NSOperationQueue() + queue.name = "Backup queue" + queue.maxConcurrentOperationCount = 1 + return queue + }() + func loadRoots() -> AnyObject { var itemsArray = []; @@ -69,4 +77,142 @@ class NetworkManager { func playerURL(item: MediaItem) -> String { return baseurl + "/service/stream" + item.root + "/" + item.path + "/" + item.name } + + func loadItems(item: MediaItem) { + if (item.type != ItemType.FOLDER) { + return + } + + if (item.loaded) { + return + } + + var j = 0 + for i in item.children { + loadItem(i, index: j) + j++ + } + } + + func loadItem(item: MediaItem, index: Int) { + if (item.type != ItemType.VIDEO) { + return + } + + if item.loaded { + return + } + + if !item.children.isEmpty { + return + } + + let url = baseurl + "/service/listfiles" + item.thumbPath + + println(url) + + Alamofire.request(.GET, url).responseSwiftyJSON( { + (request, response, json, error) in + + var hashes = Dictionary() + + for (a, c) in json { + // let (a,b) = j # + let b = c.object as! NSString + + let p = b.lastPathComponent as NSString + + if p.containsString("_thumb.jpg") { + let ts = p.substringToIndex(p.length - 10) + + hashes[ts] = b as String + } else { + let ts = p.substringToIndex(p.length - 4) + + if hashes[ts] == nil { + hashes[ts] = b as String + } + } + } + + for (ts, p) in hashes { + println ("\(ts) \(p)") + let t = (ts as NSString).doubleValue / 1000 + + let snap = MediaItem(name: item.name, path: item.path, root: item.root, type:.SNAPSHOT) + snap.time = t + snap.thumbUrl = p + +// let op = ImageLoadOperation(baseUrl: self.baseurl, item: snap) +// self.operationQueue.addOperation(op) + + snap.parent = item + item.children.append(snap) + + println ("Time: \(t)") + } + + item.loaded = true + NSNotificationCenter.defaultCenter().postNotificationName("loadedItems", object: index) +// println(error) + }) + } + + func saveItem(item: MediaItem) { + println (item.children) + + + if (item.type != ItemType.VIDEO) { + return + } + + let url = baseurl + "/service/listfiles" + item.thumbPath + + println(url) + + Alamofire.request(.GET, url).responseSwiftyJSON( { + (request, response, json, error) in + + var hashes = NSMutableSet() + + for (a, c) in json { + // let (a,b) = j # + let b = c.object as! String + hashes.addObject(b) + println(b) + } + + for c in item.children { + if let t = c.time { + let ms = Int(t * 1000) + let p = c.thumbPath + "\(ms).jpg" + let pt = c.thumbPath + "\(ms)_thumb.jpg" + println (p) + if hashes.containsObject(pt) { + println ("contained") + } + else { + let imageData = UIImageJPEGRepresentation(c.image, 1.0); + let op = UploadOperation(baseUrl: self.baseurl, data: imageData, path: p) + self.operationQueue.addOperation(op) + + let imageDataT = UIImageJPEGRepresentation(c.image!.scaleToSize(14 * 16, height: 14 * 10), 1.0); + let opT = UploadOperation(baseUrl: self.baseurl, data: imageDataT, path: pt) + self.operationQueue.addOperation(opT) + } + hashes.removeObject(p) + hashes.removeObject(pt) + } + } + + for o in hashes { + println ("To delete \(o)") + Alamofire.request(.GET, self.baseurl + "/service/deletethumb\(o)") + } + + println(error) + }) + + } + } diff --git a/kplayer/core/UploadOperation.swift b/kplayer/core/UploadOperation.swift new file mode 100644 index 0000000..179f94b --- /dev/null +++ b/kplayer/core/UploadOperation.swift @@ -0,0 +1,38 @@ +// +// Created by Marco Schmickler on 21.03.15. +// Copyright (c) 2015 Marco Schmickler. All rights reserved. +// + +import Foundation +import Alamofire +import SwiftyJSON + +class UploadOperation: NSOperation { + let baseUrl: String + let data: NSData + let path: String + + init(baseUrl: String, data: NSData, path: String) { + self.baseUrl = baseUrl + self.data = data + self.path = path + } + + override func main() { + if self.cancelled { + return + } + + var parameters = [ + "file": NetData(data: data, mimeType: MimeType.Json, filename: path), + "name": path + ] + + let urlRequest = urlRequestWithComponents(baseUrl + "/service/upload", parameters) + let request = Alamofire.upload(urlRequest) +// .progress { +// (bytesWritten, totalBytesWritten, totalBytesExpectedToWrite) in +// println("progress : \(totalBytesWritten) / \(totalBytesExpectedToWrite)") +// } + } +} diff --git a/kplayer/core/alamojson.swift b/kplayer/core/alamojson.swift new file mode 100644 index 0000000..d88e440 --- /dev/null +++ b/kplayer/core/alamojson.swift @@ -0,0 +1,48 @@ +// +// AlamofireSwiftyJSON.swift +// AlamofireSwiftyJSON +// +// Created by Pinglin Tang on 14-9-22. +// Copyright (c) 2014 SwiftyJSON. All rights reserved. +// + +import Foundation +import Alamofire +import SwiftyJSON + +// MARK: - Request for Swift JSON + +extension Request { +/** +Adds a handler to be called once the request has finished. +:param: completionHandler A closure to be executed once the request has finished. The closure takes 4 arguments: the URL request, the URL response, if one was received, the SwiftyJSON enum, if one could be created from the URL response and data, and any error produced while creating the SwiftyJSON enum. +:returns: The request. +*/ + public func responseSwiftyJSON(completionHandler: (NSURLRequest, NSHTTPURLResponse?, SwiftyJSON.JSON, NSError?) -> Void) -> Self { + return responseSwiftyJSON(queue: nil, options: NSJSONReadingOptions.AllowFragments, completionHandler: completionHandler) + } +/** +Adds a handler to be called once the request has finished. +:param: queue The queue on which the completion handler is dispatched. +:param: options The JSON serialization reading options. `.AllowFragments` by default. +:param: completionHandler A closure to be executed once the request has finished. The closure takes 4 arguments: the URL request, the URL response, if one was received, the SwiftyJSON enum, if one could be created from the URL response and data, and any error produced while creating the SwiftyJSON enum. +:returns: The request. +*/ + public func responseSwiftyJSON(queue: dispatch_queue_t? = nil, options: NSJSONReadingOptions = .AllowFragments, completionHandler: (NSURLRequest, NSHTTPURLResponse?, JSON, NSError?) -> Void) -> Self { + return response(queue: queue, serializer: Request.JSONResponseSerializer(options: options), completionHandler: { + (request, response, object, error) -> Void in + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { + var responseJSON: JSON + if error != nil || object == nil { + responseJSON = JSON.nullJSON + } else { + responseJSON = SwiftyJSON.JSON(object!) + } + dispatch_async(queue ?? dispatch_get_main_queue(), { + completionHandler(self.request, self.response, responseJSON, error) + }) + }) + }) + } +} + diff --git a/kplayer/util/UIImageExtension.swift b/kplayer/util/UIImageExtension.swift new file mode 100644 index 0000000..a631ca7 --- /dev/null +++ b/kplayer/util/UIImageExtension.swift @@ -0,0 +1,27 @@ +// +// Created by Marco Schmickler on 28.05.15. +// Copyright (c) 2015 Marco Schmickler. All rights reserved. +// + +import Foundation +import UIKit + +extension UIImage { + func scaleToSize(width: CGFloat, height: CGFloat) -> UIImage { + UIGraphicsBeginImageContext(CGSizeMake(width, height)); + + let context = UIGraphicsGetCurrentContext(); + CGContextTranslateCTM(context, 0.0, height); + CGContextScaleCTM(context, 1.0, -1.0); + + CGContextDrawImage(context, CGRectMake(0.0, 0.0, width, height), self.CGImage); + + let scaledImage = UIGraphicsGetImageFromCurrentImageContext(); + + UIGraphicsEndImageContext(); + + return scaledImage; + } + + +} \ No newline at end of file