HaishinKit.swift/Sources/RTMP/RTMPSharedObject.swift

232 lines
7.5 KiB
Swift

import Foundation
struct RTMPSharedObjectEvent {
enum `Type`:UInt8 {
case use = 1
case release = 2
case requestChange = 3
case change = 4
case success = 5
case sendMessage = 6
case status = 7
case clear = 8
case remove = 9
case requestRemove = 10
case useSuccess = 11
case unknown = 255
}
var type:Type = .unknown
var name:String? = nil
var data:Any? = nil
init(type:Type) {
self.type = type
}
init(type:Type, name:String, data:Any?) {
self.type = type
self.name = name
self.data = data
}
init?(serializer:inout AMFSerializer) throws {
guard let byte:UInt8 = try? serializer.readUInt8(), let type:Type = Type(rawValue: byte) else {
return nil
}
self.type = type
let length:Int = Int(try serializer.readUInt32())
let position:Int = serializer.position
if (0 < length) {
name = try serializer.readUTF8()
switch type {
case .status:
data = try serializer.readUTF8()
default:
if (serializer.position - position < length) {
data = try serializer.deserialize()
}
}
}
}
func serialize(_ serializer:inout AMFSerializer) {
serializer.writeUInt8(type.rawValue)
guard let name:String = name else {
serializer.writeUInt32(0)
return
}
let position:Int = serializer.position
serializer
.writeUInt32(0)
.writeUInt16(UInt16(name.utf8.count))
.writeUTF8Bytes(name)
.serialize(data)
let size:Int = serializer.position - position
serializer.position = position
serializer.writeUInt32(UInt32(size) - 4)
serializer.position = serializer.length
}
}
extension RTMPSharedObjectEvent: CustomStringConvertible {
// MARK: CustomStringConvertible
var description:String {
return Mirror(reflecting: self).description
}
}
// MARK: -
/**
flash.net.SharedObject for Swift
*/
open class RTMPSharedObject: EventDispatcher {
static fileprivate var remoteSharedObjects:[String: RTMPSharedObject] = [:]
static open func getRemote(withName: String, remotePath: String, persistence: Bool) -> RTMPSharedObject {
let key:String = remotePath + "/" + withName + "?persistence=" + persistence.description
objc_sync_enter(remoteSharedObjects)
if (remoteSharedObjects[key] == nil) {
remoteSharedObjects[key] = RTMPSharedObject(name: withName, path: remotePath, persistence: persistence)
}
objc_sync_exit(remoteSharedObjects)
return remoteSharedObjects[key]!
}
var name:String
var path:String
var timestamp:TimeInterval = 0
var persistence:Bool
var currentVersion:UInt32 = 0
open fileprivate(set) var objectEncoding:UInt8 = RTMPConnection.defaultObjectEncoding
open fileprivate(set) var data:[String: Any?] = [:]
fileprivate var succeeded:Bool = false {
didSet {
guard succeeded else {
return
}
for (key, value) in data {
setProperty(key, value)
}
}
}
override open var description:String {
return data.description
}
fileprivate var rtmpConnection:RTMPConnection? = nil
init(name:String, path:String, persistence:Bool) {
self.name = name
self.path = path
self.persistence = persistence
super.init()
}
open func setProperty(_ name:String, _ value:Any?) {
data[name] = value
guard let rtmpConnection:RTMPConnection = rtmpConnection , succeeded else {
return
}
rtmpConnection.socket.doOutput(chunk: createChunk([
RTMPSharedObjectEvent(type: .requestChange, name: name, data: value)
]))
}
open func connect(_ rtmpConnection:RTMPConnection) {
if (self.rtmpConnection != nil) {
close()
}
self.rtmpConnection = rtmpConnection
rtmpConnection.addEventListener(Event.RTMP_STATUS, selector: #selector(RTMPSharedObject.rtmpStatusHandler(_:)), observer: self)
if (rtmpConnection.connected) {
timestamp = rtmpConnection.socket.timestamp
rtmpConnection.socket.doOutput(chunk: createChunk([RTMPSharedObjectEvent(type: .use)]))
}
}
open func clear() {
data.removeAll(keepingCapacity: false)
rtmpConnection?.socket.doOutput(chunk: createChunk([RTMPSharedObjectEvent(type: .clear)]))
}
open func close() {
data.removeAll(keepingCapacity: false)
rtmpConnection?.removeEventListener(Event.RTMP_STATUS, selector: #selector(RTMPSharedObject.rtmpStatusHandler(_:)), observer: self)
rtmpConnection?.socket.doOutput(chunk: createChunk([RTMPSharedObjectEvent(type: .release)]))
rtmpConnection = nil
}
final func on(message:RTMPSharedObjectMessage) {
currentVersion = message.currentVersion
var changeList:[[String: Any?]] = []
for event in message.events {
var change:[String: Any?] = [
"code": "",
"name": event.name,
"oldValue": nil
]
switch event.type {
case .change:
change["code"] = "change"
change["oldValue"] = data.removeValue(forKey: event.name!)
data[event.name!] = event.data
case .success:
change["code"] = "success"
case .status:
change["code"] = "reject"
change["oldValue"] = data.removeValue(forKey: event.name!)
case .clear:
data.removeAll(keepingCapacity: false)
change["code"] = "clear"
case .remove:
change["code"] = "delete"
case .useSuccess:
succeeded = true
continue
default:
continue
}
changeList.append(change)
}
dispatch(Event.SYNC, bubbles: false, data: changeList)
}
func createChunk(_ events:[RTMPSharedObjectEvent]) -> RTMPChunk {
let now:Date = Date()
let timestamp:TimeInterval = now.timeIntervalSince1970 - self.timestamp
self.timestamp = now.timeIntervalSince1970
defer {
currentVersion += 1
}
return RTMPChunk(
type: succeeded ? .one : .zero,
streamId: RTMPChunk.StreamID.command.rawValue,
message: RTMPSharedObjectMessage(
timestamp: UInt32(timestamp * 1000),
objectEncoding: objectEncoding,
sharedObjectName: name,
currentVersion: succeeded ? 0 : currentVersion,
flags: [persistence ? 0x01 : 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
events: events
)
)
}
func rtmpStatusHandler(_ notification:Notification) {
let e:Event = Event.from(notification)
if let data:ASObject = e.data as? ASObject, let code:String = data["code"] as? String {
switch code {
case RTMPConnection.Code.connectSuccess.rawValue:
timestamp = rtmpConnection!.socket.timestamp
rtmpConnection!.socket.doOutput(chunk: createChunk([RTMPSharedObjectEvent(type: .use)]))
default:
break
}
}
}
}