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