mirror of
https://github.com/tappollo/OSCKit.git
synced 2026-03-27 23:44:59 +08:00
Initial commit
This commit is contained in:
1
.swift-version
Normal file
1
.swift-version
Normal file
@@ -0,0 +1 @@
|
||||
3.0
|
||||
17
OSCKit.podspec
Normal file
17
OSCKit.podspec
Normal file
@@ -0,0 +1,17 @@
|
||||
Pod::Spec.new do |s|
|
||||
|
||||
s.name = "OSCKit"
|
||||
s.version = "0.0.1"
|
||||
s.summary = "Richo OSCKit"
|
||||
s.description = "Richo OSCKit with Promise"
|
||||
s.homepage = "https://theta360.com/"
|
||||
s.license = "MIT"
|
||||
s.author = { "Zhigang Fang" => "zhigang1992@gmail.com" }
|
||||
s.platform = :ios, "9.0"
|
||||
s.source = { :git => "https://github.com/tappollo/OSCKit.git" }
|
||||
s.source_files = "Source/*.swift"
|
||||
s.frameworks = "SystemConfiguration"
|
||||
s.dependency 'SwiftyyJSON'
|
||||
s.dependency 'PromiseKit'
|
||||
s.dependency 'AwaitKit'
|
||||
end
|
||||
109
Source/Command.swift
Normal file
109
Source/Command.swift
Normal file
@@ -0,0 +1,109 @@
|
||||
//
|
||||
// Command.swift
|
||||
// ThreeSixtyCamera
|
||||
//
|
||||
// Created by Zhigang Fang on 4/18/17.
|
||||
// Copyright © 2017 Tappollo Inc. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftyyJSON
|
||||
|
||||
// swiftlint:disable identifier_name
|
||||
// We want to keep it as close to API as possible
|
||||
enum Command {
|
||||
case startSession
|
||||
case updateSession(sessionId: String)
|
||||
case closeSession(sessionId: String)
|
||||
case _finishWlan
|
||||
case takePicture(sessionId: String)
|
||||
case _startCapture(sessionId: String, mode: VideoCaptureMode)
|
||||
case _stopCapture(sessionId: String)
|
||||
case listImages
|
||||
case _listAll(entryCount: Int, detail: Bool)
|
||||
case delete
|
||||
case getImage(fileUri: String, _type: DownloadType)
|
||||
case _getVideo(fileUri: String, _type: DownloadType)
|
||||
case _getLivePreview(sessionId: String)
|
||||
case getMetadata
|
||||
case getOptions
|
||||
case setOptions(options: [Option], sessionId: String)
|
||||
case _getMySetting
|
||||
case _setMySetting
|
||||
case _stopSelfTimer
|
||||
}
|
||||
// swiftlint:enable identifier_name
|
||||
|
||||
extension Command {
|
||||
var name: String {
|
||||
switch self {
|
||||
case .startSession: return "camera.startSession"
|
||||
case .updateSession: return "camera.updateSession"
|
||||
case .closeSession: return "camera.closeSession"
|
||||
case ._finishWlan: return "camera._finishWlan"
|
||||
case .takePicture: return "camera.takePicture"
|
||||
case ._startCapture: return "camera._startCapture"
|
||||
case ._stopCapture: return "camera._stopCapture"
|
||||
case .listImages: return "camera.listImages"
|
||||
case ._listAll: return "camera._listAll"
|
||||
case .delete: return "camera.delete"
|
||||
case .getImage: return "camera.getImage"
|
||||
case ._getVideo: return "camera._getVideo"
|
||||
case ._getLivePreview: return "camera._getLivePreview"
|
||||
case .getMetadata: return "camera.getMetadata"
|
||||
case .getOptions: return "camera.getOptions"
|
||||
case .setOptions: return "camera.setOptions"
|
||||
case ._getMySetting: return "camera._getMySetting"
|
||||
case ._setMySetting: return "camera._setMySetting"
|
||||
case ._stopSelfTimer: return "camera._stopSelfTimer"
|
||||
}
|
||||
}
|
||||
|
||||
var defaultJSON: JSON {
|
||||
return ["name": self.name]
|
||||
}
|
||||
|
||||
func with(params: [String: Any]) -> JSON {
|
||||
return [
|
||||
"name": self.name,
|
||||
"parameters": params
|
||||
]
|
||||
}
|
||||
|
||||
var json: JSON {
|
||||
switch self {
|
||||
case .updateSession(sessionId: let id): return with(params: ["sessionId": id])
|
||||
case .takePicture(sessionId: let id): return with(params: ["sessionId": id])
|
||||
case let .getImage(fileUri: fileUri, _type: _type):
|
||||
return with(params: [
|
||||
"fileUri": fileUri,
|
||||
"_type": _type.rawValue
|
||||
])
|
||||
case let ._getVideo(fileUri: fileUri, _type: _type):
|
||||
return with(params: [
|
||||
"fileUri": fileUri,
|
||||
"_type": _type.rawValue
|
||||
])
|
||||
case let ._startCapture(sessionId: id, mode: mode):
|
||||
return with(params: [
|
||||
"sessionId": id,
|
||||
"mode": mode.rawValue
|
||||
])
|
||||
case ._stopCapture(sessionId: let id): return with(params: ["sessionId": id])
|
||||
case let ._listAll(entryCount: count, detail: detail):
|
||||
return with(params: [
|
||||
"entryCount": count,
|
||||
"detail": detail
|
||||
])
|
||||
case let .setOptions(options: options, sessionId: id):
|
||||
var json: JSON = [:]
|
||||
options.forEach({ json[$0.key] = $0.value })
|
||||
return with(params: [
|
||||
"sessionId": id,
|
||||
"options": json.value ?? NSNull()
|
||||
])
|
||||
case ._getLivePreview(sessionId: let id): return with(params: ["sessionId": id])
|
||||
default: return defaultJSON
|
||||
}
|
||||
}
|
||||
}
|
||||
38
Source/DeviceInfo.swift
Normal file
38
Source/DeviceInfo.swift
Normal file
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// DeviceInfo.swift
|
||||
// ThreeSixtyCamera
|
||||
//
|
||||
// Created by Zhigang Fang on 4/18/17.
|
||||
// Copyright © 2017 Tappollo Inc. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftyyJSON
|
||||
import PromiseKit
|
||||
import AwaitKit
|
||||
|
||||
public struct DeviceInfo {
|
||||
public let model: String
|
||||
public let serial: String
|
||||
public let battery: Double
|
||||
}
|
||||
|
||||
extension OSCKit {
|
||||
public var deviceInfo: Promise<DeviceInfo> {
|
||||
return async {
|
||||
let info = try await(self.info)
|
||||
let state = try await(self.state)
|
||||
|
||||
return DeviceInfo(
|
||||
model: try info["model"].string !! SDKError.unableToParse(info),
|
||||
serial: try info["serialNumber"].string !! SDKError.unableToParse(info),
|
||||
battery: try state["state"]["batteryLevel"].double !! SDKError.unableToParse(state)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public var info: Promise<JSON> { return self.requestJSON(endPoint: .info) }
|
||||
|
||||
public var state: Promise<JSON> { return self.requestJSON(endPoint: .state) }
|
||||
|
||||
}
|
||||
58
Source/Image.swift
Normal file
58
Source/Image.swift
Normal file
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// Image.swift
|
||||
// ThreeSixtyCamera
|
||||
//
|
||||
// Created by Zhigang Fang on 4/18/17.
|
||||
// Copyright © 2017 Tappollo Inc. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftyyJSON
|
||||
import PromiseKit
|
||||
import AwaitKit
|
||||
|
||||
public enum DownloadType: String {
|
||||
case thumbnail = "thumb"
|
||||
case full = "full"
|
||||
}
|
||||
|
||||
extension OSCKit {
|
||||
|
||||
public func getImage(url: String, type: DownloadType = .full) -> Promise<UIImage> {
|
||||
return async {
|
||||
let url = try await(self.getImageLocalURL(url: url, type: type))
|
||||
return try UIImage(contentsOfFile: url.path) !! SDKError.unableToFindImageAt(url)
|
||||
}
|
||||
}
|
||||
|
||||
public func getImageLocalURL(url: String, type: DownloadType = .full) -> Promise<URL> {
|
||||
return async {
|
||||
let device = try await(self.deviceInfo)
|
||||
// Adding serial key in the begining
|
||||
// To prevent cache collision between different devices
|
||||
let cacheKey = try (device.serial + url).addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) !! SDKError.unableToCreateVideoCacheKey
|
||||
let cacheFolder = try NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first.map({
|
||||
URL(fileURLWithPath: $0)
|
||||
}) !! SDKError.unableToFindCacheFolder
|
||||
let fileURL = cacheFolder.appendingPathComponent(cacheKey)
|
||||
if FileManager.default.fileExists(atPath: fileURL.path) {
|
||||
return fileURL
|
||||
}
|
||||
let data = try await(self.requestData(command: .getImage(fileUri: url, _type: type)))
|
||||
try data.write(to: fileURL)
|
||||
return fileURL
|
||||
}
|
||||
}
|
||||
|
||||
public func takePicture() -> Promise<String> {
|
||||
return async {
|
||||
let session = try await(self.session)
|
||||
try await(self.execute(command: .setOptions(options: [CaptureMode.image], sessionId: session.id)))
|
||||
try await(self.execute(command: .setOptions(options: [FileFormat.smallImage], sessionId: session.id)))
|
||||
let captureResponse = try await(self.execute(command: .takePicture(sessionId: session.id)))
|
||||
let statusID = try captureResponse["id"].string !! SDKError.unableToParse(captureResponse)
|
||||
let statusResponse = try await(self.waitForStatus(id: statusID))
|
||||
return try statusResponse["results"]["fileUri"].string !! SDKError.unableToParse(statusResponse)
|
||||
}
|
||||
}
|
||||
}
|
||||
88
Source/LivePreview.swift
Normal file
88
Source/LivePreview.swift
Normal file
@@ -0,0 +1,88 @@
|
||||
//
|
||||
// LivePreview.swift
|
||||
// ThreeSixtyCamera
|
||||
//
|
||||
// Created by Zhigang Fang on 4/18/17.
|
||||
// Copyright © 2017 Tappollo Inc. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftyyJSON
|
||||
import PromiseKit
|
||||
import AwaitKit
|
||||
|
||||
final class LivePreview: NSObject, URLSessionDataDelegate {
|
||||
static let shared = LivePreview()
|
||||
|
||||
lazy var session: URLSession = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: nil)
|
||||
|
||||
enum Status {
|
||||
case stopped
|
||||
case loading
|
||||
case playing
|
||||
}
|
||||
|
||||
var status: Status = .stopped
|
||||
private var receivedData: NSMutableData?
|
||||
private var dataTask: URLSessionDataTask?
|
||||
|
||||
var callback: (UIImage) -> Void = { _ in }
|
||||
|
||||
private override init() {}
|
||||
|
||||
func play(request: URLRequest) {
|
||||
self.receivedData = NSMutableData()
|
||||
self.dataTask?.cancel()
|
||||
self.dataTask = self.session.dataTask(with: request)
|
||||
self.dataTask?.resume()
|
||||
self.status = .loading
|
||||
}
|
||||
|
||||
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
|
||||
if let imageData = receivedData , imageData.length > 0,
|
||||
let receivedImage = UIImage(data: imageData as Data) {
|
||||
if status == .loading {
|
||||
status = .playing
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
self.callback(receivedImage)
|
||||
}
|
||||
}
|
||||
|
||||
receivedData = NSMutableData()
|
||||
completionHandler(.allow)
|
||||
}
|
||||
|
||||
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
|
||||
self.receivedData?.append(data)
|
||||
}
|
||||
|
||||
func stop() {
|
||||
self.dataTask?.cancel()
|
||||
self.dataTask = nil
|
||||
self.receivedData = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension OSCKit {
|
||||
public func startLivePreview(callback: @escaping (UIImage) -> Void) {
|
||||
async {
|
||||
let session = try await(OSCKit.shared.session)
|
||||
try await(self.execute(command: .setOptions(options: [CaptureMode.image], sessionId: session.id)))
|
||||
DispatchQueue.main.async(execute: {
|
||||
LivePreview.shared.stop()
|
||||
LivePreview.shared.callback = callback
|
||||
let json = Command._getLivePreview(sessionId: session.id).json
|
||||
let request = self.assembleRequest(endPoint: .execute, params: json)
|
||||
LivePreview.shared.play(request: request)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
public func stopLivePreview() {
|
||||
DispatchQueue.main.async {
|
||||
LivePreview.shared.stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
71
Source/MediaItems.swift
Normal file
71
Source/MediaItems.swift
Normal file
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// Items.swift
|
||||
// ThreeSixtyCamera
|
||||
//
|
||||
// Created by Zhigang Fang on 4/18/17.
|
||||
// Copyright © 2017 Tappollo Inc. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftyyJSON
|
||||
import PromiseKit
|
||||
import AwaitKit
|
||||
|
||||
public struct MediaItem {
|
||||
public enum Kind {
|
||||
case image
|
||||
case video
|
||||
}
|
||||
|
||||
public let name: String
|
||||
public let url: String
|
||||
|
||||
public let size: Int?
|
||||
public let date: String?
|
||||
public let width: Int?
|
||||
public let height: Int?
|
||||
|
||||
public var type: Kind {
|
||||
if name.lowercased().hasSuffix(".mp4") {
|
||||
return .video
|
||||
}
|
||||
return .image
|
||||
}
|
||||
|
||||
init (json: JSON) throws {
|
||||
self.name = try json["name"].string !! OSCKit.SDKError.unableToParse(json)
|
||||
self.url = try json["uri"].string !! OSCKit.SDKError.unableToParse(json)
|
||||
self.size = json["size"].int
|
||||
self.date = json["dateTimeZone"].string
|
||||
self.width = json["width"].int
|
||||
self.height = json["height"].int
|
||||
}
|
||||
}
|
||||
|
||||
extension OSCKit {
|
||||
public var listAllMediaItems: Promise<[MediaItem]> {
|
||||
return async {
|
||||
let all = try await(self.execute(command: ._listAll(entryCount: 100, detail: false)))
|
||||
let entries = try all["results"]["entries"].array !! SDKError.unableToParse(all)
|
||||
return try entries.map({try MediaItem(json: $0)})
|
||||
}
|
||||
}
|
||||
|
||||
public func getLatestMediaItem(timeout: TimeInterval = 0, withPredicate predicate: @escaping (MediaItem) -> Bool) -> Promise<MediaItem> {
|
||||
return async {
|
||||
if timeout < 0 {
|
||||
throw SDKError.fetchTimeout
|
||||
}
|
||||
let all = try await(self.execute(command: ._listAll(entryCount: 1, detail: true)))
|
||||
let json = try all["results"]["entries"].array !! SDKError.unableToParse(all)
|
||||
if let first = json.first {
|
||||
let item = try MediaItem(json: first)
|
||||
if predicate(item) {
|
||||
return item
|
||||
}
|
||||
}
|
||||
try await(after(interval: 2))
|
||||
return try await(self.getLatestMediaItem(timeout: timeout - 2, withPredicate: predicate))
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Source/OSCKit-Swift.h
Normal file
19
Source/OSCKit-Swift.h
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// OSCKit-Swift.h
|
||||
// OSCKit
|
||||
//
|
||||
// Created by Zhigang Fang on 4/18/17.
|
||||
// Copyright © 2017 matrix. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
//! Project version number for OSCKit.
|
||||
FOUNDATION_EXPORT double OSCKitKVersionNumber;
|
||||
|
||||
//! Project version string for OSCKit.
|
||||
FOUNDATION_EXPORT const unsigned char OSCKitVersionString[];
|
||||
|
||||
// In this header, you should import all the public headers of your framework using statements like #import <OSCKit/PublicHeader.h>
|
||||
|
||||
|
||||
44
Source/OSCKit.swift
Normal file
44
Source/OSCKit.swift
Normal file
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// OSCKit.swift
|
||||
// ThreeSixtyCamera
|
||||
//
|
||||
// Created by Zhigang Fang on 4/17/17.
|
||||
// Copyright © 2017 Tappollo Inc. All rights reserved.
|
||||
//
|
||||
import SwiftyyJSON
|
||||
import SystemConfiguration.CaptiveNetwork
|
||||
|
||||
public class OSCKit {
|
||||
public static let shared = OSCKit()
|
||||
|
||||
enum SDKError: Error {
|
||||
case unableToParse(JSON)
|
||||
case unableToFindImageAt(URL)
|
||||
case fetchTimeout
|
||||
case unableToFindCacheFolder
|
||||
case unableToFindVideo
|
||||
case unableToCreateVideoCacheKey
|
||||
}
|
||||
|
||||
private init() { }
|
||||
|
||||
public func isConnectedToDeviceWiFi(withPrefix prefix: String) -> Bool {
|
||||
return SSID.current?.hasPrefix(prefix) == true
|
||||
}
|
||||
}
|
||||
|
||||
struct SSID {
|
||||
static var current: String? {
|
||||
if let interfaces = CNCopySupportedInterfaces() {
|
||||
for i in 0..<CFArrayGetCount(interfaces) {
|
||||
let interfaceName: UnsafeRawPointer = CFArrayGetValueAtIndex(interfaces, i)
|
||||
let rec = unsafeBitCast(interfaceName, to: AnyObject.self)
|
||||
let unsafeInterfaceData = CNCopyCurrentNetworkInfo("\(rec)" as CFString)
|
||||
if let interfaceData: NSDictionary = unsafeInterfaceData, let ssid = interfaceData["SSID"] as? String {
|
||||
return ssid
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
30
Source/OptionalThrow.swift
Normal file
30
Source/OptionalThrow.swift
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// OptionalThrow.swift
|
||||
// ThreeSixtyCamera
|
||||
//
|
||||
// Created by Zhigang Fang on 4/18/17.
|
||||
// Copyright © 2017 Tappollo Inc. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
infix operator !! : LogicalConjunctionPrecedence
|
||||
|
||||
func !!<T>(optional: Optional<T>, error: Error) throws -> T {
|
||||
return try optional.someOrThrow(error)
|
||||
}
|
||||
|
||||
extension Optional {
|
||||
func someOrThrow(_ error: Error) throws -> Wrapped {
|
||||
if let value = self {
|
||||
return value
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
func const<T, V>(value: T) -> (V) -> T {
|
||||
return { _ in
|
||||
value
|
||||
}
|
||||
}
|
||||
34
Source/Options.swift
Normal file
34
Source/Options.swift
Normal file
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// Options.swift
|
||||
// ThreeSixtyCamera
|
||||
//
|
||||
// Created by Zhigang Fang on 4/18/17.
|
||||
// Copyright © 2017 Tappollo Inc. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftyyJSON
|
||||
|
||||
protocol Option {
|
||||
var key: String { get }
|
||||
var value: JSON { get }
|
||||
}
|
||||
|
||||
enum CaptureMode: String, Option {
|
||||
case video = "_video"
|
||||
case image = "image"
|
||||
var key: String { return "captureMode" }
|
||||
var value: JSON { return JSON(value: self.rawValue as NSObject) }
|
||||
}
|
||||
|
||||
struct FileFormat: Option {
|
||||
|
||||
let type: String
|
||||
let width: Int
|
||||
let height: Int
|
||||
|
||||
var key: String { return "fileFormat" }
|
||||
var value: JSON { return ["type": type, "width": width, "height": height] }
|
||||
|
||||
static let smallImage = FileFormat(type: "jpeg", width: 2048, height: 1024)
|
||||
}
|
||||
68
Source/Request.swift
Normal file
68
Source/Request.swift
Normal file
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// Request.swift
|
||||
// ThreeSixtyCamera
|
||||
//
|
||||
// Created by Zhigang Fang on 4/18/17.
|
||||
// Copyright © 2017 Tappollo Inc. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftyyJSON
|
||||
import PromiseKit
|
||||
import AwaitKit
|
||||
|
||||
enum Endpoint {
|
||||
case info
|
||||
case state
|
||||
|
||||
case execute
|
||||
case status
|
||||
|
||||
var path: String {
|
||||
switch self {
|
||||
case .execute: return "/osc/commands/execute"
|
||||
case .status: return "/osc/commands/status"
|
||||
case .info: return "/osc/info"
|
||||
case .state: return "/osc/state"
|
||||
}
|
||||
}
|
||||
|
||||
var method: String {
|
||||
switch self {
|
||||
case .execute, .state, .status: return "POST"
|
||||
case .info: return "GET"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension OSCKit {
|
||||
func assembleRequest(endPoint: Endpoint = .execute, params json: JSON? = nil) -> URLRequest {
|
||||
var request = URLRequest(url: URL(string: "http://192.168.1.1\(endPoint.path)")!)
|
||||
request.httpMethod = endPoint.method
|
||||
request.addValue("application/json;charset=utf-8", forHTTPHeaderField: "Content-Type")
|
||||
if let json = json {
|
||||
request.httpBody = json.encode()
|
||||
}
|
||||
return request
|
||||
}
|
||||
|
||||
func requestJSON(endPoint: Endpoint = .execute, params json: JSON? = nil) -> Promise<JSON> {
|
||||
var request = assembleRequest(endPoint: endPoint, params: json)
|
||||
request.addValue("application/json", forHTTPHeaderField: "Accept")
|
||||
return URLSession.shared.dataTask(with: request).then(execute: { data -> JSON in
|
||||
let anyObject = try JSONSerialization.jsonObject(with: data, options: [])
|
||||
return JSON(value: anyObject as? NSObject)
|
||||
})
|
||||
}
|
||||
|
||||
func requestData(command: Command) -> Promise<Data> {
|
||||
return async {
|
||||
let request = self.assembleRequest(params: command.json)
|
||||
return try await(URLSession.shared.dataTask(with: request))
|
||||
}
|
||||
}
|
||||
|
||||
func execute(command: Command) -> Promise<JSON> {
|
||||
return self.requestJSON(endPoint: .execute, params: command.json)
|
||||
}
|
||||
}
|
||||
67
Source/Session.swift
Normal file
67
Source/Session.swift
Normal file
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// Session.swift
|
||||
// ThreeSixtyCamera
|
||||
//
|
||||
// Created by Zhigang Fang on 4/18/17.
|
||||
// Copyright © 2017 Tappollo Inc. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftyyJSON
|
||||
import PromiseKit
|
||||
import AwaitKit
|
||||
|
||||
public struct Session {
|
||||
let id: String
|
||||
let expires: Date
|
||||
|
||||
fileprivate static var currentSession: Session?
|
||||
|
||||
init (json: JSON) throws {
|
||||
self.id = try json["results"]["sessionId"].string !! OSCKit.SDKError.unableToParse(json)
|
||||
let expire = try json["results"]["timeout"].int !! OSCKit.SDKError.unableToParse(json)
|
||||
self.expires = Date().addingTimeInterval(TimeInterval(expire))
|
||||
}
|
||||
|
||||
var isExpired: Bool {
|
||||
return self.expires < Date()
|
||||
}
|
||||
|
||||
var wasJustedIssued: Bool {
|
||||
return self.expires.addingTimeInterval(10) > Date()
|
||||
}
|
||||
}
|
||||
|
||||
extension OSCKit {
|
||||
public var session: Promise<Session> {
|
||||
if let currentSession = Session.currentSession {
|
||||
if currentSession.wasJustedIssued {
|
||||
return Promise(value: currentSession)
|
||||
}
|
||||
if currentSession.isExpired {
|
||||
return startSession
|
||||
}
|
||||
return update(session: currentSession)
|
||||
}
|
||||
return startSession
|
||||
}
|
||||
|
||||
var startSession: Promise<Session> {
|
||||
return async {
|
||||
let response = try await(self.execute(command: .startSession))
|
||||
return try Session(json: response)
|
||||
}
|
||||
}
|
||||
|
||||
func update(session: Session) -> Promise<Session> {
|
||||
return async {
|
||||
do {
|
||||
let response = try await(self.execute(command: .updateSession(sessionId: session.id)))
|
||||
return try Session(json: response)
|
||||
} catch {
|
||||
return try await(self.startSession)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
28
Source/Status.swift
Normal file
28
Source/Status.swift
Normal file
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// Status.swift
|
||||
// ThreeSixtyCamera
|
||||
//
|
||||
// Created by Zhigang Fang on 4/18/17.
|
||||
// Copyright © 2017 Tappollo Inc. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AwaitKit
|
||||
import PromiseKit
|
||||
import SwiftyyJSON
|
||||
|
||||
extension OSCKit {
|
||||
func waitForStatus(id: String) -> Promise<JSON> {
|
||||
return async {
|
||||
let json: JSON = [
|
||||
"id": id
|
||||
]
|
||||
let response = try await(self.requestJSON(endPoint: .status, params: json))
|
||||
if response["state"].string == "inProgress" {
|
||||
try await(after(interval: 2))
|
||||
return try await(self.waitForStatus(id: id))
|
||||
}
|
||||
return response
|
||||
}
|
||||
}
|
||||
}
|
||||
63
Source/Video.swift
Normal file
63
Source/Video.swift
Normal file
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// Video.swift
|
||||
// ThreeSixtyCamera
|
||||
//
|
||||
// Created by Zhigang Fang on 4/18/17.
|
||||
// Copyright © 2017 Tappollo Inc. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftyyJSON
|
||||
import PromiseKit
|
||||
import AwaitKit
|
||||
|
||||
public enum VideoCaptureMode: String {
|
||||
case interval
|
||||
case composite
|
||||
case bracket
|
||||
}
|
||||
|
||||
extension OSCKit {
|
||||
|
||||
public func startCapture(mode: VideoCaptureMode = .interval) -> Promise<JSON> {
|
||||
return async {
|
||||
let session = try await(self.session)
|
||||
try await(self.execute(command: .setOptions(options: [CaptureMode.video], sessionId: session.id)))
|
||||
return try await(self.execute(command: ._startCapture(sessionId: session.id, mode: mode)))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public func stopCapture() -> Promise<String> {
|
||||
return async {
|
||||
let session = try await(self.session)
|
||||
// Saving first item before capturing video
|
||||
// This is due to the face THETA API v2.0 does not return a file URL when capture finishes
|
||||
// https://developers.theta360.com/en/docs/v2.0/api_reference/commands/camera._stop_capture.html
|
||||
let lastItem = try await(self.getLatestMediaItem(withPredicate: const(value: true)))
|
||||
try await(self.execute(command: ._stopCapture(sessionId: session.id)))
|
||||
// After stop capturing video, wait until it returns a new item with type being .video
|
||||
let mediaItem = try await(self.getLatestMediaItem(withPredicate: {
|
||||
$0.url != lastItem.url && $0.type ~= .video
|
||||
}))
|
||||
return mediaItem.url
|
||||
}
|
||||
}
|
||||
|
||||
public func getVideo(url: String, type: DownloadType = .full) -> Promise<URL> {
|
||||
return async {
|
||||
let device = try await(self.deviceInfo)
|
||||
let cacheKey = try (device.serial + url).addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) !! SDKError.unableToCreateVideoCacheKey
|
||||
let cacheFolder = try NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first.map({
|
||||
URL(fileURLWithPath: $0)
|
||||
}) !! SDKError.unableToFindCacheFolder
|
||||
let fileURL = cacheFolder.appendingPathComponent(cacheKey)
|
||||
if FileManager.default.fileExists(atPath: fileURL.path) {
|
||||
return fileURL
|
||||
}
|
||||
let data = try await(self.requestData(command: ._getVideo(fileUri: url, _type: type)))
|
||||
try data.write(to: fileURL)
|
||||
return fileURL
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user