├── README.md ├── YTFloatingPlayer ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── bigBunny.imageset │ │ ├── Contents.json │ │ └── bigBunny.jpg │ └── legoToy.imageset │ │ ├── Contents.json │ │ └── legoToy.jpg ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── LICENSE ├── NextViewController.swift ├── Video.swift ├── VideoCell.swift ├── VideoCell.xib ├── ViewController.swift └── YTFloatingPlayer │ ├── Classes │ ├── CustomProgressSlider.swift │ ├── PlayerView.swift │ ├── YTFAnimation.swift │ ├── YTFPlayer.swift │ ├── YTFPlayerControls.swift │ ├── YTFPopupCloseButton.swift │ └── YTFViewController.swift │ └── Resources │ ├── NowPlayingCollapseChevronMask.png │ ├── NowPlayingCollapseChevronMask@2x.png │ ├── NowPlayingCollapseChevronMask@3x.png │ ├── YTFViewController.xib │ ├── fullscreen.png │ ├── fullscreen@2x.png │ ├── pause.png │ ├── pause@2x.png │ ├── play.png │ ├── play@2x.png │ ├── unfullscreen.png │ └── unfullscreen@2x.png ├── YTFloatingPlayerTests ├── Info.plist └── YTDraggablePlayerTests.swift ├── YTFloatingPlayerUITests ├── Info.plist └── YTDraggablePlayerUITests.swift └── YouTubeFloatingPlayer.xcodeproj ├── project.pbxproj ├── project.xcworkspace ├── contents.xcworkspacedata └── xcuserdata │ └── AnaPaula.xcuserdatad │ └── UserInterfaceState.xcuserstate └── xcuserdata └── AnaPaula.xcuserdatad ├── xcdebugger └── Breakpoints_v2.xcbkptlist └── xcschemes ├── YTFloatingPlayer.xcscheme └── xcschememanagement.plist /README.md: -------------------------------------------------------------------------------- 1 | # YouTubePlayerSwift 2 | 3 | A Swift floating/draggable player like YouTube that remains on top of all screens until it's removed. 4 | Based on [PlayerView](https://github.com/davidlondono/PlayerView) and [DraggableYoutubeFloatingVideo](https://github.com/vizllx/DraggableYoutubeFloatingVideo). 5 | 6 | ## Requirements 7 | 8 | Currently YTF view is only supported on applications supporting only portrait orientations. To implement that you must put this in your AppDelegate: 9 | ```swift 10 | func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> UIInterfaceOrientationMask { 11 | return UIInterfaceOrientationMask.Portrait 12 | } 13 | ``` 14 | ## Screen Cast 15 | [SwiftYouTubeFloatingPlayer](https://youtu.be/AdOxohhO_Pc) 16 | 17 | 18 | ## Installation 19 | Copy Classes and Resources folders to your project. 20 | 21 | ## Usage 22 | 23 | Pass a NSURL or [NSURL], customize the tableView on YTF view adopting delegate and dataSource in your ViewController before passing them as parameters and also provide a nib for cell customization: 24 | ```swift 25 | YTFPlayer.initYTF(urls, tableCellNibName: "MyCell", delegate: self, dataSource: self) 26 | ``` 27 | 28 | Show YTF view passing a ViewController: 29 | ```swift 30 | YTFPlayer.showYTFView(self) 31 | ``` 32 | 33 | Remove YTF view animated(true) or not(false): 34 | ```swift 35 | YTFPlayer.finishYTFView(true) 36 | ``` 37 | 38 | ## Author 39 | 40 | souana, apso0101@gmail.com 41 | 42 | ## License 43 | 44 | SwiftYouTubeFloatingPlayer is available under the MIT license. See the LICENSE file for more info. 45 | -------------------------------------------------------------------------------- /YTFloatingPlayer/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // YTDraggablePlayer 4 | // 5 | // Created by Ana Paula on 5/19/16. 6 | // Copyright © 2016 Ana Paula. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 18 | // Override point for customization after application launch. 19 | 20 | return true 21 | } 22 | 23 | func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> UIInterfaceOrientationMask { 24 | return UIInterfaceOrientationMask.Portrait 25 | } 26 | 27 | func applicationWillResignActive(application: UIApplication) { 28 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 29 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 30 | } 31 | 32 | func applicationDidEnterBackground(application: UIApplication) { 33 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 34 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 35 | } 36 | 37 | func applicationWillEnterForeground(application: UIApplication) { 38 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 39 | } 40 | 41 | func applicationDidBecomeActive(application: UIApplication) { 42 | // 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. 43 | } 44 | 45 | func applicationWillTerminate(application: UIApplication) { 46 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 47 | } 48 | 49 | 50 | } 51 | 52 | -------------------------------------------------------------------------------- /YTFloatingPlayer/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "83.5x83.5", 66 | "scale" : "2x" 67 | } 68 | ], 69 | "info" : { 70 | "version" : 1, 71 | "author" : "xcode" 72 | } 73 | } -------------------------------------------------------------------------------- /YTFloatingPlayer/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /YTFloatingPlayer/Assets.xcassets/bigBunny.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "bigBunny.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /YTFloatingPlayer/Assets.xcassets/bigBunny.imageset/bigBunny.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamanap/SwiftYouTubeFloatingPlayer/c4d48695d9f15cdf8adfcfeef10e687252c711d8/YTFloatingPlayer/Assets.xcassets/bigBunny.imageset/bigBunny.jpg -------------------------------------------------------------------------------- /YTFloatingPlayer/Assets.xcassets/legoToy.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "legoToy.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /YTFloatingPlayer/Assets.xcassets/legoToy.imageset/legoToy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamanap/SwiftYouTubeFloatingPlayer/c4d48695d9f15cdf8adfcfeef10e687252c711d8/YTFloatingPlayer/Assets.xcassets/legoToy.imageset/legoToy.jpg -------------------------------------------------------------------------------- /YTFloatingPlayer/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /YTFloatingPlayer/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /YTFloatingPlayer/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | UILaunchStoryboardName 31 | LaunchScreen 32 | UIMainStoryboardFile 33 | Main 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UISupportedInterfaceOrientations~ipad 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationPortraitUpsideDown 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /YTFloatingPlayer/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 iTSangar 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /YTFloatingPlayer/NextViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NextViewController.swift 3 | // YouTubeFloatingPlayer 4 | // 5 | // Created by Ana Paula on 6/9/16. 6 | // Copyright © 2016 Ana Paula. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class NextViewController: UIViewController { 12 | 13 | @IBAction func removePlayer(sender: AnyObject) { 14 | YTFPlayer.finishYTFView(true) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /YTFloatingPlayer/Video.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Song.swift 3 | // YouTubeFloatingPlayer 4 | // 5 | // Created by Ana Paula on 6/8/16. 6 | // Copyright © 2016 Ana Paula. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class Video { 12 | var name: String 13 | var artist: String 14 | var url: NSURL 15 | 16 | init(name: String, artist: String, url: NSURL) { 17 | self.name = name 18 | self.artist = artist 19 | self.url = url 20 | } 21 | } -------------------------------------------------------------------------------- /YTFloatingPlayer/VideoCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VideoCell.swift 3 | // YouTubeFloatingPlayer 4 | // 5 | // Created by Ana Paula on 6/8/16. 6 | // Copyright © 2016 Ana Paula. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class VideoCell: UITableViewCell { 12 | @IBOutlet weak var imageThumbnail: UIImageView! 13 | @IBOutlet weak var labelArtist: UILabel! 14 | @IBOutlet weak var labelTitle: UILabel! 15 | } -------------------------------------------------------------------------------- /YTFloatingPlayer/VideoCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /YTFloatingPlayer/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // YTDraggablePlayer 4 | // 5 | // Created by Ana Paula on 5/19/16. 6 | // Copyright © 2016 Ana Paula. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | let videos = [Video.init(name: "Big Bunny", artist: "Google", url: NSURL(string: "http://clips.vorwaerts-gmbh.de/VfE_html5.mp4")!), 14 | Video.init(name: "Robo Toy", artist: "Google", url: NSURL(string: "http://techslides.com/demos/sample-videos/small.mp4")!), 15 | Video.init(name: "Big Bunny", artist: "Google", url: NSURL(string: "http://clips.vorwaerts-gmbh.de/VfE_html5.mp4")!), 16 | Video.init(name: "Robo Toy", artist: "Google", url: NSURL(string: "http://techslides.com/demos/sample-videos/small.mp4")!), 17 | Video.init(name: "Big Bunny", artist: "Google", url: NSURL(string: "http://clips.vorwaerts-gmbh.de/VfE_html5.mp4")!), 18 | Video.init(name: "Robo Toy", artist: "Google", url: NSURL(string: "http://techslides.com/demos/sample-videos/small.mp4")!), 19 | Video.init(name: "Big Bunny", artist: "Google", url: NSURL(string: "http://clips.vorwaerts-gmbh.de/VfE_html5.mp4")!), 20 | Video.init(name: "Robo Toy", artist: "Google", url: NSURL(string: "http://techslides.com/demos/sample-videos/small.mp4")!)] 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | // Do any additional setup after loading the view, typically from a nib. 25 | } 26 | 27 | override func didReceiveMemoryWarning() { 28 | super.didReceiveMemoryWarning() 29 | // Dispose of any resources that can be recreated. 30 | } 31 | 32 | override func prefersStatusBarHidden() -> Bool { 33 | return true 34 | } 35 | 36 | 37 | @IBAction func showPlayer(sender: AnyObject) { 38 | var urls: [NSURL] = [] 39 | for video in videos { 40 | urls.append(video.url) 41 | } 42 | YTFPlayer.initYTF(urls, tableCellNibName: "VideoCell", delegate: self, dataSource: self) 43 | YTFPlayer.showYTFView(self) 44 | } 45 | 46 | } 47 | 48 | //MARK: YTFTableView 49 | 50 | extension ViewController: UITableViewDataSource { 51 | func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 52 | return videos.count 53 | } 54 | 55 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell 56 | { 57 | let cell = tableView.dequeueReusableCellWithIdentifier("VideoCell", forIndexPath: indexPath) as! VideoCell 58 | return cell 59 | } 60 | } 61 | 62 | extension ViewController: UITableViewDelegate { 63 | func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) { 64 | let cell = cell as! VideoCell 65 | cell.labelArtist.text = videos[indexPath.row].artist 66 | cell.labelTitle.text = videos[indexPath.row].name 67 | if (indexPath.row % 2 == 0) { 68 | cell.imageThumbnail.image = UIImage(named: "bigBunny") 69 | } else { 70 | cell.imageThumbnail.image = UIImage(named: "legoToy") 71 | } 72 | } 73 | 74 | func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { 75 | YTFPlayer.playIndex(indexPath.row) 76 | } 77 | } -------------------------------------------------------------------------------- /YTFloatingPlayer/YTFloatingPlayer/Classes/CustomProgressSlider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomSlider.swift 3 | // YTDraggablePlayer 4 | // 5 | // Created by Ana Paula on 5/25/16. 6 | // Copyright © 2016 Ana Paula. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CustomSlider: UISlider { 12 | 13 | override init (frame : CGRect) { 14 | super.init(frame : frame) 15 | } 16 | 17 | convenience init () { 18 | self.init(frame:CGRect.zero) 19 | setup() 20 | } 21 | 22 | required init?(coder aDecoder: NSCoder) { 23 | super.init(coder: aDecoder) 24 | setup() 25 | } 26 | 27 | override func drawRect(rect: CGRect) { 28 | super.drawRect(rect) 29 | } 30 | 31 | func setup() { 32 | self.maximumTrackTintColor = UIColor.clearColor() 33 | self.continuous = true 34 | } 35 | 36 | override func trackRectForBounds(bounds: CGRect) -> CGRect { 37 | var newBounds = super.trackRectForBounds(bounds) 38 | newBounds.size.height = 6 39 | return newBounds 40 | } 41 | } 42 | 43 | class CustomProgress: UIProgressView { 44 | 45 | override init (frame : CGRect) { 46 | super.init(frame : frame) 47 | } 48 | 49 | convenience init () { 50 | self.init(frame:CGRect.zero) 51 | setup() 52 | } 53 | 54 | required init?(coder aDecoder: NSCoder) { 55 | super.init(coder: aDecoder) 56 | setup() 57 | } 58 | 59 | func setup() { 60 | self.layer.cornerRadius = 2.0 61 | self.clipsToBounds = true 62 | self.transform = CGAffineTransformScale(self.transform, 1, 3) 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /YTFloatingPlayer/YTFloatingPlayer/Classes/PlayerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlayerVideoViewController.swift 3 | // PlayerVideo 4 | // 5 | // Created by David Alejandro on 2/17/16. 6 | // Copyright © 2016 David Alejandro. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation.AVPlayer 11 | 12 | private extension Selector { 13 | static let playerItemDidPlayToEndTime = #selector(PlayerView.playerItemDidPlayToEndTime(_:)) 14 | } 15 | 16 | public extension PVTimeRange{ 17 | static let zero = kCMTimeRangeZero 18 | } 19 | 20 | public typealias PVStatus = AVPlayerStatus 21 | public typealias PVItemStatus = AVPlayerItemStatus 22 | public typealias PVTimeRange = CMTimeRange 23 | public typealias PVPlayer = AVQueuePlayer 24 | public typealias PVPlayerItem = AVPlayerItem 25 | 26 | public protocol PlayerViewDelegate: class { 27 | func playerVideo(player: PlayerView, statusPlayer: PVStatus, error: NSError?) 28 | func playerVideo(player: PlayerView, statusItemPlayer: PVItemStatus, error: NSError?) 29 | func playerVideo(player: PlayerView, loadedTimeRanges: [PVTimeRange]) 30 | func playerVideo(player: PlayerView, duration: Double) 31 | func playerVideo(player: PlayerView, currentTime: Double) 32 | func playerVideo(player: PlayerView, rate: Float) 33 | func playerVideo(playerFinished player: PlayerView) 34 | } 35 | 36 | public extension PlayerViewDelegate { 37 | 38 | func playerVideo(player: PlayerView, statusPlayer: PVStatus, error: NSError?) { 39 | 40 | } 41 | func playerVideo(player: PlayerView, statusItemPlayer: PVItemStatus, error: NSError?) { 42 | 43 | } 44 | func playerVideo(player: PlayerView, loadedTimeRanges: [PVTimeRange]) { 45 | 46 | } 47 | func playerVideo(player: PlayerView, duration: Double) { 48 | 49 | } 50 | func playerVideo(player: PlayerView, currentTime: Double) { 51 | 52 | } 53 | func playerVideo(player: PlayerView, rate: Float) { 54 | 55 | } 56 | func playerVideo(playerFinished player: PlayerView) { 57 | 58 | } 59 | } 60 | 61 | public enum PlayerViewFillMode { 62 | case ResizeAspect 63 | case ResizeAspectFill 64 | case Resize 65 | 66 | init?(videoGravity: String){ 67 | switch videoGravity { 68 | case AVLayerVideoGravityResizeAspect: 69 | self = .ResizeAspect 70 | case AVLayerVideoGravityResizeAspectFill: 71 | self = .ResizeAspectFill 72 | case AVLayerVideoGravityResize: 73 | self = .Resize 74 | default: 75 | return nil 76 | } 77 | } 78 | 79 | var AVLayerVideoGravity:String { 80 | get { 81 | switch self { 82 | case .ResizeAspect: 83 | return AVLayerVideoGravityResizeAspect 84 | case .ResizeAspectFill: 85 | return AVLayerVideoGravityResizeAspectFill 86 | case .Resize: 87 | return AVLayerVideoGravityResize 88 | } 89 | } 90 | } 91 | } 92 | 93 | private extension CMTime { 94 | static var zero:CMTime { return kCMTimeZero } 95 | } 96 | /// A simple `UIView` subclass that is backed by an `AVPlayerLayer` layer. 97 | @objc public class PlayerView: UIView { 98 | 99 | 100 | 101 | private var playerLayer: AVPlayerLayer { 102 | return layer as! AVPlayerLayer 103 | } 104 | 105 | private var timeObserverToken: AnyObject? 106 | private weak var lastPlayerTimeObserve: PVPlayer? 107 | 108 | private var urlsQueue: [NSURL]? 109 | //MARK: - Public Variables 110 | public weak var delegate: PlayerViewDelegate? 111 | 112 | public var loopVideosQueue = false 113 | public var player: PVPlayer? { 114 | get { 115 | return playerLayer.player as? PVPlayer 116 | } 117 | 118 | set { 119 | playerLayer.player = newValue 120 | } 121 | } 122 | 123 | 124 | public var fillMode: PlayerViewFillMode! { 125 | didSet { 126 | playerLayer.videoGravity = fillMode.AVLayerVideoGravity 127 | } 128 | } 129 | 130 | 131 | public var currentTime: Double { 132 | get { 133 | guard let player = player else { 134 | return 0 135 | } 136 | return CMTimeGetSeconds(player.currentTime()) 137 | } 138 | set { 139 | guard let timescale = player?.currentItem?.duration.timescale else { 140 | return 141 | } 142 | let newTime = CMTimeMakeWithSeconds(newValue, timescale) 143 | player!.seekToTime(newTime,toleranceBefore: CMTime.zero,toleranceAfter: CMTime.zero) 144 | } 145 | } 146 | public var interval = CMTimeMake(1, 60) { 147 | didSet { 148 | if rate != 0 { 149 | addCurrentTimeObserver() 150 | } 151 | } 152 | } 153 | 154 | public var rate: Float { 155 | get { 156 | guard let player = player else { 157 | return 0 158 | } 159 | return player.rate 160 | } 161 | set { 162 | if newValue == 0 { 163 | removeCurrentTimeObserver() 164 | } else if rate == 0 && newValue != 0 { 165 | addCurrentTimeObserver() 166 | } 167 | 168 | player?.rate = newValue 169 | } 170 | } 171 | // MARK: private Functions 172 | 173 | 174 | /** 175 | Add all observers for a PVPlayer 176 | */ 177 | func addObserversPlayer(avPlayer: PVPlayer) { 178 | avPlayer.addObserver(self, forKeyPath: "status", options: [.New], context: &statusContext) 179 | avPlayer.addObserver(self, forKeyPath: "rate", options: [.New], context: &rateContext) 180 | avPlayer.addObserver(self, forKeyPath: "currentItem", options: [.Old,.New], context: &playerItemContext) 181 | } 182 | 183 | /** 184 | Remove all observers for a PVPlayer 185 | */ 186 | func removeObserversPlayer(avPlayer: PVPlayer) { 187 | 188 | avPlayer.removeObserver(self, forKeyPath: "status", context: &statusContext) 189 | avPlayer.removeObserver(self, forKeyPath: "rate", context: &rateContext) 190 | avPlayer.removeObserver(self, forKeyPath: "currentItem", context: &playerItemContext) 191 | 192 | if let timeObserverToken = timeObserverToken { 193 | avPlayer.removeTimeObserver(timeObserverToken) 194 | } 195 | } 196 | func addObserversVideoItem(playerItem: PVPlayerItem) { 197 | playerItem.addObserver(self, forKeyPath: "loadedTimeRanges", options: [], context: &loadedContext) 198 | playerItem.addObserver(self, forKeyPath: "duration", options: [], context: &durationContext) 199 | playerItem.addObserver(self, forKeyPath: "status", options: [], context: &statusItemContext) 200 | NSNotificationCenter.defaultCenter().addObserver(self, selector: .playerItemDidPlayToEndTime, name: AVPlayerItemDidPlayToEndTimeNotification, object: playerItem) 201 | } 202 | func removeObserversVideoItem(playerItem: PVPlayerItem) { 203 | 204 | playerItem.removeObserver(self, forKeyPath: "loadedTimeRanges", context: &loadedContext) 205 | playerItem.removeObserver(self, forKeyPath: "duration", context: &durationContext) 206 | playerItem.removeObserver(self, forKeyPath: "status", context: &statusItemContext) 207 | NSNotificationCenter.defaultCenter().removeObserver(self, name: AVPlayerItemDidPlayToEndTimeNotification, object: playerItem) 208 | } 209 | 210 | func removeCurrentTimeObserver() { 211 | 212 | if let timeObserverToken = self.timeObserverToken { 213 | lastPlayerTimeObserve?.removeTimeObserver(timeObserverToken) 214 | } 215 | timeObserverToken = nil 216 | } 217 | 218 | func addCurrentTimeObserver() { 219 | removeCurrentTimeObserver() 220 | 221 | lastPlayerTimeObserve = player 222 | self.timeObserverToken = player?.addPeriodicTimeObserverForInterval(interval, queue: dispatch_get_main_queue()) { [weak self] time-> Void in 223 | if let mySelf = self { 224 | self?.delegate?.playerVideo(mySelf, currentTime: mySelf.currentTime) 225 | } 226 | } 227 | } 228 | 229 | func playerItemDidPlayToEndTime(aNotification: NSNotification) { 230 | //notification of player to stop 231 | let item = aNotification.object as! PVPlayerItem 232 | print("Itens \(player?.items().count)") 233 | if loopVideosQueue && player?.items().count == 1, 234 | let urlsQueue = urlsQueue { 235 | 236 | self.addVideosOnQueue(urls: urlsQueue, afterItem: item) 237 | } 238 | 239 | self.delegate?.playerVideo(playerFinished: self) 240 | } 241 | // MARK: public Functions 242 | 243 | public func play() { 244 | rate = 1 245 | //player?.play() 246 | } 247 | 248 | public func pause() { 249 | rate = 0 250 | //player?.pause() 251 | } 252 | 253 | 254 | public func stop() { 255 | currentTime = 0 256 | pause() 257 | } 258 | public func next() { 259 | player?.advanceToNextItem() 260 | } 261 | 262 | public func resetPlayer() { 263 | urlsQueue = nil 264 | guard let player = player else { 265 | return 266 | } 267 | player.pause() 268 | 269 | removeObserversPlayer(player) 270 | 271 | if let playerItem = player.currentItem { 272 | removeObserversVideoItem(playerItem) 273 | } 274 | self.player = nil 275 | } 276 | 277 | public func availableDuration() -> PVTimeRange { 278 | let range = self.player?.currentItem?.loadedTimeRanges.first 279 | if let range = range { 280 | return range.CMTimeRangeValue 281 | } 282 | return PVTimeRange.zero 283 | } 284 | 285 | public func screenshot() throws -> UIImage? { 286 | guard let time = player?.currentItem?.currentTime() else { 287 | return nil 288 | } 289 | 290 | return try screenshotCMTime(time)?.0 291 | } 292 | 293 | public func screenshotTime(time: Double? = nil) throws -> (UIImage, photoTime: CMTime)?{ 294 | guard let timescale = player?.currentItem?.duration.timescale else { 295 | return nil 296 | } 297 | 298 | let timeToPicture: CMTime 299 | if let time = time { 300 | 301 | timeToPicture = CMTimeMakeWithSeconds(time, timescale) 302 | } else if let time = player?.currentItem?.currentTime() { 303 | timeToPicture = time 304 | } else { 305 | return nil 306 | } 307 | return try screenshotCMTime(timeToPicture) 308 | } 309 | 310 | private func screenshotCMTime(cmTime: CMTime) throws -> (UIImage,photoTime: CMTime)? { 311 | guard let player = player , let asset = player.currentItem?.asset else { 312 | return nil 313 | } 314 | let imageGenerator = AVAssetImageGenerator(asset: asset) 315 | 316 | var timePicture = CMTime.zero 317 | imageGenerator.appliesPreferredTrackTransform = true 318 | imageGenerator.requestedTimeToleranceAfter = CMTime.zero 319 | imageGenerator.requestedTimeToleranceBefore = CMTime.zero 320 | 321 | let ref = try imageGenerator.copyCGImageAtTime(cmTime, actualTime: &timePicture) 322 | let viewImage: UIImage = UIImage(CGImage: ref) 323 | return (viewImage, timePicture) 324 | } 325 | public var url: NSURL? { 326 | didSet { 327 | guard let url = url else { 328 | urls = nil 329 | return 330 | } 331 | urls = [url] 332 | } 333 | } 334 | 335 | public var urls: [NSURL]? { 336 | willSet(newUrls) { 337 | 338 | resetPlayer() 339 | guard let urls = newUrls else { 340 | return 341 | } 342 | //reset before put another URL 343 | 344 | urlsQueue = urls 345 | let playerItems = urls.map { (url) -> PVPlayerItem in 346 | return PVPlayerItem(URL: url) 347 | } 348 | 349 | let avPlayer = PVPlayer(items: playerItems) 350 | self.player = avPlayer 351 | 352 | avPlayer.actionAtItemEnd = .Pause 353 | 354 | 355 | let playerItem = avPlayer.currentItem! 356 | 357 | addObserversPlayer(avPlayer) 358 | addObserversVideoItem(playerItem) 359 | 360 | // Do any additional setup after loading the view, typically from a nib. 361 | } 362 | } 363 | public func addVideosOnQueue(urls urls: [NSURL], afterItem: PVPlayerItem? = nil) { 364 | //on last item on player 365 | let item = afterItem ?? player?.items().last 366 | 367 | urlsQueue?.appendContentsOf(urls) 368 | //for each url found 369 | urls.forEach({ (url) in 370 | 371 | //create a video item 372 | let itemNew = PVPlayerItem(URL: url) 373 | 374 | //and insert the item on the player 375 | player?.insertItem(itemNew, afterItem: item) 376 | }) 377 | 378 | } 379 | public func addVideosOnQueue(urls: NSURL..., afterItem: PVPlayerItem? = nil) { 380 | return addVideosOnQueue(urls: urls,afterItem: afterItem) 381 | } 382 | // MARK: public object lifecycle view 383 | 384 | override public class func layerClass() -> AnyClass { 385 | return AVPlayerLayer.self 386 | } 387 | 388 | public convenience init() { 389 | self.init(frame: CGRect.zero) 390 | 391 | self.fillMode = .ResizeAspect 392 | } 393 | 394 | public override init(frame: CGRect) { 395 | super.init(frame: frame) 396 | 397 | self.fillMode = .ResizeAspect 398 | } 399 | 400 | required public init?(coder aDecoder: NSCoder) { 401 | super.init(coder: aDecoder) 402 | 403 | self.fillMode = .ResizeAspect 404 | } 405 | 406 | deinit { 407 | delegate = nil 408 | resetPlayer() 409 | } 410 | // MARK: private variables for context KVO 411 | 412 | private var statusContext = true 413 | private var statusItemContext = true 414 | private var loadedContext = true 415 | private var durationContext = true 416 | private var currentTimeContext = true 417 | private var rateContext = true 418 | private var playerItemContext = true 419 | 420 | 421 | 422 | public override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer) { 423 | 424 | //print("CHANGE",keyPath) 425 | 426 | 427 | if context == &statusContext { 428 | 429 | guard let avPlayer = player else { 430 | super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context) 431 | return 432 | } 433 | self.delegate?.playerVideo(self, statusPlayer: avPlayer.status, error: avPlayer.error) 434 | 435 | 436 | } else if context == &loadedContext { 437 | 438 | let playerItem = player?.currentItem 439 | 440 | guard let times = playerItem?.loadedTimeRanges else { 441 | return 442 | } 443 | 444 | let values = times.map({ $0.CMTimeRangeValue}) 445 | self.delegate?.playerVideo(self, loadedTimeRanges: values) 446 | 447 | 448 | } else if context == &durationContext{ 449 | 450 | if let currentItem = player?.currentItem { 451 | self.delegate?.playerVideo(self, duration: currentItem.duration.seconds) 452 | 453 | } 454 | 455 | } else if context == &statusItemContext{ 456 | //status of item has changed 457 | if let currentItem = player?.currentItem { 458 | 459 | self.delegate?.playerVideo(self, statusItemPlayer: currentItem.status, error: currentItem.error) 460 | } 461 | 462 | } else if context == &rateContext{ 463 | guard let newRateNumber = (change?[NSKeyValueChangeNewKey] as? NSNumber) else{ 464 | return 465 | } 466 | let newRate = newRateNumber.floatValue 467 | if newRate == 0 { 468 | removeCurrentTimeObserver() 469 | } else { 470 | addCurrentTimeObserver() 471 | } 472 | 473 | self.delegate?.playerVideo(self, rate: newRate) 474 | 475 | } else if context == &playerItemContext{ 476 | guard let oldItem = (change?[NSKeyValueChangeOldKey] as? PVPlayerItem) else{ 477 | return 478 | } 479 | removeObserversVideoItem(oldItem) 480 | guard let newItem = (change?[NSKeyValueChangeNewKey] as? PVPlayerItem) else{ 481 | return 482 | } 483 | addObserversVideoItem(newItem) 484 | } else { 485 | super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context) 486 | } 487 | } 488 | } -------------------------------------------------------------------------------- /YTFloatingPlayer/YTFloatingPlayer/Classes/YTFAnimation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YTDAnimation.swift 3 | // YTDraggablePlayer 4 | // 5 | // Created by Ana Paula on 6/6/16. 6 | // Copyright © 2016 Ana Paula. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | enum UIPanGestureRecognizerDirection { 12 | case Undefined 13 | case Up 14 | case Down 15 | case Left 16 | case Right 17 | } 18 | 19 | extension YTFViewController { 20 | 21 | //MARK: Player Controls Animations 22 | 23 | func showPlayerControls() { 24 | if (!isMinimized) { 25 | UIView.animateWithDuration(0.6, delay: 0, options: UIViewAnimationOptions.CurveEaseOut, animations: { 26 | self.backPlayerControlsView.alpha = 0.55 27 | self.playerControlsView.alpha = 1.0 28 | self.minimizeButton.alpha = 1.0 29 | 30 | }, completion: nil) 31 | hideTimer?.invalidate() 32 | hideTimer = nil 33 | hideTimer = NSTimer.scheduledTimerWithTimeInterval(4.0, target: self, selector: #selector(YTFViewController.hidePlayerControls(_:)), userInfo: 1.0, repeats: false) 34 | } 35 | } 36 | 37 | func hidePlayerControls(dontAnimate: Bool) { 38 | if (dontAnimate) { 39 | self.backPlayerControlsView.alpha = 0.0 40 | self.playerControlsView.alpha = 0.0 41 | } else { 42 | if (isPlaying) { 43 | UIView.animateWithDuration(0.6, delay: 0, options: UIViewAnimationOptions.CurveEaseIn, animations: { 44 | self.backPlayerControlsView.alpha = 0.0 45 | self.playerControlsView.alpha = 0.0 46 | self.minimizeButton.alpha = 0.0 47 | 48 | }, completion: nil) 49 | } 50 | } 51 | } 52 | 53 | //MARK: Video Animations 54 | 55 | func setPlayerToFullscreen() { 56 | self.hidePlayerControls(true) 57 | 58 | UIView.animateWithDuration(0.4, delay: 0.0, options: .CurveEaseInOut, animations: { 59 | self.minimizeButton.hidden = true 60 | self.playerView.transform = CGAffineTransformMakeRotation(CGFloat(M_PI_2)) 61 | 62 | self.playerView.frame = CGRectMake(self.initialFirstViewFrame!.origin.x, self.initialFirstViewFrame!.origin.x, self.initialFirstViewFrame!.size.width, self.initialFirstViewFrame!.size.height) 63 | 64 | }, completion: { finished in 65 | self.isFullscreen = true 66 | self.fullscreen.setImage(UIImage(named: "unfullscreen"), forState: UIControlState.Normal) 67 | 68 | let originY = self.initialFirstViewFrame!.size.width - self.playerControlsFrame!.height 69 | 70 | self.backPlayerControlsView.frame.origin.x = self.initialFirstViewFrame!.origin.x 71 | self.backPlayerControlsView.frame.origin.y = originY 72 | self.backPlayerControlsView.frame.size.width = self.initialFirstViewFrame!.size.height 73 | 74 | self.playerControlsView.frame.origin.x = self.initialFirstViewFrame!.origin.x 75 | self.playerControlsView.frame.origin.y = originY 76 | self.playerControlsView.frame.size.width = self.initialFirstViewFrame!.size.height 77 | 78 | self.showPlayerControls() 79 | }) 80 | } 81 | 82 | func setPlayerToNormalScreen() { 83 | UIView.animateWithDuration(0.4, delay: 0.0, options: .CurveEaseInOut, animations: { 84 | self.playerView.transform = CGAffineTransformMakeRotation(0) 85 | 86 | self.playerView.frame = CGRectMake(self.playerViewFrame!.origin.x, self.playerViewFrame!.origin.x, self.playerViewFrame!.size.width, self.playerViewFrame!.size.height) 87 | 88 | let originY = self.playerViewFrame!.size.height - self.playerControlsFrame!.height 89 | self.backPlayerControlsView.frame.origin.x = self.initialFirstViewFrame!.origin.x 90 | self.backPlayerControlsView.frame.origin.y = originY 91 | self.backPlayerControlsView.frame.size.width = self.playerViewFrame!.size.width 92 | 93 | self.playerControlsView.frame.origin.x = self.initialFirstViewFrame!.origin.x 94 | self.playerControlsView.frame.origin.y = originY 95 | self.playerControlsView.frame.size.width = self.playerViewFrame!.size.width 96 | 97 | }, completion: { finished in 98 | self.isFullscreen = false 99 | self.minimizeButton.hidden = false 100 | self.fullscreen.setImage(UIImage(named: "fullscreen"), forState: UIControlState.Normal) 101 | }) 102 | } 103 | 104 | func panAction(recognizer: UIPanGestureRecognizer) { 105 | if (!isFullscreen) { 106 | let yPlayerLocation = recognizer.locationInView(self.view?.window).y 107 | 108 | switch recognizer.state { 109 | case .Began: 110 | onRecognizerStateBegan(yPlayerLocation, recognizer: recognizer) 111 | break 112 | case .Changed: 113 | onRecognizerStateChanged(yPlayerLocation, recognizer: recognizer) 114 | break 115 | default: 116 | onRecognizerStateEnded(yPlayerLocation, recognizer: recognizer) 117 | } 118 | } 119 | } 120 | 121 | func onRecognizerStateBegan(yPlayerLocation: CGFloat, recognizer: UIPanGestureRecognizer) { 122 | tableViewContainer.backgroundColor = UIColor.whiteColor() 123 | hidePlayerControls(true) 124 | panGestureDirection = UIPanGestureRecognizerDirection.Undefined 125 | 126 | let velocity = recognizer.velocityInView(recognizer.view) 127 | detectPanDirection(velocity) 128 | 129 | touchPositionStartY = recognizer.locationInView(self.playerView).y 130 | touchPositionStartX = recognizer.locationInView(self.playerView).x 131 | 132 | } 133 | 134 | func onRecognizerStateChanged(yPlayerLocation: CGFloat, recognizer: UIPanGestureRecognizer) { 135 | if (panGestureDirection == UIPanGestureRecognizerDirection.Down || 136 | panGestureDirection == UIPanGestureRecognizerDirection.Up) { 137 | let trueOffset = yPlayerLocation - touchPositionStartY! 138 | let xOffset = trueOffset * 0.35 139 | adjustViewOnVerticalPan(yPlayerLocation, trueOffset: trueOffset, xOffset: xOffset, recognizer: recognizer) 140 | 141 | } else { 142 | adjustViewOnHorizontalPan(recognizer) 143 | } 144 | } 145 | 146 | func onRecognizerStateEnded(yPlayerLocation: CGFloat, recognizer: UIPanGestureRecognizer) { 147 | if (panGestureDirection == UIPanGestureRecognizerDirection.Down || 148 | panGestureDirection == UIPanGestureRecognizerDirection.Up) { 149 | if (self.view.frame.origin.y < 0) { 150 | expandViews() 151 | recognizer.setTranslation(CGPointZero, inView: recognizer.view) 152 | return 153 | 154 | } else { 155 | if (self.view.frame.origin.y > (initialFirstViewFrame!.size.height / 2)) { 156 | minimizeViews() 157 | recognizer.setTranslation(CGPointZero, inView: recognizer.view) 158 | return 159 | } else { 160 | expandViews() 161 | recognizer.setTranslation(CGPointZero, inView: recognizer.view) 162 | } 163 | } 164 | 165 | } else if (panGestureDirection == UIPanGestureRecognizerDirection.Left) { 166 | if (tableViewContainer.alpha <= 0) { 167 | if (self.view?.frame.origin.x < 0) { 168 | removeViews() 169 | 170 | } else { 171 | animateViewToRightOrLeft(recognizer) 172 | 173 | } 174 | } 175 | 176 | } else { 177 | if (tableViewContainer.alpha <= 0) { 178 | if (self.view?.frame.origin.x > initialFirstViewFrame!.size.width - 50) { 179 | removeViews() 180 | 181 | } else { 182 | animateViewToRightOrLeft(recognizer) 183 | 184 | } 185 | 186 | } 187 | 188 | } 189 | } 190 | 191 | func detectPanDirection(velocity: CGPoint) { 192 | minimizeButton.hidden = true 193 | let isVerticalGesture = fabs(velocity.y) > fabs(velocity.x) 194 | 195 | if (isVerticalGesture) { 196 | 197 | if (velocity.y > 0) { 198 | panGestureDirection = UIPanGestureRecognizerDirection.Down 199 | } else { 200 | panGestureDirection = UIPanGestureRecognizerDirection.Up 201 | } 202 | 203 | } else { 204 | 205 | if (velocity.x > 0) { 206 | panGestureDirection = UIPanGestureRecognizerDirection.Right 207 | } else { 208 | panGestureDirection = UIPanGestureRecognizerDirection.Left 209 | } 210 | } 211 | } 212 | 213 | func adjustViewOnVerticalPan(yPlayerLocation: CGFloat, trueOffset: CGFloat, xOffset: CGFloat, recognizer: UIPanGestureRecognizer) { 214 | 215 | if (Float(trueOffset) >= (restrictTrueOffset! + 60) || 216 | Float(xOffset) >= (restrictOffset! + 60)) { 217 | 218 | let trueOffset = initialFirstViewFrame!.size.height - 100 219 | let xOffset = initialFirstViewFrame!.size.width - 160 220 | 221 | //Use this offset to adjust the position of your view accordingly 222 | viewMinimizedFrame?.origin.y = trueOffset 223 | viewMinimizedFrame?.origin.x = xOffset - 6 224 | viewMinimizedFrame?.size.width = initialFirstViewFrame!.size.width 225 | 226 | playerViewMinimizedFrame!.size.width = self.view.bounds.size.width - xOffset 227 | playerViewMinimizedFrame!.size.height = 200 - xOffset * 0.5 228 | 229 | UIView.animateWithDuration(0.05, delay: 0.0, options: .CurveEaseInOut, animations: { 230 | self.playerView.frame = self.playerViewMinimizedFrame! 231 | self.view.frame = self.viewMinimizedFrame! 232 | self.tableViewContainer.alpha = 0.0 233 | }, completion: { finished in 234 | self.isMinimized = true 235 | }) 236 | recognizer.setTranslation(CGPointZero, inView: recognizer.view) 237 | 238 | } else { 239 | //Use this offset to adjust the position of your view accordingly 240 | viewMinimizedFrame?.origin.y = trueOffset 241 | viewMinimizedFrame?.origin.x = xOffset - 6 242 | viewMinimizedFrame?.size.width = initialFirstViewFrame!.size.width 243 | 244 | playerViewMinimizedFrame!.size.width = self.view.bounds.size.width - xOffset 245 | playerViewMinimizedFrame!.size.height = 200 - xOffset * 0.5 246 | 247 | let restrictY = initialFirstViewFrame!.size.height - playerView!.frame.size.height - 10 248 | 249 | if (self.tableView.frame.origin.y < restrictY && self.tableView.frame.origin.y > 0) { 250 | UIView.animateWithDuration(0.09, delay: 0.0, options: .CurveEaseInOut, animations: { 251 | self.playerView.frame = self.playerViewMinimizedFrame! 252 | self.view.frame = self.viewMinimizedFrame! 253 | 254 | let percentage = (yPlayerLocation + 200) / self.initialFirstViewFrame!.size.height 255 | self.tableViewContainer.alpha = 1.0 - percentage 256 | self.transparentView!.alpha = 1.0 - percentage 257 | 258 | }, completion: { finished in 259 | if (self.panGestureDirection == UIPanGestureRecognizerDirection.Down) { 260 | self.onView?.bringSubviewToFront(self.view) 261 | } 262 | }) 263 | 264 | } else if (viewMinimizedFrame!.origin.y < restrictY && viewMinimizedFrame!.origin.y > 0) { 265 | UIView.animateWithDuration(0.09, delay: 0.0, options: .CurveEaseInOut, animations: { 266 | self.playerView.frame = self.playerViewMinimizedFrame! 267 | self.view.frame = self.viewMinimizedFrame! 268 | 269 | }, completion: nil) 270 | } 271 | 272 | recognizer.setTranslation(CGPointZero, inView: recognizer.view) 273 | } 274 | } 275 | 276 | func adjustViewOnHorizontalPan(recognizer: UIPanGestureRecognizer) { 277 | let x = self.view.frame.origin.x 278 | 279 | if (panGestureDirection == UIPanGestureRecognizerDirection.Left || 280 | panGestureDirection == UIPanGestureRecognizerDirection.Right) { 281 | if (self.tableViewContainer.alpha <= 0) { 282 | let velocity = recognizer.velocityInView(recognizer.view) 283 | 284 | let isVerticalGesture = fabs(velocity.y) > fabs(velocity.x) 285 | 286 | let translation = recognizer.translationInView(self.view) 287 | self.view?.center = CGPointMake(self.view!.center.x + translation.x, self.view!.center.y) 288 | 289 | if (!isVerticalGesture) { 290 | recognizer.view?.alpha = detectHorizontalPanRecognizerViewAlpha(x, velocity: velocity, recognizer: recognizer) 291 | } 292 | recognizer.setTranslation(CGPointZero, inView: recognizer.view) 293 | } 294 | 295 | } 296 | } 297 | 298 | func detectHorizontalPanRecognizerViewAlpha(x: CGFloat, velocity: CGPoint, recognizer: UIPanGestureRecognizer) -> CGFloat { 299 | let percentage = x / self.initialFirstViewFrame!.size.width 300 | 301 | if (panGestureDirection == UIPanGestureRecognizerDirection.Left) { 302 | return percentage 303 | 304 | } else { 305 | if (velocity.x > 0) { 306 | return 1.0 - percentage 307 | } else { 308 | return percentage 309 | } 310 | } 311 | } 312 | 313 | func animateViewToRightOrLeft(recognizer: UIPanGestureRecognizer) { 314 | UIView.animateWithDuration(0.25, delay: 0.0, options: .CurveEaseInOut, animations: { 315 | self.view.frame = self.viewMinimizedFrame! 316 | self.playerView!.frame = self.playerViewFrame! 317 | self.playerView.frame = CGRectMake(self.playerView!.frame.origin.x, self.playerView!.frame.origin.x, self.playerViewMinimizedFrame!.size.width, self.playerViewMinimizedFrame!.size.height) 318 | self.tableViewContainer!.alpha = 0.0 319 | self.playerView.alpha = 1.0 320 | 321 | }, completion: nil) 322 | 323 | recognizer.setTranslation(CGPointZero, inView: recognizer.view) 324 | 325 | } 326 | 327 | func minimizeViews() { 328 | tableViewContainer.backgroundColor = UIColor.whiteColor() 329 | minimizeButton.hidden = true 330 | hidePlayerControls(true) 331 | let trueOffset = initialFirstViewFrame!.size.height - 100 332 | let xOffset = initialFirstViewFrame!.size.width - 160 333 | 334 | viewMinimizedFrame!.origin.y = trueOffset + 2 335 | viewMinimizedFrame!.origin.x = xOffset - 6 336 | viewMinimizedFrame!.size.width = initialFirstViewFrame!.size.width 337 | 338 | playerViewMinimizedFrame!.size.width = self.view.bounds.size.width - xOffset 339 | playerViewMinimizedFrame!.size.height = playerViewMinimizedFrame!.size.width / (16/9) 340 | 341 | UIView.animateWithDuration(0.5, delay: 0.0, options: .CurveEaseOut, animations: { 342 | self.playerView.frame = self.playerViewMinimizedFrame! 343 | self.view.frame = self.viewMinimizedFrame! 344 | 345 | self.playerView.layer.borderWidth = 1 346 | self.playerView.layer.borderColor = UIColor(red:255/255.0, green:255/255.0, blue:255/255.0, alpha: 0.5).CGColor 347 | 348 | self.tableViewContainer.alpha = 0.0 349 | self.transparentView?.alpha = 0.0 350 | }, completion: { finished in 351 | self.isMinimized = true 352 | if let playerGesture = self.playerTapGesture { 353 | self.playerView.removeGestureRecognizer(playerGesture) 354 | } 355 | self.playerTapGesture = nil 356 | self.playerTapGesture = UITapGestureRecognizer(target: self, action: #selector(YTFViewController.expandViews)) 357 | self.playerView.addGestureRecognizer(self.playerTapGesture!) 358 | 359 | self.view.frame.size.height = self.playerView.frame.height 360 | 361 | UIApplication.sharedApplication().setStatusBarHidden(false, withAnimation: .Fade) 362 | }) 363 | } 364 | 365 | func expandViews() { 366 | UIView.animateWithDuration(0.5, delay: 0.0, options: .CurveEaseIn, animations: { 367 | self.playerView.frame = self.playerViewFrame! 368 | self.view.frame = self.initialFirstViewFrame! 369 | self.playerView.alpha = 1.0 370 | self.tableViewContainer.alpha = 1.0 371 | self.transparentView?.alpha = 1.0 372 | }, completion: { finished in 373 | self.isMinimized = false 374 | self.minimizeButton.hidden = false 375 | self.playerView.removeGestureRecognizer(self.playerTapGesture!) 376 | self.playerTapGesture = nil 377 | self.playerTapGesture = UITapGestureRecognizer(target: self, action: #selector(YTFViewController.showPlayerControls)) 378 | self.playerView.addGestureRecognizer(self.playerTapGesture!) 379 | self.tableViewContainer.backgroundColor = UIColor.blackColor() 380 | self.showPlayerControls() 381 | }) 382 | } 383 | 384 | func finishViewAnimated(animated: Bool) { 385 | if (animated) { 386 | UIView.animateWithDuration(0.5, delay: 0.0, options: .CurveEaseInOut, animations: { 387 | self.view.frame = CGRectMake(0.0, self.view!.frame.origin.y, self.view!.frame.size.width, self.view!.frame.size.height) 388 | self.view.alpha = 0.0 389 | 390 | }, completion: { finished in 391 | self.removeViews() 392 | }) 393 | } else { 394 | removeViews() 395 | } 396 | } 397 | 398 | func removeViews() { 399 | self.view.removeFromSuperview() 400 | self.playerView.resetPlayer() 401 | self.playerView.removeFromSuperview() 402 | self.tableView.removeFromSuperview() 403 | self.tableViewContainer.removeFromSuperview() 404 | self.transparentView?.removeFromSuperview() 405 | self.playerControlsView.removeFromSuperview() 406 | self.backPlayerControlsView.removeFromSuperview() 407 | } 408 | 409 | } -------------------------------------------------------------------------------- /YTFloatingPlayer/YTFloatingPlayer/Classes/YTFPlayer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YTDProtocol.swift 3 | // YTDraggablePlayer 4 | // 5 | // Created by Ana Paula on 6/6/16. 6 | // Copyright © 2016 Ana Paula. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public struct YTFPlayer { 12 | public static func initYTF(url: NSURL, tableCellNibName: String, delegate: UITableViewDelegate, dataSource: UITableViewDataSource) { 13 | if (dragViewController == nil) { 14 | dragViewController = YTFViewController(nibName: "YTFViewController", bundle: nil) 15 | } 16 | dragViewController?.urls = [url] 17 | dragViewController?.delegate = delegate 18 | dragViewController?.dataSource = dataSource 19 | dragViewController?.tableCellNibName = tableCellNibName 20 | } 21 | 22 | public static func initYTF(urls: [NSURL], tableCellNibName: String, delegate: UITableViewDelegate, dataSource: UITableViewDataSource) { 23 | if (dragViewController == nil) { 24 | dragViewController = YTFViewController(nibName: "YTFViewController", bundle: nil) 25 | } 26 | dragViewController?.urls = urls 27 | dragViewController?.delegate = delegate 28 | dragViewController?.dataSource = dataSource 29 | dragViewController?.tableCellNibName = tableCellNibName 30 | } 31 | 32 | public static func showYTFView(viewController: UIViewController) { 33 | if dragViewController!.isOpen == false { 34 | dragViewController!.view.frame = CGRectMake(viewController.view.frame.size.width, viewController.view.frame.size.height, viewController.view.frame.size.width, viewController.view.frame.size.height) 35 | dragViewController!.view.alpha = 0 36 | dragViewController!.view.transform = CGAffineTransformMakeScale(0.2, 0.2) 37 | dragViewController!.onView = viewController.view 38 | 39 | UIApplication.sharedApplication().keyWindow?.addSubview(dragViewController!.view) 40 | 41 | UIView.animateWithDuration(0.5, animations: { 42 | dragViewController!.view.transform = CGAffineTransformMakeScale(1.0, 1.0) 43 | dragViewController!.view.alpha = 1 44 | 45 | dragViewController!.view.frame = CGRectMake(0, 0, UIApplication.sharedApplication().keyWindow!.bounds.width, UIApplication.sharedApplication().keyWindow!.bounds.height) 46 | 47 | dragViewController!.isOpen = true 48 | }) 49 | } 50 | } 51 | 52 | public static func changeURL(url: NSURL) { 53 | dragViewController?.urls = [url] 54 | } 55 | 56 | public static func changeURLs(urls: [NSURL]) { 57 | dragViewController?.urls = urls 58 | } 59 | 60 | public static func changeCurrentIndex(index: Int) { 61 | dragViewController?.currentUrlIndex = index 62 | } 63 | 64 | public static func playIndex(index: Int) { 65 | dragViewController?.currentUrlIndex = index 66 | dragViewController?.playIndex(index) 67 | dragViewController?.hidePlayerControls(true) 68 | } 69 | 70 | public static func getIndex() -> Int { 71 | return dragViewController!.currentUrlIndex 72 | } 73 | 74 | public static func isOpen() -> Bool { 75 | return dragViewController?.isOpen == true ? true : false 76 | } 77 | 78 | public static func getYTFViewController() -> UIViewController? { 79 | return dragViewController 80 | } 81 | 82 | public static func finishYTFView(animated: Bool) { 83 | if(dragViewController != nil) { 84 | dragViewController?.isOpen = false 85 | dragViewController?.finishViewAnimated(animated) 86 | dragViewController = nil 87 | } 88 | } 89 | } 90 | 91 | var dragViewController: YTFViewController? -------------------------------------------------------------------------------- /YTFloatingPlayer/YTFloatingPlayer/Classes/YTFPlayerControls.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YTDPlayerControls.swift 3 | // YTDraggablePlayer 4 | // 5 | // Created by Ana Paula on 5/31/16. 6 | // Copyright © 2016 Ana Paula. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation.AVPlayer 11 | 12 | extension YTFViewController { 13 | 14 | @IBAction func playTouched(sender: AnyObject) { 15 | if (isPlaying) { 16 | playerView.pause() 17 | } else { 18 | playerView.play() 19 | } 20 | } 21 | 22 | @IBAction func fullScreenTouched(sender: AnyObject) { 23 | if (!isFullscreen) { 24 | setPlayerToFullscreen() 25 | } else { 26 | setPlayerToNormalScreen() 27 | } 28 | } 29 | 30 | @IBAction func touchDragInsideSlider(sender: AnyObject) { 31 | dragginSlider = true 32 | } 33 | 34 | 35 | @IBAction func valueChangedSlider(sender: AnyObject) { 36 | playerView.currentTime = Double(slider.value) 37 | playerView.play() 38 | } 39 | 40 | @IBAction func touchUpInsideSlider(sender: AnyObject) { 41 | dragginSlider = false 42 | } 43 | 44 | func playIndex(index: Int) { 45 | print("Index \(index)") 46 | playerView.url = urls![index] 47 | playerView.play() 48 | progressIndicatorView.hidden = false 49 | progressIndicatorView.startAnimating() 50 | } 51 | } 52 | 53 | extension YTFViewController: PlayerViewDelegate { 54 | 55 | func playerVideo(player: PlayerView, statusPlayer: PVStatus, error: NSError?) { 56 | 57 | switch statusPlayer { 58 | case AVPlayerStatus.Unknown: 59 | print("Unknown") 60 | break 61 | case AVPlayerStatus.Failed: 62 | print("Failed") 63 | break 64 | default: 65 | readyToPlay() 66 | } 67 | } 68 | 69 | func readyToPlay() { 70 | progressIndicatorView.stopAnimating() 71 | progressIndicatorView.hidden = true 72 | playerTapGesture = UITapGestureRecognizer(target: self, action: #selector(YTFViewController.showPlayerControls)) 73 | playerView.addGestureRecognizer(playerTapGesture!) 74 | print("Ready to Play") 75 | self.playerView.play() 76 | } 77 | 78 | func playerVideo(player: PlayerView, statusItemPlayer: PVItemStatus, error: NSError?) { 79 | } 80 | 81 | func playerVideo(player: PlayerView, loadedTimeRanges: [PVTimeRange]) { 82 | if (progressIndicatorView.hidden == false) { 83 | progressIndicatorView.stopAnimating() 84 | progressIndicatorView.hidden = true 85 | } 86 | 87 | if let first = loadedTimeRanges.first { 88 | let bufferedSeconds = Float(CMTimeGetSeconds(first.start) + CMTimeGetSeconds(first.duration)) 89 | progress.progress = bufferedSeconds / slider.maximumValue 90 | } 91 | } 92 | 93 | func playerVideo(player: PlayerView, duration: Double) { 94 | let duration = Int(duration) 95 | self.entireTime.text = timeFormatted(duration) 96 | slider.maximumValue = Float(duration) 97 | } 98 | 99 | func playerVideo(player: PlayerView, currentTime: Double) { 100 | let curTime = Int(currentTime) 101 | self.currentTime.text = timeFormatted(curTime) 102 | if (!dragginSlider && (Int(slider.value) != curTime)) { // Change every second 103 | slider.value = Float(currentTime) 104 | } 105 | } 106 | 107 | func playerVideo(player: PlayerView, rate: Float) { 108 | print(rate) 109 | if (rate == 1.0) { 110 | isPlaying = true 111 | play.setImage(UIImage(named: "pause"), forState: UIControlState.Normal) 112 | hideTimer?.invalidate() 113 | showPlayerControls() 114 | } else { 115 | isPlaying = false 116 | play.setImage(UIImage(named: "play"), forState: UIControlState.Normal) 117 | } 118 | } 119 | 120 | func playerVideo(playerFinished player: PlayerView) { 121 | currentUrlIndex += 1 122 | playIndex(currentUrlIndex) 123 | } 124 | 125 | func timeFormatted(totalSeconds: Int) -> String { 126 | 127 | let seconds: Int = totalSeconds % 60 128 | let minutes: Int = (totalSeconds / 60) % 60 129 | return String(format: "%02d:%02d", minutes, seconds) 130 | } 131 | } -------------------------------------------------------------------------------- /YTFloatingPlayer/YTFloatingPlayer/Classes/YTFPopupCloseButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YTDPopupCloseButton.swift 3 | // YTDraggablePlayer 4 | // 5 | // Created by Ana Paula on 5/26/16. 6 | // Copyright © 2016 Ana Paula. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class YTFPopupCloseButton: UIButton { 12 | var effectView: UIVisualEffectView 13 | 14 | override init(frame: CGRect) { 15 | effectView = UIVisualEffectView.init(effect: UIBlurEffect(style: .ExtraLight)) 16 | effectView.userInteractionEnabled = false 17 | super.init(frame: frame) 18 | initViews() 19 | 20 | } 21 | 22 | required init?(coder aDecoder: NSCoder) { 23 | effectView = UIVisualEffectView.init(effect: UIBlurEffect(style: .ExtraLight)) 24 | effectView.userInteractionEnabled = false 25 | super.init(coder: aDecoder) 26 | initViews() 27 | } 28 | 29 | func initViews() { 30 | self.addSubview(effectView) 31 | let highlightEffectView: UIVisualEffectView = UIVisualEffectView.init(effect: UIVibrancyEffect(forBlurEffect: effectView.effect as! UIBlurEffect)) 32 | highlightEffectView.autoresizingMask = [UIViewAutoresizing.FlexibleHeight, UIViewAutoresizing.FlexibleWidth] 33 | highlightEffectView.frame = effectView.contentView.bounds 34 | let highlightView = UIView.init(frame: highlightEffectView.contentView.bounds) 35 | highlightView.backgroundColor = UIColor(white: 1.0, alpha: 0.2) 36 | highlightView.autoresizingMask = [UIViewAutoresizing.FlexibleHeight, UIViewAutoresizing.FlexibleWidth] 37 | highlightView.alpha = 0 38 | highlightEffectView.contentView.addSubview(highlightView) 39 | effectView.contentView.addSubview(highlightEffectView) 40 | 41 | self.layer.shadowColor = UIColor.blackColor().CGColor 42 | self.layer.shadowOpacity = 0.1 43 | self.layer.shadowRadius = 3.0 44 | self.layer.shadowOffset = CGSizeMake(0, 0) 45 | self.layer.masksToBounds = false 46 | 47 | self.setTitleColor(UIColor.blackColor(), forState: .Normal) 48 | 49 | self.setImage(UIImage(named: "NowPlayingCollapseChevronMask"), forState: .Normal) 50 | 51 | self.accessibilityLabel = "Close" 52 | 53 | } 54 | 55 | override func layoutSubviews() { 56 | super.layoutSubviews() 57 | 58 | self.sendSubviewToBack(effectView) 59 | 60 | let minSideSize: CGFloat = min(self.bounds.size.width, self.bounds.size.height) 61 | 62 | effectView.frame = self.bounds 63 | let maskLayer: CAShapeLayer = CAShapeLayer() 64 | maskLayer.rasterizationScale = UIScreen.mainScreen().nativeScale 65 | maskLayer.shouldRasterize = true 66 | 67 | let path: CGPathRef = CGPathCreateWithRoundedRect(self.bounds, minSideSize / 2, minSideSize / 2, nil) 68 | maskLayer.path = path 69 | 70 | effectView.layer.mask = maskLayer 71 | 72 | var imageFrame: CGRect = self.imageView!.frame 73 | imageFrame.origin.y = imageFrame.origin.y + 0.5 74 | self.imageView!.frame = imageFrame 75 | } 76 | 77 | override func sizeThatFits(size: CGSize) -> CGSize { 78 | var superSize: CGSize = super.sizeThatFits(size) 79 | superSize.width = superSize.width + 14 80 | superSize.height = superSize.height + 2 81 | 82 | return superSize 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /YTFloatingPlayer/YTFloatingPlayer/Classes/YTFViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YTDViewController.swift 3 | // YTDraggablePlayer 4 | // 5 | // Created by Ana Paula on 5/23/16. 6 | // Copyright © 2016 Ana Paula. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class YTFViewController: UIViewController { 12 | 13 | @IBOutlet weak var play: UIButton! 14 | @IBOutlet weak var fullscreen: UIButton! 15 | @IBOutlet weak var playerView: PlayerView! 16 | @IBOutlet weak var tableView: UITableView! 17 | @IBOutlet weak var tableViewContainer: UIView! 18 | @IBOutlet weak var minimizeButton: YTFPopupCloseButton! 19 | @IBOutlet weak var playerControlsView: UIView! 20 | @IBOutlet weak var backPlayerControlsView: UIView! 21 | @IBOutlet weak var slider: CustomSlider! 22 | @IBOutlet weak var progress: CustomProgress! 23 | @IBOutlet weak var entireTime: UILabel! 24 | @IBOutlet weak var currentTime: UILabel! 25 | @IBOutlet weak var progressIndicatorView: UIActivityIndicatorView! 26 | var delegate: UITableViewDelegate? 27 | var dataSource: UITableViewDataSource? 28 | var tableCellNibName: String? 29 | var isOpen: Bool = false 30 | 31 | var isPlaying: Bool = false 32 | var isFullscreen: Bool = false 33 | var dragginSlider: Bool = false 34 | var isMinimized: Bool = false 35 | var hideTimer: NSTimer? 36 | var currentUrlIndex: Int = 0 { 37 | didSet { 38 | if (playerView != nil) { 39 | // Finish playing all items 40 | if (currentUrlIndex >= urls?.count) { 41 | // Go back to first tableView item to loop list 42 | currentUrlIndex = 0 43 | selectFirstRowOfTable() 44 | } else { 45 | playIndex(currentUrlIndex) 46 | } 47 | } 48 | } 49 | } 50 | var urls: [NSURL]? { 51 | didSet { 52 | if (playerView != nil) { 53 | currentUrlIndex = 0 54 | } 55 | } 56 | } 57 | 58 | var playerControlsFrame: CGRect? 59 | var playerViewFrame: CGRect? 60 | var tableViewContainerFrame: CGRect? 61 | var playerViewMinimizedFrame: CGRect? 62 | var minimizedPlayerFrame: CGRect? 63 | var initialFirstViewFrame: CGRect? 64 | var viewMinimizedFrame: CGRect? 65 | var restrictOffset: Float? 66 | var restrictTrueOffset: Float? 67 | var restictYaxis: Float? 68 | var transparentView: UIView? 69 | var onView: UIView? 70 | var playerTapGesture: UITapGestureRecognizer? 71 | var panGestureDirection: UIPanGestureRecognizerDirection? 72 | var touchPositionStartY: CGFloat? 73 | var touchPositionStartX: CGFloat? 74 | 75 | enum UIPanGestureRecognizerDirection { 76 | case Undefined 77 | case Up 78 | case Down 79 | case Left 80 | case Right 81 | } 82 | 83 | override func viewDidLoad() { 84 | initPlayerWithURLs() 85 | initViews() 86 | playerView.delegate = self 87 | super.viewDidLoad() 88 | 89 | } 90 | 91 | override func viewDidAppear(animated: Bool) { 92 | calculateFrames() 93 | } 94 | 95 | func initPlayerWithURLs() { 96 | if (isMinimized) { 97 | expandViews() 98 | } 99 | playIndex(currentUrlIndex) 100 | } 101 | 102 | func initViews() { 103 | self.view.backgroundColor = UIColor.clearColor() 104 | self.view.alpha = 0.0 105 | playerControlsView.alpha = 0.0 106 | backPlayerControlsView.alpha = 0.0 107 | let gesture = UIPanGestureRecognizer.init(target: self, action: #selector(YTFViewController.panAction(_:))) 108 | playerView.addGestureRecognizer(gesture) 109 | 110 | tableView.delegate = delegate 111 | tableView.dataSource = dataSource 112 | tableView.rowHeight = CGFloat(76) 113 | tableView.registerNib(UINib(nibName: tableCellNibName!, bundle: nil), forCellReuseIdentifier: tableCellNibName!) 114 | } 115 | 116 | func calculateFrames() { 117 | self.initialFirstViewFrame = self.view.frame 118 | self.playerViewFrame = self.playerView.frame 119 | self.tableViewContainerFrame = self.tableViewContainer.frame 120 | self.playerViewMinimizedFrame = self.playerView.frame 121 | self.viewMinimizedFrame = self.tableViewContainer.frame 122 | self.playerControlsFrame = self.playerControlsView.frame 123 | 124 | playerView.translatesAutoresizingMaskIntoConstraints = true 125 | tableViewContainer.translatesAutoresizingMaskIntoConstraints = true 126 | playerControlsView.translatesAutoresizingMaskIntoConstraints = true 127 | backPlayerControlsView.translatesAutoresizingMaskIntoConstraints = true 128 | tableViewContainer.frame = self.initialFirstViewFrame! 129 | self.playerControlsView.frame = self.playerControlsFrame! 130 | 131 | transparentView = UIView.init(frame: initialFirstViewFrame!) 132 | transparentView?.backgroundColor = UIColor.blackColor() 133 | transparentView?.alpha = 0.0 134 | onView?.addSubview(transparentView!) 135 | 136 | self.restrictOffset = Float(self.initialFirstViewFrame!.size.width) - 200 137 | self.restrictTrueOffset = Float(self.initialFirstViewFrame!.size.height) - 180 138 | self.restictYaxis = Float(self.initialFirstViewFrame!.size.height - playerView.frame.size.height) 139 | 140 | } 141 | 142 | @IBAction func minimizeButtonTouched(sender: AnyObject) { 143 | minimizeViews() 144 | } 145 | 146 | func selectFirstRowOfTable() { 147 | let rowToSelect:NSIndexPath = NSIndexPath(forRow: 0, inSection: 0) 148 | 149 | UIView.animateWithDuration(0.5, animations: { 150 | self.tableView.scrollToRowAtIndexPath(rowToSelect, atScrollPosition: .Top, animated: false) 151 | }) 152 | } 153 | 154 | } 155 | 156 | -------------------------------------------------------------------------------- /YTFloatingPlayer/YTFloatingPlayer/Resources/NowPlayingCollapseChevronMask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamanap/SwiftYouTubeFloatingPlayer/c4d48695d9f15cdf8adfcfeef10e687252c711d8/YTFloatingPlayer/YTFloatingPlayer/Resources/NowPlayingCollapseChevronMask.png -------------------------------------------------------------------------------- /YTFloatingPlayer/YTFloatingPlayer/Resources/NowPlayingCollapseChevronMask@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamanap/SwiftYouTubeFloatingPlayer/c4d48695d9f15cdf8adfcfeef10e687252c711d8/YTFloatingPlayer/YTFloatingPlayer/Resources/NowPlayingCollapseChevronMask@2x.png -------------------------------------------------------------------------------- /YTFloatingPlayer/YTFloatingPlayer/Resources/NowPlayingCollapseChevronMask@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamanap/SwiftYouTubeFloatingPlayer/c4d48695d9f15cdf8adfcfeef10e687252c711d8/YTFloatingPlayer/YTFloatingPlayer/Resources/NowPlayingCollapseChevronMask@3x.png -------------------------------------------------------------------------------- /YTFloatingPlayer/YTFloatingPlayer/Resources/YTFViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 77 | 87 | 97 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | -------------------------------------------------------------------------------- /YTFloatingPlayer/YTFloatingPlayer/Resources/fullscreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamanap/SwiftYouTubeFloatingPlayer/c4d48695d9f15cdf8adfcfeef10e687252c711d8/YTFloatingPlayer/YTFloatingPlayer/Resources/fullscreen.png -------------------------------------------------------------------------------- /YTFloatingPlayer/YTFloatingPlayer/Resources/fullscreen@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamanap/SwiftYouTubeFloatingPlayer/c4d48695d9f15cdf8adfcfeef10e687252c711d8/YTFloatingPlayer/YTFloatingPlayer/Resources/fullscreen@2x.png -------------------------------------------------------------------------------- /YTFloatingPlayer/YTFloatingPlayer/Resources/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamanap/SwiftYouTubeFloatingPlayer/c4d48695d9f15cdf8adfcfeef10e687252c711d8/YTFloatingPlayer/YTFloatingPlayer/Resources/pause.png -------------------------------------------------------------------------------- /YTFloatingPlayer/YTFloatingPlayer/Resources/pause@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamanap/SwiftYouTubeFloatingPlayer/c4d48695d9f15cdf8adfcfeef10e687252c711d8/YTFloatingPlayer/YTFloatingPlayer/Resources/pause@2x.png -------------------------------------------------------------------------------- /YTFloatingPlayer/YTFloatingPlayer/Resources/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamanap/SwiftYouTubeFloatingPlayer/c4d48695d9f15cdf8adfcfeef10e687252c711d8/YTFloatingPlayer/YTFloatingPlayer/Resources/play.png -------------------------------------------------------------------------------- /YTFloatingPlayer/YTFloatingPlayer/Resources/play@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamanap/SwiftYouTubeFloatingPlayer/c4d48695d9f15cdf8adfcfeef10e687252c711d8/YTFloatingPlayer/YTFloatingPlayer/Resources/play@2x.png -------------------------------------------------------------------------------- /YTFloatingPlayer/YTFloatingPlayer/Resources/unfullscreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamanap/SwiftYouTubeFloatingPlayer/c4d48695d9f15cdf8adfcfeef10e687252c711d8/YTFloatingPlayer/YTFloatingPlayer/Resources/unfullscreen.png -------------------------------------------------------------------------------- /YTFloatingPlayer/YTFloatingPlayer/Resources/unfullscreen@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamanap/SwiftYouTubeFloatingPlayer/c4d48695d9f15cdf8adfcfeef10e687252c711d8/YTFloatingPlayer/YTFloatingPlayer/Resources/unfullscreen@2x.png -------------------------------------------------------------------------------- /YTFloatingPlayerTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /YTFloatingPlayerTests/YTDraggablePlayerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YTDraggablePlayerTests.swift 3 | // YTDraggablePlayerTests 4 | // 5 | // Created by Ana Paula on 5/19/16. 6 | // Copyright © 2016 Ana Paula. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import YTDraggablePlayer 11 | 12 | class YTDraggablePlayerTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measureBlock { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /YTFloatingPlayerUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /YTFloatingPlayerUITests/YTDraggablePlayerUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YTDraggablePlayerUITests.swift 3 | // YTDraggablePlayerUITests 4 | // 5 | // Created by Ana Paula on 5/19/16. 6 | // Copyright © 2016 Ana Paula. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class YTDraggablePlayerUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | 18 | // In UI tests it is usually best to stop immediately when a failure occurs. 19 | continueAfterFailure = false 20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 21 | XCUIApplication().launch() 22 | 23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 24 | } 25 | 26 | override func tearDown() { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | super.tearDown() 29 | } 30 | 31 | func testExample() { 32 | // Use recording to get started writing UI tests. 33 | // Use XCTAssert and related functions to verify your tests produce the correct results. 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /YouTubeFloatingPlayer.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | CC17BC751D0A0FF9009FCD9C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC17BC6F1D0A0FF9009FCD9C /* AppDelegate.swift */; }; 11 | CC17BC761D0A0FF9009FCD9C /* NextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC17BC701D0A0FF9009FCD9C /* NextViewController.swift */; }; 12 | CC17BC771D0A0FF9009FCD9C /* Video.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC17BC711D0A0FF9009FCD9C /* Video.swift */; }; 13 | CC17BC7A1D0A0FF9009FCD9C /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC17BC741D0A0FF9009FCD9C /* ViewController.swift */; }; 14 | CC17BC7D1D0A1005009FCD9C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CC17BC7B1D0A1005009FCD9C /* Main.storyboard */; }; 15 | CC17BC801D0A1013009FCD9C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CC17BC7E1D0A1013009FCD9C /* LaunchScreen.storyboard */; }; 16 | CC17BC821D0A1021009FCD9C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CC17BC811D0A1021009FCD9C /* Assets.xcassets */; }; 17 | CC6C23B81D0B159400C3BBCA /* VideoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6C23B61D0B159400C3BBCA /* VideoCell.swift */; }; 18 | CC6C23B91D0B159400C3BBCA /* VideoCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = CC6C23B71D0B159400C3BBCA /* VideoCell.xib */; }; 19 | CC6C23C31D0B165E00C3BBCA /* CustomProgressSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6C23BC1D0B165E00C3BBCA /* CustomProgressSlider.swift */; }; 20 | CC6C23C41D0B165E00C3BBCA /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6C23BD1D0B165E00C3BBCA /* PlayerView.swift */; }; 21 | CC6C23C51D0B165E00C3BBCA /* YTFAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6C23BE1D0B165E00C3BBCA /* YTFAnimation.swift */; }; 22 | CC6C23C61D0B165E00C3BBCA /* YTFPlayerControls.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6C23BF1D0B165E00C3BBCA /* YTFPlayerControls.swift */; }; 23 | CC6C23C71D0B165E00C3BBCA /* YTFPopupCloseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6C23C01D0B165E00C3BBCA /* YTFPopupCloseButton.swift */; }; 24 | CC6C23C81D0B165E00C3BBCA /* YTFPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6C23C11D0B165E00C3BBCA /* YTFPlayer.swift */; }; 25 | CC6C23C91D0B165E00C3BBCA /* YTFViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6C23C21D0B165E00C3BBCA /* YTFViewController.swift */; }; 26 | CC6C23D61D0B166400C3BBCA /* fullscreen.png in Resources */ = {isa = PBXBuildFile; fileRef = CC6C23CA1D0B166400C3BBCA /* fullscreen.png */; }; 27 | CC6C23D71D0B166400C3BBCA /* fullscreen@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = CC6C23CB1D0B166400C3BBCA /* fullscreen@2x.png */; }; 28 | CC6C23D81D0B166400C3BBCA /* NowPlayingCollapseChevronMask.png in Resources */ = {isa = PBXBuildFile; fileRef = CC6C23CC1D0B166400C3BBCA /* NowPlayingCollapseChevronMask.png */; }; 29 | CC6C23D91D0B166400C3BBCA /* NowPlayingCollapseChevronMask@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = CC6C23CD1D0B166400C3BBCA /* NowPlayingCollapseChevronMask@2x.png */; }; 30 | CC6C23DA1D0B166400C3BBCA /* NowPlayingCollapseChevronMask@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = CC6C23CE1D0B166400C3BBCA /* NowPlayingCollapseChevronMask@3x.png */; }; 31 | CC6C23DB1D0B166400C3BBCA /* pause.png in Resources */ = {isa = PBXBuildFile; fileRef = CC6C23CF1D0B166400C3BBCA /* pause.png */; }; 32 | CC6C23DC1D0B166400C3BBCA /* pause@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = CC6C23D01D0B166400C3BBCA /* pause@2x.png */; }; 33 | CC6C23DD1D0B166400C3BBCA /* play.png in Resources */ = {isa = PBXBuildFile; fileRef = CC6C23D11D0B166400C3BBCA /* play.png */; }; 34 | CC6C23DE1D0B166400C3BBCA /* play@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = CC6C23D21D0B166400C3BBCA /* play@2x.png */; }; 35 | CC6C23DF1D0B166400C3BBCA /* unfullscreen.png in Resources */ = {isa = PBXBuildFile; fileRef = CC6C23D31D0B166400C3BBCA /* unfullscreen.png */; }; 36 | CC6C23E01D0B166400C3BBCA /* unfullscreen@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = CC6C23D41D0B166400C3BBCA /* unfullscreen@2x.png */; }; 37 | CC6C23E11D0B166400C3BBCA /* YTFViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = CC6C23D51D0B166400C3BBCA /* YTFViewController.xib */; }; 38 | CCE58F771D0AF3B60048E169 /* YTDraggablePlayerUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCE58F761D0AF3B60048E169 /* YTDraggablePlayerUITests.swift */; }; 39 | CCE58F791D0AF5B10048E169 /* YTDraggablePlayerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCE58F781D0AF5B10048E169 /* YTDraggablePlayerTests.swift */; }; 40 | /* End PBXBuildFile section */ 41 | 42 | /* Begin PBXContainerItemProxy section */ 43 | CC4247A11CEE3E1C009D960F /* PBXContainerItemProxy */ = { 44 | isa = PBXContainerItemProxy; 45 | containerPortal = CC4247841CEE3E1C009D960F /* Project object */; 46 | proxyType = 1; 47 | remoteGlobalIDString = CC42478B1CEE3E1C009D960F; 48 | remoteInfo = YTDraggablePlayer; 49 | }; 50 | CC4247AC1CEE3E1C009D960F /* PBXContainerItemProxy */ = { 51 | isa = PBXContainerItemProxy; 52 | containerPortal = CC4247841CEE3E1C009D960F /* Project object */; 53 | proxyType = 1; 54 | remoteGlobalIDString = CC42478B1CEE3E1C009D960F; 55 | remoteInfo = YTDraggablePlayer; 56 | }; 57 | /* End PBXContainerItemProxy section */ 58 | 59 | /* Begin PBXFileReference section */ 60 | CC17BC6F1D0A0FF9009FCD9C /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = YTFloatingPlayer/AppDelegate.swift; sourceTree = SOURCE_ROOT; }; 61 | CC17BC701D0A0FF9009FCD9C /* NextViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NextViewController.swift; path = YTFloatingPlayer/NextViewController.swift; sourceTree = SOURCE_ROOT; }; 62 | CC17BC711D0A0FF9009FCD9C /* Video.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Video.swift; path = YTFloatingPlayer/Video.swift; sourceTree = SOURCE_ROOT; }; 63 | CC17BC741D0A0FF9009FCD9C /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ViewController.swift; path = YTFloatingPlayer/ViewController.swift; sourceTree = SOURCE_ROOT; }; 64 | CC17BC7C1D0A1005009FCD9C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = YTFloatingPlayer/Base.lproj/Main.storyboard; sourceTree = SOURCE_ROOT; }; 65 | CC17BC7F1D0A1013009FCD9C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = YTFloatingPlayer/Base.lproj/LaunchScreen.storyboard; sourceTree = SOURCE_ROOT; }; 66 | CC17BC811D0A1021009FCD9C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = YTFloatingPlayer/Assets.xcassets; sourceTree = SOURCE_ROOT; }; 67 | CC17BCA51D0A138F009FCD9C /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = YTFloatingPlayer/Info.plist; sourceTree = SOURCE_ROOT; }; 68 | CC17BCA71D0A13A0009FCD9C /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = YTFloatingPlayerTests/Info.plist; sourceTree = SOURCE_ROOT; }; 69 | CC17BCA91D0A13A9009FCD9C /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = YTFloatingPlayerUITests/Info.plist; sourceTree = SOURCE_ROOT; }; 70 | CC42478C1CEE3E1C009D960F /* YouTubeFloatingPlayer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = YouTubeFloatingPlayer.app; sourceTree = BUILT_PRODUCTS_DIR; }; 71 | CC4247A01CEE3E1C009D960F /* YouTubeFloatingPlayer.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = YouTubeFloatingPlayer.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 72 | CC4247AB1CEE3E1C009D960F /* YouTubeFloatingPlayer.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = YouTubeFloatingPlayer.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 73 | CC6C23B61D0B159400C3BBCA /* VideoCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = VideoCell.swift; path = YTFloatingPlayer/VideoCell.swift; sourceTree = SOURCE_ROOT; }; 74 | CC6C23B71D0B159400C3BBCA /* VideoCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = VideoCell.xib; path = YTFloatingPlayer/VideoCell.xib; sourceTree = SOURCE_ROOT; }; 75 | CC6C23BC1D0B165E00C3BBCA /* CustomProgressSlider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CustomProgressSlider.swift; path = YTFloatingPlayer/YTFloatingPlayer/Classes/CustomProgressSlider.swift; sourceTree = SOURCE_ROOT; }; 76 | CC6C23BD1D0B165E00C3BBCA /* PlayerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PlayerView.swift; path = YTFloatingPlayer/YTFloatingPlayer/Classes/PlayerView.swift; sourceTree = SOURCE_ROOT; }; 77 | CC6C23BE1D0B165E00C3BBCA /* YTFAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = YTFAnimation.swift; path = YTFloatingPlayer/YTFloatingPlayer/Classes/YTFAnimation.swift; sourceTree = SOURCE_ROOT; }; 78 | CC6C23BF1D0B165E00C3BBCA /* YTFPlayerControls.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = YTFPlayerControls.swift; path = YTFloatingPlayer/YTFloatingPlayer/Classes/YTFPlayerControls.swift; sourceTree = SOURCE_ROOT; }; 79 | CC6C23C01D0B165E00C3BBCA /* YTFPopupCloseButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = YTFPopupCloseButton.swift; path = YTFloatingPlayer/YTFloatingPlayer/Classes/YTFPopupCloseButton.swift; sourceTree = SOURCE_ROOT; }; 80 | CC6C23C11D0B165E00C3BBCA /* YTFPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = YTFPlayer.swift; path = YTFloatingPlayer/YTFloatingPlayer/Classes/YTFPlayer.swift; sourceTree = SOURCE_ROOT; }; 81 | CC6C23C21D0B165E00C3BBCA /* YTFViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = YTFViewController.swift; path = YTFloatingPlayer/YTFloatingPlayer/Classes/YTFViewController.swift; sourceTree = SOURCE_ROOT; }; 82 | CC6C23CA1D0B166400C3BBCA /* fullscreen.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = fullscreen.png; path = YTFloatingPlayer/YTFloatingPlayer/Resources/fullscreen.png; sourceTree = SOURCE_ROOT; }; 83 | CC6C23CB1D0B166400C3BBCA /* fullscreen@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "fullscreen@2x.png"; path = "YTFloatingPlayer/YTFloatingPlayer/Resources/fullscreen@2x.png"; sourceTree = SOURCE_ROOT; }; 84 | CC6C23CC1D0B166400C3BBCA /* NowPlayingCollapseChevronMask.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = NowPlayingCollapseChevronMask.png; path = YTFloatingPlayer/YTFloatingPlayer/Resources/NowPlayingCollapseChevronMask.png; sourceTree = SOURCE_ROOT; }; 85 | CC6C23CD1D0B166400C3BBCA /* NowPlayingCollapseChevronMask@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "NowPlayingCollapseChevronMask@2x.png"; path = "YTFloatingPlayer/YTFloatingPlayer/Resources/NowPlayingCollapseChevronMask@2x.png"; sourceTree = SOURCE_ROOT; }; 86 | CC6C23CE1D0B166400C3BBCA /* NowPlayingCollapseChevronMask@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "NowPlayingCollapseChevronMask@3x.png"; path = "YTFloatingPlayer/YTFloatingPlayer/Resources/NowPlayingCollapseChevronMask@3x.png"; sourceTree = SOURCE_ROOT; }; 87 | CC6C23CF1D0B166400C3BBCA /* pause.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = pause.png; path = YTFloatingPlayer/YTFloatingPlayer/Resources/pause.png; sourceTree = SOURCE_ROOT; }; 88 | CC6C23D01D0B166400C3BBCA /* pause@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "pause@2x.png"; path = "YTFloatingPlayer/YTFloatingPlayer/Resources/pause@2x.png"; sourceTree = SOURCE_ROOT; }; 89 | CC6C23D11D0B166400C3BBCA /* play.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = play.png; path = YTFloatingPlayer/YTFloatingPlayer/Resources/play.png; sourceTree = SOURCE_ROOT; }; 90 | CC6C23D21D0B166400C3BBCA /* play@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "play@2x.png"; path = "YTFloatingPlayer/YTFloatingPlayer/Resources/play@2x.png"; sourceTree = SOURCE_ROOT; }; 91 | CC6C23D31D0B166400C3BBCA /* unfullscreen.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = unfullscreen.png; path = YTFloatingPlayer/YTFloatingPlayer/Resources/unfullscreen.png; sourceTree = SOURCE_ROOT; }; 92 | CC6C23D41D0B166400C3BBCA /* unfullscreen@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "unfullscreen@2x.png"; path = "YTFloatingPlayer/YTFloatingPlayer/Resources/unfullscreen@2x.png"; sourceTree = SOURCE_ROOT; }; 93 | CC6C23D51D0B166400C3BBCA /* YTFViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = YTFViewController.xib; path = YTFloatingPlayer/YTFloatingPlayer/Resources/YTFViewController.xib; sourceTree = SOURCE_ROOT; }; 94 | CCE58F761D0AF3B60048E169 /* YTDraggablePlayerUITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = YTDraggablePlayerUITests.swift; path = YTFloatingPlayerUITests/YTDraggablePlayerUITests.swift; sourceTree = SOURCE_ROOT; }; 95 | CCE58F781D0AF5B10048E169 /* YTDraggablePlayerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = YTDraggablePlayerTests.swift; path = YTFloatingPlayerTests/YTDraggablePlayerTests.swift; sourceTree = SOURCE_ROOT; }; 96 | /* End PBXFileReference section */ 97 | 98 | /* Begin PBXFrameworksBuildPhase section */ 99 | CC4247891CEE3E1C009D960F /* Frameworks */ = { 100 | isa = PBXFrameworksBuildPhase; 101 | buildActionMask = 2147483647; 102 | files = ( 103 | ); 104 | runOnlyForDeploymentPostprocessing = 0; 105 | }; 106 | CC42479D1CEE3E1C009D960F /* Frameworks */ = { 107 | isa = PBXFrameworksBuildPhase; 108 | buildActionMask = 2147483647; 109 | files = ( 110 | ); 111 | runOnlyForDeploymentPostprocessing = 0; 112 | }; 113 | CC4247A81CEE3E1C009D960F /* Frameworks */ = { 114 | isa = PBXFrameworksBuildPhase; 115 | buildActionMask = 2147483647; 116 | files = ( 117 | ); 118 | runOnlyForDeploymentPostprocessing = 0; 119 | }; 120 | /* End PBXFrameworksBuildPhase section */ 121 | 122 | /* Begin PBXGroup section */ 123 | CC4247831CEE3E1C009D960F = { 124 | isa = PBXGroup; 125 | children = ( 126 | CC42478E1CEE3E1C009D960F /* YTFPlayer */, 127 | CC4247A31CEE3E1C009D960F /* YTFloatingPlayerTests */, 128 | CC4247AE1CEE3E1C009D960F /* YTFloatingPlayerUITests */, 129 | CC42478D1CEE3E1C009D960F /* Products */, 130 | ); 131 | sourceTree = ""; 132 | }; 133 | CC42478D1CEE3E1C009D960F /* Products */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | CC42478C1CEE3E1C009D960F /* YouTubeFloatingPlayer.app */, 137 | CC4247A01CEE3E1C009D960F /* YouTubeFloatingPlayer.xctest */, 138 | CC4247AB1CEE3E1C009D960F /* YouTubeFloatingPlayer.xctest */, 139 | ); 140 | name = Products; 141 | sourceTree = ""; 142 | }; 143 | CC42478E1CEE3E1C009D960F /* YTFPlayer */ = { 144 | isa = PBXGroup; 145 | children = ( 146 | CC4248081CEE472C009D960F /* YTFPlayer */, 147 | CC17BC6F1D0A0FF9009FCD9C /* AppDelegate.swift */, 148 | CC17BC701D0A0FF9009FCD9C /* NextViewController.swift */, 149 | CC17BC711D0A0FF9009FCD9C /* Video.swift */, 150 | CC6C23B61D0B159400C3BBCA /* VideoCell.swift */, 151 | CC6C23B71D0B159400C3BBCA /* VideoCell.xib */, 152 | CC17BC741D0A0FF9009FCD9C /* ViewController.swift */, 153 | CC17BC7B1D0A1005009FCD9C /* Main.storyboard */, 154 | CC17BC811D0A1021009FCD9C /* Assets.xcassets */, 155 | CC17BC7E1D0A1013009FCD9C /* LaunchScreen.storyboard */, 156 | CC17BCA51D0A138F009FCD9C /* Info.plist */, 157 | ); 158 | name = YTFPlayer; 159 | path = YTDraggablePlayer; 160 | sourceTree = ""; 161 | }; 162 | CC4247A31CEE3E1C009D960F /* YTFloatingPlayerTests */ = { 163 | isa = PBXGroup; 164 | children = ( 165 | CC17BCA71D0A13A0009FCD9C /* Info.plist */, 166 | CCE58F781D0AF5B10048E169 /* YTDraggablePlayerTests.swift */, 167 | ); 168 | name = YTFloatingPlayerTests; 169 | path = YTDraggablePlayerTests; 170 | sourceTree = ""; 171 | }; 172 | CC4247AE1CEE3E1C009D960F /* YTFloatingPlayerUITests */ = { 173 | isa = PBXGroup; 174 | children = ( 175 | CC17BCA91D0A13A9009FCD9C /* Info.plist */, 176 | CCE58F761D0AF3B60048E169 /* YTDraggablePlayerUITests.swift */, 177 | ); 178 | name = YTFloatingPlayerUITests; 179 | path = YTDraggablePlayerUITests; 180 | sourceTree = ""; 181 | }; 182 | CC4248081CEE472C009D960F /* YTFPlayer */ = { 183 | isa = PBXGroup; 184 | children = ( 185 | CC6C23BB1D0B165300C3BBCA /* Resources */, 186 | CC6C23BA1D0B164900C3BBCA /* Classes */, 187 | ); 188 | name = YTFPlayer; 189 | path = YTDraggableVideoPlayer; 190 | sourceTree = ""; 191 | }; 192 | CC6C23BA1D0B164900C3BBCA /* Classes */ = { 193 | isa = PBXGroup; 194 | children = ( 195 | CC6C23BC1D0B165E00C3BBCA /* CustomProgressSlider.swift */, 196 | CC6C23BD1D0B165E00C3BBCA /* PlayerView.swift */, 197 | CC6C23BE1D0B165E00C3BBCA /* YTFAnimation.swift */, 198 | CC6C23BF1D0B165E00C3BBCA /* YTFPlayerControls.swift */, 199 | CC6C23C01D0B165E00C3BBCA /* YTFPopupCloseButton.swift */, 200 | CC6C23C11D0B165E00C3BBCA /* YTFPlayer.swift */, 201 | CC6C23C21D0B165E00C3BBCA /* YTFViewController.swift */, 202 | ); 203 | name = Classes; 204 | path = ..; 205 | sourceTree = ""; 206 | }; 207 | CC6C23BB1D0B165300C3BBCA /* Resources */ = { 208 | isa = PBXGroup; 209 | children = ( 210 | CC6C23CA1D0B166400C3BBCA /* fullscreen.png */, 211 | CC6C23CB1D0B166400C3BBCA /* fullscreen@2x.png */, 212 | CC6C23CC1D0B166400C3BBCA /* NowPlayingCollapseChevronMask.png */, 213 | CC6C23CD1D0B166400C3BBCA /* NowPlayingCollapseChevronMask@2x.png */, 214 | CC6C23CE1D0B166400C3BBCA /* NowPlayingCollapseChevronMask@3x.png */, 215 | CC6C23CF1D0B166400C3BBCA /* pause.png */, 216 | CC6C23D01D0B166400C3BBCA /* pause@2x.png */, 217 | CC6C23D11D0B166400C3BBCA /* play.png */, 218 | CC6C23D21D0B166400C3BBCA /* play@2x.png */, 219 | CC6C23D31D0B166400C3BBCA /* unfullscreen.png */, 220 | CC6C23D41D0B166400C3BBCA /* unfullscreen@2x.png */, 221 | CC6C23D51D0B166400C3BBCA /* YTFViewController.xib */, 222 | ); 223 | name = Resources; 224 | sourceTree = ""; 225 | }; 226 | /* End PBXGroup section */ 227 | 228 | /* Begin PBXNativeTarget section */ 229 | CC42478B1CEE3E1C009D960F /* YouTubeFloatingPlayer */ = { 230 | isa = PBXNativeTarget; 231 | buildConfigurationList = CC4247B41CEE3E1C009D960F /* Build configuration list for PBXNativeTarget "YouTubeFloatingPlayer" */; 232 | buildPhases = ( 233 | CC4247881CEE3E1C009D960F /* Sources */, 234 | CC4247891CEE3E1C009D960F /* Frameworks */, 235 | CC42478A1CEE3E1C009D960F /* Resources */, 236 | ); 237 | buildRules = ( 238 | ); 239 | dependencies = ( 240 | ); 241 | name = YouTubeFloatingPlayer; 242 | productName = YTDraggablePlayer; 243 | productReference = CC42478C1CEE3E1C009D960F /* YouTubeFloatingPlayer.app */; 244 | productType = "com.apple.product-type.application"; 245 | }; 246 | CC42479F1CEE3E1C009D960F /* YouTubeFloatingPlayerTests */ = { 247 | isa = PBXNativeTarget; 248 | buildConfigurationList = CC4247B71CEE3E1C009D960F /* Build configuration list for PBXNativeTarget "YouTubeFloatingPlayerTests" */; 249 | buildPhases = ( 250 | CC42479C1CEE3E1C009D960F /* Sources */, 251 | CC42479D1CEE3E1C009D960F /* Frameworks */, 252 | CC42479E1CEE3E1C009D960F /* Resources */, 253 | ); 254 | buildRules = ( 255 | ); 256 | dependencies = ( 257 | CC4247A21CEE3E1C009D960F /* PBXTargetDependency */, 258 | ); 259 | name = YouTubeFloatingPlayerTests; 260 | productName = YTDraggablePlayerTests; 261 | productReference = CC4247A01CEE3E1C009D960F /* YouTubeFloatingPlayer.xctest */; 262 | productType = "com.apple.product-type.bundle.unit-test"; 263 | }; 264 | CC4247AA1CEE3E1C009D960F /* YouTubeFloatingPlayerUITests */ = { 265 | isa = PBXNativeTarget; 266 | buildConfigurationList = CC4247BA1CEE3E1C009D960F /* Build configuration list for PBXNativeTarget "YouTubeFloatingPlayerUITests" */; 267 | buildPhases = ( 268 | CC4247A71CEE3E1C009D960F /* Sources */, 269 | CC4247A81CEE3E1C009D960F /* Frameworks */, 270 | CC4247A91CEE3E1C009D960F /* Resources */, 271 | ); 272 | buildRules = ( 273 | ); 274 | dependencies = ( 275 | CC4247AD1CEE3E1C009D960F /* PBXTargetDependency */, 276 | ); 277 | name = YouTubeFloatingPlayerUITests; 278 | productName = YTDraggablePlayerUITests; 279 | productReference = CC4247AB1CEE3E1C009D960F /* YouTubeFloatingPlayer.xctest */; 280 | productType = "com.apple.product-type.bundle.ui-testing"; 281 | }; 282 | /* End PBXNativeTarget section */ 283 | 284 | /* Begin PBXProject section */ 285 | CC4247841CEE3E1C009D960F /* Project object */ = { 286 | isa = PBXProject; 287 | attributes = { 288 | LastSwiftUpdateCheck = 0730; 289 | LastUpgradeCheck = 0730; 290 | ORGANIZATIONNAME = "Ana Paula"; 291 | TargetAttributes = { 292 | CC42478B1CEE3E1C009D960F = { 293 | CreatedOnToolsVersion = 7.3.1; 294 | DevelopmentTeam = E9BBEYTP2D; 295 | }; 296 | CC42479F1CEE3E1C009D960F = { 297 | CreatedOnToolsVersion = 7.3.1; 298 | TestTargetID = CC42478B1CEE3E1C009D960F; 299 | }; 300 | CC4247AA1CEE3E1C009D960F = { 301 | CreatedOnToolsVersion = 7.3.1; 302 | TestTargetID = CC42478B1CEE3E1C009D960F; 303 | }; 304 | }; 305 | }; 306 | buildConfigurationList = CC4247871CEE3E1C009D960F /* Build configuration list for PBXProject "YouTubeFloatingPlayer" */; 307 | compatibilityVersion = "Xcode 3.2"; 308 | developmentRegion = English; 309 | hasScannedForEncodings = 0; 310 | knownRegions = ( 311 | en, 312 | Base, 313 | ); 314 | mainGroup = CC4247831CEE3E1C009D960F; 315 | productRefGroup = CC42478D1CEE3E1C009D960F /* Products */; 316 | projectDirPath = ""; 317 | projectRoot = ""; 318 | targets = ( 319 | CC42478B1CEE3E1C009D960F /* YouTubeFloatingPlayer */, 320 | CC42479F1CEE3E1C009D960F /* YouTubeFloatingPlayerTests */, 321 | CC4247AA1CEE3E1C009D960F /* YouTubeFloatingPlayerUITests */, 322 | ); 323 | }; 324 | /* End PBXProject section */ 325 | 326 | /* Begin PBXResourcesBuildPhase section */ 327 | CC42478A1CEE3E1C009D960F /* Resources */ = { 328 | isa = PBXResourcesBuildPhase; 329 | buildActionMask = 2147483647; 330 | files = ( 331 | CC17BC821D0A1021009FCD9C /* Assets.xcassets in Resources */, 332 | CC6C23D71D0B166400C3BBCA /* fullscreen@2x.png in Resources */, 333 | CC6C23DE1D0B166400C3BBCA /* play@2x.png in Resources */, 334 | CC6C23E01D0B166400C3BBCA /* unfullscreen@2x.png in Resources */, 335 | CC6C23DB1D0B166400C3BBCA /* pause.png in Resources */, 336 | CC6C23D81D0B166400C3BBCA /* NowPlayingCollapseChevronMask.png in Resources */, 337 | CC6C23D61D0B166400C3BBCA /* fullscreen.png in Resources */, 338 | CC6C23DD1D0B166400C3BBCA /* play.png in Resources */, 339 | CC6C23E11D0B166400C3BBCA /* YTFViewController.xib in Resources */, 340 | CC6C23DF1D0B166400C3BBCA /* unfullscreen.png in Resources */, 341 | CC6C23B91D0B159400C3BBCA /* VideoCell.xib in Resources */, 342 | CC6C23D91D0B166400C3BBCA /* NowPlayingCollapseChevronMask@2x.png in Resources */, 343 | CC17BC7D1D0A1005009FCD9C /* Main.storyboard in Resources */, 344 | CC6C23DA1D0B166400C3BBCA /* NowPlayingCollapseChevronMask@3x.png in Resources */, 345 | CC17BC801D0A1013009FCD9C /* LaunchScreen.storyboard in Resources */, 346 | CC6C23DC1D0B166400C3BBCA /* pause@2x.png in Resources */, 347 | ); 348 | runOnlyForDeploymentPostprocessing = 0; 349 | }; 350 | CC42479E1CEE3E1C009D960F /* Resources */ = { 351 | isa = PBXResourcesBuildPhase; 352 | buildActionMask = 2147483647; 353 | files = ( 354 | ); 355 | runOnlyForDeploymentPostprocessing = 0; 356 | }; 357 | CC4247A91CEE3E1C009D960F /* Resources */ = { 358 | isa = PBXResourcesBuildPhase; 359 | buildActionMask = 2147483647; 360 | files = ( 361 | ); 362 | runOnlyForDeploymentPostprocessing = 0; 363 | }; 364 | /* End PBXResourcesBuildPhase section */ 365 | 366 | /* Begin PBXSourcesBuildPhase section */ 367 | CC4247881CEE3E1C009D960F /* Sources */ = { 368 | isa = PBXSourcesBuildPhase; 369 | buildActionMask = 2147483647; 370 | files = ( 371 | CC6C23C81D0B165E00C3BBCA /* YTFPlayer.swift in Sources */, 372 | CC6C23C61D0B165E00C3BBCA /* YTFPlayerControls.swift in Sources */, 373 | CC6C23C51D0B165E00C3BBCA /* YTFAnimation.swift in Sources */, 374 | CC6C23B81D0B159400C3BBCA /* VideoCell.swift in Sources */, 375 | CC6C23C91D0B165E00C3BBCA /* YTFViewController.swift in Sources */, 376 | CC17BC751D0A0FF9009FCD9C /* AppDelegate.swift in Sources */, 377 | CC6C23C41D0B165E00C3BBCA /* PlayerView.swift in Sources */, 378 | CC6C23C71D0B165E00C3BBCA /* YTFPopupCloseButton.swift in Sources */, 379 | CC17BC761D0A0FF9009FCD9C /* NextViewController.swift in Sources */, 380 | CC17BC7A1D0A0FF9009FCD9C /* ViewController.swift in Sources */, 381 | CC6C23C31D0B165E00C3BBCA /* CustomProgressSlider.swift in Sources */, 382 | CC17BC771D0A0FF9009FCD9C /* Video.swift in Sources */, 383 | ); 384 | runOnlyForDeploymentPostprocessing = 0; 385 | }; 386 | CC42479C1CEE3E1C009D960F /* Sources */ = { 387 | isa = PBXSourcesBuildPhase; 388 | buildActionMask = 2147483647; 389 | files = ( 390 | CCE58F791D0AF5B10048E169 /* YTDraggablePlayerTests.swift in Sources */, 391 | ); 392 | runOnlyForDeploymentPostprocessing = 0; 393 | }; 394 | CC4247A71CEE3E1C009D960F /* Sources */ = { 395 | isa = PBXSourcesBuildPhase; 396 | buildActionMask = 2147483647; 397 | files = ( 398 | CCE58F771D0AF3B60048E169 /* YTDraggablePlayerUITests.swift in Sources */, 399 | ); 400 | runOnlyForDeploymentPostprocessing = 0; 401 | }; 402 | /* End PBXSourcesBuildPhase section */ 403 | 404 | /* Begin PBXTargetDependency section */ 405 | CC4247A21CEE3E1C009D960F /* PBXTargetDependency */ = { 406 | isa = PBXTargetDependency; 407 | target = CC42478B1CEE3E1C009D960F /* YouTubeFloatingPlayer */; 408 | targetProxy = CC4247A11CEE3E1C009D960F /* PBXContainerItemProxy */; 409 | }; 410 | CC4247AD1CEE3E1C009D960F /* PBXTargetDependency */ = { 411 | isa = PBXTargetDependency; 412 | target = CC42478B1CEE3E1C009D960F /* YouTubeFloatingPlayer */; 413 | targetProxy = CC4247AC1CEE3E1C009D960F /* PBXContainerItemProxy */; 414 | }; 415 | /* End PBXTargetDependency section */ 416 | 417 | /* Begin PBXVariantGroup section */ 418 | CC17BC7B1D0A1005009FCD9C /* Main.storyboard */ = { 419 | isa = PBXVariantGroup; 420 | children = ( 421 | CC17BC7C1D0A1005009FCD9C /* Base */, 422 | ); 423 | name = Main.storyboard; 424 | sourceTree = ""; 425 | }; 426 | CC17BC7E1D0A1013009FCD9C /* LaunchScreen.storyboard */ = { 427 | isa = PBXVariantGroup; 428 | children = ( 429 | CC17BC7F1D0A1013009FCD9C /* Base */, 430 | ); 431 | name = LaunchScreen.storyboard; 432 | sourceTree = ""; 433 | }; 434 | /* End PBXVariantGroup section */ 435 | 436 | /* Begin XCBuildConfiguration section */ 437 | CC4247B21CEE3E1C009D960F /* Debug */ = { 438 | isa = XCBuildConfiguration; 439 | buildSettings = { 440 | ALWAYS_SEARCH_USER_PATHS = NO; 441 | CLANG_ANALYZER_NONNULL = YES; 442 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 443 | CLANG_CXX_LIBRARY = "libc++"; 444 | CLANG_ENABLE_MODULES = YES; 445 | CLANG_ENABLE_OBJC_ARC = YES; 446 | CLANG_WARN_BOOL_CONVERSION = YES; 447 | CLANG_WARN_CONSTANT_CONVERSION = YES; 448 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 449 | CLANG_WARN_EMPTY_BODY = YES; 450 | CLANG_WARN_ENUM_CONVERSION = YES; 451 | CLANG_WARN_INT_CONVERSION = YES; 452 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 453 | CLANG_WARN_UNREACHABLE_CODE = YES; 454 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 455 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 456 | COPY_PHASE_STRIP = NO; 457 | DEBUG_INFORMATION_FORMAT = dwarf; 458 | ENABLE_STRICT_OBJC_MSGSEND = YES; 459 | ENABLE_TESTABILITY = YES; 460 | GCC_C_LANGUAGE_STANDARD = gnu99; 461 | GCC_DYNAMIC_NO_PIC = NO; 462 | GCC_NO_COMMON_BLOCKS = YES; 463 | GCC_OPTIMIZATION_LEVEL = 0; 464 | GCC_PREPROCESSOR_DEFINITIONS = ( 465 | "DEBUG=1", 466 | "$(inherited)", 467 | ); 468 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 469 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 470 | GCC_WARN_UNDECLARED_SELECTOR = YES; 471 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 472 | GCC_WARN_UNUSED_FUNCTION = YES; 473 | GCC_WARN_UNUSED_VARIABLE = YES; 474 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 475 | MTL_ENABLE_DEBUG_INFO = YES; 476 | ONLY_ACTIVE_ARCH = YES; 477 | SDKROOT = iphoneos; 478 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 479 | TARGETED_DEVICE_FAMILY = "1,2"; 480 | }; 481 | name = Debug; 482 | }; 483 | CC4247B31CEE3E1C009D960F /* Release */ = { 484 | isa = XCBuildConfiguration; 485 | buildSettings = { 486 | ALWAYS_SEARCH_USER_PATHS = NO; 487 | CLANG_ANALYZER_NONNULL = YES; 488 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 489 | CLANG_CXX_LIBRARY = "libc++"; 490 | CLANG_ENABLE_MODULES = YES; 491 | CLANG_ENABLE_OBJC_ARC = YES; 492 | CLANG_WARN_BOOL_CONVERSION = YES; 493 | CLANG_WARN_CONSTANT_CONVERSION = YES; 494 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 495 | CLANG_WARN_EMPTY_BODY = YES; 496 | CLANG_WARN_ENUM_CONVERSION = YES; 497 | CLANG_WARN_INT_CONVERSION = YES; 498 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 499 | CLANG_WARN_UNREACHABLE_CODE = YES; 500 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 501 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 502 | COPY_PHASE_STRIP = NO; 503 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 504 | ENABLE_NS_ASSERTIONS = NO; 505 | ENABLE_STRICT_OBJC_MSGSEND = YES; 506 | GCC_C_LANGUAGE_STANDARD = gnu99; 507 | GCC_NO_COMMON_BLOCKS = YES; 508 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 509 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 510 | GCC_WARN_UNDECLARED_SELECTOR = YES; 511 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 512 | GCC_WARN_UNUSED_FUNCTION = YES; 513 | GCC_WARN_UNUSED_VARIABLE = YES; 514 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 515 | MTL_ENABLE_DEBUG_INFO = NO; 516 | SDKROOT = iphoneos; 517 | TARGETED_DEVICE_FAMILY = "1,2"; 518 | VALIDATE_PRODUCT = YES; 519 | }; 520 | name = Release; 521 | }; 522 | CC4247B51CEE3E1C009D960F /* Debug */ = { 523 | isa = XCBuildConfiguration; 524 | buildSettings = { 525 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 526 | CODE_SIGN_IDENTITY = "iPhone Developer"; 527 | INFOPLIST_FILE = "$(SRCROOT)/YTFloatingPlayer/Info.plist"; 528 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 529 | PRODUCT_BUNDLE_IDENTIFIER = anappaula.YTFloatingPlayer; 530 | PRODUCT_NAME = YouTubeFloatingPlayer; 531 | }; 532 | name = Debug; 533 | }; 534 | CC4247B61CEE3E1C009D960F /* Release */ = { 535 | isa = XCBuildConfiguration; 536 | buildSettings = { 537 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 538 | CODE_SIGN_IDENTITY = "iPhone Developer"; 539 | INFOPLIST_FILE = "$(SRCROOT)/YTFloatingPlayer/Info.plist"; 540 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 541 | PRODUCT_BUNDLE_IDENTIFIER = anappaula.YTFloatingPlayer; 542 | PRODUCT_NAME = YouTubeFloatingPlayer; 543 | }; 544 | name = Release; 545 | }; 546 | CC4247B81CEE3E1C009D960F /* Debug */ = { 547 | isa = XCBuildConfiguration; 548 | buildSettings = { 549 | BUNDLE_LOADER = "$(TEST_HOST)"; 550 | CLANG_ENABLE_MODULES = YES; 551 | INFOPLIST_FILE = YTDraggablePlayerTests/Info.plist; 552 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 553 | PRODUCT_BUNDLE_IDENTIFIER = anappaula.YTDraggablePlayerTests; 554 | PRODUCT_NAME = YouTubeFloatingPlayer; 555 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 556 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/YouTubeFloatingPlayer.app/YouTubeFloatingPlayer"; 557 | }; 558 | name = Debug; 559 | }; 560 | CC4247B91CEE3E1C009D960F /* Release */ = { 561 | isa = XCBuildConfiguration; 562 | buildSettings = { 563 | BUNDLE_LOADER = "$(TEST_HOST)"; 564 | CLANG_ENABLE_MODULES = YES; 565 | INFOPLIST_FILE = YTDraggablePlayerTests/Info.plist; 566 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 567 | PRODUCT_BUNDLE_IDENTIFIER = anappaula.YTDraggablePlayerTests; 568 | PRODUCT_NAME = YouTubeFloatingPlayer; 569 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/YouTubeFloatingPlayer.app/YouTubeFloatingPlayer"; 570 | }; 571 | name = Release; 572 | }; 573 | CC4247BB1CEE3E1C009D960F /* Debug */ = { 574 | isa = XCBuildConfiguration; 575 | buildSettings = { 576 | CLANG_ENABLE_MODULES = YES; 577 | INFOPLIST_FILE = YTDraggablePlayerUITests/Info.plist; 578 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 579 | PRODUCT_BUNDLE_IDENTIFIER = anappaula.YTDraggablePlayerUITests; 580 | PRODUCT_NAME = YouTubeFloatingPlayer; 581 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 582 | TEST_TARGET_NAME = YTDraggablePlayer; 583 | }; 584 | name = Debug; 585 | }; 586 | CC4247BC1CEE3E1C009D960F /* Release */ = { 587 | isa = XCBuildConfiguration; 588 | buildSettings = { 589 | CLANG_ENABLE_MODULES = YES; 590 | INFOPLIST_FILE = YTDraggablePlayerUITests/Info.plist; 591 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 592 | PRODUCT_BUNDLE_IDENTIFIER = anappaula.YTDraggablePlayerUITests; 593 | PRODUCT_NAME = YouTubeFloatingPlayer; 594 | TEST_TARGET_NAME = YTDraggablePlayer; 595 | }; 596 | name = Release; 597 | }; 598 | /* End XCBuildConfiguration section */ 599 | 600 | /* Begin XCConfigurationList section */ 601 | CC4247871CEE3E1C009D960F /* Build configuration list for PBXProject "YouTubeFloatingPlayer" */ = { 602 | isa = XCConfigurationList; 603 | buildConfigurations = ( 604 | CC4247B21CEE3E1C009D960F /* Debug */, 605 | CC4247B31CEE3E1C009D960F /* Release */, 606 | ); 607 | defaultConfigurationIsVisible = 0; 608 | defaultConfigurationName = Release; 609 | }; 610 | CC4247B41CEE3E1C009D960F /* Build configuration list for PBXNativeTarget "YouTubeFloatingPlayer" */ = { 611 | isa = XCConfigurationList; 612 | buildConfigurations = ( 613 | CC4247B51CEE3E1C009D960F /* Debug */, 614 | CC4247B61CEE3E1C009D960F /* Release */, 615 | ); 616 | defaultConfigurationIsVisible = 0; 617 | defaultConfigurationName = Release; 618 | }; 619 | CC4247B71CEE3E1C009D960F /* Build configuration list for PBXNativeTarget "YouTubeFloatingPlayerTests" */ = { 620 | isa = XCConfigurationList; 621 | buildConfigurations = ( 622 | CC4247B81CEE3E1C009D960F /* Debug */, 623 | CC4247B91CEE3E1C009D960F /* Release */, 624 | ); 625 | defaultConfigurationIsVisible = 0; 626 | defaultConfigurationName = Release; 627 | }; 628 | CC4247BA1CEE3E1C009D960F /* Build configuration list for PBXNativeTarget "YouTubeFloatingPlayerUITests" */ = { 629 | isa = XCConfigurationList; 630 | buildConfigurations = ( 631 | CC4247BB1CEE3E1C009D960F /* Debug */, 632 | CC4247BC1CEE3E1C009D960F /* Release */, 633 | ); 634 | defaultConfigurationIsVisible = 0; 635 | defaultConfigurationName = Release; 636 | }; 637 | /* End XCConfigurationList section */ 638 | }; 639 | rootObject = CC4247841CEE3E1C009D960F /* Project object */; 640 | } 641 | -------------------------------------------------------------------------------- /YouTubeFloatingPlayer.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /YouTubeFloatingPlayer.xcodeproj/project.xcworkspace/xcuserdata/AnaPaula.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamanap/SwiftYouTubeFloatingPlayer/c4d48695d9f15cdf8adfcfeef10e687252c711d8/YouTubeFloatingPlayer.xcodeproj/project.xcworkspace/xcuserdata/AnaPaula.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /YouTubeFloatingPlayer.xcodeproj/xcuserdata/AnaPaula.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 20 | 21 | 22 | 24 | 36 | 37 | 38 | 40 | 52 | 53 | 54 | 56 | 68 | 69 | 70 | 72 | 84 | 85 | 86 | 88 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /YouTubeFloatingPlayer.xcodeproj/xcuserdata/AnaPaula.xcuserdatad/xcschemes/YTFloatingPlayer.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 61 | 62 | 63 | 64 | 74 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 95 | 101 | 102 | 103 | 104 | 106 | 107 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /YouTubeFloatingPlayer.xcodeproj/xcuserdata/AnaPaula.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | YTFloatingPlayer.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | CC42478B1CEE3E1C009D960F 16 | 17 | primary 18 | 19 | 20 | CC42479F1CEE3E1C009D960F 21 | 22 | primary 23 | 24 | 25 | CC4247AA1CEE3E1C009D960F 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | --------------------------------------------------------------------------------