Merge remote-tracking branch 'origin/main' into main
This commit is contained in:
commit
a8f5c7f947
|
|
@ -14,61 +14,61 @@ class CastV2PlatformReader {
|
||||||
let stream: InputStream
|
let stream: InputStream
|
||||||
var readPosition = 0
|
var readPosition = 0
|
||||||
var buffer = Data(capacity: maxBufferLength)
|
var buffer = Data(capacity: maxBufferLength)
|
||||||
|
|
||||||
init(stream: InputStream) {
|
init(stream: InputStream) {
|
||||||
self.stream = stream
|
self.stream = stream
|
||||||
}
|
}
|
||||||
|
|
||||||
func readStream() {
|
func readStream() {
|
||||||
objc_sync_enter(self)
|
objc_sync_enter(self)
|
||||||
defer { objc_sync_exit(self) }
|
defer { objc_sync_exit(self) }
|
||||||
|
|
||||||
var totalBytesRead = 0
|
var totalBytesRead = 0
|
||||||
let bufferSize = 32
|
let bufferSize = 32
|
||||||
|
|
||||||
while stream.hasBytesAvailable {
|
while stream.hasBytesAvailable {
|
||||||
var bytes = [UInt8](repeating: 0, count: bufferSize)
|
var bytes = [UInt8](repeating: 0, count: bufferSize)
|
||||||
|
|
||||||
let bytesRead = stream.read(&bytes, maxLength: bufferSize)
|
let bytesRead = stream.read(&bytes, maxLength: bufferSize)
|
||||||
|
|
||||||
if bytesRead < 0 { continue }
|
if bytesRead < 0 { continue }
|
||||||
|
|
||||||
buffer.append(Data(bytes: &bytes, count: bytesRead))
|
buffer.append(Data(bytes: &bytes, count: bytesRead))
|
||||||
|
|
||||||
totalBytesRead += bytesRead
|
totalBytesRead += bytesRead
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func nextMessage() -> Data? {
|
func nextMessage() -> Data? {
|
||||||
objc_sync_enter(self)
|
objc_sync_enter(self)
|
||||||
defer { objc_sync_exit(self) }
|
defer { objc_sync_exit(self) }
|
||||||
|
|
||||||
let headerSize = MemoryLayout<UInt32>.size
|
let headerSize = MemoryLayout<UInt32>.size
|
||||||
guard buffer.count - readPosition >= headerSize else { return nil }
|
guard buffer.count - readPosition >= headerSize else { return nil }
|
||||||
let header = buffer.withUnsafeBytes({ (pointer: UnsafePointer<Int8>) -> UInt32 in
|
let header = buffer.withUnsafeBytes({ (pointer: UnsafePointer<Int8>) -> UInt32 in
|
||||||
return pointer.advanced(by: self.readPosition).withMemoryRebound(to: UInt32.self, capacity: 1, { $0.pointee })
|
return pointer.advanced(by: self.readPosition).withMemoryRebound(to: UInt32.self, capacity: 1, { $0.pointee })
|
||||||
})
|
})
|
||||||
|
|
||||||
let payloadSize = Int(CFSwapInt32BigToHost(header))
|
let payloadSize = Int(CFSwapInt32BigToHost(header))
|
||||||
|
|
||||||
readPosition += headerSize
|
readPosition += headerSize
|
||||||
|
|
||||||
guard buffer.count >= readPosition + payloadSize, buffer.count - readPosition >= payloadSize, payloadSize >= 0 else {
|
guard buffer.count >= readPosition + payloadSize, buffer.count - readPosition >= payloadSize, payloadSize >= 0 else {
|
||||||
//Message hasn't arrived
|
// Message hasn't arrived
|
||||||
readPosition -= headerSize
|
readPosition -= headerSize
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
let payload = buffer.withUnsafeBytes({ (pointer: UnsafePointer<Int8>) -> Data in
|
let payload = buffer.withUnsafeBytes({ (pointer: UnsafePointer<Int8>) -> Data in
|
||||||
return Data(bytes: pointer.advanced(by: self.readPosition), count: payloadSize)
|
return Data(bytes: pointer.advanced(by: self.readPosition), count: payloadSize)
|
||||||
})
|
})
|
||||||
readPosition += payloadSize
|
readPosition += payloadSize
|
||||||
|
|
||||||
resetBufferIfNeeded()
|
resetBufferIfNeeded()
|
||||||
|
|
||||||
return payload
|
return payload
|
||||||
}
|
}
|
||||||
|
|
||||||
private func resetBufferIfNeeded() {
|
private func resetBufferIfNeeded() {
|
||||||
guard buffer.count >= maxBufferLength else { return }
|
guard buffer.count >= maxBufferLength else { return }
|
||||||
|
|
||||||
|
|
@ -77,7 +77,7 @@ class CastV2PlatformReader {
|
||||||
} else {
|
} else {
|
||||||
buffer = buffer.advanced(by: readPosition)
|
buffer = buffer.advanced(by: readPosition)
|
||||||
}
|
}
|
||||||
|
|
||||||
readPosition = 0
|
readPosition = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import SwiftProtobuf
|
||||||
// incompatible with the version of SwiftProtobuf to which you are linking.
|
// incompatible with the version of SwiftProtobuf to which you are linking.
|
||||||
// Please ensure that your are building against the same version of the API
|
// Please ensure that your are building against the same version of the API
|
||||||
// that was used to generate this file.
|
// that was used to generate this file.
|
||||||
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
|
private struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
|
||||||
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
|
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
|
||||||
typealias Version = _2
|
typealias Version = _2
|
||||||
}
|
}
|
||||||
|
|
@ -221,13 +221,13 @@ struct Extensions_Api_CastChannel_CastMessage: SwiftProtobuf.Message {
|
||||||
try unknownFields.traverse(visitor: &visitor)
|
try unknownFields.traverse(visitor: &visitor)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate var _protocolVersion: Extensions_Api_CastChannel_CastMessage.ProtocolVersion? = nil
|
fileprivate var _protocolVersion: Extensions_Api_CastChannel_CastMessage.ProtocolVersion?
|
||||||
fileprivate var _sourceID: String? = nil
|
fileprivate var _sourceID: String?
|
||||||
fileprivate var _destinationID: String? = nil
|
fileprivate var _destinationID: String?
|
||||||
fileprivate var _namespace: String? = nil
|
fileprivate var _namespace: String?
|
||||||
fileprivate var _payloadType: Extensions_Api_CastChannel_CastMessage.PayloadType? = nil
|
fileprivate var _payloadType: Extensions_Api_CastChannel_CastMessage.PayloadType?
|
||||||
fileprivate var _payloadUtf8: String? = nil
|
fileprivate var _payloadUtf8: String?
|
||||||
fileprivate var _payloadBinary: Data? = nil
|
fileprivate var _payloadBinary: Data?
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Messages for authentication protocol between a sender and a receiver.
|
/// Messages for authentication protocol between a sender and a receiver.
|
||||||
|
|
@ -321,8 +321,8 @@ struct Extensions_Api_CastChannel_AuthResponse: SwiftProtobuf.Message {
|
||||||
try unknownFields.traverse(visitor: &visitor)
|
try unknownFields.traverse(visitor: &visitor)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate var _signature: Data? = nil
|
fileprivate var _signature: Data?
|
||||||
fileprivate var _clientAuthCertificate: Data? = nil
|
fileprivate var _clientAuthCertificate: Data?
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Extensions_Api_CastChannel_AuthError: SwiftProtobuf.Message {
|
struct Extensions_Api_CastChannel_AuthError: SwiftProtobuf.Message {
|
||||||
|
|
@ -398,7 +398,7 @@ struct Extensions_Api_CastChannel_AuthError: SwiftProtobuf.Message {
|
||||||
try unknownFields.traverse(visitor: &visitor)
|
try unknownFields.traverse(visitor: &visitor)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate var _errorType: Extensions_Api_CastChannel_AuthError.ErrorType? = nil
|
fileprivate var _errorType: Extensions_Api_CastChannel_AuthError.ErrorType?
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Extensions_Api_CastChannel_DeviceAuthMessage: SwiftProtobuf.Message {
|
struct Extensions_Api_CastChannel_DeviceAuthMessage: SwiftProtobuf.Message {
|
||||||
|
|
@ -487,7 +487,7 @@ struct Extensions_Api_CastChannel_DeviceAuthMessage: SwiftProtobuf.Message {
|
||||||
|
|
||||||
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
||||||
|
|
||||||
fileprivate let _protobuf_package = "extensions.api.cast_channel"
|
private let _protobuf_package = "extensions.api.cast_channel"
|
||||||
|
|
||||||
extension Extensions_Api_CastChannel_CastMessage: SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
extension Extensions_Api_CastChannel_CastMessage: SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||||
|
|
@ -497,7 +497,7 @@ extension Extensions_Api_CastChannel_CastMessage: SwiftProtobuf._MessageImplemen
|
||||||
4: .same(proto: "namespace"),
|
4: .same(proto: "namespace"),
|
||||||
5: .standard(proto: "payload_type"),
|
5: .standard(proto: "payload_type"),
|
||||||
6: .standard(proto: "payload_utf8"),
|
6: .standard(proto: "payload_utf8"),
|
||||||
7: .standard(proto: "payload_binary"),
|
7: .standard(proto: "payload_binary")
|
||||||
]
|
]
|
||||||
|
|
||||||
func _protobuf_generated_isEqualTo(other: Extensions_Api_CastChannel_CastMessage) -> Bool {
|
func _protobuf_generated_isEqualTo(other: Extensions_Api_CastChannel_CastMessage) -> Bool {
|
||||||
|
|
@ -515,14 +515,14 @@ extension Extensions_Api_CastChannel_CastMessage: SwiftProtobuf._MessageImplemen
|
||||||
|
|
||||||
extension Extensions_Api_CastChannel_CastMessage.ProtocolVersion: SwiftProtobuf._ProtoNameProviding {
|
extension Extensions_Api_CastChannel_CastMessage.ProtocolVersion: SwiftProtobuf._ProtoNameProviding {
|
||||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||||
0: .same(proto: "CASTV2_1_0"),
|
0: .same(proto: "CASTV2_1_0")
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Extensions_Api_CastChannel_CastMessage.PayloadType: SwiftProtobuf._ProtoNameProviding {
|
extension Extensions_Api_CastChannel_CastMessage.PayloadType: SwiftProtobuf._ProtoNameProviding {
|
||||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||||
0: .same(proto: "STRING"),
|
0: .same(proto: "STRING"),
|
||||||
1: .same(proto: "BINARY"),
|
1: .same(proto: "BINARY")
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -539,7 +539,7 @@ extension Extensions_Api_CastChannel_AuthResponse: SwiftProtobuf._MessageImpleme
|
||||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||||
1: .same(proto: "signature"),
|
1: .same(proto: "signature"),
|
||||||
2: .standard(proto: "client_auth_certificate"),
|
2: .standard(proto: "client_auth_certificate"),
|
||||||
3: .standard(proto: "client_ca"),
|
3: .standard(proto: "client_ca")
|
||||||
]
|
]
|
||||||
|
|
||||||
func _protobuf_generated_isEqualTo(other: Extensions_Api_CastChannel_AuthResponse) -> Bool {
|
func _protobuf_generated_isEqualTo(other: Extensions_Api_CastChannel_AuthResponse) -> Bool {
|
||||||
|
|
@ -553,7 +553,7 @@ extension Extensions_Api_CastChannel_AuthResponse: SwiftProtobuf._MessageImpleme
|
||||||
|
|
||||||
extension Extensions_Api_CastChannel_AuthError: SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
extension Extensions_Api_CastChannel_AuthError: SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||||
1: .standard(proto: "error_type"),
|
1: .standard(proto: "error_type")
|
||||||
]
|
]
|
||||||
|
|
||||||
func _protobuf_generated_isEqualTo(other: Extensions_Api_CastChannel_AuthError) -> Bool {
|
func _protobuf_generated_isEqualTo(other: Extensions_Api_CastChannel_AuthError) -> Bool {
|
||||||
|
|
@ -566,7 +566,7 @@ extension Extensions_Api_CastChannel_AuthError: SwiftProtobuf._MessageImplementa
|
||||||
extension Extensions_Api_CastChannel_AuthError.ErrorType: SwiftProtobuf._ProtoNameProviding {
|
extension Extensions_Api_CastChannel_AuthError.ErrorType: SwiftProtobuf._ProtoNameProviding {
|
||||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||||
0: .same(proto: "INTERNAL_ERROR"),
|
0: .same(proto: "INTERNAL_ERROR"),
|
||||||
1: .same(proto: "NO_TLS"),
|
1: .same(proto: "NO_TLS")
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -574,13 +574,13 @@ extension Extensions_Api_CastChannel_DeviceAuthMessage: SwiftProtobuf._MessageIm
|
||||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||||
1: .same(proto: "challenge"),
|
1: .same(proto: "challenge"),
|
||||||
2: .same(proto: "response"),
|
2: .same(proto: "response"),
|
||||||
3: .same(proto: "error"),
|
3: .same(proto: "error")
|
||||||
]
|
]
|
||||||
|
|
||||||
fileprivate class _StorageClass {
|
fileprivate class _StorageClass {
|
||||||
var _challenge: Extensions_Api_CastChannel_AuthChallenge? = nil
|
var _challenge: Extensions_Api_CastChannel_AuthChallenge?
|
||||||
var _response: Extensions_Api_CastChannel_AuthResponse? = nil
|
var _response: Extensions_Api_CastChannel_AuthResponse?
|
||||||
var _error: Extensions_Api_CastChannel_AuthError? = nil
|
var _error: Extensions_Api_CastChannel_AuthError?
|
||||||
|
|
||||||
static let defaultInstance = _StorageClass()
|
static let defaultInstance = _StorageClass()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ public class AppAvailability: NSObject {
|
||||||
extension AppAvailability {
|
extension AppAvailability {
|
||||||
convenience init(json: JSON) {
|
convenience init(json: JSON) {
|
||||||
self.init()
|
self.init()
|
||||||
|
|
||||||
if let availability = json[CastJSONPayloadKeys.availability].dictionaryObject as? [String: String] {
|
if let availability = json[CastJSONPayloadKeys.availability].dictionaryObject as? [String: String] {
|
||||||
self.availability = availability.mapValues { $0 == "APP_AVAILABLE" }
|
self.availability = availability.mapValues { $0 == "APP_AVAILABLE" }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,39 +23,39 @@ public final class CastApp: NSObject {
|
||||||
public var statusText: String = ""
|
public var statusText: String = ""
|
||||||
public var transportId: String = ""
|
public var transportId: String = ""
|
||||||
public var namespaces = [String]()
|
public var namespaces = [String]()
|
||||||
|
|
||||||
convenience init(json: JSON) {
|
convenience init(json: JSON) {
|
||||||
self.init()
|
self.init()
|
||||||
|
|
||||||
if let id = json[CastJSONPayloadKeys.appId].string {
|
if let id = json[CastJSONPayloadKeys.appId].string {
|
||||||
self.id = id
|
self.id = id
|
||||||
}
|
}
|
||||||
|
|
||||||
if let displayName = json[CastJSONPayloadKeys.displayName].string {
|
if let displayName = json[CastJSONPayloadKeys.displayName].string {
|
||||||
self.displayName = displayName
|
self.displayName = displayName
|
||||||
}
|
}
|
||||||
|
|
||||||
if let isIdleScreen = json[CastJSONPayloadKeys.isIdleScreen].bool {
|
if let isIdleScreen = json[CastJSONPayloadKeys.isIdleScreen].bool {
|
||||||
self.isIdleScreen = isIdleScreen
|
self.isIdleScreen = isIdleScreen
|
||||||
}
|
}
|
||||||
|
|
||||||
if let sessionId = json[CastJSONPayloadKeys.sessionId].string {
|
if let sessionId = json[CastJSONPayloadKeys.sessionId].string {
|
||||||
self.sessionId = sessionId
|
self.sessionId = sessionId
|
||||||
}
|
}
|
||||||
|
|
||||||
if let statusText = json[CastJSONPayloadKeys.statusText].string {
|
if let statusText = json[CastJSONPayloadKeys.statusText].string {
|
||||||
self.statusText = statusText
|
self.statusText = statusText
|
||||||
}
|
}
|
||||||
|
|
||||||
if let transportId = json[CastJSONPayloadKeys.transportId].string {
|
if let transportId = json[CastJSONPayloadKeys.transportId].string {
|
||||||
self.transportId = transportId
|
self.transportId = transportId
|
||||||
}
|
}
|
||||||
|
|
||||||
if let namespaces = json[CastJSONPayloadKeys.namespaces].array {
|
if let namespaces = json[CastJSONPayloadKeys.namespaces].array {
|
||||||
self.namespaces = namespaces.compactMap { $0[CastJSONPayloadKeys.name].string }
|
self.namespaces = namespaces.compactMap { $0[CastJSONPayloadKeys.name].string }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override var description: String {
|
public override var description: String {
|
||||||
return "CastApp(id: \(id), displayName: \(displayName), isIdleScreen: \(isIdleScreen), sessionId: \(sessionId), statusText: \(statusText), transportId: \(transportId), namespaces: \(namespaces)"
|
return "CastApp(id: \(id), displayName: \(displayName), isIdleScreen: \(isIdleScreen), sessionId: \(sessionId), statusText: \(statusText), transportId: \(transportId), namespaces: \(namespaces)"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,10 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
public struct DeviceCapabilities: OptionSet {
|
||||||
public struct DeviceCapabilities : OptionSet
|
|
||||||
{
|
|
||||||
public let rawValue: Int
|
public let rawValue: Int
|
||||||
public init(rawValue: Int) { self.rawValue = rawValue }
|
public init(rawValue: Int) { self.rawValue = rawValue }
|
||||||
|
|
||||||
public static let none = DeviceCapabilities([])
|
public static let none = DeviceCapabilities([])
|
||||||
public static let videoOut = DeviceCapabilities(rawValue: 1 << 0)
|
public static let videoOut = DeviceCapabilities(rawValue: 1 << 0)
|
||||||
public static let videoIn = DeviceCapabilities(rawValue: 1 << 1)
|
public static let videoIn = DeviceCapabilities(rawValue: 1 << 1)
|
||||||
|
|
@ -25,7 +23,7 @@ public struct DeviceCapabilities : OptionSet
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class CastDevice: NSObject, NSCopying {
|
public final class CastDevice: NSObject, NSCopying {
|
||||||
|
|
||||||
public private(set) var id: String
|
public private(set) var id: String
|
||||||
public private(set) var name: String
|
public private(set) var name: String
|
||||||
public private(set) var modelName: String
|
public private(set) var modelName: String
|
||||||
|
|
@ -35,7 +33,7 @@ public final class CastDevice: NSObject, NSCopying {
|
||||||
public private(set) var capabilities: DeviceCapabilities
|
public private(set) var capabilities: DeviceCapabilities
|
||||||
public private(set) var status: String
|
public private(set) var status: String
|
||||||
public private(set) var iconPath: String
|
public private(set) var iconPath: String
|
||||||
|
|
||||||
init(id: String, name: String, modelName: String, hostName: String, ipAddress: String, port: Int, capabilitiesMask: Int, status: String, iconPath: String) {
|
init(id: String, name: String, modelName: String, hostName: String, ipAddress: String, port: Int, capabilitiesMask: Int, status: String, iconPath: String) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
@ -49,7 +47,7 @@ public final class CastDevice: NSObject, NSCopying {
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func copy(with zone: NSZone? = nil) -> Any {
|
public func copy(with zone: NSZone? = nil) -> Any {
|
||||||
return CastDevice(id: self.id,
|
return CastDevice(id: self.id,
|
||||||
name: self.name,
|
name: self.name,
|
||||||
|
|
@ -61,7 +59,7 @@ public final class CastDevice: NSObject, NSCopying {
|
||||||
status: self.status,
|
status: self.status,
|
||||||
iconPath: iconPath)
|
iconPath: iconPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
public override var description: String {
|
public override var description: String {
|
||||||
return "CastDevice(id: \(id), name: \(name), hostName:\(hostName), ipAddress:\(ipAddress), port:\(port))"
|
return "CastDevice(id: \(id), name: \(name), hostName:\(hostName), ipAddress:\(ipAddress), port:\(port))"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,13 @@ public final class CastMedia: NSObject {
|
||||||
public let title: String
|
public let title: String
|
||||||
public let url: URL
|
public let url: URL
|
||||||
public let poster: URL?
|
public let poster: URL?
|
||||||
|
|
||||||
public let autoplay: Bool
|
public let autoplay: Bool
|
||||||
public let currentTime: Double
|
public let currentTime: Double
|
||||||
|
|
||||||
public let contentType: String
|
public let contentType: String
|
||||||
public let streamType: CastMediaStreamType
|
public let streamType: CastMediaStreamType
|
||||||
|
|
||||||
public init(title: String, url: URL, poster: URL? = nil, contentType: String, streamType: CastMediaStreamType = .buffered, autoplay: Bool = true, currentTime: Double = 0) {
|
public init(title: String, url: URL, poster: URL? = nil, contentType: String, streamType: CastMediaStreamType = .buffered, autoplay: Bool = true, currentTime: Double = 0) {
|
||||||
self.title = title
|
self.title = title
|
||||||
self.url = url
|
self.url = url
|
||||||
|
|
@ -36,7 +36,7 @@ public final class CastMedia: NSObject {
|
||||||
self.autoplay = autoplay
|
self.autoplay = autoplay
|
||||||
self.currentTime = currentTime
|
self.currentTime = currentTime
|
||||||
}
|
}
|
||||||
|
|
||||||
// public convenience init(title: String, url: URL, poster: URL, contentType: String, streamType: String, autoplay: Bool, currentTime: Double) {
|
// public convenience init(title: String, url: URL, poster: URL, contentType: String, streamType: String, autoplay: Bool, currentTime: Double) {
|
||||||
// guard let type = CastMediaStreamType(rawValue: streamType) else {
|
// guard let type = CastMediaStreamType(rawValue: streamType) else {
|
||||||
// fatalError("Invalid media stream type \(streamType)")
|
// fatalError("Invalid media stream type \(streamType)")
|
||||||
|
|
@ -47,7 +47,7 @@ public final class CastMedia: NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension CastMedia {
|
extension CastMedia {
|
||||||
|
|
||||||
var dict: [String: Any] {
|
var dict: [String: Any] {
|
||||||
if let poster = poster {
|
if let poster = poster {
|
||||||
return [
|
return [
|
||||||
|
|
@ -88,5 +88,5 @@ extension CastMedia {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ public enum CastMediaPlayerState: String {
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class CastMediaStatus: NSObject {
|
public final class CastMediaStatus: NSObject {
|
||||||
|
|
||||||
public let mediaSessionId: Int
|
public let mediaSessionId: Int
|
||||||
public let playbackRate: Int
|
public let playbackRate: Int
|
||||||
public let playerState: CastMediaPlayerState
|
public let playerState: CastMediaPlayerState
|
||||||
|
|
@ -25,36 +25,36 @@ public final class CastMediaStatus: NSObject {
|
||||||
public let metadata: JSON?
|
public let metadata: JSON?
|
||||||
public let contentID: String?
|
public let contentID: String?
|
||||||
private let createdDate = Date()
|
private let createdDate = Date()
|
||||||
|
|
||||||
public var adjustedCurrentTime: Double {
|
public var adjustedCurrentTime: Double {
|
||||||
return currentTime - Double(playbackRate)*createdDate.timeIntervalSinceNow
|
return currentTime - Double(playbackRate)*createdDate.timeIntervalSinceNow
|
||||||
}
|
}
|
||||||
|
|
||||||
public var state: String {
|
public var state: String {
|
||||||
return playerState.rawValue
|
return playerState.rawValue
|
||||||
}
|
}
|
||||||
|
|
||||||
public override var description: String {
|
public override var description: String {
|
||||||
return "MediaStatus(mediaSessionId: \(mediaSessionId), playbackRate: \(playbackRate), playerState: \(playerState.rawValue), currentTime: \(currentTime))"
|
return "MediaStatus(mediaSessionId: \(mediaSessionId), playbackRate: \(playbackRate), playerState: \(playerState.rawValue), currentTime: \(currentTime))"
|
||||||
}
|
}
|
||||||
|
|
||||||
init(json: JSON) {
|
init(json: JSON) {
|
||||||
mediaSessionId = json[CastJSONPayloadKeys.mediaSessionId].int ?? 0
|
mediaSessionId = json[CastJSONPayloadKeys.mediaSessionId].int ?? 0
|
||||||
|
|
||||||
playbackRate = json[CastJSONPayloadKeys.playbackRate].int ?? 1
|
playbackRate = json[CastJSONPayloadKeys.playbackRate].int ?? 1
|
||||||
|
|
||||||
playerState = json[CastJSONPayloadKeys.playerState].string.flatMap(CastMediaPlayerState.init) ?? .buffering
|
playerState = json[CastJSONPayloadKeys.playerState].string.flatMap(CastMediaPlayerState.init) ?? .buffering
|
||||||
|
|
||||||
currentTime = json[CastJSONPayloadKeys.currentTime].double ?? 0
|
currentTime = json[CastJSONPayloadKeys.currentTime].double ?? 0
|
||||||
|
|
||||||
metadata = json[CastJSONPayloadKeys.media][CastJSONPayloadKeys.metadata]
|
metadata = json[CastJSONPayloadKeys.media][CastJSONPayloadKeys.metadata]
|
||||||
|
|
||||||
if let contentID = json[CastJSONPayloadKeys.media][CastJSONPayloadKeys.contentId].string, let data = contentID.data(using: .utf8) {
|
if let contentID = json[CastJSONPayloadKeys.media][CastJSONPayloadKeys.contentId].string, let data = contentID.data(using: .utf8) {
|
||||||
self.contentID = (try? JSON(data: data))?[CastJSONPayloadKeys.contentId].string ?? contentID
|
self.contentID = (try? JSON(data: data))?[CastJSONPayloadKeys.contentId].string ?? contentID
|
||||||
} else {
|
} else {
|
||||||
contentID = nil
|
contentID = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,22 +15,22 @@ extension CastMessage {
|
||||||
message.sourceID = sourceId
|
message.sourceID = sourceId
|
||||||
message.destinationID = destinationId
|
message.destinationID = destinationId
|
||||||
message.namespace = namespace
|
message.namespace = namespace
|
||||||
|
|
||||||
switch payload {
|
switch payload {
|
||||||
case .json(let payload):
|
case .json(let payload):
|
||||||
let json = try JSONSerialization.data(withJSONObject: payload, options: [])
|
let json = try JSONSerialization.data(withJSONObject: payload, options: [])
|
||||||
|
|
||||||
guard let jsonString = String(data: json, encoding: .utf8) else {
|
guard let jsonString = String(data: json, encoding: .utf8) else {
|
||||||
fatalError("error forming json string")
|
fatalError("error forming json string")
|
||||||
}
|
}
|
||||||
|
|
||||||
message.payloadType = .string
|
message.payloadType = .string
|
||||||
message.payloadUtf8 = jsonString
|
message.payloadUtf8 = jsonString
|
||||||
case .data(let payload):
|
case .data(let payload):
|
||||||
message.payloadType = .binary
|
message.payloadType = .binary
|
||||||
message.payloadBinary = payload
|
message.payloadBinary = payload
|
||||||
}
|
}
|
||||||
|
|
||||||
return try message.serializedData()
|
return try message.serializedData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ public class CastMultizoneDevice {
|
||||||
public let isMuted: Bool
|
public let isMuted: Bool
|
||||||
public let capabilities: DeviceCapabilities
|
public let capabilities: DeviceCapabilities
|
||||||
public let id: String
|
public let id: String
|
||||||
|
|
||||||
public init(name: String, volume: Float, isMuted: Bool, capabilitiesMask: Int, id: String) {
|
public init(name: String, volume: Float, isMuted: Bool, capabilitiesMask: Int, id: String) {
|
||||||
self.name = name
|
self.name = name
|
||||||
self.volume = volume
|
self.volume = volume
|
||||||
|
|
@ -28,15 +28,15 @@ public class CastMultizoneDevice {
|
||||||
extension CastMultizoneDevice {
|
extension CastMultizoneDevice {
|
||||||
convenience init(json: JSON) {
|
convenience init(json: JSON) {
|
||||||
let name = json[CastJSONPayloadKeys.name].stringValue
|
let name = json[CastJSONPayloadKeys.name].stringValue
|
||||||
|
|
||||||
let volumeValues = json[CastJSONPayloadKeys.volume]
|
let volumeValues = json[CastJSONPayloadKeys.volume]
|
||||||
|
|
||||||
let volume = volumeValues[CastJSONPayloadKeys.level].floatValue
|
let volume = volumeValues[CastJSONPayloadKeys.level].floatValue
|
||||||
let isMuted = volumeValues[CastJSONPayloadKeys.muted].boolValue
|
let isMuted = volumeValues[CastJSONPayloadKeys.muted].boolValue
|
||||||
let capabilitiesMask = json[CastJSONPayloadKeys.capabilities].intValue
|
let capabilitiesMask = json[CastJSONPayloadKeys.capabilities].intValue
|
||||||
let deviceId = json[CastJSONPayloadKeys.deviceId].stringValue
|
let deviceId = json[CastJSONPayloadKeys.deviceId].stringValue
|
||||||
|
|
||||||
self.init(name: name, volume: volume, isMuted: isMuted, capabilitiesMask: capabilitiesMask, id: deviceId)
|
self.init(name: name, volume: volume, isMuted: isMuted, capabilitiesMask: capabilitiesMask, id: deviceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,19 +11,19 @@ import SwiftyJSON
|
||||||
|
|
||||||
public class CastMultizoneStatus {
|
public class CastMultizoneStatus {
|
||||||
public let devices: [CastMultizoneDevice]
|
public let devices: [CastMultizoneDevice]
|
||||||
|
|
||||||
public init(devices: [CastMultizoneDevice]) {
|
public init(devices: [CastMultizoneDevice]) {
|
||||||
self.devices = devices
|
self.devices = devices
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension CastMultizoneStatus {
|
extension CastMultizoneStatus {
|
||||||
|
|
||||||
convenience init(json: JSON) {
|
convenience init(json: JSON) {
|
||||||
let status = json[CastJSONPayloadKeys.status]
|
let status = json[CastJSONPayloadKeys.status]
|
||||||
let devices = status[CastJSONPayloadKeys.devices].array?.map(CastMultizoneDevice.init) ?? []
|
let devices = status[CastJSONPayloadKeys.devices].array?.map(CastMultizoneDevice.init) ?? []
|
||||||
|
|
||||||
self.init(devices: devices)
|
self.init(devices: devices)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,35 +10,35 @@ import Foundation
|
||||||
import SwiftyJSON
|
import SwiftyJSON
|
||||||
|
|
||||||
public final class CastStatus: NSObject {
|
public final class CastStatus: NSObject {
|
||||||
|
|
||||||
public var volume: Double = 1
|
public var volume: Double = 1
|
||||||
public var muted: Bool = false
|
public var muted: Bool = false
|
||||||
public var apps: [CastApp] = []
|
public var apps: [CastApp] = []
|
||||||
|
|
||||||
public override var description: String {
|
public override var description: String {
|
||||||
return "CastStatus(volume: \(volume), muted: \(muted), apps: \(apps))"
|
return "CastStatus(volume: \(volume), muted: \(muted), apps: \(apps))"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension CastStatus {
|
extension CastStatus {
|
||||||
|
|
||||||
convenience init(json: JSON) {
|
convenience init(json: JSON) {
|
||||||
self.init()
|
self.init()
|
||||||
// print(json)
|
// print(json)
|
||||||
let status = json[CastJSONPayloadKeys.status]
|
let status = json[CastJSONPayloadKeys.status]
|
||||||
let volume = status[CastJSONPayloadKeys.volume]
|
let volume = status[CastJSONPayloadKeys.volume]
|
||||||
|
|
||||||
if let volume = volume[CastJSONPayloadKeys.level].double {
|
if let volume = volume[CastJSONPayloadKeys.level].double {
|
||||||
self.volume = volume
|
self.volume = volume
|
||||||
}
|
}
|
||||||
if let muted = volume[CastJSONPayloadKeys.muted].bool {
|
if let muted = volume[CastJSONPayloadKeys.muted].bool {
|
||||||
self.muted = muted
|
self.muted = muted
|
||||||
}
|
}
|
||||||
|
|
||||||
if let apps = status[CastJSONPayloadKeys.applications].array {
|
if let apps = status[CastJSONPayloadKeys.applications].array {
|
||||||
self.apps = apps.compactMap(CastApp.init)
|
self.apps = apps.compactMap(CastApp.init)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,11 @@ import Result
|
||||||
public enum CastPayload {
|
public enum CastPayload {
|
||||||
case json([String: Any])
|
case json([String: Any])
|
||||||
case data(Data)
|
case data(Data)
|
||||||
|
|
||||||
init(_ json: [String: Any]) {
|
init(_ json: [String: Any]) {
|
||||||
self = .json(json)
|
self = .json(json)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(_ data: Data) {
|
init(_ data: Data) {
|
||||||
self = .data(data)
|
self = .data(data)
|
||||||
}
|
}
|
||||||
|
|
@ -41,14 +41,14 @@ public class CastRequest: NSObject {
|
||||||
var namespace: String
|
var namespace: String
|
||||||
var destinationId: String
|
var destinationId: String
|
||||||
var payload: CastPayload
|
var payload: CastPayload
|
||||||
|
|
||||||
init(id: Int, namespace: String, destinationId: String, payload: [String: Any]) {
|
init(id: Int, namespace: String, destinationId: String, payload: [String: Any]) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.namespace = namespace
|
self.namespace = namespace
|
||||||
self.destinationId = destinationId
|
self.destinationId = destinationId
|
||||||
self.payload = CastPayload(payload)
|
self.payload = CastPayload(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(id: Int, namespace: String, destinationId: String, payload: Data) {
|
init(id: Int, namespace: String, destinationId: String, payload: Data) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.namespace = namespace
|
self.namespace = namespace
|
||||||
|
|
@ -58,27 +58,27 @@ public class CastRequest: NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc public protocol CastClientDelegate: AnyObject {
|
@objc public protocol CastClientDelegate: AnyObject {
|
||||||
|
|
||||||
@objc optional func castClient(_ client: CastClient, willConnectTo device: CastDevice)
|
@objc optional func castClient(_ client: CastClient, willConnectTo device: CastDevice)
|
||||||
@objc optional func castClient(_ client: CastClient, didConnectTo device: CastDevice)
|
@objc optional func castClient(_ client: CastClient, didConnectTo device: CastDevice)
|
||||||
@objc optional func castClient(_ client: CastClient, didDisconnectFrom device: CastDevice)
|
@objc optional func castClient(_ client: CastClient, didDisconnectFrom device: CastDevice)
|
||||||
@objc optional func castClient(_ client: CastClient, connectionTo device: CastDevice, didFailWith error: Error?)
|
@objc optional func castClient(_ client: CastClient, connectionTo device: CastDevice, didFailWith error: Error?)
|
||||||
|
|
||||||
@objc optional func castClient(_ client: CastClient, deviceStatusDidChange status: CastStatus)
|
@objc optional func castClient(_ client: CastClient, deviceStatusDidChange status: CastStatus)
|
||||||
@objc optional func castClient(_ client: CastClient, mediaStatusDidChange status: CastMediaStatus)
|
@objc optional func castClient(_ client: CastClient, mediaStatusDidChange status: CastMediaStatus)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class CastClient: NSObject, RequestDispatchable, Channelable {
|
public final class CastClient: NSObject, RequestDispatchable, Channelable {
|
||||||
|
|
||||||
public let device: CastDevice
|
public let device: CastDevice
|
||||||
public weak var delegate: CastClientDelegate?
|
public weak var delegate: CastClientDelegate?
|
||||||
public var connectedApp: CastApp?
|
public var connectedApp: CastApp?
|
||||||
|
|
||||||
public private(set) var currentStatus: CastStatus? {
|
public private(set) var currentStatus: CastStatus? {
|
||||||
didSet {
|
didSet {
|
||||||
guard let status = currentStatus else { return }
|
guard let status = currentStatus else { return }
|
||||||
|
|
||||||
if oldValue != status {
|
if oldValue != status {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.delegate?.castClient?(self, deviceStatusDidChange: status)
|
self.delegate?.castClient?(self, deviceStatusDidChange: status)
|
||||||
|
|
@ -87,11 +87,11 @@ public final class CastClient: NSObject, RequestDispatchable, Channelable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public private(set) var currentMediaStatus: CastMediaStatus? {
|
public private(set) var currentMediaStatus: CastMediaStatus? {
|
||||||
didSet {
|
didSet {
|
||||||
guard let status = currentMediaStatus else { return }
|
guard let status = currentMediaStatus else { return }
|
||||||
|
|
||||||
if oldValue != status {
|
if oldValue != status {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.delegate?.castClient?(self, mediaStatusDidChange: status)
|
self.delegate?.castClient?(self, mediaStatusDidChange: status)
|
||||||
|
|
@ -100,24 +100,24 @@ public final class CastClient: NSObject, RequestDispatchable, Channelable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public private(set) var currentMultizoneStatus: CastMultizoneStatus?
|
public private(set) var currentMultizoneStatus: CastMultizoneStatus?
|
||||||
|
|
||||||
public var statusDidChange: ((CastStatus) -> Void)?
|
public var statusDidChange: ((CastStatus) -> Void)?
|
||||||
public var mediaStatusDidChange: ((CastMediaStatus) -> Void)?
|
public var mediaStatusDidChange: ((CastMediaStatus) -> Void)?
|
||||||
|
|
||||||
public init(device: CastDevice) {
|
public init(device: CastDevice) {
|
||||||
self.device = device
|
self.device = device
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
disconnect()
|
disconnect()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Socket Setup
|
// MARK: - Socket Setup
|
||||||
|
|
||||||
public var isConnected = false {
|
public var isConnected = false {
|
||||||
didSet {
|
didSet {
|
||||||
if oldValue != isConnected {
|
if oldValue != isConnected {
|
||||||
|
|
@ -129,7 +129,7 @@ public final class CastClient: NSObject, RequestDispatchable, Channelable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var inputStream: InputStream! {
|
private var inputStream: InputStream! {
|
||||||
didSet {
|
didSet {
|
||||||
if let inputStream = inputStream {
|
if let inputStream = inputStream {
|
||||||
|
|
@ -139,70 +139,70 @@ public final class CastClient: NSObject, RequestDispatchable, Channelable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var outputStream: OutputStream!
|
private var outputStream: OutputStream!
|
||||||
|
|
||||||
fileprivate lazy var socketQueue = DispatchQueue.global(qos: .userInitiated)
|
fileprivate lazy var socketQueue = DispatchQueue.global(qos: .userInitiated)
|
||||||
|
|
||||||
public func connect() {
|
public func connect() {
|
||||||
socketQueue.async {
|
socketQueue.async {
|
||||||
do {
|
do {
|
||||||
var readStream: Unmanaged<CFReadStream>?
|
var readStream: Unmanaged<CFReadStream>?
|
||||||
var writeStream: Unmanaged<CFWriteStream>?
|
var writeStream: Unmanaged<CFWriteStream>?
|
||||||
|
|
||||||
let settings: [String: Any] = [
|
let settings: [String: Any] = [
|
||||||
kCFStreamSSLValidatesCertificateChain as String: false,
|
kCFStreamSSLValidatesCertificateChain as String: false,
|
||||||
kCFStreamSSLLevel as String: kCFStreamSocketSecurityLevelTLSv1,
|
kCFStreamSSLLevel as String: kCFStreamSocketSecurityLevelTLSv1,
|
||||||
kCFStreamPropertyShouldCloseNativeSocket as String: true
|
kCFStreamPropertyShouldCloseNativeSocket as String: true
|
||||||
]
|
]
|
||||||
|
|
||||||
CFStreamCreatePairWithSocketToHost(nil, self.device.hostName as CFString, UInt32(self.device.port), &readStream, &writeStream)
|
CFStreamCreatePairWithSocketToHost(nil, self.device.hostName as CFString, UInt32(self.device.port), &readStream, &writeStream)
|
||||||
|
|
||||||
guard let readStreamRetained = readStream?.takeRetainedValue() else {
|
guard let readStreamRetained = readStream?.takeRetainedValue() else {
|
||||||
throw CastError.connection("Unable to create input stream")
|
throw CastError.connection("Unable to create input stream")
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let writeStreamRetained = writeStream?.takeRetainedValue() else {
|
guard let writeStreamRetained = writeStream?.takeRetainedValue() else {
|
||||||
throw CastError.connection("Unable to create output stream")
|
throw CastError.connection("Unable to create output stream")
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.main.async { self.delegate?.castClient?(self, willConnectTo: self.device) }
|
DispatchQueue.main.async { self.delegate?.castClient?(self, willConnectTo: self.device) }
|
||||||
|
|
||||||
CFReadStreamSetProperty(readStreamRetained, CFStreamPropertyKey(kCFStreamPropertySSLSettings), settings as CFTypeRef?)
|
CFReadStreamSetProperty(readStreamRetained, CFStreamPropertyKey(kCFStreamPropertySSLSettings), settings as CFTypeRef?)
|
||||||
CFWriteStreamSetProperty(writeStreamRetained, CFStreamPropertyKey(kCFStreamPropertySSLSettings), settings as CFTypeRef?)
|
CFWriteStreamSetProperty(writeStreamRetained, CFStreamPropertyKey(kCFStreamPropertySSLSettings), settings as CFTypeRef?)
|
||||||
|
|
||||||
self.inputStream = readStreamRetained
|
self.inputStream = readStreamRetained
|
||||||
self.outputStream = writeStreamRetained
|
self.outputStream = writeStreamRetained
|
||||||
|
|
||||||
self.inputStream.delegate = self
|
self.inputStream.delegate = self
|
||||||
|
|
||||||
self.inputStream.schedule(in: .current, forMode: RunLoop.Mode.default)
|
self.inputStream.schedule(in: .current, forMode: RunLoop.Mode.default)
|
||||||
self.outputStream.schedule(in: .current, forMode: RunLoop.Mode.default)
|
self.outputStream.schedule(in: .current, forMode: RunLoop.Mode.default)
|
||||||
|
|
||||||
self.inputStream.open()
|
self.inputStream.open()
|
||||||
self.outputStream.open()
|
self.outputStream.open()
|
||||||
|
|
||||||
RunLoop.current.run()
|
RunLoop.current.run()
|
||||||
} catch {
|
} catch {
|
||||||
DispatchQueue.main.async { self.delegate?.castClient?(self, connectionTo: self.device, didFailWith: error as NSError) }
|
DispatchQueue.main.async { self.delegate?.castClient?(self, connectionTo: self.device, didFailWith: error as NSError) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func disconnect() {
|
public func disconnect() {
|
||||||
if isConnected {
|
if isConnected {
|
||||||
isConnected = false
|
isConnected = false
|
||||||
}
|
}
|
||||||
|
|
||||||
channels.values.forEach(remove)
|
channels.values.forEach(remove)
|
||||||
|
|
||||||
socketQueue.async {
|
socketQueue.async {
|
||||||
if self.inputStream != nil {
|
if self.inputStream != nil {
|
||||||
self.inputStream.close()
|
self.inputStream.close()
|
||||||
self.inputStream.remove(from: RunLoop.current, forMode: RunLoop.Mode.default)
|
self.inputStream.remove(from: RunLoop.current, forMode: RunLoop.Mode.default)
|
||||||
self.inputStream = nil
|
self.inputStream = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.outputStream != nil {
|
if self.outputStream != nil {
|
||||||
self.outputStream.close()
|
self.outputStream.close()
|
||||||
self.outputStream.remove(from: RunLoop.current, forMode: RunLoop.Mode.default)
|
self.outputStream.remove(from: RunLoop.current, forMode: RunLoop.Mode.default)
|
||||||
|
|
@ -210,16 +210,16 @@ public final class CastClient: NSObject, RequestDispatchable, Channelable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Socket Lifecycle
|
// MARK: - Socket Lifecycle
|
||||||
|
|
||||||
private func write(data: Data) throws {
|
private func write(data: Data) throws {
|
||||||
var payloadSize = UInt32(data.count).bigEndian
|
var payloadSize = UInt32(data.count).bigEndian
|
||||||
let packet = NSMutableData(bytes: &payloadSize, length: MemoryLayout<UInt32>.size)
|
let packet = NSMutableData(bytes: &payloadSize, length: MemoryLayout<UInt32>.size)
|
||||||
packet.append(data)
|
packet.append(data)
|
||||||
|
|
||||||
let streamBytes = packet.bytes.bindMemory(to: UInt8.self, capacity: data.count)
|
let streamBytes = packet.bytes.bindMemory(to: UInt8.self, capacity: data.count)
|
||||||
|
|
||||||
if outputStream.write(streamBytes, maxLength: packet.length) < 0 {
|
if outputStream.write(streamBytes, maxLength: packet.length) < 0 {
|
||||||
if let error = outputStream.streamError {
|
if let error = outputStream.streamError {
|
||||||
throw CastError.write("Error writing \(packet.length) byte(s) to stream: \(error)")
|
throw CastError.write("Error writing \(packet.length) byte(s) to stream: \(error)")
|
||||||
|
|
@ -228,38 +228,37 @@ public final class CastClient: NSObject, RequestDispatchable, Channelable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fileprivate func sendConnectMessage() throws {
|
fileprivate func sendConnectMessage() throws {
|
||||||
guard outputStream != nil else { return }
|
guard outputStream != nil else { return }
|
||||||
|
|
||||||
_ = connectionChannel
|
_ = connectionChannel
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
_ = self.receiverControlChannel
|
_ = self.receiverControlChannel
|
||||||
_ = self.mediaControlChannel
|
_ = self.mediaControlChannel
|
||||||
_ = self.heartbeatChannel
|
_ = self.heartbeatChannel
|
||||||
|
|
||||||
if self.device.capabilities.contains(.multizoneGroup) {
|
if self.device.capabilities.contains(.multizoneGroup) {
|
||||||
_ = self.multizoneControlChannel
|
_ = self.multizoneControlChannel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var reader: CastV2PlatformReader?
|
private var reader: CastV2PlatformReader?
|
||||||
|
|
||||||
fileprivate func readStream() {
|
fileprivate func readStream() {
|
||||||
do {
|
do {
|
||||||
reader?.readStream()
|
reader?.readStream()
|
||||||
|
|
||||||
while let payload = reader?.nextMessage() {
|
while let payload = reader?.nextMessage() {
|
||||||
let message = try CastMessage(serializedData: payload)
|
let message = try CastMessage(serializedData: payload)
|
||||||
|
|
||||||
guard let channel = channels[message.namespace] else {
|
guard let channel = channels[message.namespace] else {
|
||||||
print("No channel attached for namespace \(message.namespace)")
|
print("No channel attached for namespace \(message.namespace)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch message.payloadType {
|
switch message.payloadType {
|
||||||
case .string:
|
case .string:
|
||||||
if let messageData = message.payloadUtf8.data(using: .utf8) {
|
if let messageData = message.payloadUtf8.data(using: .utf8) {
|
||||||
|
|
@ -267,7 +266,7 @@ public final class CastClient: NSObject, RequestDispatchable, Channelable {
|
||||||
|
|
||||||
channel.handleResponse(json,
|
channel.handleResponse(json,
|
||||||
sourceId: message.sourceID)
|
sourceId: message.sourceID)
|
||||||
|
|
||||||
if let requestId = json[CastJSONPayloadKeys.requestId].int {
|
if let requestId = json[CastJSONPayloadKeys.requestId].int {
|
||||||
callResponseHandler(for: requestId, with: Result(value: json))
|
callResponseHandler(for: requestId, with: Result(value: json))
|
||||||
}
|
}
|
||||||
|
|
@ -283,77 +282,77 @@ public final class CastClient: NSObject, RequestDispatchable, Channelable {
|
||||||
NSLog("Error reading: \(error)")
|
NSLog("Error reading: \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//MARK: - Channelable
|
// MARK: - Channelable
|
||||||
|
|
||||||
var channels = [String: CastChannel]()
|
var channels = [String: CastChannel]()
|
||||||
|
|
||||||
private lazy var heartbeatChannel: HeartbeatChannel = {
|
private lazy var heartbeatChannel: HeartbeatChannel = {
|
||||||
let channel = HeartbeatChannel()
|
let channel = HeartbeatChannel()
|
||||||
self.add(channel: channel)
|
self.add(channel: channel)
|
||||||
|
|
||||||
return channel
|
return channel
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private lazy var connectionChannel: DeviceConnectionChannel = {
|
private lazy var connectionChannel: DeviceConnectionChannel = {
|
||||||
let channel = DeviceConnectionChannel()
|
let channel = DeviceConnectionChannel()
|
||||||
self.add(channel: channel)
|
self.add(channel: channel)
|
||||||
|
|
||||||
return channel
|
return channel
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private lazy var receiverControlChannel: ReceiverControlChannel = {
|
private lazy var receiverControlChannel: ReceiverControlChannel = {
|
||||||
let channel = ReceiverControlChannel()
|
let channel = ReceiverControlChannel()
|
||||||
self.add(channel: channel)
|
self.add(channel: channel)
|
||||||
|
|
||||||
return channel
|
return channel
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private lazy var mediaControlChannel: MediaControlChannel = {
|
private lazy var mediaControlChannel: MediaControlChannel = {
|
||||||
let channel = MediaControlChannel()
|
let channel = MediaControlChannel()
|
||||||
self.add(channel: channel)
|
self.add(channel: channel)
|
||||||
|
|
||||||
return channel
|
return channel
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private lazy var multizoneControlChannel: MultizoneControlChannel = {
|
private lazy var multizoneControlChannel: MultizoneControlChannel = {
|
||||||
let channel = MultizoneControlChannel()
|
let channel = MultizoneControlChannel()
|
||||||
self.add(channel: channel)
|
self.add(channel: channel)
|
||||||
|
|
||||||
return channel
|
return channel
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// MARK: - Request response
|
// MARK: - Request response
|
||||||
|
|
||||||
private lazy var currentRequestId = Int(arc4random_uniform(800))
|
private lazy var currentRequestId = Int(arc4random_uniform(800))
|
||||||
|
|
||||||
func nextRequestId() -> Int {
|
func nextRequestId() -> Int {
|
||||||
currentRequestId += 1
|
currentRequestId += 1
|
||||||
|
|
||||||
return currentRequestId
|
return currentRequestId
|
||||||
}
|
}
|
||||||
|
|
||||||
private let senderName: String = "sender-\(UUID().uuidString)"
|
private let senderName: String = "sender-\(UUID().uuidString)"
|
||||||
|
|
||||||
private var responseHandlers = [Int: CastResponseHandler]()
|
private var responseHandlers = [Int: CastResponseHandler]()
|
||||||
|
|
||||||
func send(_ request: CastRequest, response: CastResponseHandler?) {
|
func send(_ request: CastRequest, response: CastResponseHandler?) {
|
||||||
if let response = response {
|
if let response = response {
|
||||||
responseHandlers[request.id] = response
|
responseHandlers[request.id] = response
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let messageData = try CastMessage.encodedMessage(payload: request.payload,
|
let messageData = try CastMessage.encodedMessage(payload: request.payload,
|
||||||
namespace: request.namespace,
|
namespace: request.namespace,
|
||||||
sourceId: senderName,
|
sourceId: senderName,
|
||||||
destinationId: request.destinationId)
|
destinationId: request.destinationId)
|
||||||
|
|
||||||
try write(data: messageData)
|
try write(data: messageData)
|
||||||
} catch {
|
} catch {
|
||||||
callResponseHandler(for: request.id, with: Result(error: .request(error.localizedDescription)))
|
callResponseHandler(for: request.id, with: Result(error: .request(error.localizedDescription)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func callResponseHandler(for requestId: Int, with result: Result<JSON, CastError>) {
|
private func callResponseHandler(for requestId: Int, with result: Result<JSON, CastError>) {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
if let handler = self.responseHandlers.removeValue(forKey: requestId) {
|
if let handler = self.responseHandlers.removeValue(forKey: requestId) {
|
||||||
|
|
@ -361,22 +360,22 @@ public final class CastClient: NSObject, RequestDispatchable, Channelable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Public messages
|
// MARK: - Public messages
|
||||||
|
|
||||||
public func getAppAvailability(apps: [CastApp], completion: @escaping (Result<AppAvailability, CastError>) -> Void) {
|
public func getAppAvailability(apps: [CastApp], completion: @escaping (Result<AppAvailability, CastError>) -> Void) {
|
||||||
guard outputStream != nil else { return }
|
guard outputStream != nil else { return }
|
||||||
|
|
||||||
receiverControlChannel.getAppAvailability(apps: apps, completion: completion)
|
receiverControlChannel.getAppAvailability(apps: apps, completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func join(app: CastApp? = nil, completion: @escaping (Result<CastApp, CastError>) -> Void) {
|
public func join(app: CastApp? = nil, completion: @escaping (Result<CastApp, CastError>) -> Void) {
|
||||||
guard outputStream != nil,
|
guard outputStream != nil,
|
||||||
let target = app ?? currentStatus?.apps.first else {
|
let target = app ?? currentStatus?.apps.first else {
|
||||||
completion(Result(error: CastError.session("No Apps Running")))
|
completion(Result(error: CastError.session("No Apps Running")))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if target == connectedApp {
|
if target == connectedApp {
|
||||||
completion(Result(value: target))
|
completion(Result(value: target))
|
||||||
} else if let existing = currentStatus?.apps.first(where: { $0.id == target.id }) {
|
} else if let existing = currentStatus?.apps.first(where: { $0.id == target.id }) {
|
||||||
|
|
@ -390,67 +389,67 @@ public final class CastClient: NSObject, RequestDispatchable, Channelable {
|
||||||
completion(Result(error: CastError.launch("Unable to get launched app instance")))
|
completion(Result(error: CastError.launch("Unable to get launched app instance")))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self?.connect(to: app)
|
self?.connect(to: app)
|
||||||
completion(Result(value: app))
|
completion(Result(value: app))
|
||||||
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
completion(Result(error: error))
|
completion(Result(error: error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func launch(appId: String, completion: @escaping (Result<CastApp, CastError>) -> Void) {
|
public func launch(appId: String, completion: @escaping (Result<CastApp, CastError>) -> Void) {
|
||||||
guard outputStream != nil else { return }
|
guard outputStream != nil else { return }
|
||||||
|
|
||||||
receiverControlChannel.launch(appId: appId) { [weak self] result in
|
receiverControlChannel.launch(appId: appId) { [weak self] result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let app):
|
case .success(let app):
|
||||||
self?.connect(to: app)
|
self?.connect(to: app)
|
||||||
fallthrough
|
fallthrough
|
||||||
|
|
||||||
default:
|
default:
|
||||||
completion(result)
|
completion(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func stopCurrentApp() {
|
public func stopCurrentApp() {
|
||||||
guard outputStream != nil, let app = currentStatus?.apps.first else { return }
|
guard outputStream != nil, let app = currentStatus?.apps.first else { return }
|
||||||
|
|
||||||
receiverControlChannel.stop(app: app)
|
receiverControlChannel.stop(app: app)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func leave(_ app: CastApp) {
|
public func leave(_ app: CastApp) {
|
||||||
guard outputStream != nil else { return }
|
guard outputStream != nil else { return }
|
||||||
|
|
||||||
connectionChannel.leave(app)
|
connectionChannel.leave(app)
|
||||||
connectedApp = nil
|
connectedApp = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
public func load(media: CastMedia, with app: CastApp, completion: @escaping (Result<CastMediaStatus, CastError>) -> Void) {
|
public func load(media: CastMedia, with app: CastApp, completion: @escaping (Result<CastMediaStatus, CastError>) -> Void) {
|
||||||
guard outputStream != nil else { return }
|
guard outputStream != nil else { return }
|
||||||
|
|
||||||
mediaControlChannel.load(media: media, with: app, completion: completion)
|
mediaControlChannel.load(media: media, with: app, completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func requestMediaStatus(for app: CastApp, completion: ((Result<CastMediaStatus, CastError>) -> Void)? = nil) {
|
public func requestMediaStatus(for app: CastApp, completion: ((Result<CastMediaStatus, CastError>) -> Void)? = nil) {
|
||||||
guard outputStream != nil else { return }
|
guard outputStream != nil else { return }
|
||||||
|
|
||||||
mediaControlChannel.requestMediaStatus(for: app)
|
mediaControlChannel.requestMediaStatus(for: app)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func connect(to app: CastApp) {
|
private func connect(to app: CastApp) {
|
||||||
guard outputStream != nil else { return }
|
guard outputStream != nil else { return }
|
||||||
|
|
||||||
connectionChannel.connect(to: app)
|
connectionChannel.connect(to: app)
|
||||||
connectedApp = app
|
connectedApp = app
|
||||||
}
|
}
|
||||||
|
|
||||||
public func pause() {
|
public func pause() {
|
||||||
guard outputStream != nil, let app = connectedApp else { return }
|
guard outputStream != nil, let app = connectedApp else { return }
|
||||||
|
|
||||||
if let mediaStatus = currentMediaStatus {
|
if let mediaStatus = currentMediaStatus {
|
||||||
mediaControlChannel.sendPause(for: app, mediaSessionId: mediaStatus.mediaSessionId)
|
mediaControlChannel.sendPause(for: app, mediaSessionId: mediaStatus.mediaSessionId)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -458,17 +457,17 @@ public final class CastClient: NSObject, RequestDispatchable, Channelable {
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let mediaStatus):
|
case .success(let mediaStatus):
|
||||||
self.mediaControlChannel.sendPause(for: app, mediaSessionId: mediaStatus.mediaSessionId)
|
self.mediaControlChannel.sendPause(for: app, mediaSessionId: mediaStatus.mediaSessionId)
|
||||||
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
print(error)
|
print(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func play() {
|
public func play() {
|
||||||
guard outputStream != nil, let app = connectedApp else { return }
|
guard outputStream != nil, let app = connectedApp else { return }
|
||||||
|
|
||||||
if let mediaStatus = currentMediaStatus {
|
if let mediaStatus = currentMediaStatus {
|
||||||
mediaControlChannel.sendPlay(for: app, mediaSessionId: mediaStatus.mediaSessionId)
|
mediaControlChannel.sendPlay(for: app, mediaSessionId: mediaStatus.mediaSessionId)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -476,17 +475,17 @@ public final class CastClient: NSObject, RequestDispatchable, Channelable {
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let mediaStatus):
|
case .success(let mediaStatus):
|
||||||
self.mediaControlChannel.sendPlay(for: app, mediaSessionId: mediaStatus.mediaSessionId)
|
self.mediaControlChannel.sendPlay(for: app, mediaSessionId: mediaStatus.mediaSessionId)
|
||||||
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
print(error)
|
print(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func stop() {
|
public func stop() {
|
||||||
guard outputStream != nil, let app = connectedApp else { return }
|
guard outputStream != nil, let app = connectedApp else { return }
|
||||||
|
|
||||||
if let mediaStatus = currentMediaStatus {
|
if let mediaStatus = currentMediaStatus {
|
||||||
mediaControlChannel.sendStop(for: app, mediaSessionId: mediaStatus.mediaSessionId)
|
mediaControlChannel.sendStop(for: app, mediaSessionId: mediaStatus.mediaSessionId)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -494,17 +493,17 @@ public final class CastClient: NSObject, RequestDispatchable, Channelable {
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let mediaStatus):
|
case .success(let mediaStatus):
|
||||||
self.mediaControlChannel.sendStop(for: app, mediaSessionId: mediaStatus.mediaSessionId)
|
self.mediaControlChannel.sendStop(for: app, mediaSessionId: mediaStatus.mediaSessionId)
|
||||||
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
print(error)
|
print(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func seek(to currentTime: Float) {
|
public func seek(to currentTime: Float) {
|
||||||
guard outputStream != nil, let app = connectedApp else { return }
|
guard outputStream != nil, let app = connectedApp else { return }
|
||||||
|
|
||||||
if let mediaStatus = currentMediaStatus {
|
if let mediaStatus = currentMediaStatus {
|
||||||
mediaControlChannel.sendSeek(to: currentTime, for: app, mediaSessionId: mediaStatus.mediaSessionId)
|
mediaControlChannel.sendSeek(to: currentTime, for: app, mediaSessionId: mediaStatus.mediaSessionId)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -512,41 +511,41 @@ public final class CastClient: NSObject, RequestDispatchable, Channelable {
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let mediaStatus):
|
case .success(let mediaStatus):
|
||||||
self.mediaControlChannel.sendSeek(to: currentTime, for: app, mediaSessionId: mediaStatus.mediaSessionId)
|
self.mediaControlChannel.sendSeek(to: currentTime, for: app, mediaSessionId: mediaStatus.mediaSessionId)
|
||||||
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
print(error)
|
print(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setVolume(_ volume: Float) {
|
public func setVolume(_ volume: Float) {
|
||||||
guard outputStream != nil else { return }
|
guard outputStream != nil else { return }
|
||||||
|
|
||||||
receiverControlChannel.setVolume(volume)
|
receiverControlChannel.setVolume(volume)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setMuted(_ muted: Bool) {
|
public func setMuted(_ muted: Bool) {
|
||||||
guard outputStream != nil else { return }
|
guard outputStream != nil else { return }
|
||||||
|
|
||||||
receiverControlChannel.setMuted(muted)
|
receiverControlChannel.setMuted(muted)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setVolume(_ volume: Float, for device: CastMultizoneDevice) {
|
public func setVolume(_ volume: Float, for device: CastMultizoneDevice) {
|
||||||
guard device.capabilities.contains(.multizoneGroup) else {
|
guard device.capabilities.contains(.multizoneGroup) else {
|
||||||
print("Attempted to set zone volume on non-multizone device")
|
print("Attempted to set zone volume on non-multizone device")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
multizoneControlChannel.setVolume(volume, for: device)
|
multizoneControlChannel.setVolume(volume, for: device)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setMuted(_ isMuted: Bool, for device: CastMultizoneDevice) {
|
public func setMuted(_ isMuted: Bool, for device: CastMultizoneDevice) {
|
||||||
guard device.capabilities.contains(.multizoneGroup) else {
|
guard device.capabilities.contains(.multizoneGroup) else {
|
||||||
print("Attempted to mute zone on non-multizone device")
|
print("Attempted to mute zone on non-multizone device")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
multizoneControlChannel.setMuted(isMuted, for: device)
|
multizoneControlChannel.setMuted(isMuted, for: device)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -559,14 +558,14 @@ extension CastClient: StreamDelegate {
|
||||||
socketQueue.async {
|
socketQueue.async {
|
||||||
do {
|
do {
|
||||||
try self.sendConnectMessage()
|
try self.sendConnectMessage()
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
NSLog("Error sending connect message: \(error)")
|
NSLog("Error sending connect message: \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case Stream.Event.errorOccurred:
|
case Stream.Event.errorOccurred:
|
||||||
NSLog("Stream error occurred: \(aStream.streamError.debugDescription)")
|
NSLog("Stream error occurred: \(aStream.streamError.debugDescription)")
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.delegate?.castClient?(self, connectionTo: self.device, didFailWith: aStream.streamError)
|
self.delegate?.castClient?(self, connectionTo: self.device, didFailWith: aStream.streamError)
|
||||||
}
|
}
|
||||||
|
|
@ -577,7 +576,7 @@ extension CastClient: StreamDelegate {
|
||||||
case Stream.Event.endEncountered:
|
case Stream.Event.endEncountered:
|
||||||
NSLog("Input stream ended")
|
NSLog("Input stream ended")
|
||||||
disconnect()
|
disconnect()
|
||||||
|
|
||||||
default: break
|
default: break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -601,7 +600,7 @@ extension CastClient: HeartbeatChannelDelegate {
|
||||||
isConnected = true
|
isConnected = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func channelDidTimeout(_ channel: HeartbeatChannel) {
|
func channelDidTimeout(_ channel: HeartbeatChannel) {
|
||||||
disconnect()
|
disconnect()
|
||||||
currentStatus = nil
|
currentStatus = nil
|
||||||
|
|
@ -612,17 +611,17 @@ extension CastClient: HeartbeatChannelDelegate {
|
||||||
|
|
||||||
extension CastClient: MultizoneControlChannelDelegate {
|
extension CastClient: MultizoneControlChannelDelegate {
|
||||||
func channel(_ channel: MultizoneControlChannel, added device: CastMultizoneDevice) {
|
func channel(_ channel: MultizoneControlChannel, added device: CastMultizoneDevice) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func channel(_ channel: MultizoneControlChannel, updated device: CastMultizoneDevice) {
|
func channel(_ channel: MultizoneControlChannel, updated device: CastMultizoneDevice) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func channel(_ channel: MultizoneControlChannel, removed deviceId: String) {
|
func channel(_ channel: MultizoneControlChannel, removed deviceId: String) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func channel(_ channel: MultizoneControlChannel, didReceive status: CastMultizoneStatus) {
|
func channel(_ channel: MultizoneControlChannel, didReceive status: CastMultizoneStatus) {
|
||||||
currentMultizoneStatus = status
|
currentMultizoneStatus = status
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,15 +11,15 @@ import Foundation
|
||||||
extension CastDevice {
|
extension CastDevice {
|
||||||
convenience init(service: NetService, info: [String: String]) {
|
convenience init(service: NetService, info: [String: String]) {
|
||||||
var ipAddress: String?
|
var ipAddress: String?
|
||||||
|
|
||||||
if let address = service.addresses?.first {
|
if let address = service.addresses?.first {
|
||||||
ipAddress = address.withUnsafeBytes { (pointer: UnsafePointer<sockaddr>) -> String? in
|
ipAddress = address.withUnsafeBytes { (pointer: UnsafePointer<sockaddr>) -> String? in
|
||||||
var hostName = [CChar](repeating: 0, count: Int(NI_MAXHOST))
|
var hostName = [CChar](repeating: 0, count: Int(NI_MAXHOST))
|
||||||
|
|
||||||
return getnameinfo(pointer, socklen_t(address.count), &hostName, socklen_t(NI_MAXHOST), nil, 0, NI_NUMERICHOST) == 0 ? String.init(cString: hostName) : nil
|
return getnameinfo(pointer, socklen_t(address.count), &hostName, socklen_t(NI_MAXHOST), nil, 0, NI_NUMERICHOST) == 0 ? String.init(cString: hostName) : nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.init(id: info["id"] ?? "",
|
self.init(id: info["id"] ?? "",
|
||||||
name: info["fn"] ?? service.name,
|
name: info["fn"] ?? service.name,
|
||||||
modelName: info["md"] ?? "Google Cast",
|
modelName: info["md"] ?? "Google Cast",
|
||||||
|
|
@ -30,129 +30,129 @@ extension CastDevice {
|
||||||
status: info["rs"] ?? "",
|
status: info["rs"] ?? "",
|
||||||
iconPath: info["ic"] ?? "")
|
iconPath: info["ic"] ?? "")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class CastDeviceScanner: NSObject {
|
public final class CastDeviceScanner: NSObject {
|
||||||
public weak var delegate: CastDeviceScannerDelegate?
|
public weak var delegate: CastDeviceScannerDelegate?
|
||||||
|
|
||||||
public static let deviceListDidChange = Notification.Name(rawValue: "DeviceScannerDeviceListDidChangeNotification")
|
public static let deviceListDidChange = Notification.Name(rawValue: "DeviceScannerDeviceListDidChangeNotification")
|
||||||
|
|
||||||
private lazy var browser: NetServiceBrowser = configureBrowser()
|
private lazy var browser: NetServiceBrowser = configureBrowser()
|
||||||
|
|
||||||
public var isScanning = false
|
public var isScanning = false
|
||||||
|
|
||||||
fileprivate var services = [NetService]()
|
fileprivate var services = [NetService]()
|
||||||
|
|
||||||
public fileprivate(set) var devices = [CastDevice]() {
|
public fileprivate(set) var devices = [CastDevice]() {
|
||||||
didSet {
|
didSet {
|
||||||
NotificationCenter.default.post(name: CastDeviceScanner.deviceListDidChange, object: self)
|
NotificationCenter.default.post(name: CastDeviceScanner.deviceListDidChange, object: self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func configureBrowser() -> NetServiceBrowser {
|
private func configureBrowser() -> NetServiceBrowser {
|
||||||
let b = NetServiceBrowser()
|
let b = NetServiceBrowser()
|
||||||
|
|
||||||
b.includesPeerToPeer = true
|
b.includesPeerToPeer = true
|
||||||
b.delegate = self
|
b.delegate = self
|
||||||
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
public func startScanning() {
|
public func startScanning() {
|
||||||
guard !isScanning else { return }
|
guard !isScanning else { return }
|
||||||
|
|
||||||
browser.stop()
|
browser.stop()
|
||||||
browser.searchForServices(ofType: "_googlecast._tcp", inDomain: "local")
|
browser.searchForServices(ofType: "_googlecast._tcp", inDomain: "local")
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
NSLog("Started scanning")
|
NSLog("Started scanning")
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public func stopScanning() {
|
public func stopScanning() {
|
||||||
guard isScanning else { return }
|
guard isScanning else { return }
|
||||||
|
|
||||||
browser.stop()
|
browser.stop()
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
NSLog("Stopped scanning")
|
NSLog("Stopped scanning")
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public func reset() {
|
public func reset() {
|
||||||
stopScanning()
|
stopScanning()
|
||||||
devices.removeAll()
|
devices.removeAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
stopScanning()
|
stopScanning()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension CastDeviceScanner: NetServiceBrowserDelegate {
|
extension CastDeviceScanner: NetServiceBrowserDelegate {
|
||||||
|
|
||||||
public func netServiceBrowserWillSearch(_ browser: NetServiceBrowser) {
|
public func netServiceBrowserWillSearch(_ browser: NetServiceBrowser) {
|
||||||
isScanning = true
|
isScanning = true
|
||||||
}
|
}
|
||||||
|
|
||||||
public func netServiceBrowserDidStopSearch(_ browser: NetServiceBrowser) {
|
public func netServiceBrowserDidStopSearch(_ browser: NetServiceBrowser) {
|
||||||
isScanning = false
|
isScanning = false
|
||||||
}
|
}
|
||||||
|
|
||||||
public func netServiceBrowser(_ browser: NetServiceBrowser, didFind service: NetService, moreComing: Bool) {
|
public func netServiceBrowser(_ browser: NetServiceBrowser, didFind service: NetService, moreComing: Bool) {
|
||||||
removeService(service)
|
removeService(service)
|
||||||
|
|
||||||
service.delegate = self
|
service.delegate = self
|
||||||
service.resolve(withTimeout: 30.0)
|
service.resolve(withTimeout: 30.0)
|
||||||
services.append(service)
|
services.append(service)
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
NSLog("Did find service: \(service) more: \(moreComing)")
|
NSLog("Did find service: \(service) more: \(moreComing)")
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public func netServiceBrowser(_ browser: NetServiceBrowser, didRemove service: NetService, moreComing: Bool) {
|
public func netServiceBrowser(_ browser: NetServiceBrowser, didRemove service: NetService, moreComing: Bool) {
|
||||||
guard let service = removeService(service) else { return }
|
guard let service = removeService(service) else { return }
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
NSLog("Did remove service: \(service)")
|
NSLog("Did remove service: \(service)")
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
guard let deviceId = service.id,
|
guard let deviceId = service.id,
|
||||||
let index = devices.firstIndex(where: { $0.id == deviceId }) else {
|
let index = devices.firstIndex(where: { $0.id == deviceId }) else {
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
NSLog("No device")
|
NSLog("No device")
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
NSLog("Removing device: \(devices[index])")
|
NSLog("Removing device: \(devices[index])")
|
||||||
#endif
|
#endif
|
||||||
let device = devices.remove(at: index)
|
let device = devices.remove(at: index)
|
||||||
delegate?.deviceDidGoOffline(device)
|
delegate?.deviceDidGoOffline(device)
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult func removeService(_ service: NetService) -> NetService? {
|
@discardableResult func removeService(_ service: NetService) -> NetService? {
|
||||||
if let index = services.firstIndex(of: service) {
|
if let index = services.firstIndex(of: service) {
|
||||||
return services.remove(at: index)
|
return services.remove(at: index)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addDevice(_ device: CastDevice) {
|
func addDevice(_ device: CastDevice) {
|
||||||
if let index = devices.firstIndex(where: { $0.id == device.id }) {
|
if let index = devices.firstIndex(where: { $0.id == device.id }) {
|
||||||
let existing = devices[index]
|
let existing = devices[index]
|
||||||
|
|
||||||
guard existing.name != device.name || existing.hostName != device.hostName else { return }
|
guard existing.name != device.name || existing.hostName != device.hostName else { return }
|
||||||
|
|
||||||
devices.remove(at: index)
|
devices.remove(at: index)
|
||||||
devices.insert(device, at: index)
|
devices.insert(device, at: index)
|
||||||
|
|
||||||
delegate?.deviceDidChange(device)
|
delegate?.deviceDidChange(device)
|
||||||
} else {
|
} else {
|
||||||
devices.append(device)
|
devices.append(device)
|
||||||
|
|
@ -162,7 +162,7 @@ extension CastDeviceScanner: NetServiceBrowserDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension CastDeviceScanner: NetServiceDelegate {
|
extension CastDeviceScanner: NetServiceDelegate {
|
||||||
|
|
||||||
public func netServiceDidResolveAddress(_ sender: NetService) {
|
public func netServiceDidResolveAddress(_ sender: NetService) {
|
||||||
guard let infoDict = sender.infoDict else {
|
guard let infoDict = sender.infoDict else {
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
|
@ -170,25 +170,25 @@ extension CastDeviceScanner: NetServiceDelegate {
|
||||||
#endif
|
#endif
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
NSLog("Did resolve service: \(sender)")
|
NSLog("Did resolve service: \(sender)")
|
||||||
NSLog("\(infoDict)")
|
NSLog("\(infoDict)")
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
guard infoDict["id"] != nil else {
|
guard infoDict["id"] != nil else {
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
NSLog("No id for device \(sender), skipping")
|
NSLog("No id for device \(sender), skipping")
|
||||||
#endif
|
#endif
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
addDevice(CastDevice(service: sender, info: infoDict))
|
addDevice(CastDevice(service: sender, info: infoDict))
|
||||||
}
|
}
|
||||||
|
|
||||||
public func netService(_ sender: NetService, didNotResolve errorDict: [String : NSNumber]) {
|
public func netService(_ sender: NetService, didNotResolve errorDict: [String: NSNumber]) {
|
||||||
removeService(sender)
|
removeService(sender)
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
NSLog("!! Failed to resolve service: \(sender) - \(errorDict) !!")
|
NSLog("!! Failed to resolve service: \(sender) - \(errorDict) !!")
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -200,13 +200,13 @@ extension NetService {
|
||||||
guard let data = txtRecordData() else {
|
guard let data = txtRecordData() else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var dict = [String: String]()
|
var dict = [String: String]()
|
||||||
NetService.dictionary(fromTXTRecord: data).forEach({ dict[$0.key] = String(data: $0.value, encoding: .utf8)! })
|
NetService.dictionary(fromTXTRecord: data).forEach({ dict[$0.key] = String(data: $0.value, encoding: .utf8)! })
|
||||||
|
|
||||||
return dict
|
return dict
|
||||||
}
|
}
|
||||||
|
|
||||||
var id: String? {
|
var id: String? {
|
||||||
return infoDict?["id"]
|
return infoDict?["id"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,19 +12,19 @@ import SwiftyJSON
|
||||||
open class CastChannel {
|
open class CastChannel {
|
||||||
let namespace: String
|
let namespace: String
|
||||||
weak var requestDispatcher: RequestDispatchable!
|
weak var requestDispatcher: RequestDispatchable!
|
||||||
|
|
||||||
init(namespace: String) {
|
init(namespace: String) {
|
||||||
self.namespace = namespace
|
self.namespace = namespace
|
||||||
}
|
}
|
||||||
|
|
||||||
open func handleResponse(_ json: JSON, sourceId: String) {
|
open func handleResponse(_ json: JSON, sourceId: String) {
|
||||||
// print(json)
|
// print(json)
|
||||||
}
|
}
|
||||||
|
|
||||||
open func handleResponse(_ data: Data, sourceId: String) {
|
open func handleResponse(_ data: Data, sourceId: String) {
|
||||||
print("\n--Binary response--\n")
|
print("\n--Binary response--\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
public func send(_ request: CastRequest, response: CastResponseHandler? = nil) {
|
public func send(_ request: CastRequest, response: CastResponseHandler? = nil) {
|
||||||
requestDispatcher.send(request, response: response)
|
requestDispatcher.send(request, response: response)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import Foundation
|
||||||
|
|
||||||
protocol Channelable: RequestDispatchable {
|
protocol Channelable: RequestDispatchable {
|
||||||
var channels: [String: CastChannel] { get set }
|
var channels: [String: CastChannel] { get set }
|
||||||
|
|
||||||
func add(channel: CastChannel)
|
func add(channel: CastChannel)
|
||||||
func remove(channel: CastChannel)
|
func remove(channel: CastChannel)
|
||||||
}
|
}
|
||||||
|
|
@ -22,18 +22,18 @@ extension Channelable {
|
||||||
print("Channel already attached for \(namespace)")
|
print("Channel already attached for \(namespace)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
channels[namespace] = channel
|
channels[namespace] = channel
|
||||||
channel.requestDispatcher = self
|
channel.requestDispatcher = self
|
||||||
}
|
}
|
||||||
|
|
||||||
public func remove(channel: CastChannel) {
|
public func remove(channel: CastChannel) {
|
||||||
let namespace = channel.namespace
|
let namespace = channel.namespace
|
||||||
guard let channel = channels.removeValue(forKey: namespace) else {
|
guard let channel = channels.removeValue(forKey: namespace) else {
|
||||||
print("No channel attached for \(namespace)")
|
print("No channel attached for \(namespace)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
channel.requestDispatcher = nil
|
channel.requestDispatcher = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,16 +11,16 @@ import Foundation
|
||||||
class DeviceAuthChannel: CastChannel {
|
class DeviceAuthChannel: CastChannel {
|
||||||
typealias CastAuthChallenge = Extensions_Api_CastChannel_AuthChallenge
|
typealias CastAuthChallenge = Extensions_Api_CastChannel_AuthChallenge
|
||||||
typealias CastAuthMessage = Extensions_Api_CastChannel_DeviceAuthMessage
|
typealias CastAuthMessage = Extensions_Api_CastChannel_DeviceAuthMessage
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
super.init(namespace: CastNamespace.auth)
|
super.init(namespace: CastNamespace.auth)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func sendAuthChallenge() throws {
|
public func sendAuthChallenge() throws {
|
||||||
let message = CastAuthMessage.with {
|
let message = CastAuthMessage.with {
|
||||||
$0.challenge = CastAuthChallenge()
|
$0.challenge = CastAuthChallenge()
|
||||||
}
|
}
|
||||||
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
let request = requestDispatcher.request(withNamespace: namespace,
|
||||||
destinationId: CastConstants.receiver,
|
destinationId: CastConstants.receiver,
|
||||||
payload: try message.serializedData())
|
payload: try message.serializedData())
|
||||||
|
|
|
||||||
|
|
@ -16,32 +16,32 @@ class DeviceConnectionChannel: CastChannel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
super.init(namespace: CastNamespace.connection)
|
super.init(namespace: CastNamespace.connection)
|
||||||
}
|
}
|
||||||
|
|
||||||
func connect() {
|
func connect() {
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
let request = requestDispatcher.request(withNamespace: namespace,
|
||||||
destinationId: CastConstants.receiver,
|
destinationId: CastConstants.receiver,
|
||||||
payload: [CastJSONPayloadKeys.type: CastMessageType.connect.rawValue])
|
payload: [CastJSONPayloadKeys.type: CastMessageType.connect.rawValue])
|
||||||
|
|
||||||
send(request)
|
send(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
func connect(to app: CastApp) {
|
func connect(to app: CastApp) {
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
let request = requestDispatcher.request(withNamespace: namespace,
|
||||||
destinationId: app.transportId,
|
destinationId: app.transportId,
|
||||||
payload: [CastJSONPayloadKeys.type: CastMessageType.connect.rawValue])
|
payload: [CastJSONPayloadKeys.type: CastMessageType.connect.rawValue])
|
||||||
|
|
||||||
send(request)
|
send(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func leave(_ app: CastApp) {
|
public func leave(_ app: CastApp) {
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
let request = requestDispatcher.request(withNamespace: namespace,
|
||||||
destinationId: app.transportId,
|
destinationId: app.transportId,
|
||||||
payload: [CastJSONPayloadKeys.type: CastMessageType.close.rawValue])
|
payload: [CastJSONPayloadKeys.type: CastMessageType.close.rawValue])
|
||||||
|
|
||||||
send(request)
|
send(request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,17 +12,17 @@ class DeviceDiscoveryChannel: CastChannel {
|
||||||
init() {
|
init() {
|
||||||
super.init(namespace: CastNamespace.discovery)
|
super.init(namespace: CastNamespace.discovery)
|
||||||
}
|
}
|
||||||
|
|
||||||
func requestDeviceInfo() {
|
func requestDeviceInfo() {
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
let request = requestDispatcher.request(withNamespace: namespace,
|
||||||
destinationId: CastConstants.receiver,
|
destinationId: CastConstants.receiver,
|
||||||
payload: [CastJSONPayloadKeys.type: CastMessageType.getDeviceInfo.rawValue])
|
payload: [CastJSONPayloadKeys.type: CastMessageType.getDeviceInfo.rawValue])
|
||||||
|
|
||||||
send(request) { result in
|
send(request) { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let json):
|
case .success(let json):
|
||||||
print(json)
|
print(json)
|
||||||
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
print(error)
|
print(error)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ class DeviceSetupChannel: CastChannel {
|
||||||
init() {
|
init() {
|
||||||
super.init(namespace: CastNamespace.setup)
|
super.init(namespace: CastNamespace.setup)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func requestDeviceConfig() {
|
public func requestDeviceConfig() {
|
||||||
let params = [
|
let params = [
|
||||||
"version",
|
"version",
|
||||||
|
|
@ -24,28 +24,28 @@ class DeviceSetupChannel: CastChannel {
|
||||||
"wifi.signal_level",
|
"wifi.signal_level",
|
||||||
"wifi.noise_level"
|
"wifi.noise_level"
|
||||||
]
|
]
|
||||||
|
|
||||||
let payload: [String: Any] = [
|
let payload: [String: Any] = [
|
||||||
CastJSONPayloadKeys.type: CastMessageType.getDeviceConfig.rawValue,
|
CastJSONPayloadKeys.type: CastMessageType.getDeviceConfig.rawValue,
|
||||||
"params": params,
|
"params": params,
|
||||||
"data": [:]
|
"data": [:]
|
||||||
]
|
]
|
||||||
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
let request = requestDispatcher.request(withNamespace: namespace,
|
||||||
destinationId: CastConstants.receiver,
|
destinationId: CastConstants.receiver,
|
||||||
payload: payload)
|
payload: payload)
|
||||||
|
|
||||||
send(request) { result in
|
send(request) { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let json):
|
case .success(let json):
|
||||||
print(json)
|
print(json)
|
||||||
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
print(error)
|
print(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func requestSetDeviceConfig() {
|
public func requestSetDeviceConfig() {
|
||||||
// let data: [String: Any] = [
|
// let data: [String: Any] = [
|
||||||
// "name": "JUNK",
|
// "name": "JUNK",
|
||||||
|
|
@ -53,42 +53,42 @@ class DeviceSetupChannel: CastChannel {
|
||||||
//
|
//
|
||||||
// ]
|
// ]
|
||||||
// ]
|
// ]
|
||||||
|
|
||||||
let payload: [String: Any] = [
|
let payload: [String: Any] = [
|
||||||
CastJSONPayloadKeys.type: CastMessageType.getDeviceConfig.rawValue,
|
CastJSONPayloadKeys.type: CastMessageType.getDeviceConfig.rawValue,
|
||||||
"data": [:]
|
"data": [:]
|
||||||
]
|
]
|
||||||
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
let request = requestDispatcher.request(withNamespace: namespace,
|
||||||
destinationId: CastConstants.receiver,
|
destinationId: CastConstants.receiver,
|
||||||
payload: payload)
|
payload: payload)
|
||||||
|
|
||||||
send(request) { result in
|
send(request) { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let json):
|
case .success(let json):
|
||||||
print(json)
|
print(json)
|
||||||
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
print(error)
|
print(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func requestAppDeviceId(app: CastApp) {
|
public func requestAppDeviceId(app: CastApp) {
|
||||||
let payload: [String: Any] = [
|
let payload: [String: Any] = [
|
||||||
CastJSONPayloadKeys.type: CastMessageType.getAppDeviceId.rawValue,
|
CastJSONPayloadKeys.type: CastMessageType.getAppDeviceId.rawValue,
|
||||||
"data": ["app_id": app.id]
|
"data": ["app_id": app.id]
|
||||||
]
|
]
|
||||||
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
let request = requestDispatcher.request(withNamespace: namespace,
|
||||||
destinationId: CastConstants.receiver,
|
destinationId: CastConstants.receiver,
|
||||||
payload: payload)
|
payload: payload)
|
||||||
|
|
||||||
send(request) { result in
|
send(request) { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let json):
|
case .success(let json):
|
||||||
print(json)
|
print(json)
|
||||||
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
print(error)
|
print(error)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import SwiftyJSON
|
||||||
|
|
||||||
class HeartbeatChannel: CastChannel {
|
class HeartbeatChannel: CastChannel {
|
||||||
private lazy var timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(sendPing), userInfo: nil, repeats: true)
|
private lazy var timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(sendPing), userInfo: nil, repeats: true)
|
||||||
|
|
||||||
private let disconnectTimeout: TimeInterval = 10
|
private let disconnectTimeout: TimeInterval = 10
|
||||||
private var disconnectTimer: Timer? {
|
private var disconnectTimer: Timer? {
|
||||||
willSet {
|
willSet {
|
||||||
|
|
@ -19,11 +19,11 @@ class HeartbeatChannel: CastChannel {
|
||||||
}
|
}
|
||||||
didSet {
|
didSet {
|
||||||
guard let timer = disconnectTimer else { return }
|
guard let timer = disconnectTimer else { return }
|
||||||
|
|
||||||
RunLoop.main.add(timer, forMode: RunLoop.Mode.common)
|
RunLoop.main.add(timer, forMode: RunLoop.Mode.common)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override weak var requestDispatcher: RequestDispatchable! {
|
override weak var requestDispatcher: RequestDispatchable! {
|
||||||
didSet {
|
didSet {
|
||||||
if let _ = requestDispatcher {
|
if let _ = requestDispatcher {
|
||||||
|
|
@ -33,26 +33,26 @@ class HeartbeatChannel: CastChannel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var delegate: HeartbeatChannelDelegate? {
|
private var delegate: HeartbeatChannelDelegate? {
|
||||||
return requestDispatcher as? HeartbeatChannelDelegate
|
return requestDispatcher as? HeartbeatChannelDelegate
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
super.init(namespace: CastNamespace.heartbeat)
|
super.init(namespace: CastNamespace.heartbeat)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func handleResponse(_ json: JSON, sourceId: String) {
|
override func handleResponse(_ json: JSON, sourceId: String) {
|
||||||
delegate?.channelDidConnect(self)
|
delegate?.channelDidConnect(self)
|
||||||
|
|
||||||
guard let rawType = json["type"].string else { return }
|
guard let rawType = json["type"].string else { return }
|
||||||
|
|
||||||
guard let type = CastMessageType(rawValue: rawType) else {
|
guard let type = CastMessageType(rawValue: rawType) else {
|
||||||
print("Unknown type: \(rawType)")
|
print("Unknown type: \(rawType)")
|
||||||
print(json)
|
print(json)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if type == .ping {
|
if type == .ping {
|
||||||
sendPong(to: sourceId)
|
sendPong(to: sourceId)
|
||||||
print("PING from \(sourceId)")
|
print("PING from \(sourceId)")
|
||||||
|
|
@ -69,23 +69,23 @@ class HeartbeatChannel: CastChannel {
|
||||||
_ = timer
|
_ = timer
|
||||||
sendPing()
|
sendPing()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func sendPing() {
|
@objc private func sendPing() {
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
let request = requestDispatcher.request(withNamespace: namespace,
|
||||||
destinationId: CastConstants.transport,
|
destinationId: CastConstants.transport,
|
||||||
payload: [CastJSONPayloadKeys.type: CastMessageType.ping.rawValue])
|
payload: [CastJSONPayloadKeys.type: CastMessageType.ping.rawValue])
|
||||||
|
|
||||||
send(request)
|
send(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func sendPong(to destinationId: String) {
|
private func sendPong(to destinationId: String) {
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
let request = requestDispatcher.request(withNamespace: namespace,
|
||||||
destinationId: destinationId,
|
destinationId: destinationId,
|
||||||
payload: [CastJSONPayloadKeys.type: CastMessageType.pong.rawValue])
|
payload: [CastJSONPayloadKeys.type: CastMessageType.pong.rawValue])
|
||||||
|
|
||||||
send(request)
|
send(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func handleTimeout() {
|
@objc private func handleTimeout() {
|
||||||
delegate?.channelDidTimeout(self)
|
delegate?.channelDidTimeout(self)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,47 +14,47 @@ class MediaControlChannel: CastChannel {
|
||||||
private var delegate: MediaControlChannelDelegate? {
|
private var delegate: MediaControlChannelDelegate? {
|
||||||
return requestDispatcher as? MediaControlChannelDelegate
|
return requestDispatcher as? MediaControlChannelDelegate
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
super.init(namespace: CastNamespace.media)
|
super.init(namespace: CastNamespace.media)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func handleResponse(_ json: JSON, sourceId: String) {
|
override func handleResponse(_ json: JSON, sourceId: String) {
|
||||||
guard let rawType = json["type"].string else { return }
|
guard let rawType = json["type"].string else { return }
|
||||||
|
|
||||||
guard let type = CastMessageType(rawValue: rawType) else {
|
guard let type = CastMessageType(rawValue: rawType) else {
|
||||||
print("Unknown type: \(rawType)")
|
print("Unknown type: \(rawType)")
|
||||||
print(json)
|
print(json)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch type {
|
switch type {
|
||||||
case .mediaStatus:
|
case .mediaStatus:
|
||||||
guard let status = json["status"].array?.first else { return }
|
guard let status = json["status"].array?.first else { return }
|
||||||
|
|
||||||
delegate?.channel(self, didReceive: CastMediaStatus(json: status))
|
delegate?.channel(self, didReceive: CastMediaStatus(json: status))
|
||||||
|
|
||||||
default:
|
default:
|
||||||
print(rawType)
|
print(rawType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func requestMediaStatus(for app: CastApp, completion: ((Result<CastMediaStatus, CastError>) -> Void)? = nil) {
|
public func requestMediaStatus(for app: CastApp, completion: ((Result<CastMediaStatus, CastError>) -> Void)? = nil) {
|
||||||
let payload: [String: Any] = [
|
let payload: [String: Any] = [
|
||||||
CastJSONPayloadKeys.type: CastMessageType.statusRequest.rawValue,
|
CastJSONPayloadKeys.type: CastMessageType.statusRequest.rawValue,
|
||||||
CastJSONPayloadKeys.sessionId: app.sessionId
|
CastJSONPayloadKeys.sessionId: app.sessionId
|
||||||
]
|
]
|
||||||
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
let request = requestDispatcher.request(withNamespace: namespace,
|
||||||
destinationId: app.transportId,
|
destinationId: app.transportId,
|
||||||
payload: payload)
|
payload: payload)
|
||||||
|
|
||||||
if let completion = completion {
|
if let completion = completion {
|
||||||
send(request) { result in
|
send(request) { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let json):
|
case .success(let json):
|
||||||
completion(Result(value: CastMediaStatus(json: json)))
|
completion(Result(value: CastMediaStatus(json: json)))
|
||||||
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
completion(Result(error: error))
|
completion(Result(error: error))
|
||||||
}
|
}
|
||||||
|
|
@ -63,19 +63,19 @@ class MediaControlChannel: CastChannel {
|
||||||
send(request)
|
send(request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func sendPause(for app: CastApp, mediaSessionId: Int) {
|
public func sendPause(for app: CastApp, mediaSessionId: Int) {
|
||||||
send(.pause, for: app, mediaSessionId: mediaSessionId)
|
send(.pause, for: app, mediaSessionId: mediaSessionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func sendPlay(for app: CastApp, mediaSessionId: Int) {
|
public func sendPlay(for app: CastApp, mediaSessionId: Int) {
|
||||||
send(.play, for: app, mediaSessionId: mediaSessionId)
|
send(.play, for: app, mediaSessionId: mediaSessionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func sendStop(for app: CastApp, mediaSessionId: Int) {
|
public func sendStop(for app: CastApp, mediaSessionId: Int) {
|
||||||
send(.stop, for: app, mediaSessionId: mediaSessionId)
|
send(.stop, for: app, mediaSessionId: mediaSessionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func sendSeek(to currentTime: Float, for app: CastApp, mediaSessionId: Int) {
|
public func sendSeek(to currentTime: Float, for app: CastApp, mediaSessionId: Int) {
|
||||||
let payload: [String: Any] = [
|
let payload: [String: Any] = [
|
||||||
CastJSONPayloadKeys.type: CastMessageType.seek.rawValue,
|
CastJSONPayloadKeys.type: CastMessageType.seek.rawValue,
|
||||||
|
|
@ -83,32 +83,32 @@ class MediaControlChannel: CastChannel {
|
||||||
CastJSONPayloadKeys.currentTime: currentTime,
|
CastJSONPayloadKeys.currentTime: currentTime,
|
||||||
CastJSONPayloadKeys.mediaSessionId: mediaSessionId
|
CastJSONPayloadKeys.mediaSessionId: mediaSessionId
|
||||||
]
|
]
|
||||||
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
let request = requestDispatcher.request(withNamespace: namespace,
|
||||||
destinationId: app.transportId,
|
destinationId: app.transportId,
|
||||||
payload: payload)
|
payload: payload)
|
||||||
|
|
||||||
send(request)
|
send(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func send(_ message: CastMessageType, for app: CastApp, mediaSessionId: Int) {
|
private func send(_ message: CastMessageType, for app: CastApp, mediaSessionId: Int) {
|
||||||
let payload: [String: Any] = [
|
let payload: [String: Any] = [
|
||||||
CastJSONPayloadKeys.type: message.rawValue,
|
CastJSONPayloadKeys.type: message.rawValue,
|
||||||
CastJSONPayloadKeys.mediaSessionId: mediaSessionId
|
CastJSONPayloadKeys.mediaSessionId: mediaSessionId
|
||||||
]
|
]
|
||||||
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
let request = requestDispatcher.request(withNamespace: namespace,
|
||||||
destinationId: app.transportId,
|
destinationId: app.transportId,
|
||||||
payload: payload)
|
payload: payload)
|
||||||
|
|
||||||
send(request)
|
send(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func load(media: CastMedia, with app: CastApp, completion: @escaping (Result<CastMediaStatus, CastError>) -> Void) {
|
public func load(media: CastMedia, with app: CastApp, completion: @escaping (Result<CastMediaStatus, CastError>) -> Void) {
|
||||||
var payload = media.dict
|
var payload = media.dict
|
||||||
payload[CastJSONPayloadKeys.type] = CastMessageType.load.rawValue
|
payload[CastJSONPayloadKeys.type] = CastMessageType.load.rawValue
|
||||||
payload[CastJSONPayloadKeys.sessionId] = app.sessionId
|
payload[CastJSONPayloadKeys.sessionId] = app.sessionId
|
||||||
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
let request = requestDispatcher.request(withNamespace: namespace,
|
||||||
destinationId: app.transportId,
|
destinationId: app.transportId,
|
||||||
payload: payload)
|
payload: payload)
|
||||||
|
|
@ -117,9 +117,9 @@ class MediaControlChannel: CastChannel {
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let json):
|
case .success(let json):
|
||||||
guard let status = json["status"].array?.first else { return }
|
guard let status = json["status"].array?.first else { return }
|
||||||
|
|
||||||
completion(Result(value: CastMediaStatus(json: status)))
|
completion(Result(value: CastMediaStatus(json: status)))
|
||||||
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
completion(Result(error: CastError.load(error.localizedDescription)))
|
completion(Result(error: CastError.load(error.localizedDescription)))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,15 +18,15 @@ class MultizoneControlChannel: CastChannel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var delegate: MultizoneControlChannelDelegate? {
|
private var delegate: MultizoneControlChannelDelegate? {
|
||||||
return requestDispatcher as? MultizoneControlChannelDelegate
|
return requestDispatcher as? MultizoneControlChannelDelegate
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
super.init(namespace: CastNamespace.multizone)
|
super.init(namespace: CastNamespace.multizone)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func handleResponse(_ json: JSON, sourceId: String) {
|
override func handleResponse(_ json: JSON, sourceId: String) {
|
||||||
guard let rawType = json["type"].string else { return }
|
guard let rawType = json["type"].string else { return }
|
||||||
|
|
||||||
|
|
@ -35,40 +35,40 @@ class MultizoneControlChannel: CastChannel {
|
||||||
print(json)
|
print(json)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch type {
|
switch type {
|
||||||
case .multizoneStatus:
|
case .multizoneStatus:
|
||||||
delegate?.channel(self, didReceive: CastMultizoneStatus(json: json))
|
delegate?.channel(self, didReceive: CastMultizoneStatus(json: json))
|
||||||
|
|
||||||
case .deviceAdded:
|
case .deviceAdded:
|
||||||
let device = CastMultizoneDevice(json: json[CastJSONPayloadKeys.device])
|
let device = CastMultizoneDevice(json: json[CastJSONPayloadKeys.device])
|
||||||
delegate?.channel(self, added: device)
|
delegate?.channel(self, added: device)
|
||||||
|
|
||||||
case .deviceUpdated:
|
case .deviceUpdated:
|
||||||
let device = CastMultizoneDevice(json: json[CastJSONPayloadKeys.device])
|
let device = CastMultizoneDevice(json: json[CastJSONPayloadKeys.device])
|
||||||
delegate?.channel(self, updated: device)
|
delegate?.channel(self, updated: device)
|
||||||
|
|
||||||
case .deviceRemoved:
|
case .deviceRemoved:
|
||||||
guard let deviceId = json[CastJSONPayloadKeys.deviceId].string else { return }
|
guard let deviceId = json[CastJSONPayloadKeys.deviceId].string else { return }
|
||||||
delegate?.channel(self, removed: deviceId)
|
delegate?.channel(self, removed: deviceId)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
print(rawType)
|
print(rawType)
|
||||||
print(json)
|
print(json)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func requestStatus(completion: ((Result<CastStatus, CastError>) -> Void)? = nil) {
|
public func requestStatus(completion: ((Result<CastStatus, CastError>) -> Void)? = nil) {
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
let request = requestDispatcher.request(withNamespace: namespace,
|
||||||
destinationId: CastConstants.receiver,
|
destinationId: CastConstants.receiver,
|
||||||
payload: [CastJSONPayloadKeys.type: CastMessageType.statusRequest.rawValue])
|
payload: [CastJSONPayloadKeys.type: CastMessageType.statusRequest.rawValue])
|
||||||
|
|
||||||
if let completion = completion {
|
if let completion = completion {
|
||||||
send(request) { result in
|
send(request) { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let json):
|
case .success(let json):
|
||||||
completion(Result(value: CastStatus(json: json)))
|
completion(Result(value: CastStatus(json: json)))
|
||||||
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
completion(Result(error: error))
|
completion(Result(error: error))
|
||||||
}
|
}
|
||||||
|
|
@ -77,32 +77,32 @@ class MultizoneControlChannel: CastChannel {
|
||||||
send(request)
|
send(request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setVolume(_ volume: Float, for device: CastMultizoneDevice) {
|
public func setVolume(_ volume: Float, for device: CastMultizoneDevice) {
|
||||||
let payload: [String: Any] = [
|
let payload: [String: Any] = [
|
||||||
CastJSONPayloadKeys.type: CastMessageType.setDeviceVolume.rawValue,
|
CastJSONPayloadKeys.type: CastMessageType.setDeviceVolume.rawValue,
|
||||||
CastJSONPayloadKeys.volume: [CastJSONPayloadKeys.level: volume],
|
CastJSONPayloadKeys.volume: [CastJSONPayloadKeys.level: volume],
|
||||||
CastJSONPayloadKeys.deviceId: device.id
|
CastJSONPayloadKeys.deviceId: device.id
|
||||||
]
|
]
|
||||||
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
let request = requestDispatcher.request(withNamespace: namespace,
|
||||||
destinationId: CastConstants.receiver,
|
destinationId: CastConstants.receiver,
|
||||||
payload: payload)
|
payload: payload)
|
||||||
|
|
||||||
send(request)
|
send(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setMuted(_ isMuted: Bool, for device: CastMultizoneDevice) {
|
public func setMuted(_ isMuted: Bool, for device: CastMultizoneDevice) {
|
||||||
let payload: [String: Any] = [
|
let payload: [String: Any] = [
|
||||||
CastJSONPayloadKeys.type: CastMessageType.setVolume.rawValue,
|
CastJSONPayloadKeys.type: CastMessageType.setVolume.rawValue,
|
||||||
CastJSONPayloadKeys.volume: [CastJSONPayloadKeys.muted: isMuted],
|
CastJSONPayloadKeys.volume: [CastJSONPayloadKeys.muted: isMuted],
|
||||||
CastJSONPayloadKeys.deviceId: device.id
|
CastJSONPayloadKeys.deviceId: device.id
|
||||||
]
|
]
|
||||||
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
let request = requestDispatcher.request(withNamespace: namespace,
|
||||||
destinationId: CastConstants.receiver,
|
destinationId: CastConstants.receiver,
|
||||||
payload: payload)
|
payload: payload)
|
||||||
|
|
||||||
send(request)
|
send(request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,43 +18,43 @@ class ReceiverControlChannel: CastChannel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var delegate: ReceiverControlChannelDelegate? {
|
private var delegate: ReceiverControlChannelDelegate? {
|
||||||
return requestDispatcher as? ReceiverControlChannelDelegate
|
return requestDispatcher as? ReceiverControlChannelDelegate
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
super.init(namespace: CastNamespace.receiver)
|
super.init(namespace: CastNamespace.receiver)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func handleResponse(_ json: JSON, sourceId: String) {
|
override func handleResponse(_ json: JSON, sourceId: String) {
|
||||||
guard let rawType = json["type"].string else { return }
|
guard let rawType = json["type"].string else { return }
|
||||||
|
|
||||||
guard let type = CastMessageType(rawValue: rawType) else {
|
guard let type = CastMessageType(rawValue: rawType) else {
|
||||||
print("Unknown type: \(rawType)")
|
print("Unknown type: \(rawType)")
|
||||||
print(json)
|
print(json)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch type {
|
switch type {
|
||||||
case .status:
|
case .status:
|
||||||
delegate?.channel(self, didReceive: CastStatus(json: json))
|
delegate?.channel(self, didReceive: CastStatus(json: json))
|
||||||
|
|
||||||
default:
|
default:
|
||||||
print(rawType)
|
print(rawType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getAppAvailability(apps: [CastApp], completion: @escaping (Result<AppAvailability, CastError>) -> Void) {
|
public func getAppAvailability(apps: [CastApp], completion: @escaping (Result<AppAvailability, CastError>) -> Void) {
|
||||||
let payload: [String: Any] = [
|
let payload: [String: Any] = [
|
||||||
CastJSONPayloadKeys.type: CastMessageType.availableApps.rawValue,
|
CastJSONPayloadKeys.type: CastMessageType.availableApps.rawValue,
|
||||||
CastJSONPayloadKeys.appId: apps.map { $0.id }
|
CastJSONPayloadKeys.appId: apps.map { $0.id }
|
||||||
]
|
]
|
||||||
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
let request = requestDispatcher.request(withNamespace: namespace,
|
||||||
destinationId: CastConstants.receiver,
|
destinationId: CastConstants.receiver,
|
||||||
payload: payload)
|
payload: payload)
|
||||||
|
|
||||||
send(request) { result in
|
send(request) { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let json):
|
case .success(let json):
|
||||||
|
|
@ -64,18 +64,18 @@ class ReceiverControlChannel: CastChannel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func requestStatus(completion: ((Result<CastStatus, CastError>) -> Void)? = nil) {
|
public func requestStatus(completion: ((Result<CastStatus, CastError>) -> Void)? = nil) {
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
let request = requestDispatcher.request(withNamespace: namespace,
|
||||||
destinationId: CastConstants.receiver,
|
destinationId: CastConstants.receiver,
|
||||||
payload: [CastJSONPayloadKeys.type: CastMessageType.statusRequest.rawValue])
|
payload: [CastJSONPayloadKeys.type: CastMessageType.statusRequest.rawValue])
|
||||||
|
|
||||||
if let completion = completion {
|
if let completion = completion {
|
||||||
send(request) { result in
|
send(request) { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let json):
|
case .success(let json):
|
||||||
completion(Result(value: CastStatus(json: json)))
|
completion(Result(value: CastStatus(json: json)))
|
||||||
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
completion(Result(error: error))
|
completion(Result(error: error))
|
||||||
}
|
}
|
||||||
|
|
@ -84,17 +84,17 @@ class ReceiverControlChannel: CastChannel {
|
||||||
send(request)
|
send(request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func launch(appId: String, completion: @escaping (Result<CastApp, CastError>) -> Void) {
|
func launch(appId: String, completion: @escaping (Result<CastApp, CastError>) -> Void) {
|
||||||
let payload: [String: Any] = [
|
let payload: [String: Any] = [
|
||||||
CastJSONPayloadKeys.type: CastMessageType.launch.rawValue,
|
CastJSONPayloadKeys.type: CastMessageType.launch.rawValue,
|
||||||
CastJSONPayloadKeys.appId: appId
|
CastJSONPayloadKeys.appId: appId
|
||||||
]
|
]
|
||||||
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
let request = requestDispatcher.request(withNamespace: namespace,
|
||||||
destinationId: CastConstants.receiver,
|
destinationId: CastConstants.receiver,
|
||||||
payload: payload)
|
payload: payload)
|
||||||
|
|
||||||
send(request) { result in
|
send(request) { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let json):
|
case .success(let json):
|
||||||
|
|
@ -102,52 +102,52 @@ class ReceiverControlChannel: CastChannel {
|
||||||
completion(Result(error: CastError.launch("Unable to get launched app instance")))
|
completion(Result(error: CastError.launch("Unable to get launched app instance")))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
completion(Result(value: app))
|
completion(Result(value: app))
|
||||||
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
completion(Result(error: error))
|
completion(Result(error: error))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func stop(app: CastApp) {
|
public func stop(app: CastApp) {
|
||||||
let payload: [String: Any] = [
|
let payload: [String: Any] = [
|
||||||
CastJSONPayloadKeys.type: CastMessageType.stop.rawValue,
|
CastJSONPayloadKeys.type: CastMessageType.stop.rawValue,
|
||||||
CastJSONPayloadKeys.sessionId: app.sessionId
|
CastJSONPayloadKeys.sessionId: app.sessionId
|
||||||
]
|
]
|
||||||
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
let request = requestDispatcher.request(withNamespace: namespace,
|
||||||
destinationId: CastConstants.receiver,
|
destinationId: CastConstants.receiver,
|
||||||
payload: payload)
|
payload: payload)
|
||||||
|
|
||||||
send(request)
|
send(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setVolume(_ volume: Float) {
|
public func setVolume(_ volume: Float) {
|
||||||
let payload: [String: Any] = [
|
let payload: [String: Any] = [
|
||||||
CastJSONPayloadKeys.type: CastMessageType.setVolume.rawValue,
|
CastJSONPayloadKeys.type: CastMessageType.setVolume.rawValue,
|
||||||
CastJSONPayloadKeys.volume: [CastJSONPayloadKeys.level: volume]
|
CastJSONPayloadKeys.volume: [CastJSONPayloadKeys.level: volume]
|
||||||
]
|
]
|
||||||
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
let request = requestDispatcher.request(withNamespace: namespace,
|
||||||
destinationId: CastConstants.receiver,
|
destinationId: CastConstants.receiver,
|
||||||
payload: payload)
|
payload: payload)
|
||||||
|
|
||||||
send(request)
|
send(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setMuted(_ isMuted: Bool) {
|
public func setMuted(_ isMuted: Bool) {
|
||||||
let payload: [String: Any] = [
|
let payload: [String: Any] = [
|
||||||
CastJSONPayloadKeys.type: CastMessageType.setVolume.rawValue,
|
CastJSONPayloadKeys.type: CastMessageType.setVolume.rawValue,
|
||||||
CastJSONPayloadKeys.volume: [CastJSONPayloadKeys.muted: isMuted]
|
CastJSONPayloadKeys.volume: [CastJSONPayloadKeys.muted: isMuted]
|
||||||
]
|
]
|
||||||
|
|
||||||
let request = requestDispatcher.request(withNamespace: namespace,
|
let request = requestDispatcher.request(withNamespace: namespace,
|
||||||
destinationId: CastConstants.receiver,
|
destinationId: CastConstants.receiver,
|
||||||
payload: payload)
|
payload: payload)
|
||||||
|
|
||||||
send(request)
|
send(request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,10 @@ import Foundation
|
||||||
|
|
||||||
protocol RequestDispatchable: AnyObject {
|
protocol RequestDispatchable: AnyObject {
|
||||||
func nextRequestId() -> Int
|
func nextRequestId() -> Int
|
||||||
|
|
||||||
func request(withNamespace namespace: String, destinationId: String, payload: [String: Any]) -> CastRequest
|
func request(withNamespace namespace: String, destinationId: String, payload: [String: Any]) -> CastRequest
|
||||||
func request(withNamespace namespace: String, destinationId: String, payload: Data) -> CastRequest
|
func request(withNamespace namespace: String, destinationId: String, payload: Data) -> CastRequest
|
||||||
|
|
||||||
func send(_ request: CastRequest, response: CastResponseHandler?)
|
func send(_ request: CastRequest, response: CastResponseHandler?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -22,13 +22,13 @@ extension RequestDispatchable {
|
||||||
var payload = payload
|
var payload = payload
|
||||||
let requestId = nextRequestId()
|
let requestId = nextRequestId()
|
||||||
payload[CastJSONPayloadKeys.requestId] = requestId
|
payload[CastJSONPayloadKeys.requestId] = requestId
|
||||||
|
|
||||||
return CastRequest(id: requestId,
|
return CastRequest(id: requestId,
|
||||||
namespace: namespace,
|
namespace: namespace,
|
||||||
destinationId: destinationId,
|
destinationId: destinationId,
|
||||||
payload: payload)
|
payload: payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
func request(withNamespace namespace: String, destinationId: String, payload: Data) -> CastRequest {
|
func request(withNamespace namespace: String, destinationId: String, payload: Data) -> CastRequest {
|
||||||
return CastRequest(id: nextRequestId(),
|
return CastRequest(id: nextRequestId(),
|
||||||
namespace: namespace,
|
namespace: namespace,
|
||||||
|
|
|
||||||
|
|
@ -263,8 +263,6 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
|
||||||
override func remoteControlReceived(with event: UIEvent?) {
|
override func remoteControlReceived(with event: UIEvent?) {
|
||||||
dump(event)
|
dump(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// MARK: viewDidLoad
|
// MARK: viewDidLoad
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
|
|
@ -273,9 +271,9 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
|
||||||
} else {
|
} else {
|
||||||
titleLabel.text = "S\(String(manifest.parentIndexNumber ?? 0)):E\(String(manifest.indexNumber ?? 0)) “\(manifest.name ?? "")”"
|
titleLabel.text = "S\(String(manifest.parentIndexNumber ?? 0)):E\(String(manifest.indexNumber ?? 0)) “\(manifest.name ?? "")”"
|
||||||
}
|
}
|
||||||
|
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
if(!UIDevice.current.orientation.isLandscape) {
|
if !UIDevice.current.orientation.isLandscape {
|
||||||
let value = UIInterfaceOrientation.landscapeLeft.rawValue
|
let value = UIInterfaceOrientation.landscapeLeft.rawValue
|
||||||
UIDevice.current.setValue(value, forKey: "orientation")
|
UIDevice.current.setValue(value, forKey: "orientation")
|
||||||
}
|
}
|
||||||
|
|
@ -415,9 +413,9 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
|
||||||
selectedAudioTrack = audioTrackArray[0].id
|
selectedAudioTrack = audioTrackArray[0].id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
print("gotToEnd")
|
print("gotToEnd")
|
||||||
|
|
||||||
self.sendPlayReport()
|
self.sendPlayReport()
|
||||||
playbackItem = item
|
playbackItem = item
|
||||||
}
|
}
|
||||||
|
|
@ -436,8 +434,8 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
|
||||||
|
|
||||||
// Pause and load captions into memory.
|
// Pause and load captions into memory.
|
||||||
mediaPlayer.pause()
|
mediaPlayer.pause()
|
||||||
|
|
||||||
var shouldHaveSubtitleTracks = 0;
|
var shouldHaveSubtitleTracks = 0
|
||||||
subtitleTrackArray.forEach { sub in
|
subtitleTrackArray.forEach { sub in
|
||||||
if sub.id != -1 && sub.delivery == .external && sub.codec != "subrip" {
|
if sub.id != -1 && sub.delivery == .external && sub.codec != "subrip" {
|
||||||
shouldHaveSubtitleTracks = shouldHaveSubtitleTracks + 1
|
shouldHaveSubtitleTracks = shouldHaveSubtitleTracks + 1
|
||||||
|
|
@ -579,9 +577,9 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
|
||||||
|
|
||||||
func sendPlayReport() {
|
func sendPlayReport() {
|
||||||
startTime = Int(Date().timeIntervalSince1970) * 10000000
|
startTime = Int(Date().timeIntervalSince1970) * 10000000
|
||||||
|
|
||||||
print("sending play report!")
|
print("sending play report!")
|
||||||
|
|
||||||
let startInfo = PlaybackStartInfo(canSeek: true, item: manifest, itemId: manifest.id, sessionId: playSessionId, mediaSourceId: manifest.id, audioStreamIndex: Int(selectedAudioTrack), subtitleStreamIndex: Int(selectedCaptionTrack), isPaused: false, isMuted: false, positionTicks: manifest.userData?.playbackPositionTicks, playbackStartTimeTicks: Int64(startTime), volumeLevel: 100, brightness: 100, aspectRatio: nil, playMethod: playbackItem.videoType, liveStreamId: nil, playSessionId: playSessionId, repeatMode: .repeatNone, nowPlayingQueue: [], playlistItemId: "playlistItem0")
|
let startInfo = PlaybackStartInfo(canSeek: true, item: manifest, itemId: manifest.id, sessionId: playSessionId, mediaSourceId: manifest.id, audioStreamIndex: Int(selectedAudioTrack), subtitleStreamIndex: Int(selectedCaptionTrack), isPaused: false, isMuted: false, positionTicks: manifest.userData?.playbackPositionTicks, playbackStartTimeTicks: Int64(startTime), volumeLevel: 100, brightness: 100, aspectRatio: nil, playMethod: playbackItem.videoType, liveStreamId: nil, playSessionId: playSessionId, repeatMode: .repeatNone, nowPlayingQueue: [], playlistItemId: "playlistItem0")
|
||||||
|
|
||||||
PlaystateAPI.reportPlaybackStart(playbackStartInfo: startInfo)
|
PlaystateAPI.reportPlaybackStart(playbackStartInfo: startInfo)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue