diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..d821048 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/kplayer.iml b/.idea/kplayer.iml new file mode 100644 index 0000000..cb345e8 --- /dev/null +++ b/.idea/kplayer.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..8662aa9 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..e96c31c --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml new file mode 100644 index 0000000..922003b --- /dev/null +++ b/.idea/scopes/scope_settings.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/xcode.xml b/.idea/xcode.xml new file mode 100644 index 0000000..341efb1 --- /dev/null +++ b/.idea/xcode.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Podfile b/Podfile new file mode 100644 index 0000000..d1af914 --- /dev/null +++ b/Podfile @@ -0,0 +1,7 @@ +platform :ios, '8.3' + +source 'https://github.com/CocoaPods/Specs.git' +use_frameworks! + +# pod 'Player' +pod 'Alamofire', '~> 1.2' diff --git a/kplayer.xcodeproj/project.pbxproj b/kplayer.xcodeproj/project.pbxproj index 9fc6752..438cd7d 100644 --- a/kplayer.xcodeproj/project.pbxproj +++ b/kplayer.xcodeproj/project.pbxproj @@ -7,6 +7,14 @@ objects = { /* Begin PBXBuildFile section */ + 1C736261CBA1D13D16DCBAFB /* VideoPlayerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C7366EF59D75216EBC0D3F0 /* VideoPlayerController.swift */; }; + 1C73635138BBD2BB480A308F /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C736777456388CA571DA17B /* MediaPlayer.framework */; }; + 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 */; }; + 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, ); }; }; C98AF5D51B124D6A00D196CC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C98AF5D41B124D6A00D196CC /* AppDelegate.swift */; }; C98AF5D81B124D6A00D196CC /* kplayer.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = C98AF5D61B124D6A00D196CC /* kplayer.xcdatamodeld */; }; C98AF5DA1B124D6A00D196CC /* MasterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C98AF5D91B124D6A00D196CC /* MasterViewController.swift */; }; @@ -28,6 +36,16 @@ /* End PBXContainerItemProxy section */ /* 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 = ""; }; + 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 = ""; }; + 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 = ""; }; + 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; }; C98AF5D31B124D6A00D196CC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C98AF5D41B124D6A00D196CC /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -47,6 +65,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + A5D637AE4588AAB5DC1CBC6B /* Pods.framework in Frameworks */, + 1C73635138BBD2BB480A308F /* MediaPlayer.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -60,12 +80,41 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 1C736DC8C3AFB991541A2079 /* core */ = { + isa = PBXGroup; + children = ( + 1C7365B06FA66294E99AC2D3 /* NetworkManager.swift */, + 1C73688DAB88F9360FB62A49 /* MediaItem.swift */, + ); + path = core; + sourceTree = ""; + }; + 8CB608B13A2BDFA9D708982B /* Frameworks */ = { + isa = PBXGroup; + children = ( + 127AC1F28342F9AAE3CEC5C2 /* Pods.framework */, + 1C736777456388CA571DA17B /* MediaPlayer.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 8EB26707CC8BD3E10F328A9E /* Pods */ = { + isa = PBXGroup; + children = ( + 5C6CBA548F885BF342F594EA /* Pods.debug.xcconfig */, + A170BFB886D61D57F7009BFC /* Pods.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; C98AF5C61B124D6A00D196CC = { isa = PBXGroup; children = ( C98AF5D11B124D6A00D196CC /* kplayer */, C98AF5EC1B124D6A00D196CC /* kplayerTests */, C98AF5D01B124D6A00D196CC /* Products */, + 8EB26707CC8BD3E10F328A9E /* Pods */, + 8CB608B13A2BDFA9D708982B /* Frameworks */, ); sourceTree = ""; }; @@ -89,6 +138,11 @@ C98AF5E21B124D6A00D196CC /* LaunchScreen.xib */, C98AF5D61B124D6A00D196CC /* kplayer.xcdatamodeld */, C98AF5D21B124D6A00D196CC /* Supporting Files */, + 1C736DC8C3AFB991541A2079 /* core */, + 1C7362C647AEBF03F5FD9FEB /* ItemCell.swift */, + 1C73649CEA7BDD2AC1496F76 /* LayoutTools.swift */, + 1C7368DC7EF11A553145E169 /* Kirschkeks-256x256.png */, + 1C7366EF59D75216EBC0D3F0 /* VideoPlayerController.swift */, ); path = kplayer; sourceTree = ""; @@ -125,9 +179,12 @@ isa = PBXNativeTarget; buildConfigurationList = C98AF5F31B124D6A00D196CC /* Build configuration list for PBXNativeTarget "kplayer" */; buildPhases = ( + FA5E42A873B9445C28CCC025 /* Check Pods Manifest.lock */, C98AF5CB1B124D6A00D196CC /* Sources */, C98AF5CC1B124D6A00D196CC /* Frameworks */, C98AF5CD1B124D6A00D196CC /* Resources */, + 81E453B289EA9615E1980098 /* Embed Pods Frameworks */, + F554AE493C60519505E761A6 /* Copy Pods Resources */, ); buildRules = ( ); @@ -201,6 +258,7 @@ C98AF5DF1B124D6A00D196CC /* Main.storyboard in Resources */, C98AF5E41B124D6A00D196CC /* LaunchScreen.xib in Resources */, C98AF5E11B124D6A00D196CC /* Images.xcassets in Resources */, + 1C7363D9DC8F9D1F866DE935 /* Kirschkeks-256x256.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -213,6 +271,54 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 81E453B289EA9615E1980098 /* Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + F554AE493C60519505E761A6 /* Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + FA5E42A873B9445C28CCC025 /* Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ C98AF5CB1B124D6A00D196CC /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -222,6 +328,11 @@ C98AF5D51B124D6A00D196CC /* AppDelegate.swift in Sources */, C98AF5DA1B124D6A00D196CC /* MasterViewController.swift in Sources */, C98AF5DC1B124D6A00D196CC /* DetailViewController.swift in Sources */, + 1C736503B656C999E5E12081 /* NetworkManager.swift in Sources */, + 1C736FB92B19FE17E4357C85 /* MediaItem.swift in Sources */, + 1C736C90DB50C4FDED266C3D /* ItemCell.swift in Sources */, + 1C73641627BE29D9FA819F3C /* LayoutTools.swift in Sources */, + 1C736261CBA1D13D16DCBAFB /* VideoPlayerController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -348,6 +459,7 @@ }; C98AF5F41B124D6A00D196CC /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 5C6CBA548F885BF342F594EA /* Pods.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = kplayer/Info.plist; @@ -358,6 +470,7 @@ }; C98AF5F51B124D6A00D196CC /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = A170BFB886D61D57F7009BFC /* Pods.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = kplayer/Info.plist; @@ -419,6 +532,7 @@ C98AF5F51B124D6A00D196CC /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; C98AF5F61B124D6A00D196CC /* Build configuration list for PBXNativeTarget "kplayerTests" */ = { isa = XCConfigurationList; @@ -427,6 +541,7 @@ C98AF5F81B124D6A00D196CC /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; /* End XCConfigurationList section */ diff --git a/kplayer/AppDelegate.swift b/kplayer/AppDelegate.swift index a76d230..1be94f6 100644 --- a/kplayer/AppDelegate.swift +++ b/kplayer/AppDelegate.swift @@ -24,7 +24,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele let masterNavigationController = splitViewController.viewControllers[0] as! UINavigationController let controller = masterNavigationController.topViewController as! MasterViewController - controller.managedObjectContext = self.managedObjectContext return true } diff --git a/kplayer/Base.lproj/Main.storyboard b/kplayer/Base.lproj/Main.storyboard index b3b5a78..91a3dfb 100644 --- a/kplayer/Base.lproj/Main.storyboard +++ b/kplayer/Base.lproj/Main.storyboard @@ -1,7 +1,7 @@ - + - + @@ -22,7 +22,7 @@ - + @@ -35,7 +35,7 @@ - + @@ -73,7 +73,7 @@ - + @@ -90,7 +90,7 @@ - + diff --git a/kplayer/DetailViewController.swift b/kplayer/DetailViewController.swift index 09f284f..cbdbcd0 100644 --- a/kplayer/DetailViewController.swift +++ b/kplayer/DetailViewController.swift @@ -7,32 +7,164 @@ // import UIKit +import MediaPlayer -class DetailViewController: UIViewController { +class DetailViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource { @IBOutlet weak var detailDescriptionLabel: UILabel! + var moviePlayer: MPMoviePlayerController? - var detailItem: AnyObject? { + var collectionView: UICollectionView! + + var detailItem: MediaItem? { didSet { + println(detailItem!.children) + if collectionView != nil { + collectionView.reloadData() + } // Update the view. - self.configureView() + // self.configureView() } } + var currentItem: MediaItem? + func configureView() { // Update the user interface for the detail item. - if let detail: AnyObject = self.detailItem { + println(detailItem) + if let detail: MediaItem = self.detailItem { + println(detail) + println(detail.children) + if let label = self.detailDescriptionLabel { - label.text = detail.valueForKey("timeStamp")!.description + label.text = detail.name + } + + if count(detail.children) > 0 { + let i = detail.children[0] + var url = NetworkManager.sharedInstance.playerURL(i) + + println(url) + +// if moviePlayer == nil { + 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() + + } override func viewDidLoad() { super.viewDidLoad() + + 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.headerReferenceSize = CGSize(width: 50, height: 50) + collectionView = UICollectionView(frame: view.frame, collectionViewLayout: layout) + + collectionView.dataSource = self + collectionView.delegate = self + collectionView.registerClass(ItemCell.self, forCellWithReuseIdentifier: "Cell") + collectionView.backgroundColor = UIColor.greenColor() + collectionView.registerClass(ItemCell.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "HeaderView"); + + view.addSubview(collectionView) +// view.autoresizesSubviews = true +// collectionView.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight // Do any additional setup after loading the view, typically from a nib. - self.configureView() + + collectionView.reloadData() + } + + override func viewDidAppear(animated: Bool) { + super.viewDidAppear(animated) + +// self.configureView() + } override func didReceiveMemoryWarning() { @@ -40,6 +172,146 @@ class DetailViewController: UIViewController { // Dispose of any resources that can be recreated. } + func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int { + if let detail: MediaItem = self.detailItem { + let cnt = count(detail.children) + println(cnt) + return cnt + } + return 0 + } + + func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + if let detail: MediaItem = self.detailItem { + return count(detail.children[section].children) + 1 + } + return 0 + } + + func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! ItemCell + + if let detail: MediaItem = self.detailItem { + let items = detail.children[indexPath.section] + if indexPath.item >= count(items.children) { + cell.setItem(items) + } else { + let item = items.children[indexPath.item] + cell.setItem(item) + } + } + cell.backgroundColor = UIColor.blueColor() + return cell + } + + func collectionView(collectionView: UICollectionView, + viewForSupplementaryElementOfKind kind: String, + atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView { + switch kind { + case UICollectionElementKindSectionHeader: + let headerView = + collectionView.dequeueReusableSupplementaryViewOfKind(kind, + withReuseIdentifier: "HeaderView", + forIndexPath: indexPath) + 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 + default: + assert(false, "Unexpected element kind") + } + } + + 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) + } + + 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 + } + } + return nil + } } diff --git a/kplayer/ItemCell.swift b/kplayer/ItemCell.swift new file mode 100644 index 0000000..e38b99d --- /dev/null +++ b/kplayer/ItemCell.swift @@ -0,0 +1,53 @@ +// +// Created by Marco Schmickler on 25.05.15. +// Copyright (c) 2015 Marco Schmickler. All rights reserved. +// + +import Foundation +import UIKit + +class ItemCell: UICollectionViewCell { + var item: MediaItem? + + var label: UILabel! + var image: UIImageView! + + required init(coder aDecoder: NSCoder) + { + super.init(coder: aDecoder) + + } + + + override init(frame: CGRect) { + + super.init(frame: frame) + + label = UILabel(frame: frame) + + image = UIImageView() + image.layer.borderWidth = 2.0; + + autolayout(["label": label, "imag": image], + constraints: + "H:|-5-[label]-5-|", + "H:|-5-[imag]-5-|", + "V:|-5-[label(<=50)]-5-[imag]-5-|" ) + } + + func setItem(item: MediaItem) { + self.item = item + + label.text = item.name + + if let i = item.image { + println (i) + image.image = i + image.sizeToFit() + } + else { + image.image = UIImage(named: "Kirschkeks-256x256.png") + } + + } +} diff --git a/kplayer/Kirschkeks-256x256.png b/kplayer/Kirschkeks-256x256.png new file mode 100644 index 0000000..180be0b Binary files /dev/null and b/kplayer/Kirschkeks-256x256.png differ diff --git a/kplayer/LayoutTools.swift b/kplayer/LayoutTools.swift new file mode 100644 index 0000000..9efe279 --- /dev/null +++ b/kplayer/LayoutTools.swift @@ -0,0 +1,45 @@ +// +// Created by Marco Schmickler on 25.05.15. +// Copyright (c) 2015 Marco Schmickler. All rights reserved. +// + +import Foundation +import UIKit + +public extension UIView { + + func autolayout(views: [String:UIView!], constraints: Any...) { + autolay(views, constraints:constraints) + } + + func autolay(views: [String:UIView!], constraints: [Any]) { + var index = 0; + + for value in views.values { + let vv: UIView = value + addSubview(vv) + vv.setTranslatesAutoresizingMaskIntoConstraints(false) + } + + while index < constraints.count { + let c = constraints[index] + if c is String { + if index + 1 < constraints.count && !(constraints[index + 1] is String) { + index++ + let constr: NSArray = NSLayoutConstraint.constraintsWithVisualFormat(c as! String, + options: constraints[index] as! NSLayoutFormatOptions, + metrics: nil, + views: views) + addConstraints(constr as [AnyObject]) + } else { + let constr: NSArray = NSLayoutConstraint.constraintsWithVisualFormat(c as! String, + options: NSLayoutFormatOptions(0), metrics: nil, views: views) + addConstraints(constr as [AnyObject]) + } + } + index++ + } + } + + +} \ No newline at end of file diff --git a/kplayer/MasterViewController.swift b/kplayer/MasterViewController.swift index fcc2deb..6192f33 100644 --- a/kplayer/MasterViewController.swift +++ b/kplayer/MasterViewController.swift @@ -9,11 +9,11 @@ import UIKit import CoreData -class MasterViewController: UITableViewController, NSFetchedResultsControllerDelegate { +class MasterViewController: UITableViewController { var detailViewController: DetailViewController? = nil - var managedObjectContext: NSManagedObjectContext? = nil + var items = [MediaItem]() override func awakeFromNib() { super.awakeFromNib() @@ -25,6 +25,28 @@ class MasterViewController: UITableViewController, NSFetchedResultsControllerDel override func viewDidLoad() { super.viewDidLoad() + + NetworkManager.sharedInstance.loadDirs("/srv/samba/ren/knk_archiv4") { (g) in + var d = Dictionary() + + for f in g { + var p = d[f.path] + + if p == nil { + p = MediaItem(name: "", path: f.path, root: f.root, type: ItemType.FOLDER) + d[f.path] = p! + println(p) + } + + p!.children.append(f) + f.parent = p! + } + + self.items = Array(d.values) + + self.tableView.reloadData() + } + // Do any additional setup after loading the view, typically from a nib. self.navigationItem.leftBarButtonItem = self.editButtonItem() @@ -42,22 +64,6 @@ class MasterViewController: UITableViewController, NSFetchedResultsControllerDel } func insertNewObject(sender: AnyObject) { - let context = self.fetchedResultsController.managedObjectContext - let entity = self.fetchedResultsController.fetchRequest.entity! - let newManagedObject = NSEntityDescription.insertNewObjectForEntityForName(entity.name!, inManagedObjectContext: context) as! NSManagedObject - - // If appropriate, configure the new managed object. - // Normally you should use accessor methods, but using KVC here avoids the need to add a custom class to the template. - newManagedObject.setValue(NSDate(), forKey: "timeStamp") - - // Save the context. - var error: NSError? = nil - if !context.save(&error) { - // Replace this implementation with code to handle the error appropriately. - // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. - //println("Unresolved error \(error), \(error.userInfo)") - abort() - } } // MARK: - Segues @@ -65,7 +71,7 @@ class MasterViewController: UITableViewController, NSFetchedResultsControllerDel override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == "showDetail" { if let indexPath = self.tableView.indexPathForSelectedRow() { - let object = self.fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject + let object = items[indexPath.row] let controller = (segue.destinationViewController as! UINavigationController).topViewController as! DetailViewController controller.detailItem = object controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem() @@ -77,12 +83,11 @@ class MasterViewController: UITableViewController, NSFetchedResultsControllerDel // MARK: - Table View override func numberOfSectionsInTableView(tableView: UITableView) -> Int { - return self.fetchedResultsController.sections?.count ?? 0 + return 1 } override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - let sectionInfo = self.fetchedResultsController.sections![section] as! NSFetchedResultsSectionInfo - return sectionInfo.numberOfObjects + return count(items) } override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { @@ -93,111 +98,19 @@ class MasterViewController: UITableViewController, NSFetchedResultsControllerDel override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool { // Return false if you do not want the specified item to be editable. - return true + return false } override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) { if editingStyle == .Delete { - let context = self.fetchedResultsController.managedObjectContext - context.deleteObject(self.fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject) - - var error: NSError? = nil - if !context.save(&error) { - // Replace this implementation with code to handle the error appropriately. - // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. - //println("Unresolved error \(error), \(error.userInfo)") - abort() - } } } func configureCell(cell: UITableViewCell, atIndexPath indexPath: NSIndexPath) { - let object = self.fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject - cell.textLabel!.text = object.valueForKey("timeStamp")!.description - } - - // MARK: - Fetched results controller - - var fetchedResultsController: NSFetchedResultsController { - if _fetchedResultsController != nil { - return _fetchedResultsController! - } - - let fetchRequest = NSFetchRequest() - // Edit the entity name as appropriate. - let entity = NSEntityDescription.entityForName("Event", inManagedObjectContext: self.managedObjectContext!) - fetchRequest.entity = entity - - // Set the batch size to a suitable number. - fetchRequest.fetchBatchSize = 20 - - // Edit the sort key as appropriate. - let sortDescriptor = NSSortDescriptor(key: "timeStamp", ascending: false) - let sortDescriptors = [sortDescriptor] - - fetchRequest.sortDescriptors = [sortDescriptor] - - // Edit the section name key path and cache name if appropriate. - // nil for section name key path means "no sections". - let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext!, sectionNameKeyPath: nil, cacheName: "Master") - aFetchedResultsController.delegate = self - _fetchedResultsController = aFetchedResultsController - - var error: NSError? = nil - if !_fetchedResultsController!.performFetch(&error) { - // Replace this implementation with code to handle the error appropriately. - // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. - //println("Unresolved error \(error), \(error.userInfo)") - abort() - } - - return _fetchedResultsController! - } - var _fetchedResultsController: NSFetchedResultsController? = nil - - func controllerWillChangeContent(controller: NSFetchedResultsController) { - self.tableView.beginUpdates() - } - - func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) { - switch type { - case .Insert: - self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade) - case .Delete: - self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade) - default: - return - } - } - - func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { - switch type { - case .Insert: - tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade) - case .Delete: - tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade) - case .Update: - self.configureCell(tableView.cellForRowAtIndexPath(indexPath!)!, atIndexPath: indexPath!) - case .Move: - tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade) - tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade) - default: - return - } - } - - func controllerDidChangeContent(controller: NSFetchedResultsController) { - self.tableView.endUpdates() + let object = items[indexPath.row] + cell.textLabel!.text = object.path } - /* - // Implementing the above methods to update the table view in response to individual changes may have performance implications if a large number of changes are made simultaneously. If this proves to be an issue, you can instead just implement controllerDidChangeContent: which notifies the delegate that all section and object changes have been processed. - - func controllerDidChangeContent(controller: NSFetchedResultsController) { - // In the simplest, most efficient, case, reload the table view. - self.tableView.reloadData() - } - */ } diff --git a/kplayer/VideoPlayerController.swift b/kplayer/VideoPlayerController.swift new file mode 100644 index 0000000..5cd3e88 --- /dev/null +++ b/kplayer/VideoPlayerController.swift @@ -0,0 +1,9 @@ +// +// Created by Marco Schmickler on 26.05.15. +// Copyright (c) 2015 Marco Schmickler. All rights reserved. +// + +import Foundation + +class VideoPlayerController { +} diff --git a/kplayer/core/MediaItem.swift b/kplayer/core/MediaItem.swift new file mode 100644 index 0000000..d85044d --- /dev/null +++ b/kplayer/core/MediaItem.swift @@ -0,0 +1,38 @@ +// +// Created by Marco Schmickler on 25.05.15. +// Copyright (c) 2015 Marco Schmickler. All rights reserved. +// + +import Foundation +import UIKit + +enum ItemType { + case FOLDER + case VIDEO + case SNAPSHOT +} + +class MediaItem : DebugPrintable { + var name : String + var path : String + var root: String + + var image: UIImage? + var time: NSTimeInterval? + + var children: [MediaItem] + var parent: MediaItem? + var type: ItemType + + init(name: String, path: String, root: String, type: ItemType) { + self.name = name + self.path = path + self.root = root + self.type = type + children = [MediaItem]() + } + + var debugDescription: String { + return root + " " + path + " " + name + } +} diff --git a/kplayer/core/NetworkManager.swift b/kplayer/core/NetworkManager.swift new file mode 100644 index 0000000..477a910 --- /dev/null +++ b/kplayer/core/NetworkManager.swift @@ -0,0 +1,72 @@ +// +// Created by Marco Schmickler on 24.05.15. +// Copyright (c) 2015 Marco Schmickler. All rights reserved. +// + +import Foundation +import Alamofire + +class NetworkManager { + static let sharedInstance = NetworkManager() + + let baseurl = "http://linkstation/tomcat/media" + + func loadRoots() -> AnyObject { + var itemsArray = []; + + Alamofire.request(.GET, baseurl + "/service/roots") + .responseString { + (_, _, string, _) in + let f = split(string!) { + $0 == "\r\n" + } + println(string) + println(f) + } + +// .response { (request, response, data, error) in +// println(request) +// println(response) +// +// var r = (response as NSString).componentsSeparatedByString("\r\n") +// +// println(error) +// } + + return itemsArray; + } + + func loadDirs(root: String, completionHandler: ([MediaItem]) -> Void) -> Void { + var itemsArray = []; + + Alamofire.request(.GET, baseurl + "/service/dirs" + root) + .responseString { + (_, _, string, _) in + let f = split(string!) { + $0 == "\r\n" + } + + let len = count(root) + + var res = [MediaItem]() + + for s in f { + if (s.hasSuffix(".mp4")) { + let l = count(s) + let name = (s as NSString).lastPathComponent + let pathlen = l - len - count(name) + let path = (s as NSString).substringWithRange(NSMakeRange(len + 1, pathlen - 2)) + let i = MediaItem(name: name, path: path, root: root, type: ItemType.VIDEO) + if !name.hasPrefix(".") { + res.append(i) + } + } + } + completionHandler(res) + } + } + + func playerURL(item: MediaItem) -> String { + return baseurl + "/service/stream" + item.root + "/" + item.path + "/" + item.name + } +}