Repository: danylokostyshyn/popcorntime-ios
Branch: master
Commit: 555aff8d10e3
Files: 66
Total size: 352.1 KB
Directory structure:
gitextract_6kv41eo_/
├── .gitignore
├── Podfile
├── PopcornTime/
│ ├── Controllers/
│ │ ├── AnimeDetailsViewController.swift
│ │ ├── AnimeViewController.swift
│ │ ├── BarHidingViewController.swift
│ │ ├── BaseCollectionViewController.swift
│ │ ├── BaseDetailsViewController.swift
│ │ ├── ColorfullTabBarController.swift
│ │ ├── FavoritesViewController.swift
│ │ ├── LoadingViewController.swift
│ │ ├── MovieDetailsViewController.swift
│ │ ├── MoviesViewController.swift
│ │ ├── OAuthViewController.swift
│ │ ├── PagedViewController.swift
│ │ ├── ParseViewController.swift
│ │ ├── SettingsViewController.swift
│ │ ├── ShowDetailsViewController.swift
│ │ └── ShowsViewController.swift
│ ├── Info.plist
│ ├── Models/
│ │ ├── APIManager.swift
│ │ ├── Anime.swift
│ │ ├── AppDelegate.swift
│ │ ├── BaseStructures.swift
│ │ ├── BasicInfo.swift
│ │ ├── DataManager.swift
│ │ ├── Extensions.swift
│ │ ├── Image.swift
│ │ ├── ImageProvider.swift
│ │ ├── Movie.swift
│ │ ├── PTAPIManager.h
│ │ ├── PTAPIManager.m
│ │ ├── PTTorrentStreamer.h
│ │ ├── PTTorrentStreamer.mm
│ │ ├── ParseManager.swift
│ │ └── Show.swift
│ ├── PopcornTime-Bridging-Header.h
│ ├── Resources/
│ │ ├── Images.xcassets/
│ │ │ ├── AnimeIcon.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── AppIcon.appiconset/
│ │ │ │ └── Contents.json
│ │ │ ├── BigLogo.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── Contents.json
│ │ │ └── SubwayIconSet/
│ │ │ ├── AddToFavoritesIcon.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── FavoritesIcon.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── MoviesIcon.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── RemoveFromFavoritesIcon.imageset/
│ │ │ │ └── Contents.json
│ │ │ ├── SettingsIcon.imageset/
│ │ │ │ └── Contents.json
│ │ │ └── ShowsIcon.imageset/
│ │ │ └── Contents.json
│ │ ├── Launch Screen.xib
│ │ └── Main.storyboard
│ └── Views/
│ ├── EpisodeCell.swift
│ ├── EpisodeCell.xib
│ ├── MoreShowsCollectionViewCell.swift
│ ├── MoreShowsCollectionViewCell.xib
│ ├── SeasonHeader.swift
│ ├── SeasonHeader.xib
│ ├── ShowCollectionViewCell.swift
│ ├── ShowCollectionViewCell.xib
│ ├── StratchyHeader.swift
│ ├── StratchyHeader.xib
│ └── StratchyHeaderLayout.swift
├── PopcornTime.xcodeproj/
│ ├── project.pbxproj
│ └── xcshareddata/
│ └── xcschemes/
│ └── PopcornTime.xcscheme
├── README.md
├── Thirdparties/
│ └── VLCKit/
│ └── Dropin-Player/
│ ├── VDLPlaybackViewController.h
│ ├── VDLPlaybackViewController.m
│ └── VDLPlaybackViewController.xib
└── popcorntime_api.paw
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# Created by https://www.gitignore.io
### OSX ###
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear on external disk
.Spotlight-V100
.Trashes
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Xcode ###
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.xcuserstate
### Objective-C ###
# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
#
*.xcworkspace/
Pods/
================================================
FILE: Podfile
================================================
# Uncomment this line to define a global platform for your project
# platform :ios, '8.0'
inhibit_all_warnings!
source 'https://github.com/danylokostyshyn/private-podspecs.git'
source 'https://github.com/CocoaPods/Specs'
target 'PopcornTime' do
pod 'private-MobileVLCKit'
pod 'private-boost'
pod 'private-libtorrent'
pod 'private-openssl'
pod 'Reachability'
pod 'CocoaSecurity'
pod 'SDWebImage'
pod 'Crashlytics'
pod 'Fabric'
end
================================================
FILE: PopcornTime/Controllers/AnimeDetailsViewController.swift
================================================
//
// ShowDetailsViewController.swift
// PopcornTime
//
// Created by Andrew K. on 3/13/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
import UIKit
class AnimeDetailsViewController: BaseDetailsViewController {
var anime: Anime! {
get {
return self.item as! Anime
}
}
// MARK: - UIViewController
override func viewDidLoad() {
super.viewDidLoad()
preferedOtherHeadersHeight = 0.0
}
// MARK: - BaseDetailsViewController
override func reloadData() {
PTAPIManager.shared().showInfo(with: .anime, withId: item.identifier, success: { (item) -> Void in
guard let item = item else { return }
self.anime.update(item)
self.collectionView?.reloadData()
}, failure: nil)
}
// MARK: - DetailViewControllerDataSource
override func numberOfSeasons() -> Int {
return anime.seasons.count
}
override func numberOfEpisodesInSeason(_ seasonsIndex: Int) -> Int {
return anime.seasons[seasonsIndex].episodes.count
}
override func setupCell(_ cell: EpisodeCell, seasonIndex: Int, episodeIndex: Int) {
let episode = anime.seasons[seasonIndex].episodes[episodeIndex]
cell.titleLabel.text = "\(episode.episodeNumber)"
}
override func setupSeasonHeader(_ header: SeasonHeader, seasonIndex: Int) {
}
override func cellWasPressed(_ cell: UICollectionViewCell, seasonIndex: Int, episodeIndex: Int) {
let episode = anime.seasons[seasonIndex].episodes[episodeIndex]
showVideoPickerPopupForEpisode(episode, basicInfo: self.item, fromView: cell)
}
override func cellWasLongPressed(_ cell: UICollectionViewCell, seasonIndex: Int, episodeIndex: Int) {
// let episode = anime.episodeFor(seasonIndex: seasonIndex, episodeIndex: episodeIndex)
// let seasonEpisodes = anime.episodesFor(seasonIndex: seasonIndex)
}
}
================================================
FILE: PopcornTime/Controllers/AnimeViewController.swift
================================================
//
// MoviesViewController.swift
// PopcornTime
//
// Created by Andrew K. on 3/15/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
import UIKit
class AnimeViewController: PagedViewController {
override var showType: PTItemType {
get {
return .anime
}
}
override func map(_ response: [AnyObject]) -> [BasicInfo] {
return response.map({ Anime(dictionary: $0 as! [AnyHashable: Any]) })
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath){
//Check if cell is MoreShowsCell
if let _ = cell as? MoreShowsCollectionViewCell{
loadMore()
} else {
performSegue(withIdentifier: "showDetails", sender: cell)
}
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if segue.identifier == "showDetails"{
if let episodesVC = segue.destination as? AnimeDetailsViewController{
if let senderCell = sender as? UICollectionViewCell{
if let indexPath = collectionView!.indexPath(for: senderCell){
var item: BasicInfo!
if (searchController!.isActive) {
item = searchResults[indexPath.row]
} else {
item = items[indexPath.row]
}
episodesVC.item = item as! Anime
}
}
}
}
}
}
================================================
FILE: PopcornTime/Controllers/BarHidingViewController.swift
================================================
//
// BarHidingViewController.swift
// PopcornTime
//
// Created by Andrew K. on 3/13/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
import UIKit
class BarHidingViewController: UIViewController {
fileprivate var barsVisible:Bool = true {
willSet {
self.tabBarController?.tabBar.isHidden = !newValue
self.navigationController?.navigationBar.isHidden = !newValue
UIApplication.shared.setStatusBarHidden(!newValue, with: .fade)
}
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
//Hide bars if needed
let sizeClass = (horizontal: self.view.traitCollection.horizontalSizeClass, vertical: self.view.traitCollection.verticalSizeClass)
switch sizeClass{
case (_,.compact):
self.barsVisible = false
default: self.barsVisible = true
}
}
}
================================================
FILE: PopcornTime/Controllers/BaseCollectionViewController.swift
================================================
//
// ShowsCollectionViewController.swift
// PopcornTime
//
// Created by Andrew K. on 3/8/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
import UIKit
let reuseIdentifierShow = "ShowCell"
let reuseIdentifierMore = "MoreShowsCell"
///Base class for displaying collection of shows, subclass MUST override reloadData() and set self.shows in it
class BaseCollectionViewController: BarHidingViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UICollectionViewDelegate {
fileprivate struct Constants{
static let desirediPadCellWidth = 160
static let desirediPadCellHeight = 205
static let numberOfLinesiPhonePortrait = 2
static let numberOfItemsiPhonePortrait = 2
static let numberOfLinesiPhoneLandscape = 2
static let numberOfItemsiPhoneLandscape = 5
}
var items = [BasicInfo]()
var showLoadMoreCell = false
@IBOutlet weak var collectionView: UICollectionView!{
didSet{
collectionView.alwaysBounceVertical = true
collectionView.dataSource = self
collectionView.delegate = self
}
}
@IBOutlet weak var collectionViewLayout: UICollectionViewFlowLayout!
// MARK: UIViewController
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView!.register(UINib(nibName: "ShowCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: reuseIdentifierShow)
self.collectionView!.register(UINib(nibName: "MoreShowsCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: reuseIdentifierMore)
self.collectionView?.delegate = self
self.collectionView?.collectionViewLayout.invalidateLayout()
self.reloadData()
}
// MARK: UICollectionViewDataSource
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
let additionalCellsCount = self.showLoadMoreCell ? 1 : 0
return (items.count + additionalCellsCount)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if (self.showLoadMoreCell && indexPath.row == items.count) {
//Last cell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifierMore, for: indexPath) as! MoreShowsCollectionViewCell
return cell
} else {
//Ordinary show cell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifierShow, for: indexPath) as! ShowCollectionViewCell
let item = items[indexPath.row]
cell.title = item.title
let imageItem = item.smallImage
switch imageItem?.status {
case .new?:
imageItem?.status = .downloading
ImageProvider.sharedInstance.imageFromURL(URL: imageItem?.URL) { (downloadedImage) -> () in
imageItem?.image = downloadedImage
imageItem?.status = .finished
collectionView.reloadItems(at: [indexPath])
}
case .finished?:
cell.image = imageItem?.image
default: break
}
return cell
}
}
// MARK: UICollectionViewDelegateFlowLayout & UICollectionViewDelegate
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let visibleAreaHeight = collectionView.bounds.height - navigationController!.navigationBar.bounds.height - UIApplication.shared.statusBarFrame.height - self.tabBarController!.tabBar.bounds.height
let visibleAreaWidth = collectionView.bounds.width
//Set cell size based on size class.
let sizeClass = (horizontal: self.view.traitCollection.horizontalSizeClass, vertical: self.view.traitCollection.verticalSizeClass)
if let flowLayout = collectionViewLayout as? UICollectionViewFlowLayout{
switch sizeClass{
case (.compact,.regular):
//iPhone portrait
let cellWidth = ((visibleAreaWidth - CGFloat(Constants.numberOfItemsiPhonePortrait - 1)*flowLayout.minimumInteritemSpacing - flowLayout.sectionInset.top - flowLayout.sectionInset.bottom)/CGFloat(Constants.numberOfItemsiPhonePortrait))
let cellHeight = ((visibleAreaHeight - CGFloat(Constants.numberOfLinesiPhonePortrait - 1)*flowLayout.minimumLineSpacing - flowLayout.sectionInset.left - flowLayout.sectionInset.right)/CGFloat(Constants.numberOfLinesiPhonePortrait))
return CGSize(width: cellWidth, height: cellHeight)
case (_,.compact):
//iPhone landscape
let cellWidth = ((collectionView.bounds.width - CGFloat(Constants.numberOfItemsiPhoneLandscape - 1)*flowLayout.minimumInteritemSpacing - flowLayout.sectionInset.top - flowLayout.sectionInset.bottom)/CGFloat(Constants.numberOfItemsiPhoneLandscape))
let cellHeight = ((collectionView.bounds.height - CGFloat(Constants.numberOfLinesiPhoneLandscape - 1)*flowLayout.minimumLineSpacing - flowLayout.sectionInset.left - flowLayout.sectionInset.right)/CGFloat(Constants.numberOfLinesiPhoneLandscape))
return CGSize(width: cellWidth, height: cellHeight)
case (_,_):
// iPad. Calculate cell size based on desired size
let numberOfLines = Int(visibleAreaHeight) / Constants.desirediPadCellHeight
let betweenLinesSpaceSum = CGFloat(numberOfLines - 1) * flowLayout.minimumLineSpacing
let sectionInsetsVerticalSum = flowLayout.sectionInset.top + flowLayout.sectionInset.bottom
let adjustedHeight = (visibleAreaHeight - betweenLinesSpaceSum - sectionInsetsVerticalSum)/CGFloat(numberOfLines)
let adjustedWidth = adjustedHeight * CGFloat(Constants.desirediPadCellWidth) / CGFloat(Constants.desirediPadCellHeight)
return CGSize(width: adjustedWidth, height: adjustedHeight)
}
}
return CGSize(width: 50, height: 50)
}
// MARK: - ShowsCollectionViewController
func reloadData(){
assert(true, "Sublcass MUST override this method")
}
}
================================================
FILE: PopcornTime/Controllers/BaseDetailsViewController.swift
================================================
//
// BaseDetailsViewController.swift
// PopcornTime
//
// Created by Danylo Kostyshyn on 3/21/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
import UIKit
/// With this protocol we encapsulate calls of collectionView indexPathes. For now we have one extra section at the top (empty one with stratchy header), this way if anything changes here we will change all logic here, and all users of this protocol will not have to hcange anything. So it's a good idea to use seasonIndex, episodeIndex instead of indexPathes.
protocol DetailViewControllerDataSource {
func numberOfSeasons() -> Int
func numberOfEpisodesInSeason(_ seasonsIndex: Int) -> Int
func setupCell(_ cell: EpisodeCell, seasonIndex: Int, episodeIndex: Int)
func setupSeasonHeader(_ header: SeasonHeader, seasonIndex: Int)
func cellWasPressed(_ cell: UICollectionViewCell, seasonIndex: Int, episodeIndex: Int)
func cellWasLongPressed(_ cell: UICollectionViewCell, seasonIndex: Int, episodeIndex: Int)
}
class BaseDetailsViewController: BarHidingViewController, VDLPlaybackViewControllerDelegate, LoadingViewControllerDelegate, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, DetailViewControllerDataSource {
// MARK: - Header related
let headerMinAspectRatio: CGFloat = 0.4
let headerWidthToCollectionWidthKoef: CGFloat = 0.3
var header: StratchyHeader?
var preferedOtherHeadersHeight: CGFloat = 35
var headerSize: CGSize {
let width = collectionView.bounds.size.width
let minHeight = width * headerMinAspectRatio
var height = collectionView.bounds.size.height * headerWidthToCollectionWidthKoef
height = max(height, minHeight)
return CGSize(width: width, height: height)
}
// MARK: -
let cellReuseIdentifier = "EpisodeCell"
let firstHeaderReuseIdentifier = "StratchyHeader"
let otherHeadersReuseIdentifier = "OtherHeader"
let episodeCellReuseIdentifier = "EpisodeCell"
var layout: StratchyHeaderLayout?
var item: BasicInfo! {
didSet {
navigationItem.title = item.title
reloadData()
}
}
@IBOutlet weak var collectionView: UICollectionView!{
didSet{
collectionView.alwaysBounceVertical = true
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(UINib(nibName: "StratchyHeader", bundle: nil), forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: firstHeaderReuseIdentifier)
collectionView.register(UINib(nibName: "SeasonHeader", bundle: nil), forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: otherHeadersReuseIdentifier)
collectionView.register(UINib(nibName: "EpisodeCell", bundle: nil), forCellWithReuseIdentifier: episodeCellReuseIdentifier)
layout = collectionView.collectionViewLayout as? StratchyHeaderLayout
}
}
// MARK: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
configureFavoriteBarButton()
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(BaseDetailsViewController.longPress(_:)))
longPress.minimumPressDuration = 0.5
longPress.delaysTouchesBegan = true
collectionView.addGestureRecognizer(longPress)
}
final func longPress(_ gesture: UILongPressGestureRecognizer) {
if gesture.state == .ended {
return
}
let p = gesture.location(in: collectionView)
if let indexPath = collectionView.indexPathForItem(at: p) {
if let cell = collectionView.cellForItem(at: indexPath) {
cellWasLongPressed(cell, seasonIndex: indexPath.section - 1, episodeIndex: indexPath.item)
}
}
}
func configureFavoriteBarButton() {
if (item.isFavorite) {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage.removeFromFavoritesImage(),
style: .done, target: self, action: #selector(BaseDetailsViewController.removeFromFavorites))
} else {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage.addToFavoritesImage(),
style: .done, target: self, action: #selector(BaseDetailsViewController.addToFavorites))
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// update header size
header?.headerSize = headerSize
layout?.headerSize = headerSize
}
// MARK: - Favorites
func addToFavorites() {
DataManager.sharedManager().addToFavorites(item)
configureFavoriteBarButton()
}
func removeFromFavorites() {
DataManager.sharedManager().removeFromFavorites(item)
configureFavoriteBarButton()
}
// MARK: - BaseDetailsViewController
func reloadData() {
}
func startPlayback(_ episode: Episode, basicInfo: BasicInfo, magnetLink: String, loadingTitle: String) {
let loadingVC = self.storyboard?.instantiateViewController(withIdentifier: "loadingViewController") as! LoadingViewController
loadingVC.delegate = self
loadingVC.status = "Downloading..."
loadingVC.loadingTitle = loadingTitle
loadingVC.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
self.tabBarController?.present(loadingVC, animated: true, completion: nil)
PTTorrentStreamer.shared().startStreaming(fromFileOrMagnetLink: magnetLink, progress: { (status) -> Void in
loadingVC.progress = status.bufferingProgress
loadingVC.speed = Int(status.downloadSpeed)
loadingVC.seeds = Int(status.seeds)
loadingVC.peers = Int(status.peers)
}, readyToPlay: { (url) -> Void in
loadingVC.dismiss(animated: false, completion: nil)
let vdl = VDLPlaybackViewController(nibName: "VDLPlaybackViewController", bundle: nil)
vdl.delegate = self
self.navigationController?.present(vdl, animated: true, completion: nil)
vdl.playMedia(from: url)
}, failure: { (error) -> Void in
loadingVC.dismiss(animated: true, completion: nil)
})
}
// MARK: - VDLPlaybackViewControllerDelegate
func playbackControllerDidFinishPlayback(_ playbackController: VDLPlaybackViewController!) {
self.navigationController?.dismiss(animated: true, completion: nil)
PTTorrentStreamer.shared().cancelStreaming()
}
// MARK: - LoadingViewControllerDelegate
func didCancelLoading(_ controller: LoadingViewController) {
PTTorrentStreamer.shared().cancelStreaming()
controller.dismiss(animated: true, completion: nil)
}
// MARK: - UICollectionViewDataSource
final func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
switch section {
case 0: return 0
default:
let seasonIndex = section - 1
return self.numberOfEpisodesInSeason(seasonIndex)
}
}
final func numberOfSections(in collectionView: UICollectionView) -> Int {
return self.numberOfSeasons() + 1 // extra section for header
}
final func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let seasonIndex = indexPath.section - 1
let episode = indexPath.item
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellReuseIdentifier, for: indexPath) as! EpisodeCell
self.setupCell(cell, seasonIndex: seasonIndex, episodeIndex: episode)
return cell
}
final func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
switch section {
case 0: return headerSize
default : return CGSize(width: collectionView.bounds.width, height: preferedOtherHeadersHeight)
}
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
if indexPath.section == 0 {
if (header == nil){
header = (collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: firstHeaderReuseIdentifier, for: indexPath) as! StratchyHeader)
header?.delegate = layout
if let image = item.bigImage?.image {
header?.image = image
} else {
ImageProvider.sharedInstance.imageFromURL(URL: item.bigImage?.URL) { (downloadedImage) -> () in
self.item.bigImage?.image = downloadedImage
self.header?.image = downloadedImage
}
}
if let image = item.smallImage?.image {
header?.foregroundImage.image = image
} else {
ImageProvider.sharedInstance.imageFromURL(URL: item.smallImage?.URL) { (downloadedImage) -> () in
self.item.smallImage?.image = downloadedImage
self.header?.foregroundImage.image = downloadedImage
}
}
}
header!.synopsisTextView.text = item.synopsis
return header!
} else {
let otherHeader = (collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: otherHeadersReuseIdentifier, for: indexPath) as! SeasonHeader)
let seasonIndex = (indexPath.section - 1)
self.setupSeasonHeader(otherHeader, seasonIndex: seasonIndex)
return otherHeader
}
}
// MARK: - UICollectionViewDelegate
final func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) {
cellWasPressed(cell, seasonIndex: indexPath.section - 1, episodeIndex: indexPath.item)
}
}
func showVideoPickerPopupForEpisode(_ episode: Episode, basicInfo: BasicInfo, fromView view: UIView) {
let videos = episode.videos
if (videos.count > 0) {
let actionSheetController = UIAlertController(title: episode.title, message: episode.desc, preferredStyle: UIAlertControllerStyle.actionSheet)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
actionSheetController.addAction(cancelAction)
for video in videos {
var title = ""
if let subGroup = video.subGroup {
title += "[\(subGroup)] "
}
if let quality = video.quality {
title += quality
}
let action = UIAlertAction(title: title, style: UIAlertActionStyle.default, handler: { (action) -> Void in
let magnetLink = video.magnetLink
let episodeTitle = episode.title ?? ""
let loadingTitle = "\(episodeTitle) - \(title)"
self.startPlayback(episode, basicInfo: basicInfo , magnetLink: magnetLink, loadingTitle: loadingTitle)
})
actionSheetController.addAction(action)
}
let popOver = actionSheetController.popoverPresentationController
popOver?.sourceView = view
popOver?.sourceRect = view.bounds
popOver?.permittedArrowDirections = UIPopoverArrowDirection.any
self.present(actionSheetController, animated: true, completion: nil)
}
}
// MARK: - DetailViewControllerDataSource
func numberOfSeasons() -> Int {
assertionFailure("Should be overriden by subclass")
return 0
}
func numberOfEpisodesInSeason(_ seasonsIndex: Int) -> Int {
assertionFailure("Should be overriden by subclass")
return 0
}
func setupCell(_ cell: EpisodeCell, seasonIndex: Int, episodeIndex: Int) {
assertionFailure("Should be overriden by subclass")
}
func setupSeasonHeader(_ header: SeasonHeader, seasonIndex: Int) {
assertionFailure("Should be overriden by subclass")
}
func cellWasPressed(_ cell: UICollectionViewCell, seasonIndex: Int, episodeIndex: Int) {
assertionFailure("Should be overriden by subclass")
}
func cellWasLongPressed(_ cell: UICollectionViewCell, seasonIndex: Int, episodeIndex: Int) {
}
}
================================================
FILE: PopcornTime/Controllers/ColorfullTabBarController.swift
================================================
//
// ColorfullTabBarController.swift
// PopcornTime
//
// Created by Andrew K. on 4/10/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
import UIKit
class ColorfullTabBarController: UITabBarController, UITabBarControllerDelegate {
fileprivate struct ColorConstants {
static let favoritesTintColor = UIColor(red: 235/255, green: 66/255, blue: 69/255, alpha: 1.0)
static let moviesTintColor = UIColor(red: 66/255, green: 166/255, blue: 235/255, alpha: 1.0)
static let showsTintColor = UIColor(red: 33/255, green: 181/255, blue: 42/255, alpha: 1.0)
static let animeTintColor = UIColor(red: 235/255, green: 66/255, blue: 164/255, alpha: 1.0)
}
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
deinit {
NotificationCenter.default.removeObserver(self)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
assignColors()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
assignColors()
}
fileprivate func assignColors() {
switch selectedIndex {
case 0: view.window?.tintColor = ColorConstants.favoritesTintColor
case 1: view.window?.tintColor = ColorConstants.moviesTintColor
case 2: view.window?.tintColor = ColorConstants.showsTintColor
case 3: view.window?.tintColor = ColorConstants.animeTintColor
default: break
}
}
// MARK: - UITabBarControllerDelegate
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
self.assignColors()
}
}
================================================
FILE: PopcornTime/Controllers/FavoritesViewController.swift
================================================
//
// FavoritesViewController.swift
// PopcornTime
//
// Created by Danylo Kostyshyn on 3/13/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
import UIKit
class FavoritesViewController: PagedViewController {
override func reloadData() {
if let favoriteItems = DataManager.sharedManager().favorites {
self.items = favoriteItems
self.collectionView?.reloadData()
}
}
override func loadMore() {
}
// MARK: View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
// No search in favorites
self.searchController = nil
self.navigationItem.titleView = nil
NotificationCenter.default.addObserver(
self,
selector: #selector(FavoritesViewController.favoritesDidChange(_:)),
name: NSNotification.Name(rawValue: Notifications.FavoritesDidChangeNotification),
object: nil
)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let item = sender as? BasicInfo {
if let episodesVC = segue.destination as? BaseDetailsViewController {
switch item {
case item as Anime:
episodesVC.item = item as! Anime
case item as Show:
episodesVC.item = item as! Show
case item as Movie:
episodesVC.item = item as! Movie
default:
break
}
}
}
}
// MARK: - Notifications
func favoritesDidChange(_ notification: Notification) {
self.reloadData()
}
// MARK: - UICollectionViewDelegate
func collectionView(_ collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: IndexPath) {
let item = self.items[indexPath.row]
switch item {
case item as Anime:
performSegue(withIdentifier: "showDetailsForFavoriteAnime", sender: item)
case item as Show:
performSegue(withIdentifier: "showDetailsForFavoriteShow", sender: item)
case item as Movie:
performSegue(withIdentifier: "showDetailsForFavoriteMovie", sender: item)
default:
break
}
}
}
================================================
FILE: PopcornTime/Controllers/LoadingViewController.swift
================================================
//
// LoadingViewController.swift
// PopcornTime
//
// Created by Danylo Kostyshyn on 3/15/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
import UIKit
protocol LoadingViewControllerDelegate {
func didCancelLoading(_ controller: LoadingViewController)
}
class LoadingViewController: UIViewController {
var delegate: LoadingViewControllerDelegate?
@IBOutlet fileprivate weak var statusLabel: UILabel!
@IBOutlet fileprivate weak var progressLabel: UILabel!
@IBOutlet fileprivate weak var progressView: UIProgressView!
@IBOutlet fileprivate weak var speedLabel: UILabel!
@IBOutlet fileprivate weak var seedsLabel: UILabel!
@IBOutlet fileprivate weak var peersLabel: UILabel!
@IBOutlet fileprivate weak var titleLabel: UILabel!
var status: String? = nil {
didSet {
if let status = status {
statusLabel?.text = status
}
}
}
var progress: Float = 0.0 {
didSet {
progressView.progress = progress
progressLabel.text = String(format: "%.0f%%", progress*100)
}
}
var speed: Int = 0 { // bytes/s
didSet {
let formattedSpeed = ByteCountFormatter.string(fromByteCount: Int64(speed), countStyle: .binary) + "/s"
speedLabel.text = String(format:"Speed: %@", formattedSpeed)
}
}
var seeds: Int = 0 {
didSet {
seedsLabel.text = String(format: "Seeds: %d", seeds)
}
}
var peers: Int = 0 {
didSet {
peersLabel.text = String(format: "Peers: %d", peers)
}
}
var loadingTitle: String? = nil {
didSet {
if let title = loadingTitle {
titleLabel?.text = title
}
}
}
// MARK: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
titleLabel?.text = loadingTitle
status = "Loading..."
progress = 0.0
speed = 0
seeds = 0
peers = 0
UIApplication.shared.isIdleTimerDisabled = true;
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidLoad()
UIApplication.shared.isIdleTimerDisabled = false;
}
// MARK: - Actions
@IBAction fileprivate func cancelButtonPressed(_ sender: AnyObject) {
delegate?.didCancelLoading(self)
}
}
================================================
FILE: PopcornTime/Controllers/MovieDetailsViewController.swift
================================================
//
// ShowDetailsViewController.swift
// PopcornTime
//
// Created by Andrew K. on 3/13/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
import UIKit
class MovieDetailsViewController: BaseDetailsViewController {
var movie: Movie! {
get {
return self.item as! Movie
}
}
// MARK: - UIViewController
override func viewDidLoad() {
super.viewDidLoad()
preferedOtherHeadersHeight = 0.0
}
// MARK: - BaseDetailsViewController
override func reloadData() {
PTAPIManager.shared().showInfo(with: .movie, withId: item.identifier, success: { (item) -> Void in
guard let item = item else { return }
self.movie.update(item)
self.collectionView?.reloadData()
}, failure: nil)
}
// MARK: - DetailViewControllerDataSource
override func numberOfSeasons() -> Int {
return 1
}
override func numberOfEpisodesInSeason(_ seasonsIndex: Int) -> Int {
return movie.videos.count
}
override func setupCell(_ cell: EpisodeCell, seasonIndex: Int, episodeIndex: Int) {
let video = movie.videos[episodeIndex]
var title = ""
if let quality = video.quality {
title += quality + " "
}
if let name = video.name {
title += name
}
cell.titleLabel.text = title
}
override func setupSeasonHeader(_ header: SeasonHeader, seasonIndex: Int) {
}
override func cellWasPressed(_ cell: UICollectionViewCell, seasonIndex: Int, episodeIndex: Int) {
let video = movie.videos[episodeIndex]
let magnetLink = video.magnetLink
let title = movie.title ?? ""
let fakeEpisode = Episode(title: title, desc: "", seasonNumber: 0, episodeNumber: 0, videos: [Video]())
startPlayback(fakeEpisode, basicInfo: movie, magnetLink: magnetLink, loadingTitle: title)
}
}
================================================
FILE: PopcornTime/Controllers/MoviesViewController.swift
================================================
//
// MoviesViewController.swift
// PopcornTime
//
// Created by Andrew K. on 3/19/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
import UIKit
class MoviesViewController: PagedViewController {
override var showType: PTItemType {
get {
return .movie
}
}
override func map(_ response: [AnyObject]) -> [BasicInfo] {
let items = response.map({ Movie(dictionary: $0 as! [AnyHashable: Any]) }) as [BasicInfo]
return items
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath){
//Check if cell is MoreShowsCell
if let _ = cell as? MoreShowsCollectionViewCell{
loadMore()
} else {
performSegue(withIdentifier: "showDetails", sender: cell)
}
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if segue.identifier == "showDetails"{
if let episodesVC = segue.destination as? MovieDetailsViewController{
if let senderCell = sender as? UICollectionViewCell{
if let indexPath = collectionView!.indexPath(for: senderCell) {
var item: BasicInfo!
if (searchController!.isActive) {
item = searchResults[indexPath.row]
} else {
item = items[indexPath.row]
}
episodesVC.item = item as! Movie
}
}
}
}
}
}
================================================
FILE: PopcornTime/Controllers/OAuthViewController.swift
================================================
//
// WebViewController.swift
// PopcornTime
//
// Created by Danylo Kostyshyn on 3/15/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
import UIKit
protocol OAuthViewControllerDelegate {
func oauthViewControllerDidFinish(_ controller: OAuthViewController, token: String?, error: NSError?)
}
class OAuthViewController: UIViewController, UIWebViewDelegate {
@IBOutlet weak var webView: UIWebView!
@IBOutlet weak var navigationBar: UINavigationBar!
var delegate: OAuthViewControllerDelegate?
var URL: Foundation.URL?
// MARK: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
if let URL = URL {
webView.loadRequest(URLRequest(url: URL))
}
}
// MARK: - UIWebViewDelegate
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if let code = request.url?.lastPathComponent {
if code.characters.count == 64 {
// PTAPIManager.sharedManager().accessTokenWithAuthorizationCode(code, success: { (accessToken) -> Void in
// println("OAuth access token: \(accessToken)")
// self.delegate?.oauthViewControllerDidFinish(self, token: accessToken, error: nil)
// }, failure: { (error) -> Void in
// println("\(error)")
// self.delegate?.oauthViewControllerDidFinish(self, token: nil, error: error)
// })
}
}
return true;
}
func webViewDidStartLoad(_ webView: UIWebView) {
}
func webViewDidFinishLoad(_ webView: UIWebView) {
}
func webView(_ webView: UIWebView, didFailLoadWithError error: Error) {
delegate?.oauthViewControllerDidFinish(self, token: nil, error: error as NSError?)
}
// MARK: - Actions
@IBAction func cancelButtonPressed(_ sender: AnyObject) {
delegate?.oauthViewControllerDidFinish(self, token: nil, error: nil)
}
}
================================================
FILE: PopcornTime/Controllers/PagedViewController.swift
================================================
//
// PagedViewController.swift
// PopcornTime
//
// Created by Andrew K. on 3/19/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
import UIKit
class PagedViewController: BaseCollectionViewController, UISearchBarDelegate, UISearchResultsUpdating {
fileprivate var contentPage: UInt = 0
var searchResults = [BasicInfo]()
var searchController: UISearchController?
var searchTimer: Timer?
var showType: PTItemType {
get {
assert(false, "this must be overriden by subclass")
return .movie
}
}
// MARK: View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
setupSearch()
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
collectionViewLayout?.invalidateLayout()
}
fileprivate func setupSearch() {
self.definesPresentationContext = true
searchController = UISearchController(searchResultsController: nil)
searchController!.searchResultsUpdater = self
searchController!.hidesNavigationBarDuringPresentation = false
searchController!.dimsBackgroundDuringPresentation = false
let searchBar = searchController!.searchBar
searchBar.delegate = self
searchBar.barStyle = .black
searchBar.backgroundImage = UIImage()
let searchBarContainer = UIView(frame: navigationController!.navigationBar.bounds)
searchBarContainer.addSubview(searchBar)
searchBar.translatesAutoresizingMaskIntoConstraints = false
navigationItem.titleView = searchBarContainer
let views = ["searchBar" : searchBar]
searchBarContainer.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[searchBar]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views))
searchBarContainer.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[searchBar]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views))
}
// MARK:
func map(_ response: [AnyObject]) -> [BasicInfo] {
return [BasicInfo]()
}
override func reloadData() {
PTAPIManager.shared().topShows(with: showType, withPage: contentPage, success: { (items) -> Void in
self.showLoadMoreCell = true
if let items = items {
self.items = self.map(items as [AnyObject])
self.collectionView?.reloadData()
}
}, failure: nil)
}
func loadMore() {
PTAPIManager.shared().topShows(with: showType, withPage: contentPage+1, success: { (items) -> Void in
if let items = items {
self.contentPage += 1
let newItems = self.map(items as [AnyObject])
let newShowsIndexPathes = newItems.enumerated().map({ (index, item) in
return IndexPath(row: (self.items.count + index), section: 0)
})
self.items += newItems
self.collectionView?.insertItems(at: newShowsIndexPathes)
}
}, failure: nil)
}
func performSearch() {
let text = searchController!.searchBar.text
if text!.characters.count > 0 {
PTAPIManager.shared().searchForShow(with: showType, name: text, success: { (items) -> Void in
self.showLoadMoreCell = false
if let items = items {
self.searchResults = self.map(items as [AnyObject])
} else {
self.searchResults.removeAll(keepingCapacity: false)
}
self.collectionView?.reloadData()
}, failure: nil)
}
}
// MARK: UICollectionViewDataSource
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if searchController != nil && searchController!.isActive {
return searchResults.count
}
return super.collectionView(collectionView, numberOfItemsInSection: section)
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if searchController != nil && searchController!.isActive {
//Ordinary show cell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifierShow, for: indexPath) as! ShowCollectionViewCell
let item = searchResults[indexPath.row]
cell.title = item.title
let imageItem = item.smallImage
switch imageItem?.status {
case .new?:
imageItem?.status = .downloading
ImageProvider.sharedInstance.imageFromURL(URL: imageItem?.URL) { (downloadedImage) -> () in
imageItem?.image = downloadedImage
imageItem?.status = .finished
collectionView.reloadItems(at: [indexPath])
}
case .finished?:
cell.image = imageItem?.image
default: break
}
return cell
}
return super.collectionView(collectionView, cellForItemAt: indexPath)
}
// MARK: UICollectionViewDelegate
func collectionView(_ collectionView: UICollectionView, willDisplayCell cell: UICollectionViewCell, forItemAtIndexPath indexPath: IndexPath) {
if indexPath.row == items.count {
loadMore()
}
}
// MARK: UISearchBarDelegate
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
self.searchTimer?.invalidate()
self.searchTimer = nil
performSearch()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
self.showLoadMoreCell = true
self.searchTimer?.invalidate()
self.searchTimer = nil
searchResults.removeAll(keepingCapacity: false)
self.collectionView.reloadData()
}
// MARK: UISearchResultsUpdating
func updateSearchResults(for searchController: UISearchController) {
self.searchTimer?.invalidate()
self.collectionView.reloadData()
self.searchTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(PagedViewController.performSearch), userInfo: nil, repeats: false)
}
}
================================================
FILE: PopcornTime/Controllers/ParseViewController.swift
================================================
//
// ParseViewController.swift
//
//
// Created by Andriy K. on 6/22/15.
//
//
import UIKit
class ParseViewController: UIViewController, PFLogInViewControllerDelegate {
private var canPromptLogin = true
// MARK: - UIViewController
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
promptLoginIfNeeded(true)
}
// MARK: - Login
func promptLoginIfNeeded(animated: Bool) {
let currentUser = ParseManager.sharedInstance.user
if currentUser == nil {
// Show the signup or login screen
let logInController = PFLogInViewController()
logInController.delegate = self
logInController.fields =
[PFLogInFields.DismissButton, PFLogInFields.Facebook]
logInController.facebookPermissions = ["public_profile"]
self.presentViewController(logInController, animated:animated, completion: nil)
}
}
// MARK: PFLogInViewControllerDelegate
func logInViewController(logInController: PFLogInViewController, didLogInUser user: PFUser) {
dissmiss(nil)
}
func logInViewController(logInController: PFLogInViewController, didFailToLogInWithError error: NSError?) {
}
func logInViewControllerDidCancelLogIn(logInController: PFLogInViewController) {
dissmiss(nil)
dissmiss(nil)
}
// MARK: - Actions
@IBAction func dissmiss(sender: AnyObject?) {
canPromptLogin = false
self.dismissViewControllerAnimated(true, completion: nil)
}
@IBAction func logOutPressed(sender: UIBarButtonItem) {
PFUser.logOut()
dissmiss(nil)
}
@IBAction func clearAllDataPressed(sender: UIBarButtonItem) {
}
}
================================================
FILE: PopcornTime/Controllers/SettingsViewController.swift
================================================
//
// SettingsViewController.swift
// PopcornTime
//
// Created by Danylo Kostyshyn on 4/4/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
import UIKit
class SettingsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
// MARK: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: -
func appInfoString() -> String {
let displayName = Bundle.main.infoDictionary?["CFBundleDisplayName"] as! String
let version = Bundle.main.infoDictionary?["CFBundleVersion"] as! String
let shortVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as! String
return "\(displayName) \(shortVersion) (\(version))"
}
// MARK: - UITableViewDataSource
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier = "SettingsCell"
var cell: UITableViewCell! = tableView.dequeueReusableCell(withIdentifier: "SettingsCell")
if cell == nil {
cell = UITableViewCell(style: .default, reuseIdentifier: identifier)
}
cell.textLabel?.text = "Hello, PopcornTime!"
return cell
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let label = UILabel(frame: CGRect(x: 0.0, y: 0.0, width: tableView.bounds.width, height: 0.0))
label.backgroundColor = UIColor.clear
label.font = UIFont.systemFont(ofSize: 14.0)
label.text = appInfoString()
label.textAlignment = .center
return label
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 30.0
}
// MARK: - UITableViewDelegate
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView .deselectRow(at: indexPath, animated: true)
}
@IBAction func doneButtonTapped(_ sender: AnyObject) {
self.dismiss(animated: true, completion: nil)
}
}
================================================
FILE: PopcornTime/Controllers/ShowDetailsViewController.swift
================================================
//
// ShowDetailsViewController.swift
// PopcornTime
//
// Created by Andrew K. on 3/13/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
import UIKit
class ShowDetailsViewController: BaseDetailsViewController {
var show: Show! {
get {
return self.item as! Show
}
}
// MARK: - BaseDetailsViewController
override func reloadData() {
PTAPIManager.shared().showInfo(with: .show, withId: show.identifier, success: { (item) -> Void in
guard let item = item else { return }
self.show.update(item)
self.collectionView?.reloadData()
}, failure: nil)
}
// MARK: - DetailViewControllerDataSource
override func numberOfSeasons() -> Int {
return show.seasons.count
}
override func numberOfEpisodesInSeason(_ seasonsIndex: Int) -> Int {
return show.seasons[seasonsIndex].episodes.count
}
override func setupCell(_ cell: EpisodeCell, seasonIndex: Int, episodeIndex: Int) {
let episode = show.seasons[seasonIndex].episodes[episodeIndex]
if let title = episode.title {
cell.titleLabel.text = "S\(episode.seasonNumber)E\(episode.episodeNumber): \(title)"
} else {
cell.titleLabel.text = "S\(episode.seasonNumber)E\(episode.episodeNumber)"
}
}
override func setupSeasonHeader(_ header: SeasonHeader, seasonIndex: Int) {
let seasonNumber = self.show.seasons[seasonIndex].seasonNumber
header.titleLabel.text = "Season \(seasonNumber)"
}
override func cellWasPressed(_ cell: UICollectionViewCell, seasonIndex: Int, episodeIndex: Int) {
let episode = show.episodeFor(seasonIndex: seasonIndex, episodeIndex: episodeIndex)
showVideoPickerPopupForEpisode(episode, basicInfo: self.item, fromView: cell)
}
override func cellWasLongPressed(_ cell: UICollectionViewCell, seasonIndex: Int, episodeIndex: Int) {
// let episode = show.episodeFor(seasonIndex: seasonIndex, episodeIndex: episodeIndex)
// let seasonEpisodes = show.episodesFor(seasonIndex: seasonIndex)
}
}
================================================
FILE: PopcornTime/Controllers/ShowsViewController.swift
================================================
//
// TVSeriesShowsViewController.swift
// PopcornTime
//
// Created by Andrew K. on 3/9/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
import UIKit
class ShowsViewController: PagedViewController {
override var showType: PTItemType {
get {
return .show
}
}
override func map(_ response: [AnyObject]) -> [BasicInfo] {
return response.map({ Show(dictionary: $0 as! [AnyHashable: Any]) })
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath){
//Check if cell is MoreShowsCell
if let _ = cell as? MoreShowsCollectionViewCell {
loadMore()
} else {
performSegue(withIdentifier: "showDetails", sender: cell)
}
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if segue.identifier == "showDetails" {
if let episodesVC = segue.destination as? ShowDetailsViewController {
if let senderCell = sender as? UICollectionViewCell {
if let indexPath = collectionView!.indexPath(for: senderCell) {
var item: BasicInfo!
if (searchController!.isActive) {
item = searchResults[indexPath.row]
} else {
item = items[indexPath.row]
}
episodesVC.item = item as! Show
}
}
}
}
}
}
================================================
FILE: PopcornTime/Info.plist
================================================
CFBundleDevelopmentRegion
en
CFBundleDisplayName
Popcorn Time
CFBundleExecutable
${EXECUTABLE_NAME}
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
${PRODUCT_NAME}
CFBundlePackageType
APPL
CFBundleShortVersionString
1.0
CFBundleSignature
????
CFBundleVersion
1.0
Fabric
APIKey
API_KEY
Kits
KitInfo
KitName
Crashlytics
LSRequiresIPhoneOS
NSAppTransportSecurity
NSAllowsArbitraryLoads
UILaunchStoryboardName
Launch Screen
UIMainStoryboardFile
Main
UIRequiredDeviceCapabilities
UIStatusBarHidden
UIStatusBarStyle
UIStatusBarStyleBlackOpaque
UISupportedInterfaceOrientations
UIInterfaceOrientationPortrait
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
UIViewControllerBasedStatusBarAppearance
================================================
FILE: PopcornTime/Models/APIManager.swift
================================================
//
// APIManager.swift
// popcornTime
//
// Created by Danylo Kostyshyn on 3/13/15.
// Copyright (c) 2015 Danylo Kostyshyn. All rights reserved.
//
/*
http://ytspt.re/api/list.json?limit=30&order=desc&sort=seeds
http://ytspt.re/api/listimdb.json?imdb_id=tt2245084
http://ytspt.re/api/list.json?limit=30&keywords=terminator&order=desc&sort=seeds&set=1
http://www.yifysubtitles.com//subtitle-api/big-hero-6-yify-36523.zip
http://eztvapi.re/shows/1?limit=30&order=desc&sort=seeds
http://eztvapi.re/show/tt0898266
http://eztvapi.re/shows/1?limit=30&keywords=the+big+bang&order=desc&sort=seeds
http://ptp.haruhichan.com/list.php?
http://ptp.haruhichan.com/anime.php?id=912
*/
import Foundation
class APIManager {
typealias APIManagerFailure = (error: NSError?) -> ()
typealias APIManagerSuccessItems = (items: [AnyObject]?) -> ()
typealias APIManagerSuccessItem = (item: [String: AnyObject]?) -> ()
private let APIManagerMoviesEndPoint = "http://ytspt.re/api"
private let APIManagerShowsEndPoint = "http://eztvapi.re"
private let APIManagerResultsLimit = 30
class func sharedManager() -> APIManager {
struct Static { static let instance: APIManager = APIManager() }
return Static.instance
}
private func data(url: NSURL, sucess: ((AnyObject?) -> ())?, failure: APIManagerFailure?) {
NSURLSession.sharedSession().dataTaskWithURL(url, completionHandler: {
(data, response, error) -> Void in
var serializationError: NSError?
var JSONObject: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &serializationError)
if let serializationError = serializationError {
println("\(serializationError)")
if let failure = failure {
failure(error: serializationError)
}
}
if let sucess = sucess {
sucess(JSONObject)
}
}).resume()
}
// MARK: Movies
func topMovies(success: APIManagerSuccessItems?, failure: APIManagerFailure?) {
var path = String(format: "list.json?limit=%d&order=desc&sort=seeds", APIManagerResultsLimit)
var url = NSURL(string: APIManagerMoviesEndPoint.stringByAppendingPathComponent(path))
data(url!, sucess: { (JSONObject) -> () in
if let success = success {
var dict = JSONObject as [String: AnyObject]
success(items: dict["MovieList"] as [AnyObject]?)
}
}, failure: failure)
}
func movieInfo(imdbId: String, success: APIManagerSuccessItems?, failure: APIManagerFailure?) {
var path = String(format: "listimdb.json?imdb_id=%@", imdbId)
var url = NSURL(string: APIManagerMoviesEndPoint.stringByAppendingPathComponent(path))
data(url!, sucess: { (JSONObject) -> () in
if let success = success {
var dict = JSONObject as [String: AnyObject]
success(items: dict["MovieList"] as [AnyObject]?)
}
}, failure: failure)
}
func searchMovie(name: String, success: APIManagerSuccessItems?, failure: APIManagerFailure?) {
var path = String(format: "list.json?limit=%lu&keywords=%@&order=desc&sort=seeds&set=1", APIManagerResultsLimit, name)
var url = NSURL(string: APIManagerMoviesEndPoint.stringByAppendingPathComponent(path))
data(url!, sucess: { (JSONObject) -> () in
if let success = success {
var dict = JSONObject as [String: AnyObject]
success(items: dict["MovieList"] as [AnyObject]?)
}
}, failure: failure)
}
// MARK: Shows
func topShows(page: UInt, success: APIManagerSuccessItems?, failure: APIManagerFailure?) {
var path = String(format: "shows/%lu?limit=%lu&order=desc&sort=seeds", (page + 1), APIManagerResultsLimit)
var url = NSURL(string: APIManagerShowsEndPoint.stringByAppendingPathComponent(path))
data(url!, sucess: { (JSONObject) -> () in
if let success = success {
success(items: JSONObject as [AnyObject]?)
}
}, failure: failure)
}
func showInfo(imdbId: String, success: APIManagerSuccessItem?, failure: APIManagerFailure?) {
var path = String(format: "show/%@", imdbId)
var url = NSURL(string: APIManagerShowsEndPoint.stringByAppendingPathComponent(path))
data(url!, sucess: { (JSONObject) -> () in
if let success = success {
success(item: JSONObject as [String: AnyObject]?)
}
}, failure: failure)
}
func searchShow(name: String, success: APIManagerSuccessItems?, failure: APIManagerFailure?) {
var path = String(format: "shows/1?limit=%lu&keywords=%@&sort=seeds", APIManagerResultsLimit, name)
var url = NSURL(string: APIManagerShowsEndPoint.stringByAppendingPathComponent(path))
data(url!, sucess: { (JSONObject) -> () in
if let success = success {
success(items: JSONObject as [AnyObject]?)
}
}, failure: failure)
}
}
================================================
FILE: PopcornTime/Models/Anime.swift
================================================
//
// Anime.swift
// PopcornTime
//
// Created by Danylo Kostyshyn on 3/19/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
import Foundation
class Anime: BasicInfo {
var seasons = [Season]()
required init(dictionary: [AnyHashable: Any]) {
super.init(dictionary: dictionary)
let id = dictionary["id"] as! Int
identifier = "\(id)"
title = dictionary["name"] as? String
year = dictionary["name"] as? String
if let poster = dictionary["malimg"] as? String {
images = [Image]()
let URL = Foundation.URL(string: poster)
let image = Image(URL: URL!, type: .poster)
images.append(image)
}
smallImage = images.filter({$0.type == ImageType.poster}).first
bigImage = smallImage
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
override func update(_ dictionary: [AnyHashable: Any]) {
seasons.removeAll(keepingCapacity: true)
let episodesDicts = dictionary["episodes"] as! [[AnyHashable: Any]]
let seasonNumber:UInt = 0
var videosContainer = [Int: [Video]]()
var episodesContainer = [Int: Episode]()
synopsis = dictionary["synopsis"] as? String
if let sps = synopsis {
synopsis = sps.replacingOccurrences(of: "oatRightHeader\">EditSynopsis\n", with: "")
}
for episodeDict in episodesDicts{
let title = episodeDict["name"] as! String
let numbersFromTitle = numbersFromAnimeTitle(title)
let synopsis = episodeDict["overview"] as? String
if numbersFromTitle.count > 0 {
if let quality = episodeDict["quality"] as? String{
// Get entry data
let subGroup = episodeDict["subgroup"] as? String
let episodeNumber = numbersFromTitle.first!
let magnetLink = episodeDict["magnet"] as! String
let video = Video(name: title, quality: quality, size: 0, duration: 0, subGroup: subGroup, magnetLink: magnetLink)
var videos = videosContainer[episodeNumber]
if (videos == nil) {
videos = [Video]()
videosContainer[episodeNumber] = videos
}
videosContainer[episodeNumber]!.append(video)
var episode = episodesContainer[episodeNumber]
if (episode == nil) {
episode = Episode(title: title, desc: synopsis, seasonNumber: seasonNumber, episodeNumber: UInt(episodeNumber), videos: [Video]())
episodesContainer[episodeNumber] = episode!
}
}
}
}
for entry in videosContainer {
let episodeNumber = entry.0
let videos = entry.1
episodesContainer[episodeNumber]!.videos = videos
}
let episodes = Array(episodesContainer.values).sorted { (a, b) -> Bool in
return a.episodeNumber > b.episodeNumber
}
let season = Season(seasonNumber: seasonNumber, episodes: episodes)
seasons.append(season)
}
fileprivate func numbersFromAnimeTitle(_ title: String) -> [Int]{
let components = title.components(separatedBy: CharacterSet(charactersIn: "[]_() "))
var numbers = [Int]()
for component in components {
if let number = Int(component) {
if number < 10000 {
numbers.append(number)
}
}
}
return numbers
}
}
extension Anime: ContainsEpisodes {
func episodeFor(seasonIndex: Int, episodeIndex: Int) -> Episode {
let episode = seasons[seasonIndex].episodes[episodeIndex]
return episode
}
func episodesFor(seasonIndex: Int) -> [Episode] {
return seasons[seasonIndex].episodes
}
}
================================================
FILE: PopcornTime/Models/AppDelegate.swift
================================================
//
// AppDelegate.swift
// PopcornTime
//
// Created by Danylo Kostyshyn on 3/13/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
import UIKit
#if RELEASE
import Fabric
import Crashlytics
#endif
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
#if RELEASE
Fabric.with([Crashlytics()])
#endif
return true
}
}
================================================
FILE: PopcornTime/Models/BaseStructures.swift
================================================
//
// Video.swift
// PopcornTime
//
// Created by Danylo Kostyshyn on 3/21/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
struct Video {
let name: String?
let quality: String?
let size: UInt?
let duration: UInt?
let subGroup: String?
let magnetLink: String
}
struct Episode {
let title: String?
let desc: String?
let seasonNumber: UInt
let episodeNumber: UInt
var videos = [Video]()
}
struct Season {
let seasonNumber: UInt
let episodes: [Episode]
}
================================================
FILE: PopcornTime/Models/BasicInfo.swift
================================================
//
// BasicInfo.swift
// PopcornTime
//
// Created by Danylo Kostyshyn on 3/19/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
import Foundation
protocol ContainsEpisodes {
func episodeFor(seasonIndex: Int, episodeIndex: Int) -> Episode
func episodesFor(seasonIndex: Int) -> [Episode]
}
protocol BasicInfoProtocol {
var identifier: String! {get}
var title: String? {get}
var year: String? {get}
var images: [Image]! {get}
var smallImage: Image? {get}
var bigImage: Image? {get}
var isFavorite: Bool {get}
init(dictionary: [AnyHashable: Any])
func update(_ dictionary: [AnyHashable: Any])
}
class BasicInfo: NSObject, BasicInfoProtocol, NSCoding {
var identifier: String!
var title: String?
var year: String?
var images: [Image]!
var smallImage: Image?
var bigImage: Image?
var synopsis: String?
var isFavorite : Bool {
get {
return DataManager.sharedManager().isFavorite(self)
}
set {
if (isFavorite == true) {
return DataManager.sharedManager().addToFavorites(self)
} else {
return DataManager.sharedManager().removeFromFavorites(self)
}
}
}
required init(dictionary: [AnyHashable: Any]) {
// fatalError("init(dictionary:) has not been implemented")
}
func update(_ dictionary: [AnyHashable: Any]) {
fatalError("update(dictionary:) has not been implemented")
}
// MARK: - NSCoding
required init?(coder aDecoder: NSCoder) {
guard
let identifier = aDecoder.decodeObject(forKey: "identifier") as? String
else { return nil }
self.identifier = identifier
self.title = aDecoder.decodeObject(forKey: "title") as? String
self.year = aDecoder.decodeObject(forKey: "year") as? String
self.smallImage = aDecoder.decodeObject(forKey: "smallImage") as? Image
self.bigImage = aDecoder.decodeObject(forKey: "bigImage") as? Image
}
func encode(with aCoder: NSCoder) {
aCoder.encode(identifier, forKey: "identifier")
aCoder.encode(title, forKey: "title")
aCoder.encode(year, forKey: "year")
aCoder.encode(smallImage, forKey: "smallImage")
aCoder.encode(bigImage, forKey: "bigImage")
}
}
================================================
FILE: PopcornTime/Models/DataManager.swift
================================================
//
// DataManager.swift
// PopcornTime
//
// Created by Danylo Kostyshyn on 3/24/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
import Foundation
struct Notifications {
static let FavoritesDidChangeNotification = "FavoritesDidChangeNotification"
}
class DataManager {
let documentsDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first! as String
let fileName = "Favorites.plist"
var filePath: String {
get {
return fileURL.path
}
}
var fileURL: URL {
let url = URL(fileURLWithPath: documentsDirectory, isDirectory: true)
return url.appendingPathComponent(fileName)
}
var favorites: [BasicInfo]?
init() {
loadFavorites()
}
class func sharedManager() -> DataManager {
struct Static { static let instance: DataManager = DataManager() }
return Static.instance
}
fileprivate func loadFavorites() -> [BasicInfo]? {
if FileManager.default.fileExists(atPath: filePath) {
let data = try? Data(contentsOf: URL(fileURLWithPath: filePath))
if let data = data {
self.favorites = NSKeyedUnarchiver.unarchiveObject(with: data) as! [BasicInfo]?
return self.favorites
}
}
return nil
}
fileprivate func saveFavorites(_ items: [BasicInfo]) {
let data = NSKeyedArchiver.archivedData(withRootObject: items)
try? data.write(to: URL(fileURLWithPath: filePath), options: [.atomic])
self.favorites = items
}
// MARK: -
func isFavorite(_ item: BasicInfo) -> Bool {
let favoriteItem = self.favorites?.filter({ $0.identifier == item.identifier }).first
if favoriteItem != nil {
return true
}
return false
}
func addToFavorites(_ item: BasicInfo) {
var items = [BasicInfo]()
if let favorites = loadFavorites() {
items += favorites
}
items.append(item)
saveFavorites(items)
NotificationCenter.default.post(name: Notification.Name(rawValue: Notifications.FavoritesDidChangeNotification), object: nil)
}
func removeFromFavorites(_ item: BasicInfo) {
let items = loadFavorites()
if var items = items {
let favoriteItem = items.filter({ $0.identifier == item.identifier }).first
if let favoriteItem = favoriteItem {
let idx = items.index(of: favoriteItem)
items.remove(at: idx!)
saveFavorites(items)
NotificationCenter.default.post(name: Notification.Name(rawValue: Notifications.FavoritesDidChangeNotification), object: nil)
}
}
}
}
================================================
FILE: PopcornTime/Models/Extensions.swift
================================================
//
// UIImage+PopcornTime.swift
// PopcornTime
//
// Created by Danylo Kostyshyn on 3/30/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
extension UIImage {
class func addToFavoritesImage() -> UIImage? {
return UIImage(named: "AddToFavoritesIcon")
}
class func removeFromFavoritesImage() -> UIImage? {
return UIImage(named: "RemoveFromFavoritesIcon")
}
}
================================================
FILE: PopcornTime/Models/Image.swift
================================================
//
// File.swift
// PopcornTime
//
// Created by Danylo Kostyshyn on 3/21/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
import UIKit
enum ImageType: Int {
case banner, poster, fanart
}
enum ImageStatus {
case new, downloading, finished
}
class Image: NSObject, NSCoding {
let URL: Foundation.URL
var image: UIImage?
let type: ImageType
var status: ImageStatus = .new
init(URL: Foundation.URL, type: ImageType) {
self.URL = URL
self.type = type
}
// MARK: - NSCoding
required init?(coder aDecoder: NSCoder) {
let typeRaw = aDecoder.decodeInteger(forKey: "type")
guard
let URL = aDecoder.decodeObject(forKey: "URL") as? Foundation.URL,
let type = ImageType(rawValue: typeRaw)
else { return nil }
self.URL = URL
self.type = type
}
func encode(with aCoder: NSCoder) {
aCoder.encode(URL, forKey: "URL")
aCoder.encode(type.rawValue, forKey: "type")
}
}
================================================
FILE: PopcornTime/Models/ImageProvider.swift
================================================
//
// ImageProvider.swift
// PopcornTime
//
// Created by Andrew K. on 3/10/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
import UIKit
class ImageProvider: NSObject {
static let sharedInstance = ImageProvider()
func imageFromURL(URL: Foundation.URL?, completionBlock: @escaping (_ downloadedImage: UIImage?)->()) {
guard let URL = URL else { return }
SDWebImageDownloader.shared().downloadImage(with: URL, options: [SDWebImageDownloaderOptions.useNSURLCache], progress: nil) {
(image, data, error, finished) -> Void in
if let _ = error { NSLog("\(#function): \(error)") }
DispatchQueue.main.async(execute: {
completionBlock(image)
})
}
}
}
================================================
FILE: PopcornTime/Models/Movie.swift
================================================
//
// Movie.swift
// PopcornTime
//
// Created by Danylo Kostyshyn on 3/19/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
import Foundation
class Movie: BasicInfo {
var videos = [Video]()
required init(dictionary: [AnyHashable: Any]) {
super.init(dictionary: dictionary)
let id = (dictionary["id"] as! NSString).intValue
identifier = "\(id)"
title = dictionary["title"] as? String
year = String(describing: dictionary["year"])
images = [Image]()
if let cover = dictionary["poster_med"] as? String {
let image = Image(URL: URL(string: cover)!, type: .poster)
images.append(image)
}
if let cover = dictionary["poster_big"] as? String {
let image = Image(URL: URL(string: cover)!, type: .banner)
images.append(image)
}
smallImage = self.images.filter({$0.type == ImageType.poster}).first
bigImage = self.images.filter({$0.type == ImageType.banner}).first
synopsis = dictionary["description"] as? String
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
override func update(_ dictionary: [AnyHashable: Any]) {
videos.removeAll(keepingCapacity: true)
let title = dictionary["title"] as! String
guard let movieList = dictionary["items"] as? [[AnyHashable: Any]] else { return }
for movieDict in movieList {
let quality = movieDict["quality"] as! String
let magnetLink = movieDict["torrent_magnet"] as! String
let size = movieDict["size_bytes"] as! UInt
let video = Video(name: title, quality: quality, size: size, duration: 0, subGroup: nil, magnetLink: magnetLink)
videos.append(video)
}
}
}
================================================
FILE: PopcornTime/Models/PTAPIManager.h
================================================
//
// PTAPIManager.h
// PopcornTime
//
// Created by Danylo Kostyshyn on 2/25/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
#import
typedef void (^PTAPIManagerFailure)(NSError *error);
typedef void (^PTAPIManagerSuccessItems)(NSArray *items);
typedef void (^PTAPIManagerSuccessItem)(NSDictionary *item);
typedef void (^PTAPIManagerSuccessNone)();
typedef NS_ENUM(NSInteger, PTItemType) {
PTItemTypeMovie,
PTItemTypeShow,
PTItemTypeAnime,
};
@interface PTAPIManager : NSObject
+ (instancetype)sharedManager;
- (void)showInfoWithType:(PTItemType)type
withId:(NSString *)imdbId
success:(PTAPIManagerSuccessItem)success
failure:(PTAPIManagerFailure)failure;
- (void)searchForShowWithType:(PTItemType)type
name:(NSString *)name
success:(PTAPIManagerSuccessItems)success
failure:(PTAPIManagerFailure)failure;
- (void)topShowsWithType:(PTItemType)type
withPage:(NSUInteger)page
success:(PTAPIManagerSuccessItems)success
failure:(PTAPIManagerFailure)failure;
// Trakt.tv
/*
+ (NSString *)trakttvAccessToken;
+ (void)updateTrakttvAccessToken:(NSString *)accessToken;
+ (NSURL *)trakttvAuthorizationURL;
- (void)accessTokenWithAuthorizationCode:(NSString *)authorizationCode
success:(void(^)(NSString *accessToken))success
failure:(PTAPIManagerFailure)failure;
- (void)createListWithName:(NSString *)name
success:(PTAPIManagerSuccessNone)success
failure:(PTAPIManagerFailure)failure;
*/
@end
================================================
FILE: PopcornTime/Models/PTAPIManager.m
================================================
//
// PTAPIManager.m
// PopcornTime
//
// Created by Danylo Kostyshyn on 2/25/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
#import "PTAPIManager.h"
#import
NSUInteger const PTAPIManagerResultsLimit = 30;
NSString *const PTAPIManagerMoviesEndPoint = @"http://api.torrentsapi.com/";
NSString *const PTAPIManagerShowsEndPoint = @"https://api-fetch.website/tv/";
NSString *const PTAPIManagerAnimeEndPoint = @"http://ptp.haruhichan.com";
@implementation PTAPIManager
static NSDictionary *YTSHTTPHeaders;
#pragma mark - Public API
+ (void)initialize
{
YTSHTTPHeaders = @{@"Host": @"eqwww.image.yt"};
}
+ (instancetype)sharedManager
{
static dispatch_once_t onceToken;
static PTAPIManager *sharedManager;
dispatch_once(&onceToken, ^{
sharedManager = [[PTAPIManager alloc] init];
});
return sharedManager;
}
- (void)showInfoWithType:(PTItemType)type
withId:(NSString *)imdbId
success:(PTAPIManagerSuccessItem)success
failure:(PTAPIManagerFailure)failure
{
switch (type) {
case PTItemTypeMovie: { [self movieInfoWithId:imdbId success:success failure:failure]; break; }
case PTItemTypeShow: { [self tvSeriesInfoWithId:imdbId success:success failure:failure]; break; }
case PTItemTypeAnime: { [self animeInfoWithId:imdbId success:success failure:failure]; break; }
default: break;
}
}
- (void)searchForShowWithType:(PTItemType)type
name:(NSString *)name
success:(PTAPIManagerSuccessItems)success
failure:(PTAPIManagerFailure)failure
{
switch (type) {
case PTItemTypeMovie: { [self searchForMovieWithName:name success:success failure:failure]; break; }
case PTItemTypeShow: { [self searchForTVSeriesWithName:name success:success failure:failure]; break; }
case PTItemTypeAnime: { [self searchForAnimeWithName:name success:success failure:failure]; break; }
default: break;
}
}
- (void)topShowsWithType:(PTItemType)type
withPage:(NSUInteger)page
success:(PTAPIManagerSuccessItems)success
failure:(PTAPIManagerFailure)failure{
switch (type) {
case PTItemTypeMovie: { [self topMoviesWithPage:page success:success failure:failure]; break; }
case PTItemTypeShow: { [self topTVSeriesWithPage:page success:success failure:failure]; break; }
case PTItemTypeAnime: { [self topAnimeWithPage:page success:success failure:failure]; break; }
default: break;
}
}
#pragma mark - Private Methods
- (void)dataFromURL:(NSURL *)URL
success:(void(^)(id JSONObject))success
failure:(PTAPIManagerFailure)failure
{
return [self dataFromURL:URL HTTPheaders:nil success:success failure:failure];
}
- (void)dataFromURL:(NSURL *)URL
HTTPheaders:(NSDictionary *)HTTPheaders
success:(void(^)(id JSONObject))success
failure:(PTAPIManagerFailure)failure
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
if (HTTPheaders) {
for (NSString *key in HTTPheaders.allKeys) {
[request addValue:HTTPheaders[key] forHTTPHeaderField:key];
}
}
[[[NSURLSession sharedSession] dataTaskWithRequest:request
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
void (^handleError)(NSError *) = ^(NSError *error) {
if (error) { NSLog(@"%@", error); if (failure) { failure(error); } }
};
if (error) { handleError(error); return; }
NSError *JSONError;
id JSONObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:&JSONError];
if (JSONError) { handleError(JSONError); return; }
if (success) { success(JSONObject); };
});
}] resume];
}
#pragma mark Movies
- (void)topMoviesWithPage:(NSUInteger)page
success:(PTAPIManagerSuccessItems)success
failure:(PTAPIManagerFailure)failure
{
NSString *path = [NSString stringWithFormat:@"list?"
"page=%ld&limit=%ld&order_by=desc&sort_by=seeds&with_rt_ratings=true", (long)page + 1, (long)PTAPIManagerResultsLimit];
NSString *URLString = [PTAPIManagerMoviesEndPoint stringByAppendingPathComponent:path];
[self dataFromURL:[NSURL URLWithString:URLString] success:^(id JSONObject) {
if (success) {
NSArray *items = [((NSDictionary *)JSONObject) objectForKey:@"MovieList"];
success(items);
}
} failure:failure];
}
- (void)movieInfoWithId:(NSString *)imdbId
success:(PTAPIManagerSuccessItem)success
failure:(PTAPIManagerFailure)failure
{
NSString *path = [NSString stringWithFormat:@"list?"
"page=1&limit=5&order_by=desc&sort_by=seeds&with_rt_ratings=true"];
// NSString *path = [NSString stringWithFormat:@"list?id=%@", imdbId];
NSString *URLString = [PTAPIManagerMoviesEndPoint stringByAppendingPathComponent:path];
[self dataFromURL:[NSURL URLWithString:URLString] success:^(id JSONObject) {
if (success) {
NSDictionary *items = [((NSDictionary *)JSONObject) objectForKey:@"MovieList"];
for(id key in items) {
NSString *id = [key objectForKey:@"id"];
if([id isEqualToString:imdbId]) {
success(key);
break;
}
}
// success(items);
}
} failure:failure];
}
- (void)searchForMovieWithName:(NSString *)name
success:(PTAPIManagerSuccessItems)success
failure:(PTAPIManagerFailure)failure
{
NSString *path = [[NSString stringWithFormat:@"list?sort_by=seeds&limit=%ld&with_rt_ratings=true&page=1&keywords=%@", (long)PTAPIManagerResultsLimit, name]
stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString *URLString = [PTAPIManagerMoviesEndPoint stringByAppendingPathComponent:path];
[self dataFromURL:[NSURL URLWithString:URLString] success:^(id JSONObject) {
if (success) {
NSArray *items = [((NSDictionary *)JSONObject) objectForKey:@"MovieList"];
success(items);
}
} failure:failure];
}
#pragma mark TVSeries
- (void)topTVSeriesWithPage:(NSUInteger)page
success:(PTAPIManagerSuccessItems)success
failure:(PTAPIManagerFailure)failure
{
NSString *path = [NSString stringWithFormat:@"shows/%ld?limit=%lu", (long)page + 1, (long)PTAPIManagerResultsLimit];
NSString *URLString = [PTAPIManagerShowsEndPoint stringByAppendingPathComponent:path];
[self dataFromURL:[NSURL URLWithString:URLString] success:^(id JSONObject) {
if (success) {
success((NSArray *)JSONObject);
}
} failure:failure];
}
- (void)tvSeriesInfoWithId:(NSString *)imdbId
success:(PTAPIManagerSuccessItem)success
failure:(PTAPIManagerFailure)failure
{
NSString *path = [NSString stringWithFormat:@"show/%@", imdbId];
NSString *URLString = [PTAPIManagerShowsEndPoint stringByAppendingPathComponent:path];
[self dataFromURL:[NSURL URLWithString:URLString] success:^(id JSONObject) {
if (success) {
success((NSDictionary *)JSONObject);
}
} failure:failure];
}
- (void)searchForTVSeriesWithName:(NSString *)name
success:(PTAPIManagerSuccessItems)success
failure:(PTAPIManagerFailure)failure
{
NSString *path = [[NSString stringWithFormat:@"shows/1?limit=%ld&keywords=%@&sort=seeds", (long)PTAPIManagerResultsLimit, name]
stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString *URLString = [PTAPIManagerShowsEndPoint stringByAppendingPathComponent:path];
[self dataFromURL:[NSURL URLWithString:URLString] success:^(id JSONObject) {
if (success) {
success((NSArray *)JSONObject);
}
} failure:failure];
}
#pragma mark Anime
- (void)topAnimeWithPage:(NSUInteger)page
success:(PTAPIManagerSuccessItems)success
failure:(PTAPIManagerFailure)failure
{
NSString *path = [[NSString stringWithFormat:@"list.php?page=%ld&limit=%ld&sort=popularity&type=All", (long)page, (long)PTAPIManagerResultsLimit]
stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString *URLString = [PTAPIManagerAnimeEndPoint stringByAppendingPathComponent:path];
[self dataFromURL:[NSURL URLWithString:URLString] success:^(id JSONObject) {
if (success) {
success((NSArray *)JSONObject);
}
} failure:failure];
}
- (void)animeInfoWithId:(NSString *)imdbId
success:(PTAPIManagerSuccessItem)success
failure:(PTAPIManagerFailure)failure
{
NSString *path = [NSString stringWithFormat:@"anime.php?id=%@", imdbId];
NSString *URLString = [PTAPIManagerAnimeEndPoint stringByAppendingPathComponent:path];
[self dataFromURL:[NSURL URLWithString:URLString] success:^(id JSONObject) {
if (success) {
success((NSDictionary *)JSONObject);
}
} failure:failure];
}
- (void)searchForAnimeWithName:(NSString *)name
success:(PTAPIManagerSuccessItems)success
failure:(PTAPIManagerFailure)failure {
NSString *path = [[NSString stringWithFormat:@"/list.php?search=%@&limit=%ld&sort=popularity&type=All",
[name stringByReplacingOccurrencesOfString:@" " withString:@"+"], (long)PTAPIManagerResultsLimit]
stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString *URLString = [PTAPIManagerAnimeEndPoint stringByAppendingPathComponent:path];
[self dataFromURL:[NSURL URLWithString:URLString] success:^(id JSONObject) {
if (success) {
success((NSArray *)JSONObject);
}
} failure:failure];
}
#pragma mark - Trakt.tv
/*
NSString *const PTAPIManagerTrakttvAccessTokenKey = @"TrakttvAccessToken";
NSString *const PTAPIManagerTrakttvAPIEndPoint = @"http://api.trakt.tv";
NSString *const PTAPIManagerTrakttvAPIKey = @"df8d400233727be104e5caf40e07d785b6963c0e194dcbd24f806e8a4e243167";
NSString *const PTAPIManagerTrakttvAPIVersion = @"2";
NSString *const PTAPIManagerTrakttvClientId = @"df8d400233727be104e5caf40e07d785b6963c0e194dcbd24f806e8a4e243167";
NSString *const PTAPIManagerTrakttvClientSecret = @"1a98885c5271f7162ac51b2c1dd09decc55df127f5e1b29af533d35eee5df9b2";
NSString *const PTAPIManagerTrakttvRedirectURL = @"urn:ietf:wg:oauth:2.0:oob";
+ (NSString *)trakttvAccessToken
{
return [[NSUserDefaults standardUserDefaults] objectForKey:PTAPIManagerTrakttvAccessTokenKey];
}
+ (void)updateTrakttvAccessToken:(NSString *)accessToken
{
[[NSUserDefaults standardUserDefaults] setObject:accessToken forKey:PTAPIManagerTrakttvAccessTokenKey];
[[NSUserDefaults standardUserDefaults] synchronize];
}
+ (NSURL *)trakttvAuthorizationURL
{
NSString *authPath = [NSString stringWithFormat:@"http://trakt.tv/oauth/authorize?client_id=%@&redirect_uri=%@&response_type=code",
PTAPIManagerTrakttvClientId,
PTAPIManagerTrakttvRedirectURL];
return [NSURL URLWithString:authPath];
}
- (void)trakttvPerformRequestWithAPIMethod:(NSString *)APIMethod
HTTPMethod:(NSString *)HTTPMethod
HTTPBodyPayload:(NSDictionary *)HTTPBodyPayload
OAuthRequired:(BOOL)OAuthRequired
success:(void(^)(id JSONObject))success
failure:(PTAPIManagerFailure)failure
{
NSString *path = [PTAPIManagerTrakttvAPIEndPoint stringByAppendingPathComponent:APIMethod];
NSURL *URL = [NSURL URLWithString:path];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
request.HTTPMethod = HTTPMethod;
// Configure headers
[request addValue:@"application/json" forHTTPHeaderField:@"Content-type"];
[request addValue:PTAPIManagerTrakttvAPIKey forHTTPHeaderField:@"trakt-api-key"];
[request addValue:PTAPIManagerTrakttvAPIVersion forHTTPHeaderField:@"trakt-api-version"];
if (OAuthRequired) {
NSString *bearerToken = [NSString stringWithFormat:@"Bearer [%@]", [PTAPIManager trakttvAccessToken]];
[request addValue:bearerToken forHTTPHeaderField:@"Authorization"];
}
void (^handleError)(NSError *) = ^(NSError *error) {
if (error) { NSLog(@"%@", error); if (failure) { failure(error); } }
};
// Configure body
NSError *JSONError;
NSData *JSONBody = [NSJSONSerialization dataWithJSONObject:HTTPBodyPayload options:0 error:&JSONError];
handleError(JSONError);
request.HTTPBody = JSONBody;
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) { handleError(error); return; }
NSError *JSONError;
id JSONObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:&JSONError];
if (JSONError) { handleError(JSONError); return; }
if (success) { success(JSONObject); }
});
}] resume];
}
- (void)accessTokenWithAuthorizationCode:(NSString *)authorizationCode
success:(void(^)(NSString *accessToken))success
failure:(PTAPIManagerFailure)failure
{
NSString *APIMethod = @"oauth/token";
NSDictionary *HTTPBodyPayload = @{@"code": authorizationCode,
@"client_id": PTAPIManagerTrakttvClientId,
@"client_secret": PTAPIManagerTrakttvClientSecret,
@"redirect_uri": PTAPIManagerTrakttvRedirectURL,
@"grant_type": @"authorization_code"};
[self trakttvPerformRequestWithAPIMethod:APIMethod
HTTPMethod:@"POST"
HTTPBodyPayload:HTTPBodyPayload
OAuthRequired:NO
success:^(id JSONObject) {
if (success) {
success([((NSDictionary *)JSONObject) objectForKey:@"access_token"]);
}
}
failure:failure];
}
- (void)createListWithName:(NSString *)name
success:(PTAPIManagerSuccessNone)success
failure:(PTAPIManagerFailure)failure
{
NSString *APIMethod = @"users/me/lists";
NSDictionary *HTTPBodyPayload = @{@"name": @"ololo",
@"description": @"ololo",
@"privacy": @"private",
@"display_numbers": @"false",
@"allow_comments": @"true"};
[self trakttvPerformRequestWithAPIMethod:APIMethod
HTTPMethod:@"POST"
HTTPBodyPayload:HTTPBodyPayload
OAuthRequired:YES
success:nil failure:failure];
}
*/
@end
================================================
FILE: PopcornTime/Models/PTTorrentStreamer.h
================================================
//
// PTTorrentStreamer.h
// PopcornTime
//
// Created by Danylo Kostyshyn on 2/23/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
#import
typedef struct {
float bufferingProgress;
float totalProgreess;
int downloadSpeed;
int upoadSpeed;
int seeds;
int peers;
} PTTorrentStatus;
typedef void (^PTTorrentStreamerProgress)(PTTorrentStatus status);
typedef void (^PTTorrentStreamerReadyToPlay)(NSURL *videoFileURL);
typedef void (^PTTorrentStreamerFailure)(NSError *error);
@interface PTTorrentStreamer : NSObject
+ (instancetype)sharedStreamer;
- (void)startStreamingFromFileOrMagnetLink:(NSString *)filePathOrMagnetLink
progress:(PTTorrentStreamerProgress)progreess
readyToPlay:(PTTorrentStreamerReadyToPlay)readyToPlay
failure:(PTTorrentStreamerFailure)failure;
- (void)cancelStreaming;
@end
================================================
FILE: PopcornTime/Models/PTTorrentStreamer.mm
================================================
//
// PTTorrentStreamer.m
// PopcornTime
//
// Created by Danylo Kostyshyn on 2/23/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
#import "PTTorrentStreamer.h"
#import
#import
#import
#import
#import
#import
using namespace libtorrent;
@interface PTTorrentStreamer()
@property (nonatomic, strong) dispatch_queue_t alertsQueue;
@property (nonatomic, getter=isAlertsLoopActive) BOOL alertsLoopActive;
@property (nonatomic, strong) NSString *savePath;
@property (nonatomic, getter=isDownloading) BOOL downloading;
@property (nonatomic, getter=isStreaming) BOOL streaming;
@property (nonatomic, copy) PTTorrentStreamerProgress progressBlock;
@property (nonatomic, copy) PTTorrentStreamerReadyToPlay readyToPlayBlock;
@property (nonatomic, copy) PTTorrentStreamerFailure failureBlock;
@end
@implementation PTTorrentStreamer
{
session *_session;
std::vector required_pieces;
}
+ (instancetype)sharedStreamer
{
static dispatch_once_t onceToken;
static PTTorrentStreamer *sharedStreamer;
dispatch_once(&onceToken, ^{
sharedStreamer = [[PTTorrentStreamer alloc] init];
});
return sharedStreamer;
}
- (instancetype)init
{
self = [super init];
if (self) {
[self setupSession];
}
return self;
}
#pragma mark -
+ (NSString *)downloadsDirectory
{
NSString *downloadsDirectoryPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"Downloads"];
if (![[NSFileManager defaultManager] fileExistsAtPath:downloadsDirectoryPath]) {
NSError *error;
[[NSFileManager defaultManager] createDirectoryAtPath:downloadsDirectoryPath
withIntermediateDirectories:YES
attributes:nil
error:&error];
if (error) {
NSLog(@"%@", error);
return nil;
}
}
return downloadsDirectoryPath;
}
- (void)setupSession
{
// _session = new session(fingerprint("PopcornTime", 1, 0, 0, 0),
// std::make_pair(6881, 6889),
// 0,
// session::start_default_features | session::add_default_plugins,
// alert::all_categories);
error_code ec;
_session = new session();
_session->set_alert_mask(alert::all_categories);
_session->listen_on(std::make_pair(6881, 6889), ec);
if (ec) {
NSLog(@"failed to open listen socket: %s", ec.message().c_str());
}
session_settings settings = _session->settings();
settings.announce_to_all_tiers = true;
settings.announce_to_all_trackers = true;
settings.prefer_udp_trackers = false;
settings.max_peerlist_size = 0;
_session->set_settings(settings);
}
- (void)startStreamingFromFileOrMagnetLink:(NSString *)filePathOrMagnetLink
progress:(PTTorrentStreamerProgress)progreess
readyToPlay:(PTTorrentStreamerReadyToPlay)readyToPlay
failure:(PTTorrentStreamerFailure)failure;
{
self.progressBlock = progreess;
self.readyToPlayBlock = readyToPlay;
self.failureBlock = failure;
self.alertsQueue = dispatch_queue_create("com.popcorntime.ios.torrentstreamer.alerts", DISPATCH_QUEUE_SERIAL);
self.alertsLoopActive = YES;
dispatch_async(self.alertsQueue, ^{
[self alertsLoop];
});
error_code ec;
add_torrent_params tp;
NSString *MD5String = nil;
if ([filePathOrMagnetLink hasPrefix:@"magnet"]) {
NSString *magnetLink = filePathOrMagnetLink;
magnetLink = [magnetLink stringByAppendingString:@"&tr=udp://open.demonii.com:1337"
"&tr=udp://tracker.coppersurfer.tk:6969"];
tp.url = std::string([magnetLink UTF8String]);
MD5String = [CocoaSecurity md5:magnetLink].hexLower;
} else {
NSString *filePath = filePathOrMagnetLink;
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
NSData *fileData = [NSData dataWithContentsOfFile:filePath];
MD5String = [CocoaSecurity md5WithData:fileData].hexLower;
tp.ti = new torrent_info([filePathOrMagnetLink UTF8String], ec);
if (ec) {
NSLog(@"%s", ec.message().c_str());
return;
}
} else {
NSLog(@"File doesn't exists at path: %@", filePath);
return;
}
}
NSString *halfMD5String = [MD5String substringToIndex:16];
self.savePath = [[PTTorrentStreamer downloadsDirectory] stringByAppendingPathComponent:halfMD5String];
NSError *error;
[[NSFileManager defaultManager] createDirectoryAtPath:self.savePath
withIntermediateDirectories:YES
attributes:nil
error:&error];
if (error) {
NSLog(@"Can't create directory at path: %@", self.savePath);
return;
}
tp.save_path = std::string([self.savePath UTF8String]);
tp.storage_mode = storage_mode_allocate;
torrent_handle th = _session->add_torrent(tp, ec);
th.set_sequential_download(true);
if (ec) {
NSLog(@"%s", ec.message().c_str());
return;
}
self.downloading = YES;
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}
- (void)cancelStreaming
{
if ([self isDownloading]) {
self.alertsQueue = nil;
self.alertsLoopActive = NO;
std::vector ths = _session->get_torrents();
for(std::vector::size_type i = 0; i != ths.size(); i++) {
_session->remove_torrent(ths[i], session::delete_files);
}
required_pieces.clear();
self.progressBlock = nil;
self.readyToPlayBlock = nil;
self.failureBlock = nil;
NSError *error;
[[NSFileManager defaultManager] removeItemAtPath:self.savePath error:&error];
if (error) NSLog(@"%@", error);
self.savePath = nil;
self.streaming = NO;
self.downloading = NO;
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
}
#pragma mark - Alerts Loop
#define ALERTS_LOOP_WAIT_MILLIS 500
#define MIN_PIECES 15
#define PIECE_DEADLINE_MILLIS 100
#define LIBTORRENT_PRIORITY_SKIP 0
#define LIBTORRENT_PRIORITY_MAXIMUM 7
- (void)alertsLoop
{
std::deque deque;
time_duration max_wait = milliseconds(ALERTS_LOOP_WAIT_MILLIS);
while ([self isAlertsLoopActive])
{
const alert *ptr = _session->wait_for_alert(max_wait);
if (ptr != nullptr) {
_session->pop_alerts(&deque);
for (std::deque::iterator it=deque.begin(); it != deque.end(); ++it) {
std::unique_ptr alert(*it);
// NSLog(@"type:%d msg:%s", alert->type(), alert->message().c_str());
switch (alert->type()) {
case metadata_received_alert::alert_type:
[self metadataReceivedAlert:(metadata_received_alert *)alert.get()];
break;
case block_finished_alert::alert_type:
[self pieceFinishedAlert:(piece_finished_alert *)alert.get()];
break;
// In case the video file is already fully downloaded
case torrent_finished_alert::alert_type:
[self torrentFinishedAlert:(torrent_finished_alert *)alert.get()];
break;
default: break;
}
}
deque.clear();
}
}
}
- (void)prioritizeNextPieces:(torrent_handle)th
{
int next_required_piece = required_pieces[MIN_PIECES-1]+1;
required_pieces.clear();
boost::intrusive_ptr ti = th.torrent_file();
for (int i=next_required_piece; inum_pieces()) {
th.piece_priority(i, LIBTORRENT_PRIORITY_MAXIMUM);
th.set_piece_deadline(i, PIECE_DEADLINE_MILLIS, torrent_handle::alert_when_available);
required_pieces.push_back(i);
}
}
}
- (void)processTorrent:(torrent_handle)th
{
if (![self isStreaming]) {
self.streaming = YES;
if (self.readyToPlayBlock) {
boost::intrusive_ptr ti = th.torrent_file();
int file_index = [self indexOfLargestFileInTorrent:th];
file_entry fe = ti->file_at(file_index);
std::string path = fe.path;
NSString *fileName = [NSString stringWithCString:path.c_str() encoding:NSUTF8StringEncoding];
NSURL *fileURL = [NSURL fileURLWithPath:[self.savePath stringByAppendingPathComponent:fileName]];
dispatch_async(dispatch_get_main_queue(), ^{
self.readyToPlayBlock(fileURL);
});
}
}
}
- (int)indexOfLargestFileInTorrent:(torrent_handle)th
{
boost::intrusive_ptr ti = th.torrent_file();
int files_count = ti->num_files();
if (files_count > 1) {
size_type largest_size = -1;
int largest_file_index = -1;
for (int i=0; ifile_at(i);
if (fe.size > largest_size) {
largest_size = fe.size;
largest_file_index = i;
}
}
return largest_file_index;
}
return 0;
}
#pragma mark - Logging
- (void)logPiecesStatus:(torrent_handle)th
{
NSString *pieceStatus = @"";
boost::intrusive_ptr ti = th.torrent_file();
for(std::vector::size_type i=0; i!=required_pieces.size(); i++) {
int piece = required_pieces[i];
pieceStatus = [pieceStatus stringByAppendingFormat:@"%d:%d ", piece, th.have_piece(piece)];
}
NSLog(@"%@", pieceStatus);
}
- (void)logTorrentStatus:(PTTorrentStatus)status
{
NSString *speedString = [NSByteCountFormatter stringFromByteCount:status.downloadSpeed
countStyle:NSByteCountFormatterCountStyleBinary];
NSLog(@"%.0f%%, %.0f%%, %@/s, %d, %d",
status.bufferingProgress*100, status.totalProgreess*100,
speedString, status.seeds, status.peers);
}
#pragma mark - Alerts
- (void)metadataReceivedAlert:(metadata_received_alert *)alert
{
torrent_handle th = alert->handle;
int file_index = [self indexOfLargestFileInTorrent:th];
std::vector file_priorities = th.file_priorities();
std::fill(file_priorities.begin(), file_priorities.end(), LIBTORRENT_PRIORITY_SKIP);
file_priorities[file_index] = LIBTORRENT_PRIORITY_MAXIMUM;
th.prioritize_files(file_priorities);
boost::intrusive_ptr ti = th.torrent_file();
int first_piece = ti->map_file(file_index, 0, 0).piece;
for (int i=first_piece; ifile_at(file_index).size;
int last_piece = ti->map_file(file_index, file_size-1, 0).piece;
required_pieces.push_back(last_piece);
for (int i=1; i<10; i++) {
required_pieces.push_back(last_piece-i);
}
for(std::vector::size_type i=0; i!=required_pieces.size(); i++) {
int piece = required_pieces[i];
th.piece_priority(piece, LIBTORRENT_PRIORITY_MAXIMUM);
th.set_piece_deadline(piece, PIECE_DEADLINE_MILLIS, torrent_handle::alert_when_available);
}
}
- (void)pieceFinishedAlert:(piece_finished_alert *)alert
{
torrent_handle th = alert->handle;
torrent_status status = th.status();
int requiredPiecesDownloaded = 0;
BOOL allRequiredPiecesDownloaded = YES;
for(std::vector::size_type i=0; i!=required_pieces.size(); i++) {
int piece = required_pieces[i];
if (th.have_piece(piece)) {
requiredPiecesDownloaded++;
} else {
allRequiredPiecesDownloaded = NO;
}
}
[self logPiecesStatus:th];
int requiredPieces = (int)required_pieces.size();
float bufferingProgress = 1.0 - (requiredPieces-requiredPiecesDownloaded)/(float)requiredPieces;
PTTorrentStatus torrentStatus = {bufferingProgress,
status.progress,
status.download_rate,
status.upload_rate,
status.num_seeds,
status.num_peers};
[self logTorrentStatus:torrentStatus];
if (self.progressBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
self.progressBlock(torrentStatus);
});
}
if (allRequiredPiecesDownloaded) {
[self prioritizeNextPieces:th];
[self processTorrent:th];
}
}
- (void)torrentFinishedAlert:(torrent_finished_alert *)alert
{
[self processTorrent:alert->handle];
}
@end
================================================
FILE: PopcornTime/Models/ParseManager.swift
================================================
//
// ParseManager.swift
//
//
// Created by Andriy K. on 6/23/15.
//
//
import UIKit
class ParseShowData: NSObject {
private var collection = [String : PFObject]()
convenience init(episodesFromParse: [PFObject]) {
self.init()
for episode in episodesFromParse {
let seasonIndex = episode.objectForKey(ParseManager.sharedInstance.seasonKey) as! Int
let episodeIndex = episode.objectForKey(ParseManager.sharedInstance.episodeKey) as! Int
let key = dictKey(seasonIndex, episode: episodeIndex)
collection[key] = episode
}
}
func isEpisodeWatched(season: Int, episode: Int) -> Bool {
let key = dictKey(season, episode: episode)
if let episode = collection[key] {
if let isWatched = episode.objectForKey(ParseManager.sharedInstance.watchedKey) as? Bool {
return isWatched
}
}
return false
}
private func dictKey(season: Int, episode: Int) -> String {
return "\(season)_\(episode)"
}
}
class ParseManager: NSObject {
static let sharedInstance = ParseManager()
let showClassName = "Show"
let episodeClassName = "Episode"
let showIdKey = "showId"
let userKey = "user"
let titleKey = "title"
let seasonKey = "season"
let episodeKey = "episodeNumber"
let showKey = "show"
let watchedKey = "watched"
private override init() {
}
// MARK: - Public API
var user: PFUser? {
return PFUser.currentUser()
}
func markEpisode(episodeInfo: Episode, basicInfo: BasicInfo) {
if let user = user {
let query = PFQuery(className:showClassName)
query.whereKey(userKey, equalTo:user)
query.whereKey(showIdKey, equalTo:basicInfo.identifier)
query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
var show: PFObject
if let object = objects?.first as PFObject? {
show = object
} else {
show = PFObject(className: self.showClassName)
show.setObject(basicInfo.identifier, forKey: self.showIdKey)
if let title = basicInfo.title {
show.setObject(title, forKey: self.titleKey)
}
let relation = show.relationForKey(self.userKey)
relation.addObject(user)
}
show.saveInBackgroundWithBlock({ (success, error) -> Void in
let queryEpisode = PFQuery(className:self.episodeClassName)
queryEpisode.whereKey(self.seasonKey, equalTo:episodeInfo.seasonNumber)
queryEpisode.whereKey(self.episodeKey, equalTo:episodeInfo.episodeNumber)
queryEpisode.whereKey(self.showKey, equalTo: show)
queryEpisode.findObjectsInBackgroundWithBlock { (episodes, episodeError) -> Void in
var episode: PFObject
if let ep = episodes?.first as PFObject? {
episode = ep
} else {
let newEpisode = PFObject(className: self.episodeClassName)
let relationShow = newEpisode.relationForKey(self.showKey)
relationShow.addObject(show)
newEpisode.setObject(episodeInfo.seasonNumber, forKey: self.seasonKey)
newEpisode.setObject(episodeInfo.episodeNumber, forKey: self.episodeKey)
episode = newEpisode
}
episode.setObject(true, forKey: self.watchedKey)
episode.saveInBackgroundWithBlock(nil)
}
})
}
}
}
/// Mark [Episode] as watched on Parse.
func markEpisodes(episodesInfo: [Episode], basicInfo: BasicInfo, completionHandler: PFBooleanResultBlock?) {
if episodesInfo.count == 0 {
return
}
let episodeNumbers = episodesInfo.map(){ episode in
return episode.episodeNumber
}
let seasonNumber = episodesInfo.first!.seasonNumber
if let user = user {
let query = PFQuery(className:showClassName)
query.whereKey(userKey, equalTo:user)
query.whereKey(showIdKey, equalTo:basicInfo.identifier)
query.findObjectsInBackgroundWithBlock {
(objects, error) -> Void in
var show: PFObject
if let object = objects?.first as PFObject? {
show = object
} else {
show = PFObject(className: self.showClassName)
show.setObject(basicInfo.identifier, forKey: self.showIdKey)
if let title = basicInfo.title {
show.setObject(title, forKey: self.titleKey)
}
let relation = show.relationForKey(self.userKey)
relation.addObject(user)
}
show.saveInBackgroundWithBlock({ (success, error) -> Void in
let queryEpisode = PFQuery(className:self.episodeClassName)
queryEpisode.whereKey(self.seasonKey, equalTo: seasonNumber)
queryEpisode.whereKey(self.episodeKey, containedIn: episodeNumbers)
queryEpisode.whereKey(self.showKey, equalTo: show)
queryEpisode.findObjectsInBackgroundWithBlock { (results, episodeError) -> Void in
var marked = [UInt]()
var pfObjects = [PFObject]()
if let parseEpisodes = results as [PFObject]? {
print("\(parseEpisodes.count): episodes already on Parse")
for parseEp in parseEpisodes {
parseEp.setObject(true, forKey: self.watchedKey)
if let parseEpNumber = parseEp.objectForKey(self.episodeKey) as? UInt {
marked.append(parseEpNumber)
pfObjects.append(parseEp)
print("parse ep:\(parseEpNumber) marked")
}
}
}
for ep in episodesInfo {
if marked.contains(ep.episodeNumber) == false {
let newEpisode = PFObject(className: self.episodeClassName)
let relationShow = newEpisode.relationForKey(self.showKey)
relationShow.addObject(show)
newEpisode.setObject(ep.seasonNumber, forKey: self.seasonKey)
newEpisode.setObject(ep.episodeNumber, forKey: self.episodeKey)
newEpisode.setObject(true, forKey: self.watchedKey)
marked.append(ep.episodeNumber)
pfObjects.append(newEpisode)
print("ep:\(ep.episodeNumber) marked")
}
}
PFObject.saveAllInBackground(pfObjects, block: completionHandler)
}
})
}
}
}
func parseEpisodesData(basicInfo: BasicInfo, handler: (ParseShowData) -> Void) {
if let user = user {
let query = PFQuery(className: showClassName)
query.whereKey(userKey, equalTo: user)
query.whereKey(showIdKey, equalTo:basicInfo.identifier)
query.findObjectsInBackgroundWithBlock({ (results, error) -> Void in
if let show = results?.first as PFObject? {
let queryEpisode = PFQuery(className: self.episodeClassName)
queryEpisode.whereKey(self.showKey, equalTo: show)
do {
let episodes = try queryEpisode.findObjects() as [PFObject]
let parserData = ParseShowData(episodesFromParse: episodes)
handler(parserData)
} catch let error as NSError {
print(error)
}
}
})
}
}
}
================================================
FILE: PopcornTime/Models/Show.swift
================================================
//
// Show.swift
// PopcornTime
//
// Created by Danylo Kostyshyn on 3/19/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
import Foundation
class Show: BasicInfo {
var seasons = [Season]()
func thumbnail(_ original: String) -> String {
return original.replacingOccurrences(of: "original", with: "thumb",
options: NSString.CompareOptions.caseInsensitive, range: nil)
}
required init(dictionary: [AnyHashable: Any]) {
super.init(dictionary: dictionary)
identifier = dictionary["imdb_id"] as! String
title = dictionary["title"] as? String
year = dictionary["year"] as? String
if let imagesDict = dictionary["images"] as? NSDictionary {
images = [Image]()
if let banner = imagesDict["banner"] as? String {
let URL = Foundation.URL(string: thumbnail(banner))
let image = Image(URL: URL!, type: .banner)
images.append(image)
}
if let fanart = imagesDict["fanart"] as? String {
let URL = Foundation.URL(string: thumbnail(fanart))
let image = Image(URL: URL!, type: .fanart)
images.append(image)
}
if let poster = imagesDict["poster"] as? String {
let URL = Foundation.URL(string: thumbnail(poster))
let image = Image(URL: URL!, type: .poster)
images.append(image)
}
smallImage = images.filter({$0.type == ImageType.poster}).first
bigImage = images.filter({$0.type == ImageType.fanart}).first
}
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
override func update(_ dictionary: [AnyHashable: Any]) {
synopsis = dictionary["synopsis"] as? String
seasons.removeAll(keepingCapacity: true)
var allEpisodes = [Episode]()
var allSeasonsNumbers = [UInt:Bool]()
guard let episodesDicts = dictionary["episodes"] as? [[AnyHashable: Any]] else { return }
for episodeDict in episodesDicts {
var videos = [Video]()
if let torrents = episodeDict["torrents"] as? [String : NSDictionary] {
for torrent in torrents {
let quality = torrent.0
if quality == "0" {
continue
}
let url = torrent.1["url"] as! String
let video = Video(name: nil, quality: quality, size: 0, duration: 0, subGroup: nil, magnetLink: url)
videos.append(video)
}
}
videos = videos.sorted(by: { (a, b) -> Bool in
var aQuality: Int = 0
Scanner(string: a.quality!).scanInt(&aQuality)
var bQuality: Int = 0
Scanner(string: b.quality!).scanInt(&bQuality)
return aQuality < bQuality
})
let seasonNumber = (episodeDict["season"] as! UInt)
if (allSeasonsNumbers[seasonNumber] == nil) {
allSeasonsNumbers[seasonNumber] = true
}
let title = episodeDict["title"] as? String
let episodeNumber = (episodeDict["episode"] as! UInt)
let synopsis = episodeDict["overview"] as? String
let episode = Episode(title: title, desc: synopsis, seasonNumber: seasonNumber, episodeNumber: episodeNumber, videos: videos)
allEpisodes.append(episode)
}
var seasonsNumbers = Array(allSeasonsNumbers.keys)
seasonsNumbers.sort(by: { (a, b) -> Bool in
return a < b
})
for seasonNumber in seasonsNumbers {
let seasonEpisodes = allEpisodes.filter({ (episode) -> Bool in
return episode.seasonNumber == seasonNumber
})
if seasonEpisodes.count > 0{
let season = Season(seasonNumber: seasonNumber, episodes: seasonEpisodes)
seasons.append(season)
}
}
}
}
extension Show: ContainsEpisodes {
func episodeFor(seasonIndex: Int, episodeIndex: Int) -> Episode {
let episode = seasons[seasonIndex].episodes[episodeIndex]
return episode
}
func episodesFor(seasonIndex: Int) -> [Episode] {
return seasons[seasonIndex].episodes
}
}
================================================
FILE: PopcornTime/PopcornTime-Bridging-Header.h
================================================
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "PTAPIManager.h"
#import "PTTorrentStreamer.h"
#import "VDLPlaybackViewController.h"
#import
#import
#import
================================================
FILE: PopcornTime/Resources/Images.xcassets/AnimeIcon.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x",
"filename" : "anime.png"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: PopcornTime/Resources/Images.xcassets/AppIcon.appiconset/Contents.json
================================================
{
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "AppIcon60@2x.png",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "AppIcon76.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "AppIcon76@2x.png",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "83.5x83.5",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: PopcornTime/Resources/Images.xcassets/BigLogo.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x",
"filename" : "popcorn-time-logo.png"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: PopcornTime/Resources/Images.xcassets/Contents.json
================================================
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: PopcornTime/Resources/Images.xcassets/SubwayIconSet/AddToFavoritesIcon.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x",
"filename" : "icon_087@2x.png"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: PopcornTime/Resources/Images.xcassets/SubwayIconSet/FavoritesIcon.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x",
"filename" : "icon_086@2x.png"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: PopcornTime/Resources/Images.xcassets/SubwayIconSet/MoviesIcon.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x",
"filename" : "icon_0281@2x.png"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: PopcornTime/Resources/Images.xcassets/SubwayIconSet/RemoveFromFavoritesIcon.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x",
"filename" : "icon_088@2x.png"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: PopcornTime/Resources/Images.xcassets/SubwayIconSet/SettingsIcon.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x",
"filename" : "icon_0186@2x.png"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: PopcornTime/Resources/Images.xcassets/SubwayIconSet/ShowsIcon.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x",
"filename" : "icon_0304@2x.png"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: PopcornTime/Resources/Launch Screen.xib
================================================
================================================
FILE: PopcornTime/Resources/Main.storyboard
================================================
================================================
FILE: PopcornTime/Views/EpisodeCell.swift
================================================
//
// EpisodeCell.swift
// PopcornTime
//
// Created by Andrew K. on 3/13/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
import UIKit
class EpisodeCell: UICollectionViewCell {
let watchedAlpha:CGFloat = 0.5
let defaultAlpha:CGFloat = 1.0
@IBOutlet weak var titleLabel: UILabel!
var watchedEpisode = false {
didSet {
if watchedEpisode {
alpha = watchedAlpha
} else {
alpha = defaultAlpha
}
}
}
}
================================================
FILE: PopcornTime/Views/EpisodeCell.xib
================================================
================================================
FILE: PopcornTime/Views/MoreShowsCollectionViewCell.swift
================================================
//
// MoreShowsCollectionViewCell.swift
// PopcornTime
//
// Created by Andrew K. on 3/10/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
import UIKit
class MoreShowsCollectionViewCell: UICollectionViewCell {
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
}
================================================
FILE: PopcornTime/Views/MoreShowsCollectionViewCell.xib
================================================
================================================
FILE: PopcornTime/Views/SeasonHeader.swift
================================================
//
// SeasonHeader.swift
// PopcornTime
//
// Created by Andrew K. on 4/14/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
import UIKit
class SeasonHeader: UICollectionReusableView {
@IBOutlet weak var container: UIView!
@IBOutlet weak var titleLabel: UILabel!
}
================================================
FILE: PopcornTime/Views/SeasonHeader.xib
================================================
================================================
FILE: PopcornTime/Views/ShowCollectionViewCell.swift
================================================
//
// ShowCollectionViewCell.swift
// PopcornTime
//
// Created by Andrew K. on 3/8/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
import UIKit
class ShowCollectionViewCell: UICollectionViewCell {
@IBOutlet weak fileprivate var imageView: UIImageView!
@IBOutlet weak fileprivate var titleLabel: UILabel!
var image: UIImage? {
didSet {
self.imageView?.image = image
self.titleLabel.isHidden = image != nil
}
}
var title: String? {
didSet {
titleLabel.text = title
}
}
override func prepareForReuse() {
super.prepareForReuse()
self.image = nil
}
}
================================================
FILE: PopcornTime/Views/ShowCollectionViewCell.xib
================================================
================================================
FILE: PopcornTime/Views/StratchyHeader.swift
================================================
//
// StratchyHeader.swift
// PopcornTime
//
// Created by Andrew K. on 4/6/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
import UIKit
protocol StratchyHeaderDelegate: class {
///Triggers when header max stratch value is recalculated
func stratchyHeader(_ header: StratchyHeader, didResetMaxStratchValue value: CGFloat)
}
class StratchyHeader: UICollectionReusableView {
weak var delegate: StratchyHeaderDelegate?
// MARK: - Public API
var image: UIImage? {
didSet {
if let image = image {
imageAspectRatio = image.size.height / image.size.width
backgroundImageView.image = image
updateImageViewConstraints()
}
}
}
var headerSize: CGSize = CGSize(width: 1, height: 1) {
didSet {
updateImageViewConstraints()
}
}
// MARK: - UICollectionReusableView
override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
super.apply(layoutAttributes)
let attributes = layoutAttributes as! StratchyLayoutAttributes
let height = attributes.frame.height
if (previousHeight != height) {
if (maxStratch != 0) {
let alpha = 1 - attributes.deltaY / maxStratch
foregroundView.alpha = alpha
}
if (imageAspectRatio != 0) {
heightConstraint.constant = imageViewActualHeight - attributes.deltaY
widthConstraint.constant = imageViewActualWidth - (attributes.deltaY / imageAspectRatio)
}
previousHeight = height
}
}
// MARK: - Private
@IBOutlet weak fileprivate var widthConstraint: NSLayoutConstraint!
@IBOutlet weak fileprivate var heightConstraint: NSLayoutConstraint!
@IBOutlet weak fileprivate var backgroundImageView: UIImageView!
@IBOutlet weak var foregroundView: UIView!
@IBOutlet weak var foregroundImage: UIImageView!
@IBOutlet weak var synopsisTextView: UILabel!
fileprivate var maxStratch: CGFloat = 0
fileprivate var zoomWidthCoef: CGFloat {
get {
let headerAspectRatio = headerSize.height / headerSize.width
return (1.7 * headerAspectRatio) / 0.5325 // Experimentally calculated value :]
}
}
fileprivate var imageAspectRatio: CGFloat = 0
fileprivate var imageViewActualWidth: CGFloat {
return headerSize.width * zoomWidthCoef
}
fileprivate var imageViewActualHeight: CGFloat {
return imageViewActualWidth * imageAspectRatio
}
fileprivate var previousHeight: CGFloat = 0
fileprivate func updateImageViewConstraints() {
// let dX = fabs(headerSize.height - imageViewActualHeight)/2
let dY = fabs(headerSize.width - imageViewActualWidth)/2
maxStratch = dY//max(dX, dY)
self.delegate?.stratchyHeader(self, didResetMaxStratchValue: maxStratch)
widthConstraint.constant = imageViewActualWidth
heightConstraint.constant = imageViewActualHeight
}
}
================================================
FILE: PopcornTime/Views/StratchyHeader.xib
================================================
================================================
FILE: PopcornTime/Views/StratchyHeaderLayout.swift
================================================
//
// StratchyHeaderLayout.swift
// PopcornTime
//
// Created by Andrew K. on 3/13/15.
// Copyright (c) 2015 PopcornTime. All rights reserved.
//
import UIKit
class StratchyLayoutAttributes: UICollectionViewLayoutAttributes {
var deltaY: CGFloat = 0
var maxDelta: CGFloat = CGFloat.greatestFiniteMagnitude
override func copy(with zone: NSZone?) -> Any {
let copy = super.copy(with: zone) as! StratchyLayoutAttributes
copy.deltaY = deltaY
return copy
}
override func isEqual(_ object: Any?) -> Bool {
if let attributes = object as? StratchyLayoutAttributes {
if attributes.deltaY == deltaY {
return super.isEqual(object)
}
}
return false
}
}
class StratchyHeaderLayout: UICollectionViewFlowLayout, StratchyHeaderDelegate {
var headerSize = CGSize.zero
var maxDelta: CGFloat = CGFloat.greatestFiniteMagnitude
let minCellWidth: CGFloat = 300
let cellAspectRatio: CGFloat = 370/46
override class var layoutAttributesClass : AnyClass {
return StratchyLayoutAttributes.self
}
override var collectionViewContentSize : CGSize {
sectionInset.bottom = 15.0
sectionInset.top = 5.0
minimumInteritemSpacing = sectionInset.left
let width = self.collectionView!.bounds.width - sectionInset.left - sectionInset.right
let numberOfColumns = Int(width / minCellWidth)
let cellWidth = CGFloat((Int(width) - Int(minimumInteritemSpacing) * (numberOfColumns - 1)) / numberOfColumns)
let cellHeight = cellWidth / cellAspectRatio
self.itemSize = CGSize(width: cellWidth, height: cellHeight)
return super.collectionViewContentSize
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let insets = collectionView!.contentInset
let offset = collectionView!.contentOffset
let minY = -insets.top
let attributes = super.layoutAttributesForElements(in: rect)
if let stratchyAttributes = attributes as? [StratchyLayoutAttributes] {
// Check if we've pulled below past the lowest position
if (offset.y < minY){
let deltaY = fabs(offset.y - minY)
for attribute in stratchyAttributes{
if (attribute.indexPath.section == 0){
if let kind = attribute.representedElementKind{
if (kind == UICollectionElementKindSectionHeader) {
var headerRect = attribute.frame
headerRect.size.height = min(headerSize.height + maxDelta, max(minY, headerSize.height + deltaY));
headerRect.origin.y = headerRect.minY - deltaY;
attribute.frame = headerRect
attribute.deltaY = deltaY
attribute.maxDelta = maxDelta
}
}
}
}
}
}
return attributes
}
// MARK: - StratchyHeaderDelegate
func stratchyHeader(_ header: StratchyHeader, didResetMaxStratchValue value: CGFloat) {
maxDelta = value
}
}
================================================
FILE: PopcornTime.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
0C0A54281AAEE7CB00B4B9B3 /* ImageProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0A54271AAEE7CB00B4B9B3 /* ImageProvider.swift */; };
0C43395E1AD427BD00542A9B /* StratchyHeader.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0C43395D1AD427BD00542A9B /* StratchyHeader.xib */; };
0C4F84C51AB2E21C00779B1A /* StratchyHeaderLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C4F84C41AB2E21C00779B1A /* StratchyHeaderLayout.swift */; };
0C4F84C71AB32D3A00779B1A /* BarHidingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C4F84C61AB32D3A00779B1A /* BarHidingViewController.swift */; };
0C4F84CE1AB385D600779B1A /* EpisodeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C4F84CC1AB385D600779B1A /* EpisodeCell.swift */; };
0C4F84CF1AB385D600779B1A /* EpisodeCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0C4F84CD1AB385D600779B1A /* EpisodeCell.xib */; };
0C5312D11AACE65A001CB097 /* ShowCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C5312CF1AACE65A001CB097 /* ShowCollectionViewCell.swift */; };
0C5312D21AACE65A001CB097 /* ShowCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0C5312D01AACE65A001CB097 /* ShowCollectionViewCell.xib */; };
0C77A4191AD2CA8300F8B610 /* StratchyHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C77A4181AD2CA8300F8B610 /* StratchyHeader.swift */; };
0C7FB3881AAF3D57007AF38C /* MoreShowsCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C7FB3861AAF3D57007AF38C /* MoreShowsCollectionViewCell.swift */; };
0C7FB3891AAF3D57007AF38C /* MoreShowsCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0C7FB3871AAF3D57007AF38C /* MoreShowsCollectionViewCell.xib */; };
0C90B9B71AE110D4000D4B10 /* SeasonHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C90B9B51AE110D4000D4B10 /* SeasonHeader.swift */; };
0C90B9B81AE110D4000D4B10 /* SeasonHeader.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0C90B9B61AE110D4000D4B10 /* SeasonHeader.xib */; };
0CC1A6761ABB55A6008F7A31 /* PagedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CC1A6751ABB55A6008F7A31 /* PagedViewController.swift */; };
4348D3021B38161800028043 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4348D3011B38161800028043 /* AudioToolbox.framework */; };
4348D3041B38162200028043 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4348D3031B38162200028043 /* CFNetwork.framework */; };
4348D3061B38162F00028043 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4348D3051B38162F00028043 /* CoreGraphics.framework */; };
4348D3081B38163700028043 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4348D3071B38163700028043 /* CoreLocation.framework */; };
4348D30A1B38164300028043 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4348D3091B38164300028043 /* MobileCoreServices.framework */; };
4348D30C1B38164D00028043 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4348D30B1B38164D00028043 /* QuartzCore.framework */; };
4348D30E1B38165900028043 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4348D30D1B38165900028043 /* Security.framework */; };
4348D3121B38166F00028043 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4348D3111B38166F00028043 /* SystemConfiguration.framework */; };
4348D3141B38168800028043 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 4348D3131B38168800028043 /* libsqlite3.dylib */; };
4348D3181B38179100028043 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 4348D3171B38179100028043 /* libz.dylib */; };
4348D31D1B3817D800028043 /* Accounts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4348D3191B3817A400028043 /* Accounts.framework */; };
4348D31E1B3817DF00028043 /* Social.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4348D31B1B3817AB00028043 /* Social.framework */; };
65ADB189759E25BAEE4DD696 /* libPods-PopcornTime.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E136A40A06A3FB238998C59 /* libPods-PopcornTime.a */; };
773399A41AF7E43A008DF31A /* Launch Screen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 773399A31AF7E43A008DF31A /* Launch Screen.xib */; };
773399A61AF7E4C8008DF31A /* FavoritesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 773399A51AF7E4C8008DF31A /* FavoritesViewController.swift */; };
773399A81AF7E4E8008DF31A /* ColorfullTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 773399A71AF7E4E8008DF31A /* ColorfullTabBarController.swift */; };
77F3FE931C7F3F6800E18D42 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 77F3FE921C7F3F6800E18D42 /* AVFoundation.framework */; };
7F01A53E1ABD953900D2B923 /* Anime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F01A53A1ABD953900D2B923 /* Anime.swift */; };
7F01A53F1ABD953900D2B923 /* BasicInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F01A53B1ABD953900D2B923 /* BasicInfo.swift */; };
7F01A5401ABD953900D2B923 /* Movie.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F01A53C1ABD953900D2B923 /* Movie.swift */; };
7F01A5411ABD953900D2B923 /* Show.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F01A53D1ABD953900D2B923 /* Show.swift */; };
7F01A5431ABD9BB600D2B923 /* BaseStructures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F01A5421ABD9BB600D2B923 /* BaseStructures.swift */; };
7F0420501ABDCE4E00E27FA1 /* AnimeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F04204B1ABDCE4E00E27FA1 /* AnimeViewController.swift */; };
7F0420511ABDCE4E00E27FA1 /* BaseCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F04204C1ABDCE4E00E27FA1 /* BaseCollectionViewController.swift */; };
7F0420521ABDCE4E00E27FA1 /* MoviesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F04204D1ABDCE4E00E27FA1 /* MoviesViewController.swift */; };
7F0420531ABDCE4E00E27FA1 /* ShowDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F04204E1ABDCE4E00E27FA1 /* ShowDetailsViewController.swift */; };
7F0420541ABDCE4E00E27FA1 /* ShowsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F04204F1ABDCE4E00E27FA1 /* ShowsViewController.swift */; };
7F0420561ABDD3A100E27FA1 /* BaseDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0420551ABDD3A100E27FA1 /* BaseDetailsViewController.swift */; };
7F0420591ABDD7D700E27FA1 /* AnimeDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0420571ABDD7D700E27FA1 /* AnimeDetailsViewController.swift */; };
7F04205A1ABDD7D700E27FA1 /* MovieDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0420581ABDD7D700E27FA1 /* MovieDetailsViewController.swift */; };
7F083C661AB36D84001C938B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F083C651AB36D84001C938B /* AppDelegate.swift */; };
7F095FDE1AD059F000C10D98 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F095FDD1AD059F000C10D98 /* SettingsViewController.swift */; };
7F4992A21A2491DF00D8D36E /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7F4992981A2491DF00D8D36E /* Images.xcassets */; };
7F5047C31AC2095200521FC4 /* DataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F5047C21AC2095200521FC4 /* DataManager.swift */; };
7F540A271AC8ADE100BCC4AC /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F540A261AC8ADE100BCC4AC /* Extensions.swift */; };
7F8B3EB31ABDC55500BE192D /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F8B3EB21ABDC55500BE192D /* Image.swift */; };
7F8D1A671A9D430100C236BC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7F8D1A661A9D430100C236BC /* Main.storyboard */; };
7F8D1A6A1A9D460000C236BC /* PTAPIManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F8D1A691A9D460000C236BC /* PTAPIManager.m */; };
7FB0E6DC1A9CCC6100DA2454 /* libiconv.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FB0E6DB1A9CCC6100DA2454 /* libiconv.dylib */; };
7FB0E6DE1A9CCC7E00DA2454 /* libbz2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FB0E6DD1A9CCC7E00DA2454 /* libbz2.dylib */; };
7FCFC3A01A23351700B3B8C2 /* libstdc++.6.0.9.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FCFC3951A23341600B3B8C2 /* libstdc++.6.0.9.dylib */; };
7FD762A01AB632E300BD462C /* LoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FD7629C1AB632E300BD462C /* LoadingViewController.swift */; };
7FD762A21AB632E300BD462C /* OAuthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FD7629E1AB632E300BD462C /* OAuthViewController.swift */; };
7FD9B3631A9A89E400DB89B7 /* PTTorrentStreamer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7FD9B3621A9A89E400DB89B7 /* PTTorrentStreamer.mm */; };
7FFEB1971AD079AB00344094 /* OpenSans-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7FFEB1961AD079AB00344094 /* OpenSans-Regular.ttf */; };
7FFEB19D1AD081C900344094 /* VDLPlaybackViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FFEB19A1AD081C900344094 /* VDLPlaybackViewController.m */; };
7FFEB19E1AD081C900344094 /* VDLPlaybackViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7FFEB19B1AD081C900344094 /* VDLPlaybackViewController.xib */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
0C0A54271AAEE7CB00B4B9B3 /* ImageProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageProvider.swift; sourceTree = ""; };
0C0C18FED81000FDE3F1BAC8 /* Pods-PopcornTime.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PopcornTime.debug.xcconfig"; path = "Pods/Target Support Files/Pods-PopcornTime/Pods-PopcornTime.debug.xcconfig"; sourceTree = ""; };
0C43395D1AD427BD00542A9B /* StratchyHeader.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = StratchyHeader.xib; sourceTree = ""; };
0C4F84C41AB2E21C00779B1A /* StratchyHeaderLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StratchyHeaderLayout.swift; sourceTree = ""; };
0C4F84C61AB32D3A00779B1A /* BarHidingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BarHidingViewController.swift; path = Controllers/BarHidingViewController.swift; sourceTree = ""; };
0C4F84CC1AB385D600779B1A /* EpisodeCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EpisodeCell.swift; sourceTree = ""; };
0C4F84CD1AB385D600779B1A /* EpisodeCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = EpisodeCell.xib; sourceTree = ""; };
0C5312C91AACDF34001CB097 /* PopcornTime-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "PopcornTime-Bridging-Header.h"; sourceTree = ""; };
0C5312CF1AACE65A001CB097 /* ShowCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShowCollectionViewCell.swift; sourceTree = ""; };
0C5312D01AACE65A001CB097 /* ShowCollectionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ShowCollectionViewCell.xib; sourceTree = ""; };
0C77A4181AD2CA8300F8B610 /* StratchyHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StratchyHeader.swift; sourceTree = ""; };
0C7FB3861AAF3D57007AF38C /* MoreShowsCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MoreShowsCollectionViewCell.swift; sourceTree = ""; };
0C7FB3871AAF3D57007AF38C /* MoreShowsCollectionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MoreShowsCollectionViewCell.xib; sourceTree = ""; };
0C90B9B51AE110D4000D4B10 /* SeasonHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeasonHeader.swift; sourceTree = ""; };
0C90B9B61AE110D4000D4B10 /* SeasonHeader.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SeasonHeader.xib; sourceTree = ""; };
0CC1A6751ABB55A6008F7A31 /* PagedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PagedViewController.swift; path = Controllers/PagedViewController.swift; sourceTree = ""; };
4348D3011B38161800028043 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; };
4348D3031B38162200028043 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; };
4348D3051B38162F00028043 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
4348D3071B38163700028043 /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; };
4348D3091B38164300028043 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; };
4348D30B1B38164D00028043 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
4348D30D1B38165900028043 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
4348D3111B38166F00028043 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
4348D3131B38168800028043 /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = usr/lib/libsqlite3.dylib; sourceTree = SDKROOT; };
4348D3171B38179100028043 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; };
4348D3191B3817A400028043 /* Accounts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accounts.framework; path = System/Library/Frameworks/Accounts.framework; sourceTree = SDKROOT; };
4348D31B1B3817AB00028043 /* Social.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Social.framework; path = System/Library/Frameworks/Social.framework; sourceTree = SDKROOT; };
5892EA13CDF70C028E9A7CB2 /* Pods-PopcornTime.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PopcornTime.release.xcconfig"; path = "Pods/Target Support Files/Pods-PopcornTime/Pods-PopcornTime.release.xcconfig"; sourceTree = ""; };
5E136A40A06A3FB238998C59 /* libPods-PopcornTime.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-PopcornTime.a"; sourceTree = BUILT_PRODUCTS_DIR; };
60E49EA58E27726D5CF6EA80 /* Pods-PopcornTime.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PopcornTime.debug.xcconfig"; path = "Pods/Target Support Files/Pods-PopcornTime/Pods-PopcornTime.debug.xcconfig"; sourceTree = ""; };
773399A31AF7E43A008DF31A /* Launch Screen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = "Launch Screen.xib"; path = "PopcornTime/Resources/Launch Screen.xib"; sourceTree = SOURCE_ROOT; };
773399A51AF7E4C8008DF31A /* FavoritesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FavoritesViewController.swift; path = Controllers/FavoritesViewController.swift; sourceTree = ""; };
773399A71AF7E4E8008DF31A /* ColorfullTabBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ColorfullTabBarController.swift; path = Controllers/ColorfullTabBarController.swift; sourceTree = ""; };
77F3FE921C7F3F6800E18D42 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
7F01A53A1ABD953900D2B923 /* Anime.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Anime.swift; sourceTree = ""; };
7F01A53B1ABD953900D2B923 /* BasicInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasicInfo.swift; sourceTree = ""; };
7F01A53C1ABD953900D2B923 /* Movie.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Movie.swift; sourceTree = ""; };
7F01A53D1ABD953900D2B923 /* Show.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Show.swift; sourceTree = ""; };
7F01A5421ABD9BB600D2B923 /* BaseStructures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseStructures.swift; sourceTree = ""; };
7F04204B1ABDCE4E00E27FA1 /* AnimeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AnimeViewController.swift; path = Controllers/AnimeViewController.swift; sourceTree = ""; };
7F04204C1ABDCE4E00E27FA1 /* BaseCollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BaseCollectionViewController.swift; path = Controllers/BaseCollectionViewController.swift; sourceTree = ""; };
7F04204D1ABDCE4E00E27FA1 /* MoviesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MoviesViewController.swift; path = Controllers/MoviesViewController.swift; sourceTree = ""; };
7F04204E1ABDCE4E00E27FA1 /* ShowDetailsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ShowDetailsViewController.swift; path = Controllers/ShowDetailsViewController.swift; sourceTree = ""; };
7F04204F1ABDCE4E00E27FA1 /* ShowsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ShowsViewController.swift; path = Controllers/ShowsViewController.swift; sourceTree = ""; };
7F0420551ABDD3A100E27FA1 /* BaseDetailsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BaseDetailsViewController.swift; path = Controllers/BaseDetailsViewController.swift; sourceTree = ""; };
7F0420571ABDD7D700E27FA1 /* AnimeDetailsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AnimeDetailsViewController.swift; path = Controllers/AnimeDetailsViewController.swift; sourceTree = ""; };
7F0420581ABDD7D700E27FA1 /* MovieDetailsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MovieDetailsViewController.swift; path = Controllers/MovieDetailsViewController.swift; sourceTree = ""; };
7F083C651AB36D84001C938B /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
7F095FDD1AD059F000C10D98 /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SettingsViewController.swift; path = Controllers/SettingsViewController.swift; sourceTree = ""; };
7F4992981A2491DF00D8D36E /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Resources/Images.xcassets; sourceTree = ""; };
7F5047C21AC2095200521FC4 /* DataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataManager.swift; sourceTree = ""; };
7F540A261AC8ADE100BCC4AC /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; };
7F8824C71A227A0100E2710A /* PopcornTime.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PopcornTime.app; sourceTree = BUILT_PRODUCTS_DIR; };
7F8B3EB21ABDC55500BE192D /* Image.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Image.swift; sourceTree = ""; };
7F8D1A661A9D430100C236BC /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Main.storyboard; path = Resources/Main.storyboard; sourceTree = ""; };
7F8D1A681A9D460000C236BC /* PTAPIManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PTAPIManager.h; sourceTree = ""; };
7F8D1A691A9D460000C236BC /* PTAPIManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PTAPIManager.m; sourceTree = ""; };
7FB0E6DB1A9CCC6100DA2454 /* libiconv.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libiconv.dylib; path = usr/lib/libiconv.dylib; sourceTree = SDKROOT; };
7FB0E6DD1A9CCC7E00DA2454 /* libbz2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libbz2.dylib; path = usr/lib/libbz2.dylib; sourceTree = SDKROOT; };
7FCFC3951A23341600B3B8C2 /* libstdc++.6.0.9.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = "libstdc++.6.0.9.dylib"; path = "usr/lib/libstdc++.6.0.9.dylib"; sourceTree = SDKROOT; };
7FD2C38D1A233C9000CA896A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
7FD7629C1AB632E300BD462C /* LoadingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LoadingViewController.swift; path = Controllers/LoadingViewController.swift; sourceTree = ""; };
7FD7629E1AB632E300BD462C /* OAuthViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OAuthViewController.swift; path = Controllers/OAuthViewController.swift; sourceTree = ""; };
7FD9B3611A9A89E400DB89B7 /* PTTorrentStreamer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PTTorrentStreamer.h; sourceTree = ""; };
7FD9B3621A9A89E400DB89B7 /* PTTorrentStreamer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PTTorrentStreamer.mm; sourceTree = ""; };
7FFEB1961AD079AB00344094 /* OpenSans-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "OpenSans-Regular.ttf"; path = "Resources/OpenSans-Regular.ttf"; sourceTree = ""; };
7FFEB1991AD081C900344094 /* VDLPlaybackViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VDLPlaybackViewController.h; sourceTree = ""; };
7FFEB19A1AD081C900344094 /* VDLPlaybackViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VDLPlaybackViewController.m; sourceTree = ""; };
7FFEB19B1AD081C900344094 /* VDLPlaybackViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = VDLPlaybackViewController.xib; sourceTree = ""; };
C2ADA07C3D291A9561B464E9 /* Pods-PopcornTime.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PopcornTime.release.xcconfig"; path = "Pods/Target Support Files/Pods-PopcornTime/Pods-PopcornTime.release.xcconfig"; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
7F8824C41A227A0100E2710A /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
77F3FE931C7F3F6800E18D42 /* AVFoundation.framework in Frameworks */,
4348D31E1B3817DF00028043 /* Social.framework in Frameworks */,
4348D31D1B3817D800028043 /* Accounts.framework in Frameworks */,
4348D3181B38179100028043 /* libz.dylib in Frameworks */,
4348D3141B38168800028043 /* libsqlite3.dylib in Frameworks */,
4348D3121B38166F00028043 /* SystemConfiguration.framework in Frameworks */,
4348D30E1B38165900028043 /* Security.framework in Frameworks */,
4348D30C1B38164D00028043 /* QuartzCore.framework in Frameworks */,
4348D30A1B38164300028043 /* MobileCoreServices.framework in Frameworks */,
4348D3081B38163700028043 /* CoreLocation.framework in Frameworks */,
4348D3061B38162F00028043 /* CoreGraphics.framework in Frameworks */,
4348D3041B38162200028043 /* CFNetwork.framework in Frameworks */,
4348D3021B38161800028043 /* AudioToolbox.framework in Frameworks */,
7FB0E6DE1A9CCC7E00DA2454 /* libbz2.dylib in Frameworks */,
7FB0E6DC1A9CCC6100DA2454 /* libiconv.dylib in Frameworks */,
7FCFC3A01A23351700B3B8C2 /* libstdc++.6.0.9.dylib in Frameworks */,
65ADB189759E25BAEE4DD696 /* libPods-PopcornTime.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
0C5312CE1AACE5AF001CB097 /* Views */ = {
isa = PBXGroup;
children = (
0C4F84CC1AB385D600779B1A /* EpisodeCell.swift */,
0C4F84CD1AB385D600779B1A /* EpisodeCell.xib */,
0C5312CF1AACE65A001CB097 /* ShowCollectionViewCell.swift */,
0C5312D01AACE65A001CB097 /* ShowCollectionViewCell.xib */,
0C7FB3861AAF3D57007AF38C /* MoreShowsCollectionViewCell.swift */,
0C7FB3871AAF3D57007AF38C /* MoreShowsCollectionViewCell.xib */,
0C90B9B51AE110D4000D4B10 /* SeasonHeader.swift */,
0C90B9B61AE110D4000D4B10 /* SeasonHeader.xib */,
0C4F84C41AB2E21C00779B1A /* StratchyHeaderLayout.swift */,
0C77A4181AD2CA8300F8B610 /* StratchyHeader.swift */,
0C43395D1AD427BD00542A9B /* StratchyHeader.xib */,
);
path = Views;
sourceTree = "";
};
4EBD161BC990BBB37DA12395 /* Pods */ = {
isa = PBXGroup;
children = (
0C0C18FED81000FDE3F1BAC8 /* Pods-PopcornTime.debug.xcconfig */,
C2ADA07C3D291A9561B464E9 /* Pods-PopcornTime.release.xcconfig */,
60E49EA58E27726D5CF6EA80 /* Pods-PopcornTime.debug.xcconfig */,
5892EA13CDF70C028E9A7CB2 /* Pods-PopcornTime.release.xcconfig */,
);
name = Pods;
sourceTree = "";
};
7F01A5481ABD9F5800D2B923 /* Base */ = {
isa = PBXGroup;
children = (
7F01A5421ABD9BB600D2B923 /* BaseStructures.swift */,
7F8B3EB21ABDC55500BE192D /* Image.swift */,
7F01A53B1ABD953900D2B923 /* BasicInfo.swift */,
7F01A53D1ABD953900D2B923 /* Show.swift */,
7F01A53C1ABD953900D2B923 /* Movie.swift */,
7F01A53A1ABD953900D2B923 /* Anime.swift */,
);
name = Base;
sourceTree = "";
};
7F4992B71A24928200D8D36E /* Models */ = {
isa = PBXGroup;
children = (
7F083C651AB36D84001C938B /* AppDelegate.swift */,
7F8D1A681A9D460000C236BC /* PTAPIManager.h */,
7F8D1A691A9D460000C236BC /* PTAPIManager.m */,
7F5047C21AC2095200521FC4 /* DataManager.swift */,
7FD9B3611A9A89E400DB89B7 /* PTTorrentStreamer.h */,
7FD9B3621A9A89E400DB89B7 /* PTTorrentStreamer.mm */,
0C0A54271AAEE7CB00B4B9B3 /* ImageProvider.swift */,
7F540A261AC8ADE100BCC4AC /* Extensions.swift */,
7F01A5481ABD9F5800D2B923 /* Base */,
);
path = Models;
sourceTree = "";
};
7F4992DE1A24928C00D8D36E /* Thirdparties */ = {
isa = PBXGroup;
children = (
7FFEB1981AD081C900344094 /* Dropin-Player */,
);
name = Thirdparties;
sourceTree = "";
};
7F8824BE1A227A0000E2710A = {
isa = PBXGroup;
children = (
7F8824C91A227A0100E2710A /* PopcornTime */,
7FCFC2F01A227FA800B3B8C2 /* Frameworks */,
7F8824C81A227A0100E2710A /* Products */,
4EBD161BC990BBB37DA12395 /* Pods */,
);
indentWidth = 4;
sourceTree = "";
tabWidth = 4;
};
7F8824C81A227A0100E2710A /* Products */ = {
isa = PBXGroup;
children = (
7F8824C71A227A0100E2710A /* PopcornTime.app */,
);
name = Products;
sourceTree = "";
};
7F8824C91A227A0100E2710A /* PopcornTime */ = {
isa = PBXGroup;
children = (
7F4992DE1A24928C00D8D36E /* Thirdparties */,
7F4992B71A24928200D8D36E /* Models */,
0C5312CE1AACE5AF001CB097 /* Views */,
7F8D1A6C1A9DB78100C236BC /* Controllers */,
7FCFC31B1A2281B900B3B8C2 /* Resources */,
7F8824CA1A227A0100E2710A /* Supporting Files */,
);
path = PopcornTime;
sourceTree = "";
};
7F8824CA1A227A0100E2710A /* Supporting Files */ = {
isa = PBXGroup;
children = (
0C5312C91AACDF34001CB097 /* PopcornTime-Bridging-Header.h */,
7FD2C38D1A233C9000CA896A /* Info.plist */,
);
name = "Supporting Files";
sourceTree = "";
};
7F8D1A6C1A9DB78100C236BC /* Controllers */ = {
isa = PBXGroup;
children = (
7FD7629E1AB632E300BD462C /* OAuthViewController.swift */,
7FD7629C1AB632E300BD462C /* LoadingViewController.swift */,
0C4F84C61AB32D3A00779B1A /* BarHidingViewController.swift */,
7F04204C1ABDCE4E00E27FA1 /* BaseCollectionViewController.swift */,
0CC1A6751ABB55A6008F7A31 /* PagedViewController.swift */,
773399A51AF7E4C8008DF31A /* FavoritesViewController.swift */,
7F0420551ABDD3A100E27FA1 /* BaseDetailsViewController.swift */,
7F04204F1ABDCE4E00E27FA1 /* ShowsViewController.swift */,
7F04204E1ABDCE4E00E27FA1 /* ShowDetailsViewController.swift */,
7F04204D1ABDCE4E00E27FA1 /* MoviesViewController.swift */,
7F0420581ABDD7D700E27FA1 /* MovieDetailsViewController.swift */,
7F04204B1ABDCE4E00E27FA1 /* AnimeViewController.swift */,
7F0420571ABDD7D700E27FA1 /* AnimeDetailsViewController.swift */,
7F095FDD1AD059F000C10D98 /* SettingsViewController.swift */,
773399A71AF7E4E8008DF31A /* ColorfullTabBarController.swift */,
);
name = Controllers;
sourceTree = "";
};
7FCFC2F01A227FA800B3B8C2 /* Frameworks */ = {
isa = PBXGroup;
children = (
77F3FE921C7F3F6800E18D42 /* AVFoundation.framework */,
4348D31B1B3817AB00028043 /* Social.framework */,
4348D3191B3817A400028043 /* Accounts.framework */,
4348D3171B38179100028043 /* libz.dylib */,
4348D3131B38168800028043 /* libsqlite3.dylib */,
4348D3111B38166F00028043 /* SystemConfiguration.framework */,
4348D30D1B38165900028043 /* Security.framework */,
4348D30B1B38164D00028043 /* QuartzCore.framework */,
4348D3091B38164300028043 /* MobileCoreServices.framework */,
4348D3071B38163700028043 /* CoreLocation.framework */,
4348D3051B38162F00028043 /* CoreGraphics.framework */,
4348D3031B38162200028043 /* CFNetwork.framework */,
4348D3011B38161800028043 /* AudioToolbox.framework */,
7FCFC3951A23341600B3B8C2 /* libstdc++.6.0.9.dylib */,
7FB0E6DD1A9CCC7E00DA2454 /* libbz2.dylib */,
7FB0E6DB1A9CCC6100DA2454 /* libiconv.dylib */,
5E136A40A06A3FB238998C59 /* libPods-PopcornTime.a */,
);
name = Frameworks;
sourceTree = "";
};
7FCFC31B1A2281B900B3B8C2 /* Resources */ = {
isa = PBXGroup;
children = (
773399A31AF7E43A008DF31A /* Launch Screen.xib */,
7F8D1A661A9D430100C236BC /* Main.storyboard */,
7F4992981A2491DF00D8D36E /* Images.xcassets */,
7FFEB1961AD079AB00344094 /* OpenSans-Regular.ttf */,
);
name = Resources;
sourceTree = "";
};
7FFEB1981AD081C900344094 /* Dropin-Player */ = {
isa = PBXGroup;
children = (
7FFEB1991AD081C900344094 /* VDLPlaybackViewController.h */,
7FFEB19A1AD081C900344094 /* VDLPlaybackViewController.m */,
7FFEB19B1AD081C900344094 /* VDLPlaybackViewController.xib */,
);
name = "Dropin-Player";
path = "Thirdparties/VLCKit/Dropin-Player";
sourceTree = SOURCE_ROOT;
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
7F8824C61A227A0100E2710A /* PopcornTime */ = {
isa = PBXNativeTarget;
buildConfigurationList = 7F8824EA1A227A0100E2710A /* Build configuration list for PBXNativeTarget "PopcornTime" */;
buildPhases = (
C38234B7FF1C570F03EDA121 /* Check Pods Manifest.lock */,
7F8824C31A227A0100E2710A /* Sources */,
7F8824C41A227A0100E2710A /* Frameworks */,
7F8824C51A227A0100E2710A /* Resources */,
814B281F6347C227DBD77ABF /* Copy Pods Resources */,
7742C4881B038132001B783E /* ShellScript */,
7BC4B191B7B88187B684FB7B /* Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = PopcornTime;
productName = PopcornTime;
productReference = 7F8824C71A227A0100E2710A /* PopcornTime.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
7F8824BF1A227A0000E2710A /* Project object */ = {
isa = PBXProject;
attributes = {
CLASSPREFIX = "";
LastSwiftMigration = 0700;
LastSwiftUpdateCheck = 0700;
LastUpgradeCheck = 0820;
ORGANIZATIONNAME = PopcornTime;
TargetAttributes = {
7F8824C61A227A0100E2710A = {
CreatedOnToolsVersion = 6.1;
DevelopmentTeam = DBR2MMCRF5;
LastSwiftMigration = 0820;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.InAppPurchase = {
enabled = 0;
};
};
};
};
};
buildConfigurationList = 7F8824C21A227A0100E2710A /* Build configuration list for PBXProject "PopcornTime" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 7F8824BE1A227A0000E2710A;
productRefGroup = 7F8824C81A227A0100E2710A /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
7F8824C61A227A0100E2710A /* PopcornTime */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
7F8824C51A227A0100E2710A /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0C7FB3891AAF3D57007AF38C /* MoreShowsCollectionViewCell.xib in Resources */,
0C5312D21AACE65A001CB097 /* ShowCollectionViewCell.xib in Resources */,
0C43395E1AD427BD00542A9B /* StratchyHeader.xib in Resources */,
7FFEB19E1AD081C900344094 /* VDLPlaybackViewController.xib in Resources */,
7F8D1A671A9D430100C236BC /* Main.storyboard in Resources */,
7F4992A21A2491DF00D8D36E /* Images.xcassets in Resources */,
0C90B9B81AE110D4000D4B10 /* SeasonHeader.xib in Resources */,
7FFEB1971AD079AB00344094 /* OpenSans-Regular.ttf in Resources */,
0C4F84CF1AB385D600779B1A /* EpisodeCell.xib in Resources */,
773399A41AF7E43A008DF31A /* Launch Screen.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
7742C4881B038132001B783E /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "if [ -n \"$FABRIC_API_KEY\" ] && [ -n \"$FABRIC_BUILD_SECRET\" ]; then\n ${PODS_ROOT}/Fabric/run ${FABRIC_API_KEY} ${FABRIC_BUILD_SECRET} || true\nelse\n echo \"FABRIC_API_KEY and/or FABRIC_BUILD_SECRET are not set\"\nfi";
};
7BC4B191B7B88187B684FB7B /* Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Embed Pods Frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-PopcornTime/Pods-PopcornTime-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
814B281F6347C227DBD77ABF /* Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-PopcornTime/Pods-PopcornTime-resources.sh\"\n";
showEnvVarsInLog = 0;
};
C38234B7FF1C570F03EDA121 /* Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Check Pods Manifest.lock";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
7F8824C31A227A0100E2710A /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
7F5047C31AC2095200521FC4 /* DataManager.swift in Sources */,
7F8B3EB31ABDC55500BE192D /* Image.swift in Sources */,
7F0420561ABDD3A100E27FA1 /* BaseDetailsViewController.swift in Sources */,
7FD9B3631A9A89E400DB89B7 /* PTTorrentStreamer.mm in Sources */,
7FD762A01AB632E300BD462C /* LoadingViewController.swift in Sources */,
7F01A5401ABD953900D2B923 /* Movie.swift in Sources */,
0C5312D11AACE65A001CB097 /* ShowCollectionViewCell.swift in Sources */,
7F0420541ABDCE4E00E27FA1 /* ShowsViewController.swift in Sources */,
773399A61AF7E4C8008DF31A /* FavoritesViewController.swift in Sources */,
7F01A5411ABD953900D2B923 /* Show.swift in Sources */,
7F540A271AC8ADE100BCC4AC /* Extensions.swift in Sources */,
0C77A4191AD2CA8300F8B610 /* StratchyHeader.swift in Sources */,
7F01A5431ABD9BB600D2B923 /* BaseStructures.swift in Sources */,
0C4F84CE1AB385D600779B1A /* EpisodeCell.swift in Sources */,
773399A81AF7E4E8008DF31A /* ColorfullTabBarController.swift in Sources */,
7F01A53F1ABD953900D2B923 /* BasicInfo.swift in Sources */,
7FFEB19D1AD081C900344094 /* VDLPlaybackViewController.m in Sources */,
0C4F84C71AB32D3A00779B1A /* BarHidingViewController.swift in Sources */,
7F8D1A6A1A9D460000C236BC /* PTAPIManager.m in Sources */,
0C7FB3881AAF3D57007AF38C /* MoreShowsCollectionViewCell.swift in Sources */,
0CC1A6761ABB55A6008F7A31 /* PagedViewController.swift in Sources */,
7FD762A21AB632E300BD462C /* OAuthViewController.swift in Sources */,
7F0420591ABDD7D700E27FA1 /* AnimeDetailsViewController.swift in Sources */,
7F095FDE1AD059F000C10D98 /* SettingsViewController.swift in Sources */,
7F0420531ABDCE4E00E27FA1 /* ShowDetailsViewController.swift in Sources */,
7F0420501ABDCE4E00E27FA1 /* AnimeViewController.swift in Sources */,
0C4F84C51AB2E21C00779B1A /* StratchyHeaderLayout.swift in Sources */,
7F01A53E1ABD953900D2B923 /* Anime.swift in Sources */,
7F0420511ABDCE4E00E27FA1 /* BaseCollectionViewController.swift in Sources */,
7F04205A1ABDD7D700E27FA1 /* MovieDetailsViewController.swift in Sources */,
7F0420521ABDCE4E00E27FA1 /* MoviesViewController.swift in Sources */,
0C0A54281AAEE7CB00B4B9B3 /* ImageProvider.swift in Sources */,
7F083C661AB36D84001C938B /* AppDelegate.swift in Sources */,
0C90B9B71AE110D4000D4B10 /* SeasonHeader.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
7F8824E81A227A0100E2710A /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEVELOPMENT_TEAM = DBR2MMCRF5;
ENABLE_BITCODE = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
OTHER_SWIFT_FLAGS = "-D DEBUG";
SDKROOT = iphoneos;
};
name = Debug;
};
7F8824E91A227A0100E2710A /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = YES;
DEVELOPMENT_TEAM = DBR2MMCRF5;
ENABLE_BITCODE = NO;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
7F8824EB1A227A0100E2710A /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 60E49EA58E27726D5CF6EA80 /* Pods-PopcornTime.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
);
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
INFOPLIST_FILE = "$(SRCROOT)/PopcornTime/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
ONLY_ACTIVE_ARCH = YES;
OTHER_SWIFT_FLAGS = "-DDEBUG";
PRODUCT_BUNDLE_IDENTIFIER = com.popcorntime.ios.dk;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "PopcornTime/PopcornTime-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 3.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALID_ARCHS = "arm64 armv7 armv7s";
};
name = Debug;
};
7F8824EC1A227A0100E2710A /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 5892EA13CDF70C028E9A7CB2 /* Pods-PopcornTime.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
);
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
INFOPLIST_FILE = "$(SRCROOT)/PopcornTime/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
OTHER_SWIFT_FLAGS = "-DRELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.popcorntime.ios.dk;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "PopcornTime/PopcornTime-Bridging-Header.h";
SWIFT_VERSION = 3.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALID_ARCHS = "arm64 armv7 armv7s";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
7F8824C21A227A0100E2710A /* Build configuration list for PBXProject "PopcornTime" */ = {
isa = XCConfigurationList;
buildConfigurations = (
7F8824E81A227A0100E2710A /* Debug */,
7F8824E91A227A0100E2710A /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
7F8824EA1A227A0100E2710A /* Build configuration list for PBXNativeTarget "PopcornTime" */ = {
isa = XCConfigurationList;
buildConfigurations = (
7F8824EB1A227A0100E2710A /* Debug */,
7F8824EC1A227A0100E2710A /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 7F8824BF1A227A0000E2710A /* Project object */;
}
================================================
FILE: PopcornTime.xcodeproj/xcshareddata/xcschemes/PopcornTime.xcscheme
================================================
================================================
FILE: README.md
================================================
## PopcornTime for iOS
[](https://www.bitrise.io/app/9ee06c0598c7cbdb)
Version of PopcornTime app for iOS based on [libtorrent](http://www.libtorrent.org) and [MobileVLCKit](https://wiki.videolan.org/VLCKit/). There is still a lot of work to do, but in most cases it works.
### Screenshots



### Getting Started
This project uses [CocoaPods](http://cocoapods.org/).
``` bash
$ git clone https://github.com/danylokostyshyn/popcorntime-ios.git
$ cd popcorntime-ios/
$ pod install
$ open PopcornTime.xcworkspace/
```
================================================
FILE: Thirdparties/VLCKit/Dropin-Player/VDLPlaybackViewController.h
================================================
/* Copyright (c) 2013, Felix Paul Kühne and VideoLAN
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE. */
#import
#import
@class VDLPlaybackViewController;
@protocol VDLPlaybackViewControllerDelegate
- (void)playbackControllerDidFinishPlayback:(VDLPlaybackViewController *)playbackController;
@end
@interface VDLPlaybackViewController : UIViewController
@property (nonatomic, strong) IBOutlet UIView *movieView;
@property (nonatomic, strong) IBOutlet UISlider *positionSlider;
@property (nonatomic, strong) IBOutlet UIButton *timeDisplay;
@property (nonatomic, strong) IBOutlet UIButton *playPauseButton;
@property (nonatomic, strong) IBOutlet UIButton *subtitleSwitcherButton;
@property (nonatomic, strong) IBOutlet UIButton *audioSwitcherButton;
@property (nonatomic, strong) IBOutlet UINavigationBar *toolbar;
@property (nonatomic, strong) IBOutlet UIView *controllerPanel;
@property (nonatomic, strong) IBOutlet MPVolumeView *volumeView;
@property (nonatomic, weak) id delegate;
- (void)playMediaFromURL:(NSURL*)theURL;
- (IBAction)closePlayback:(id)sender;
- (IBAction)positionSliderDrag:(id)sender;
- (IBAction)positionSliderAction:(id)sender;
- (IBAction)toggleTimeDisplay:(id)sender;
- (IBAction)playandPause:(id)sender;
- (IBAction)switchAudioTrack:(id)sender;
- (IBAction)switchSubtitleTrack:(id)sender;
- (IBAction)switchVideoDimensions:(id)sender;
@end
================================================
FILE: Thirdparties/VLCKit/Dropin-Player/VDLPlaybackViewController.m
================================================
/* Copyright (c) 2013, Felix Paul Kühne and VideoLAN
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE. */
#import "VDLPlaybackViewController.h"
#import
#import
@interface VDLPlaybackViewController ()
{
VLCMediaPlayer *_mediaplayer;
BOOL _setPosition;
BOOL _displayRemainingTime;
int _currentAspectRatioMask;
NSArray *_aspectRatios;
UIActionSheet *_audiotrackActionSheet;
UIActionSheet *_subtitleActionSheet;
NSURL *_url;
NSTimer *_idleTimer;
}
@end
@implementation VDLPlaybackViewController
- (void)viewDidLoad
{
[super viewDidLoad];
/* fix-up UI */
self.wantsFullScreenLayout = YES;
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
/* we want to influence the system volume */
[[AVAudioSession sharedInstance] setDelegate:self];
/* populate array of supported aspect ratios (there are more!) */
_aspectRatios = @[@"DEFAULT", @"FILL_TO_SCREEN", @"4:3", @"16:9", @"16:10", @"2.21:1"];
/* fix-up the UI */
CGRect rect = self.toolbar.frame;
rect.size.height += 20.;
self.toolbar.frame = rect;
[self.timeDisplay setTitle:@"" forState:UIControlStateNormal];
/* this looks a bit weird, but let's try to support iOS 5 */
UISlider *volumeSlider = nil;
for (id aView in self.volumeView.subviews){
if ([[[aView class] description] isEqualToString:@"MPVolumeSlider"]){
volumeSlider = (UISlider *)aView;
break;
}
}
[volumeSlider addTarget:self
action:@selector(volumeSliderAction:)
forControlEvents:UIControlEventValueChanged];
/* setup gesture recognizer to toggle controls' visibility */
UITapGestureRecognizer *tapOnVideoRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleControlsVisible)];
[self.movieView addGestureRecognizer:tapOnVideoRecognizer];
}
- (void)playMediaFromURL:(NSURL*)theURL
{
_url = theURL;
}
- (IBAction)playandPause:(id)sender
{
if (_mediaplayer.isPlaying)
[_mediaplayer pause];
[_mediaplayer play];
}
- (IBAction)closePlayback:(id)sender
{
[self.delegate playbackControllerDidFinishPlayback:self];
self.delegate = nil;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.navigationController setNavigationBarHidden:YES animated:YES];
/* setup the media player instance, give it a delegate and something to draw into */
// NSString *documents = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
// NSString *logFilePath = [documents stringByAppendingPathComponent:@"log.txt"];
// NSString *logParam = [NSString stringWithFormat:@"--logfile=\"%@\"", logFilePath];
_mediaplayer = [[VLCMediaPlayer alloc] initWithOptions:@[@"--avi-index=2", @"--play-and-pause"]];
_mediaplayer.delegate = self;
_mediaplayer.drawable = self.movieView;
/* listen for notifications from the player */
[_mediaplayer addObserver:self forKeyPath:@"time" options:0 context:nil];
[_mediaplayer addObserver:self forKeyPath:@"remainingTime" options:0 context:nil];
/* create a media object and give it to the player */
_mediaplayer.media = [VLCMedia mediaWithURL:_url];
[_mediaplayer play];
if (self.controllerPanel.hidden)
[self toggleControlsVisible];
[self _resetIdleTimer];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if (_mediaplayer) {
@try {
[_mediaplayer removeObserver:self forKeyPath:@"time"];
[_mediaplayer removeObserver:self forKeyPath:@"remainingTime"];
}
@catch (NSException *exception) {
NSLog(@"we weren't an observer yet");
}
if (_mediaplayer.media)
[_mediaplayer stop];
if (_mediaplayer)
_mediaplayer = nil;
}
if (_idleTimer) {
[_idleTimer invalidate];
_idleTimer = nil;
}
[self.navigationController setNavigationBarHidden:NO animated:YES];
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
}
- (IBAction)positionSliderAction:(UISlider *)sender
{
[self _resetIdleTimer];
/* we need to limit the number of events sent by the slider, since otherwise, the user
* wouldn't see the I-frames when seeking on current mobile devices. This isn't a problem
* within the Simulator, but especially on older ARMv7 devices, it's clearly noticeable. */
[self performSelector:@selector(_setPositionForReal) withObject:nil afterDelay:0.3];
_setPosition = NO;
}
- (void)_setPositionForReal
{
if (!_setPosition) {
_mediaplayer.position = _positionSlider.value;
_setPosition = YES;
}
}
- (IBAction)positionSliderDrag:(id)sender
{
[self _resetIdleTimer];
}
- (IBAction)volumeSliderAction:(id)sender
{
[self _resetIdleTimer];
}
- (void)mediaPlayerStateChanged:(NSNotification *)aNotification
{
VLCMediaPlayerState currentState = _mediaplayer.state;
/* distruct view controller on error */
if (currentState == VLCMediaPlayerStateError)
[self performSelector:@selector(closePlayback:) withObject:nil afterDelay:2.];
/* or if playback ended */
if (currentState == VLCMediaPlayerStateEnded || currentState == VLCMediaPlayerStateStopped)
[self performSelector:@selector(closePlayback:) withObject:nil afterDelay:2.];
[self.playPauseButton setTitle:[_mediaplayer isPlaying]? @"Pause" : @"Play" forState:UIControlStateNormal];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
self.positionSlider.value = [_mediaplayer position];
if (_displayRemainingTime)
[self.timeDisplay setTitle:[[_mediaplayer remainingTime] stringValue] forState:UIControlStateNormal];
else
[self.timeDisplay setTitle:[[_mediaplayer time] stringValue] forState:UIControlStateNormal];
}
- (IBAction)toggleTimeDisplay:(id)sender
{
[self _resetIdleTimer];
_displayRemainingTime = !_displayRemainingTime;
}
- (void)toggleControlsVisible
{
BOOL controlsHidden = !self.controllerPanel.hidden;
self.controllerPanel.hidden = controlsHidden;
self.toolbar.hidden = controlsHidden;
[[UIApplication sharedApplication] setStatusBarHidden:controlsHidden withAnimation:UIStatusBarAnimationFade];
}
- (void)_resetIdleTimer
{
if (!_idleTimer)
_idleTimer = [NSTimer scheduledTimerWithTimeInterval:5.
target:self
selector:@selector(idleTimerExceeded)
userInfo:nil
repeats:NO];
else {
if (fabs([_idleTimer.fireDate timeIntervalSinceNow]) < 5.)
[_idleTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:5.]];
}
}
- (void)idleTimerExceeded
{
_idleTimer = nil;
if (!self.controllerPanel.hidden)
[self toggleControlsVisible];
}
- (IBAction)switchVideoDimensions:(id)sender
{
[self _resetIdleTimer];
NSUInteger count = [_aspectRatios count];
if (_currentAspectRatioMask + 1 > count - 1) {
_mediaplayer.videoAspectRatio = NULL;
_mediaplayer.videoCropGeometry = NULL;
_currentAspectRatioMask = 0;
NSLog(@"crop disabled");
} else {
_currentAspectRatioMask++;
if ([_aspectRatios[_currentAspectRatioMask] isEqualToString:@"FILL_TO_SCREEN"]) {
UIScreen *screen = [UIScreen mainScreen];
float f_ar = screen.bounds.size.width / screen.bounds.size.height;
if (f_ar == (float)(640./1136.)) // iPhone 5 aka 16:9.01
_mediaplayer.videoCropGeometry = "16:9";
else if (f_ar == (float)(2./3.)) // all other iPhones
_mediaplayer.videoCropGeometry = "16:10"; // libvlc doesn't support 2:3 crop
else if (f_ar == .75) // all iPads
_mediaplayer.videoCropGeometry = "4:3";
else if (f_ar == .5625) // AirPlay
_mediaplayer.videoCropGeometry = "16:9";
else
NSLog(@"unknown screen format %f, can't crop", f_ar);
NSLog(@"FILL_TO_SCREEN");
return;
}
_mediaplayer.videoCropGeometry = NULL;
_mediaplayer.videoAspectRatio = (char *)[_aspectRatios[_currentAspectRatioMask] UTF8String];
NSLog(@"crop switched to %@", _aspectRatios[_currentAspectRatioMask]);
}
}
- (IBAction)switchAudioTrack:(id)sender
{
_audiotrackActionSheet = [[UIActionSheet alloc] initWithTitle:@"audio track selector" delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles: nil];
NSArray *audioTracks = [_mediaplayer audioTrackNames];
NSArray *audioTrackIndexes = [_mediaplayer audioTrackIndexes];
NSUInteger count = [audioTracks count];
for (NSUInteger i = 0; i < count; i++) {
NSString *indexIndicator = ([audioTrackIndexes[i] intValue] == [_mediaplayer currentAudioTrackIndex])? @"\u2713": @"";
NSString *buttonTitle = [NSString stringWithFormat:@"%@ %@", indexIndicator, audioTracks[i]];
[_audiotrackActionSheet addButtonWithTitle:buttonTitle];
}
[_audiotrackActionSheet addButtonWithTitle:@"Cancel"];
[_audiotrackActionSheet setCancelButtonIndex:[_audiotrackActionSheet numberOfButtons] - 1];
[_audiotrackActionSheet showInView:self.audioSwitcherButton];
}
- (IBAction)switchSubtitleTrack:(id)sender
{
NSArray *spuTracks = [_mediaplayer videoSubTitlesNames];
NSArray *spuTrackIndexes = [_mediaplayer videoSubTitlesIndexes];
NSUInteger count = [spuTracks count];
if (count <= 1)
return;
_subtitleActionSheet = [[UIActionSheet alloc] initWithTitle:@"subtitle track selector" delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles: nil];
for (NSUInteger i = 0; i < count; i++) {
NSString *indexIndicator = ([spuTrackIndexes[i] intValue] == [_mediaplayer currentVideoSubTitleIndex])? @"\u2713": @"";
NSString *buttonTitle = [NSString stringWithFormat:@"%@ %@", indexIndicator, spuTracks[i]];
[_subtitleActionSheet addButtonWithTitle:buttonTitle];
}
[_subtitleActionSheet addButtonWithTitle:@"Cancel"];
[_subtitleActionSheet setCancelButtonIndex:[_subtitleActionSheet numberOfButtons] - 1];
[_subtitleActionSheet showInView: self.subtitleSwitcherButton];
}
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex == [actionSheet cancelButtonIndex])
return;
NSArray *indexArray;
if (actionSheet == _subtitleActionSheet) {
indexArray = _mediaplayer.videoSubTitlesIndexes;
if (buttonIndex <= indexArray.count) {
_mediaplayer.currentVideoSubTitleIndex = [indexArray[buttonIndex] intValue];
}
} else if (actionSheet == _audiotrackActionSheet) {
indexArray = _mediaplayer.audioTrackIndexes;
if (buttonIndex <= indexArray.count) {
_mediaplayer.currentAudioTrackIndex = [indexArray[buttonIndex] intValue];
}
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
================================================
FILE: Thirdparties/VLCKit/Dropin-Player/VDLPlaybackViewController.xib
================================================
1552
12F45
4514
1187.40
626.00
IBProxyObject
IBUIBarButtonItem
IBUIButton
IBUINavigationBar
IBUINavigationItem
IBUISlider
IBUIView
com.apple.InterfaceBuilder.IBCocoaTouchPlugin
290
290
{{5, 10}, {188, 23}}
_NS:9
{250, 250}
NO
IBCocoaTouchFramework
0
0
0.5
289
{{241, 6}, {59, 29}}
_NS:9
NO
IBCocoaTouchFramework
0
0
VidDi
289
{{193, 11}, {50, 20}}
_NS:9
NO
IBCocoaTouchFramework
0
0
--:--
2
13
HelveticaNeue-Bold
13
16
{300, 40}
_NS:9
IBCocoaTouchFramework
IBCocoaTouchFramework
1
0
view
7
movieView
29
positionSlider
179
timeDisplay
181
volumeView
182
controllerPanel
183
toolbar
184
audioSwitcherButton
190
subtitleSwitcherButton
191
playPauseButton
192
toggleTimeDisplay:
7
180
switchVideoDimensions:
7
177
positionSliderAction:
13
178
positionSliderDrag:
3
193
positionSliderDrag:
4
194
switchSubtitleTrack:
7
176
playandPause:
7
174
switchAudioTrack:
7
175
titleView
186
leftBarButtonItem
188
closePlayback:
189
0
-1
File's Owner
-2
6
14
30
Time view
31
32
33
Position Slider
45
59
47
Controls panel
56
52
50
48
187
VDLPlaybackViewController
com.apple.InterfaceBuilder.IBCocoaTouchPlugin
UIResponder
com.apple.InterfaceBuilder.IBCocoaTouchPlugin
com.apple.InterfaceBuilder.IBCocoaTouchPlugin
com.apple.InterfaceBuilder.IBCocoaTouchPlugin
com.apple.InterfaceBuilder.IBCocoaTouchPlugin
com.apple.InterfaceBuilder.IBCocoaTouchPlugin
com.apple.InterfaceBuilder.IBCocoaTouchPlugin
com.apple.InterfaceBuilder.IBCocoaTouchPlugin
com.apple.InterfaceBuilder.IBCocoaTouchPlugin
com.apple.InterfaceBuilder.IBCocoaTouchPlugin
com.apple.InterfaceBuilder.IBCocoaTouchPlugin
com.apple.InterfaceBuilder.IBCocoaTouchPlugin
com.apple.InterfaceBuilder.IBCocoaTouchPlugin
MPVolumeView
com.apple.InterfaceBuilder.IBCocoaTouchPlugin
com.apple.InterfaceBuilder.IBCocoaTouchPlugin
com.apple.InterfaceBuilder.IBCocoaTouchPlugin
194
MPVolumeView
UIView
IBProjectSource
./Classes/MPVolumeView.h
VDLPlaybackViewController
UIViewController
id
id
UISlider
id
id
id
id
id
id
closePlayback:
id
playandPause:
id
positionSliderAction:
UISlider
positionSliderDrag:
id
switchAudioTrack:
id
switchSubtitleTrack:
id
switchVideoDimensions:
id
toggleTimeDisplay:
id
volumeSliderAction:
id
UIButton
UIView
UIView
UIButton
UISlider
UIButton
UIButton
UINavigationBar
MPVolumeView
audioSwitcherButton
UIButton
controllerPanel
UIView
movieView
UIView
playPauseButton
UIButton
positionSlider
UISlider
subtitleSwitcherButton
UIButton
timeDisplay
UIButton
toolbar
UINavigationBar
volumeView
MPVolumeView
IBProjectSource
./Classes/VDLPlaybackViewController.h
0
IBCocoaTouchFramework
YES
com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS
com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3
YES
3
3747
================================================
FILE: popcorntime_api.paw
================================================
134481920
8F8C1300-C155-4119-86D2-01F70529817A
142
NSPersistenceFrameworkVersion
526
NSStoreModelVersionHashes
LMCookieJar
Fttmf2L4PrGvKUF496+nqgVVGek45TjOe7sUMtjNg8I=
LMEnvironment
uzBoVFcO4YvR9/3ej4AJ1UOOsA/u5DKY2aemusoIseU=
LMEnvironmentDomain
yM1GPGHdquS8IWLtuczlNoqKhIhD9FW6IReSfFffJgs=
LMEnvironmentVariable
P8e0lYd5JZKRabS/eXVSOJ4oitilz67xtv+pLqW1Jqg=
LMEnvironmentVariableValue
my5hNPJ51oDCSa8EgdNxWAnRcDLcERUGjtuXnzhSxQ0=
LMKeyValue
bIXXbyYF2xAv2MXg8JTVFsslmMKuvsfnR86QdUcFkdM=
LMRequest
kYB6By9dZHqmH3YNw3h9tYPoxeG5ZWHPfhLXXp7OLFs=
LMRequestGroup
N3ml+gYVWc4m0LSGLnBDJ37p9isOc41y+TtaM0Eacrc=
LMRequestTreeItem
ak+hYb/lDeG55U0kgGvU5ej7HUltUj0RTrX5z/izNrs=
NSStoreModelVersionHashesVersion
3
NSStoreModelVersionIdentifiers
LMDocumentVersion3
8A4913AA-D95C-4BE4-BBA8-50A8965064BA
1
Default Jar
688A4A66-39E9-4757-9B88-BE8FF22FAF56
0
Default Environment
6CD41C5D-7A66-4EE0-BEED-013C74D9E2FD
http://ytspt.re/api/list.json?limit=30\u2600order=desc\u2600sort=seeds
1
1
0
0
GET
0
0
top
0
1
8C2DA06B-D03E-4AA2-8D3B-4966CB54878F
0
3C5F00CA-5004-4FA5-8D2A-C940F479D3D5
0
Default Domain
37442FBC-2F84-4413-9141-F8A3FD57C17A
http://ytspt.re/api/listimdb.json?imdb_id=tt2245084
1
1
0
0
GET
0
2
desc
0
1
0
1
20FEA1CC-A132-4A59-A394-7250AD2DC5AF
http://ytspt.re/api/list.json?limit=30\u2600keywords=terminator\u2600order=desc\u2600sort=seeds\u2600set=1
1
1
0
0
GET
0
1
search
41E1A38E-9097-42C3-997C-68D3E7D245E0
http://eztvapi.re/shows/1?limit=30\u2600order=desc\u2600sort=seeds
1
1
0
0
GET
0
0
top
0
1
87BCD354-93F9-457F-8435-5C16D622B78B
http://eztvapi.re/show/tt0898266
1
1
0
0
GET
0
2
desc tbbt
0
1
C0ABC6A0-CC89-4280-9191-7D0E134441BE
http://eztvapi.re/shows/1?limit=30\u2600order=desc\u2600sort=seeds\u2600keywords=silicon%20valley
1
1
0
0
GET
0
1
search
0
1
0
1
C6E38767-167B-4552-A709-B0407F71C8ED
http://ptp.haruhichan.com/list.php?page=1\u2600limit=50\u2600sort=popularity\u2600type=All
1
1
0
0
GET
0
0
top
0
1
367921AE-A56C-48E1-80D1-B9AA4C888534
http://ptp.haruhichan.com/anime.php?id=912
1
1
0
0
GET
0
2
desc
0
1
FEFB12B4-1BA8-4137-A79D-5AAF4D0C1CEC
http://ptp.haruhichan.com/list.php?search=pokemon\u2600limit=50\u2600sort=popularity\u2600type=All
1
1
0
0
GET
0
1
search
442876A0-7D7A-4986-98DB-8F93F085A06B
1
movies
9BDE5244-0786-473C-8781-5BBC99E68567
5
shows
DADAE43E-EE55-431B-A266-6AFB5ECEEEEF
http://eztvapi.re/show/tt2575988
1
1
0
0
GET
0
3
desc sv
22C26C40-8D1A-4787-942F-D134607AF258
9
anime
0
1
BD8350BF-4631-44DF-9BDC-77A5458F2E4F
http://cloudflare.com/api/v2/list_movies.json?page=1\u2600limit=20\u2600order_by=desc\u2600sort_by=seeds
1
1
0
0
GET
0
0
top
eqwww.image.yt
Host
0
1
C5597A41-F783-4F61-B3FC-A37F6FC924FB
2
deprecated
eqwww.image.yt
Host
0
1
43436530-B6BD-466C-A1A8-12F57EBAD1BE
http://cloudflare.com/api/v2/list_movies.json?query_term=terminator
1
1
0
0
GET
0
2
search
42E5E5DA-21B7-4077-93AE-B5EF84E238F8
http://cloudflare.com/api/v2/movie_details.json?movie_id=4132
1
1
0
0
GET
0
3
desc
eqwww.image.yt
Host
0
1
D9A5D01E-B8E3-4F85-8F6D-4CC3B79FE78D
0
v2
1
1
1
1
1
1