BrashUp Replay Kit Live #81

This commit is contained in:
shogo4405 2016-09-15 22:22:05 +09:00
parent 7eacded159
commit bc708b6dae
21 changed files with 516 additions and 217 deletions

View File

@ -30,9 +30,9 @@
<key>NSExtensionPointIdentifier</key> <key>NSExtensionPointIdentifier</key>
<string>com.apple.broadcast-services</string> <string>com.apple.broadcast-services</string>
<key>NSExtensionPrincipalClass</key> <key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).SampleHandler</string> <string>$(PRODUCT_MODULE_NAME).MovieClipHandler</string>
<key>RPBroadcastProcessMode</key> <key>RPBroadcastProcessMode</key>
<string>RPBroadcastProcessModeSampleBuffer</string> <string>RPBroadcastProcessModeMP4Clip</string>
</dict> </dict>
</dict> </dict>
</plist> </plist>

View File

@ -0,0 +1,5 @@
import lf
import Foundation
class MovieClipHandler: RTMPMP4ClipHandler {
}

View File

@ -22,6 +22,10 @@ class BroadcastViewController: UIViewController {
] ]
let broadcastConfiguration:RPBroadcastConfiguration = RPBroadcastConfiguration() let broadcastConfiguration:RPBroadcastConfiguration = RPBroadcastConfiguration()
broadcastConfiguration.clipDuration = 2
broadcastConfiguration.videoCompressionProperties = [
AVVideoProfileLevelKey: AVVideoProfileLevelH264BaselineAutoLevel as NSSecureCoding & NSObjectProtocol,
]
self.extensionContext?.completeRequest( self.extensionContext?.completeRequest(
withBroadcast: broadcastURL, withBroadcast: broadcastURL,

View File

@ -158,12 +158,14 @@ final class LiveViewController: NSViewController {
httpStream.attach(camera: nil) httpStream.attach(camera: nil)
rtmpStream.attach(audio: DeviceUtil.device(withLocalizedName: audioPopUpButton.itemTitles[audioPopUpButton.indexOfSelectedItem], mediaType: AVMediaTypeAudio)) rtmpStream.attach(audio: DeviceUtil.device(withLocalizedName: audioPopUpButton.itemTitles[audioPopUpButton.indexOfSelectedItem], mediaType: AVMediaTypeAudio))
rtmpStream.attach(camera: DeviceUtil.device(withLocalizedName: cameraPopUpButton.itemTitles[cameraPopUpButton.indexOfSelectedItem], mediaType: AVMediaTypeVideo)) rtmpStream.attach(camera: DeviceUtil.device(withLocalizedName: cameraPopUpButton.itemTitles[cameraPopUpButton.indexOfSelectedItem], mediaType: AVMediaTypeVideo))
lfView.attach(stream: rtmpStream)
urlField.stringValue = LiveViewController.defaultURL urlField.stringValue = LiveViewController.defaultURL
case 1: case 1:
rtmpStream.attach(audio: nil) rtmpStream.attach(audio: nil)
rtmpStream.attach(camera: nil) rtmpStream.attach(camera: nil)
httpStream.attach(audio: DeviceUtil.device(withLocalizedName: audioPopUpButton.itemTitles[audioPopUpButton.indexOfSelectedItem], mediaType: AVMediaTypeAudio)) httpStream.attach(audio: DeviceUtil.device(withLocalizedName: audioPopUpButton.itemTitles[audioPopUpButton.indexOfSelectedItem], mediaType: AVMediaTypeAudio))
httpStream.attach(camera: DeviceUtil.device(withLocalizedName: cameraPopUpButton.itemTitles[cameraPopUpButton.indexOfSelectedItem], mediaType: AVMediaTypeVideo)) httpStream.attach(camera: DeviceUtil.device(withLocalizedName: cameraPopUpButton.itemTitles[cameraPopUpButton.indexOfSelectedItem], mediaType: AVMediaTypeVideo))
lfView.attach(stream: httpStream)
urlField.stringValue = "http://{ipAddress}:8080/hello/playlist.m3u8" urlField.stringValue = "http://{ipAddress}:8080/hello/playlist.m3u8"
default: default:
break break

View File

@ -3,20 +3,39 @@ import Foundation
@available(iOS 10.0, *) @available(iOS 10.0, *)
public class RTMPBroadcaster: RTMPConnection { public class RTMPBroadcaster: RTMPConnection {
private var stream:RTMPStream? static let `default`:RTMPBroadcaster = RTMPBroadcaster()
func process(sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) { private var stream:RTMPStream!
guard let stream:RTMPStream = stream, stream.readyState == .publishing else { private var connecting:Bool = false
private let lockQueue:DispatchQueue = DispatchQueue(label: "com.github.shogo4405.lf.RTMPBroadcaster.lock")
override init() {
super.init()
stream = RTMPStream(connection: self)
}
override public func connect(_ command: String, arguments: Any?...) {
lockQueue.sync {
if (connecting) {
return
}
connecting = true
super.connect(command, arguments: arguments)
}
}
override public func close() {
connecting = false
super.close()
}
func processMP4Clip(mp4ClipURL: URL?, completionHandler: MP4Sampler.Handler) {
guard let url:URL = mp4ClipURL else {
completionHandler()
return return
} }
switch sampleBufferType { stream.append(url: url)
case .video: completionHandler()
stream.mixer.videoIO.captureOutput(nil, didOutputSampleBuffer: sampleBuffer, from: nil)
case .audioApp:
break
case .audioMic:
stream.mixer.audioIO.captureOutput(nil, didOutputSampleBuffer: sampleBuffer, from: nil)
}
} }
override func on(status:Notification) { override func on(status:Notification) {
@ -27,8 +46,7 @@ public class RTMPBroadcaster: RTMPConnection {
} }
switch code { switch code {
case RTMPConnection.Code.connectSuccess.rawValue: case RTMPConnection.Code.connectSuccess.rawValue:
stream = RTMPStream(connection: self) stream.publish("live")
stream?.publish("live")
default: default:
break break
} }
@ -36,34 +54,21 @@ public class RTMPBroadcaster: RTMPConnection {
} }
@available(iOS 10.0, *) @available(iOS 10.0, *)
open class RTMPSampleHandler: RPBroadcastSampleHandler { open class RTMPMP4ClipHandler: RPBroadcastMP4ClipHandler {
public static var broadcaster:RTMPBroadcaster? deinit {
logger.info("deinit: \(self)")
}
override open func broadcastStarted(withSetupInfo setupInfo: [String : NSObject]?) { override open func processMP4Clip(with mp4ClipURL: URL?, setupInfo: [String : NSObject]?, finished: Bool) {
logger.info("broadcastStarted:\(setupInfo)")
guard let endpointURL:String = setupInfo?["endpointURL"] as? String else { guard let endpointURL:String = setupInfo?["endpointURL"] as? String else {
return return
} }
RTMPSampleHandler.broadcaster = RTMPBroadcaster() RTMPBroadcaster.default.connect(endpointURL, arguments: nil)
RTMPSampleHandler.broadcaster?.connect(endpointURL, arguments: nil) RTMPBroadcaster.default.processMP4Clip(mp4ClipURL: mp4ClipURL) {_ in
} if (finished) {
RTMPBroadcaster.default.close()
override open func broadcastPaused() { }
logger.info("broadcastPaused") }
}
override open func broadcastResumed() {
logger.info("broadcastResumed")
}
override open func broadcastFinished() {
logger.info("broadcastFinished")
RTMPSampleHandler.broadcaster?.close()
RTMPSampleHandler.broadcaster = nil
}
override open func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) {
RTMPSampleHandler.broadcaster?.process(sampleBuffer: sampleBuffer, with: sampleBufferType)
} }
} }

View File

@ -37,13 +37,16 @@ class MP4Box {
} }
} }
var leafNode:Bool {
return false
}
fileprivate(set) var type:String = "undf" fileprivate(set) var type:String = "undf"
fileprivate(set) var size:UInt32 = 0 fileprivate(set) var size:UInt32 = 0
fileprivate(set) var offset:UInt64 = 0 fileprivate(set) var offset:UInt64 = 0
fileprivate(set) var parent:MP4Box? = nil fileprivate(set) var parent:MP4Box? = nil
var leafNode:Bool { init() {
return false
} }
init(size: UInt32, type:String) { init(size: UInt32, type:String) {
@ -51,7 +54,7 @@ class MP4Box {
self.type = type self.type = type
} }
func loadFile(_ fileHandle: FileHandle) throws -> UInt32 { func load(_ fileHandle:FileHandle) throws -> UInt32 {
if (size == 0) { if (size == 0) {
size = UInt32(fileHandle.seekToEndOfFile() - offset) size = UInt32(fileHandle.seekToEndOfFile() - offset)
return size return size
@ -60,7 +63,7 @@ class MP4Box {
return size return size
} }
func getBoxesByName(_ name:String) -> [MP4Box] { func getBoxes(byName:String) -> [MP4Box] {
return [] return []
} }
@ -76,8 +79,8 @@ class MP4Box {
} }
} }
// MARK: CustomStringConvertible
extension MP4Box: CustomStringConvertible { extension MP4Box: CustomStringConvertible {
// MARK: CustomStringConvertible
var description:String { var description:String {
return Mirror(reflecting: self).description return Mirror(reflecting: self).description
} }
@ -92,29 +95,29 @@ class MP4ContainerBox: MP4Box {
return false return false
} }
override func loadFile(_ fileHandle: FileHandle) throws -> UInt32 { override func load(_ file:FileHandle) throws -> UInt32 {
children.removeAll(keepingCapacity: false) children.removeAll(keepingCapacity: false)
var offset:UInt32 = parent == nil ? 0 : 8 var offset:UInt32 = parent == nil ? 0 : 8
fileHandle.seek(toFileOffset: self.offset + UInt64(offset)) file.seek(toFileOffset: self.offset + UInt64(offset))
while (size != offset) { while (size != offset) {
let child:MP4Box = try create(fileHandle.readData(ofLength: 8), offset: offset) let child:MP4Box = try create(file.readData(ofLength: 8), offset: offset)
offset += try child.loadFile(fileHandle) offset += try child.load(file)
children.append(child) children.append(child)
} }
return offset return offset
} }
override func getBoxesByName(_ name:String) -> [MP4Box] { override func getBoxes(byName:String) -> [MP4Box] {
var list:[MP4Box] = [] var list:[MP4Box] = []
for child in children { for child in children {
if (name == child.type || name == "*" ) { if (byName == child.type || byName == "*" ) {
list.append(child) list.append(child)
} }
if (!child.leafNode) { if (!child.leafNode) {
list += child.getBoxesByName(name) list += child.getBoxes(byName: byName)
} }
} }
return list return list
@ -139,7 +142,7 @@ final class MP4MediaHeaderBox: MP4Box {
var language:UInt16 = 0 var language:UInt16 = 0
var quality:UInt16 = 0 var quality:UInt16 = 0
override func loadFile(_ fileHandle: FileHandle) throws -> UInt32 { override func load(_ fileHandle: FileHandle) throws -> UInt32 {
let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: Int(size) - 8)) let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: Int(size) - 8))
version = try buffer.readUInt8() version = try buffer.readUInt8()
buffer.position += 3 buffer.position += 3
@ -158,7 +161,7 @@ final class MP4MediaHeaderBox: MP4Box {
final class MP4ChunkOffsetBox: MP4Box { final class MP4ChunkOffsetBox: MP4Box {
var entries:[UInt32] = [] var entries:[UInt32] = []
override func loadFile(_ fileHandle: FileHandle) throws -> UInt32 { override func load(_ fileHandle: FileHandle) throws -> UInt32 {
let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: Int(size) - 8)) let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: Int(size) - 8))
buffer.position += 4 buffer.position += 4
@ -176,7 +179,7 @@ final class MP4ChunkOffsetBox: MP4Box {
final class MP4SyncSampleBox: MP4Box { final class MP4SyncSampleBox: MP4Box {
var entries:[UInt32] = [] var entries:[UInt32] = []
override func loadFile(_ fileHandle: FileHandle) throws -> UInt32 { override func load(_ fileHandle: FileHandle) throws -> UInt32 {
entries.removeAll(keepingCapacity: false) entries.removeAll(keepingCapacity: false)
let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: Int(size) - 8)) let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: Int(size) - 8))
@ -209,7 +212,7 @@ final class MP4TimeToSampleBox: MP4Box {
var entries:[Entry] = [] var entries:[Entry] = []
override func loadFile(_ fileHandle: FileHandle) throws -> UInt32 { override func load(_ fileHandle: FileHandle) throws -> UInt32 {
entries.removeAll(keepingCapacity: false) entries.removeAll(keepingCapacity: false)
let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: Int(size) - 8)) let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: Int(size) - 8))
@ -231,7 +234,7 @@ final class MP4TimeToSampleBox: MP4Box {
final class MP4SampleSizeBox: MP4Box { final class MP4SampleSizeBox: MP4Box {
var entries:[UInt32] = [] var entries:[UInt32] = []
override func loadFile(_ fileHandle: FileHandle) throws -> UInt32 { override func load(_ fileHandle: FileHandle) throws -> UInt32 {
entries.removeAll(keepingCapacity: false) entries.removeAll(keepingCapacity: false)
let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: Int(self.size) - 8)) let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: Int(self.size) - 8))
@ -266,7 +269,7 @@ final class MP4ElementaryStreamDescriptorBox: MP4ContainerBox {
var ocrStreamFlag:UInt8 = 0 var ocrStreamFlag:UInt8 = 0
var streamPriority:UInt8 = 0 var streamPriority:UInt8 = 0
override func loadFile(_ fileHandle: FileHandle) throws -> UInt32 { override func load(_ fileHandle: FileHandle) throws -> UInt32 {
var tagSize:UInt8 = 0 var tagSize:UInt8 = 0
let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: Int(self.size) - 8)) let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: Int(self.size) - 8))
buffer.position += 4 buffer.position += 4
@ -329,7 +332,7 @@ final class MP4AudioSampleEntryBox: MP4ContainerBox {
var soundVersion2Data:[UInt8] = [] var soundVersion2Data:[UInt8] = []
override func loadFile(_ fileHandle: FileHandle) throws -> UInt32 { override func load(_ fileHandle: FileHandle) throws -> UInt32 {
let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: Int(size) - 8)) let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: Int(size) - 8))
buffer.position += 8 buffer.position += 8
@ -361,7 +364,7 @@ final class MP4AudioSampleEntryBox: MP4ContainerBox {
fileHandle.seek(toFileOffset: self.offset + UInt64(offset)) fileHandle.seek(toFileOffset: self.offset + UInt64(offset))
let esds:MP4Box = try create(fileHandle.readData(ofLength: 8), offset: offset) let esds:MP4Box = try create(fileHandle.readData(ofLength: 8), offset: offset)
offset += try esds.loadFile(fileHandle) offset += try esds.load(fileHandle)
children.append(esds) children.append(esds)
// skip // skip
@ -383,7 +386,7 @@ final class MP4VisualSampleEntryBox: MP4ContainerBox {
var compressorname:String = "" var compressorname:String = ""
var depth:UInt16 = 16 var depth:UInt16 = 16
override func loadFile(_ fileHandle: FileHandle) throws -> UInt32 { override func load(_ fileHandle: FileHandle) throws -> UInt32 {
let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: MP4VisualSampleEntryBox.dataSize)) let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: MP4VisualSampleEntryBox.dataSize))
buffer.position += 24 buffer.position += 24
@ -402,7 +405,7 @@ final class MP4VisualSampleEntryBox: MP4ContainerBox {
let child:MP4Box = try MP4Box.create(fileHandle.readData(ofLength: 8)) let child:MP4Box = try MP4Box.create(fileHandle.readData(ofLength: 8))
child.parent = self child.parent = self
child.offset = self.offset + UInt64(offset) + 8 child.offset = self.offset + UInt64(offset) + 8
offset += try child.loadFile(fileHandle) offset += try child.load(fileHandle)
children.append(child) children.append(child)
// skip // skip
@ -414,7 +417,7 @@ final class MP4VisualSampleEntryBox: MP4ContainerBox {
// MARK: - // MARK: -
final class MP4SampleDescriptionBox: MP4ContainerBox { final class MP4SampleDescriptionBox: MP4ContainerBox {
override func loadFile(_ fileHandle: FileHandle) throws -> UInt32 { override func load(_ fileHandle: FileHandle) throws -> UInt32 {
children.removeAll(keepingCapacity: false) children.removeAll(keepingCapacity: false)
let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: 8)) let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: 8))
@ -424,7 +427,7 @@ final class MP4SampleDescriptionBox: MP4ContainerBox {
let numberOfEntries:UInt32 = try buffer.readUInt32() let numberOfEntries:UInt32 = try buffer.readUInt32()
for _ in 0..<numberOfEntries { for _ in 0..<numberOfEntries {
let child:MP4Box = try create(fileHandle.readData(ofLength: 8), offset: offset) let child:MP4Box = try create(fileHandle.readData(ofLength: 8), offset: offset)
offset += try child.loadFile(fileHandle) offset += try child.load(fileHandle)
children.append(child) children.append(child)
} }
@ -452,7 +455,7 @@ final class MP4SampleToChunkBox: MP4Box {
var entries:[Entry] = [] var entries:[Entry] = []
override func loadFile(_ fileHandle: FileHandle) throws -> UInt32 { override func load(_ fileHandle: FileHandle) throws -> UInt32 {
let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: Int(size) - 8)) let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: Int(size) - 8))
buffer.position += 4 buffer.position += 4
@ -491,7 +494,7 @@ final class MP4EditListBox: MP4Box {
var version:UInt32 = 0 var version:UInt32 = 0
var entries:[Entry] = [] var entries:[Entry] = []
override func loadFile(_ fileHandle: FileHandle) throws -> UInt32 { override func load(_ fileHandle: FileHandle) throws -> UInt32 {
let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: Int(size) - 8)) let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: Int(size) - 8))
version = try buffer.readUInt32() version = try buffer.readUInt32()
@ -511,7 +514,7 @@ final class MP4EditListBox: MP4Box {
} }
// MARK: - // MARK: -
final class MP4File: MP4ContainerBox { final class MP4Reader: MP4ContainerBox {
var url:URL? = nil { var url:URL? = nil {
didSet { didSet {
if (url == nil) { if (url == nil) {
@ -525,46 +528,46 @@ final class MP4File: MP4ContainerBox {
} }
} }
fileprivate var fileHandle:FileHandle? = nil var isEmpty:Bool {
return getBoxes(byName: "mdhd").isEmpty
func isEmpty() -> Bool {
return getBoxesByName("mdhd").isEmpty
} }
func readDataOfLength(_ length: Int) -> Data { private var fileHandle:FileHandle? = nil
return fileHandle!.readData(ofLength: length)
func seek(toFileOffset: UInt64) {
return fileHandle!.seek(toFileOffset: toFileOffset)
} }
func seekToFileOffset(_ offset: UInt64) { func readData(ofLength: Int) -> Data {
return fileHandle!.seek(toFileOffset: offset) return fileHandle!.readData(ofLength: ofLength)
} }
func readDataOfBox(_ box:MP4Box) -> Data { func readData(ofBox:MP4Box) -> Data {
if (fileHandle == nil) { guard let fileHandle:FileHandle = fileHandle else {
return Data() return Data()
} }
let currentOffsetInFile:UInt64 = fileHandle!.offsetInFile let currentOffsetInFile:UInt64 = fileHandle.offsetInFile
fileHandle!.seek(toFileOffset: box.offset + 8) fileHandle.seek(toFileOffset: ofBox.offset + 8)
let data:Data = fileHandle!.readData(ofLength: Int(box.size) - 8) let data:Data = fileHandle.readData(ofLength: Int(ofBox.size) - 8)
fileHandle!.seek(toFileOffset: currentOffsetInFile) fileHandle.seek(toFileOffset: currentOffsetInFile)
return data return data
} }
func loadFile() throws -> UInt32 { func load() throws -> UInt32 {
if (fileHandle == nil) { guard let fileHandle:FileHandle = self.fileHandle else {
return 0 return 0
} }
return try self.loadFile(fileHandle!) return try load(fileHandle)
} }
func closeFile() { func close() {
fileHandle?.closeFile() fileHandle?.closeFile()
} }
override func loadFile(_ fileHandle: FileHandle) throws -> UInt32 { override func load(_ fileHandle: FileHandle) throws -> UInt32 {
let size:UInt64 = fileHandle.seekToEndOfFile() let size:UInt64 = fileHandle.seekToEndOfFile()
fileHandle.seek(toFileOffset: 0) fileHandle.seek(toFileOffset: 0)
self.size = UInt32(size) self.size = UInt32(size)
return try super.loadFile(fileHandle) return try super.load(fileHandle)
} }
} }

View File

@ -0,0 +1,215 @@
import Foundation
struct MP4SampleTable {
var trak:MP4Box
var currentOffset:UInt64 {
return UInt64(offset[cursor])
}
var currentIsKeyframe:Bool {
return keyframe[cursor] != nil
}
var currentDuration:Double {
return Double(totalTimeToSample) * 1000 / Double(timeScale)
}
var currentTimeToSample:Double {
return Double(timeToSample[cursor]) * 1000 / Double(timeScale)
}
var currentSampleSize:Int {
return Int((sampleSize.count == 1) ? sampleSize[0] : sampleSize[cursor])
}
private var cursor:Int = 0
private var offset:[UInt32] = []
private var keyframe:[Int:Bool] = [:]
private var timeScale:UInt32 = 0
private var sampleSize:[UInt32] = []
private var timeToSample:[UInt32] = []
private var totalTimeToSample:UInt32 = 0
init(trak:MP4Box) {
self.trak = trak
let mdhd:MP4Box? = trak.getBoxes(byName: "mdhd").first
if let mdhd:MP4MediaHeaderBox = mdhd as? MP4MediaHeaderBox {
timeScale = mdhd.timeScale
}
let stss:MP4Box? = trak.getBoxes(byName: "stss").first
if let stss:MP4SyncSampleBox = stss as? MP4SyncSampleBox {
var keyframes:[UInt32] = stss.entries
for i in 0..<keyframes.count {
keyframe[Int(keyframes[i])] = true
}
}
let stts:MP4Box? = trak.getBoxes(byName: "stts").first
if let stts:MP4TimeToSampleBox = stts as? MP4TimeToSampleBox {
var timeToSample:[MP4TimeToSampleBox.Entry] = stts.entries
for i in 0..<timeToSample.count {
let entry:MP4TimeToSampleBox.Entry = timeToSample[i]
for _ in 0..<entry.sampleCount {
self.timeToSample.append(entry.sampleDuration)
}
}
}
let stsz:MP4Box? = trak.getBoxes(byName: "stsz").first
if let stsz:MP4SampleSizeBox = stsz as? MP4SampleSizeBox {
sampleSize = stsz.entries
}
let stco:MP4Box = trak.getBoxes(byName: "stco").first!
let stsc:MP4Box = trak.getBoxes(byName: "stsc").first!
var offsets:[UInt32] = (stco as! MP4ChunkOffsetBox).entries
var sampleToChunk:[MP4SampleToChunkBox.Entry] = (stsc as! MP4SampleToChunkBox).entries
var index:Int = 0
let count:Int = sampleToChunk.count
for i in 0..<count {
let j:Int = Int(sampleToChunk[i].firstChunk) - 1
let m:Int = (i + 1 < count) ? Int(sampleToChunk[i + 1].firstChunk) - 1 : offsets.count
for _ in j..<m {
var offset:UInt32 = offsets[j]
for _ in 0..<sampleToChunk[i].samplesPerChunk {
self.offset.append(offset)
offset += sampleSize[index]
index += 1
}
}
}
totalTimeToSample = timeToSample[cursor]
}
func hasNext() -> Bool {
return cursor + 1 < offset.count
}
mutating func next() {
defer {
cursor += 1
}
totalTimeToSample += timeToSample[cursor]
}
}
extension MP4SampleTable: CustomStringConvertible {
// MARK: CustomStringConvertible
var description:String {
return Mirror(reflecting: self).description
}
}
// MARK: -
protocol MP4SamplerDelegate: class {
func didSet(avcC: Data, withType:Int)
func didSet(audioDecorderSpecificConfig: Data, withType:Int)
func output(data: Data, withType:Int, currentTime:Double, keyframe:Bool)
}
// MARK: -
class MP4Sampler {
typealias Handler = () -> Void
weak var delegate:MP4SamplerDelegate?
fileprivate(set) var running:Bool = false
fileprivate var files:[URL:Handler?] = [:]
fileprivate let fileQueue:DispatchQueue = DispatchQueue(label: "com.github.shogo4405.lf.MP4Sampler.file")
fileprivate let lockQueue:DispatchQueue = DispatchQueue(label: "com.github.shgoo4405.lf.MP4Sampler.lock")
private var reader:MP4Reader = MP4Reader()
private var sampleTables:[MP4SampleTable] = []
func append(file:URL, completionHandler: Handler? = nil) {
fileQueue.async {
self.files[file] = completionHandler
}
}
fileprivate func execute(url:URL) {
reader.url = url
do {
let _:UInt32 = try reader.load()
} catch {
logger.warning("")
return
}
sampleTables.removeAll()
var traks:[MP4Box] = reader.getBoxes(byName: "trak")
for i in 0..<traks.count {
sampleTables.append(MP4SampleTable(trak: traks[i]))
}
for i in 0..<sampleTables.count {
if let avcC:MP4Box = sampleTables[i].trak.getBoxes(byName: "avcC").first {
delegate?.didSet(avcC: reader.readData(ofBox: avcC), withType: i)
}
if let esds:MP4ElementaryStreamDescriptorBox = sampleTables[i].trak.getBoxes(byName: "esds").first as? MP4ElementaryStreamDescriptorBox {
delegate?.didSet(audioDecorderSpecificConfig: Data(esds.audioDecorderSpecificConfig), withType: i)
}
}
repeat {
for i in 0..<sampleTables.count {
autoreleasepool {
reader.seek(toFileOffset: sampleTables[i].currentOffset)
let length:Int = sampleTables[i].currentSampleSize
delegate?.output(
data: reader.readData(ofLength: length),
withType: i,
currentTime: sampleTables[i].currentTimeToSample,
keyframe: sampleTables[i].currentIsKeyframe
)
}
if (sampleTables[i].hasNext()) {
sampleTables[i].next()
}
}
}
while inLoop(sampleTables: sampleTables)
reader.close()
}
fileprivate func run() {
if (files.isEmpty) {
return
}
let (key: url, value: handler) = files.popFirst()!
execute(url: url)
handler?()
}
private func inLoop(sampleTables:[MP4SampleTable]) -> Bool{
for i in sampleTables {
if (i.hasNext()) {
return true
}
}
return false
}
}
extension MP4Sampler: Runnable {
// MARK: Runnable
final func startRunning() {
lockQueue.async {
self.running = true
while (self.running) {
self.fileQueue.async {
self.run()
}
}
}
}
final func stopRunning() {
lockQueue.async {
self.running = false
}
}
}

View File

@ -194,7 +194,7 @@ struct PacketizedElementaryStream: PESPacketHeader {
packet.adaptationFieldFlag = true packet.adaptationFieldFlag = true
packet.adaptationField = TSAdaptationField() packet.adaptationField = TSAdaptationField()
packet.adaptationField?.PCRFlag = true packet.adaptationField?.PCRFlag = true
packet.adaptationField?.PCR = TSProgramClockReference.encode(0, UInt16(PCR)) packet.adaptationField?.PCR = TSProgramClockReference.encode(PCR, 0)
packet.adaptationField?.compute() packet.adaptationField?.compute()
} }
packet.payloadUnitStartIndicator = true packet.payloadUnitStartIndicator = true

View File

@ -58,10 +58,11 @@ class TSWriter {
} }
func writeSampleBuffer(_ PID:UInt16, streamID:UInt8, sampleBuffer:CMSampleBuffer) { func writeSampleBuffer(_ PID:UInt16, streamID:UInt8, sampleBuffer:CMSampleBuffer) {
let presentationTimeStamp:CMTime = sampleBuffer.presentationTimeStamp
if (timestamps[PID] == nil) { if (timestamps[PID] == nil) {
timestamps[PID] = sampleBuffer.presentationTimeStamp timestamps[PID] = presentationTimeStamp
if (PCRPID == PID) { if (PCRPID == PID) {
PCRTimestamp = sampleBuffer.presentationTimeStamp PCRTimestamp = presentationTimeStamp
} }
} }
let config:Any? = streamID == 192 ? audioConfig : videoConfig let config:Any? = streamID == 192 ? audioConfig : videoConfig
@ -71,9 +72,14 @@ class TSWriter {
return return
} }
PES.streamID = streamID PES.streamID = streamID
let decodeTimeStamp:CMTime = sampleBuffer.decodeTimeStamp
var decodeTimeStamp:CMTime = sampleBuffer.decodeTimeStamp
if (decodeTimeStamp == kCMTimeInvalid) {
decodeTimeStamp = presentationTimeStamp
}
var packets:[TSPacket] = split(PID, PES: PES, timestamp: decodeTimeStamp) var packets:[TSPacket] = split(PID, PES: PES, timestamp: decodeTimeStamp)
if (PCRPID == PID && rorateFileHandle(decodeTimeStamp, next: sampleBuffer.decodeTimeStamp)) { if (PCRPID == PID && rorateFileHandle(decodeTimeStamp)) {
packets[0].adaptationField?.randomAccessIndicator = true packets[0].adaptationField?.randomAccessIndicator = true
packets[0].adaptationField?.discontinuityIndicator = true packets[0].adaptationField?.discontinuityIndicator = true
} }
@ -106,7 +112,7 @@ class TSWriter {
return packets return packets
} }
func rorateFileHandle(_ timestamp:CMTime, next:CMTime) -> Bool { func rorateFileHandle(_ timestamp:CMTime) -> Bool {
let duration:Double = timestamp.seconds - rotatedTimestamp.seconds let duration:Double = timestamp.seconds - rotatedTimestamp.seconds
if (duration <= segmentDuration) { if (duration <= segmentDuration) {
return false return false

View File

@ -10,7 +10,7 @@ final class NetClient: NetSocket {
static let defaultBufferSize:Int = 8192 static let defaultBufferSize:Int = 8192
weak var delegate:NetClientDelegate? weak var delegate:NetClientDelegate?
fileprivate(set) var service:Foundation.NetService? private(set) var service:Foundation.NetService?
init(service:Foundation.NetService, inputStream:InputStream, outputStream:OutputStream) { init(service:Foundation.NetService, inputStream:InputStream, outputStream:OutputStream) {
super.init() super.init()

View File

@ -153,50 +153,51 @@ enum FLVAudioCodec:UInt8 {
} }
// MARK: - // MARK: -
struct FLVTag { enum FLVTagType:UInt8 {
case audio = 8
case video = 9
case data = 18
enum TagType: UInt8 { var streamId:UInt16 {
case audio = 8 switch self {
case video = 9 case .audio:
case data = 18 return RTMPChunk.StreamID.audio.rawValue
case .video:
var streamId:UInt16 { return RTMPChunk.StreamID.video.rawValue
switch self { case .data:
case .audio: return 0
return RTMPChunk.StreamID.audio.rawValue
case .video:
return RTMPChunk.StreamID.video.rawValue
case .data:
return 0
}
}
var headerSize:Int {
switch self {
case .audio:
return 2
case .video:
return 5
case .data:
return 0
}
}
func createMessage(_ streamId: UInt32, timestamp: UInt32, buffer:Foundation.Data) -> RTMPMessage {
switch self {
case .audio:
return RTMPAudioMessage(streamId: streamId, timestamp: timestamp, buffer: buffer)
case .video:
return RTMPVideoMessage(streamId: streamId, timestamp: timestamp, buffer: buffer)
case .data:
return RTMPDataMessage(objectEncoding: 0x00)
}
} }
} }
var headerSize:Int {
switch self {
case .audio:
return 2
case .video:
return 5
case .data:
return 0
}
}
func message(with streamId: UInt32, timestamp: UInt32, buffer:Data) -> RTMPMessage {
switch self {
case .audio:
return RTMPAudioMessage(streamId: streamId, timestamp: timestamp, buffer: buffer)
case .video:
return RTMPVideoMessage(streamId: streamId, timestamp: timestamp, buffer: buffer)
case .data:
return RTMPDataMessage(objectEncoding: 0x00)
}
}
}
// MARK: -
struct FLVTag {
static let headerSize = 11 static let headerSize = 11
var tagType:TagType = .data var tagType:FLVTagType = .data
var dataSize:UInt32 = 0 var dataSize:UInt32 = 0
var timestamp:UInt32 = 0 var timestamp:UInt32 = 0
var timestampExtended:UInt8 = 0 var timestampExtended:UInt8 = 0

View File

@ -5,10 +5,12 @@ import Foundation
*/ */
open class Responder: NSObject { open class Responder: NSObject {
private var result:(_ data:[Any?]) -> Void public typealias Handler = (_ data:[Any?]) -> Void
private var status:((_ data:[Any?]) -> Void)?
public init(result:@escaping (_ data:[Any?]) -> Void, status:((_ data:[Any?]) -> Void)? = nil) { private var result:Handler
private var status:Handler?
public init(result:@escaping Handler, status:Handler? = nil) {
self.result = result self.result = result
self.status = status self.status = status
} }
@ -330,18 +332,6 @@ open class RTMPConnection: EventDispatcher {
} }
} }
@objc private func on(timer:Timer) {
let totalBytesIn:Int64 = self.totalBytesIn
let totalBytesOut:Int64 = self.totalBytesOut
currentBytesInPerSecond = Int32(totalBytesIn - previousTotalBytesIn)
currentBytesOutPerSecond = Int32(totalBytesOut - previousTotalBytesOut)
previousTotalBytesIn = totalBytesIn
previousTotalBytesOut = totalBytesOut
for (_, stream) in streams {
stream.on(timer: timer)
}
}
fileprivate func createConnectionChunk() -> RTMPChunk? { fileprivate func createConnectionChunk() -> RTMPChunk? {
guard let uri:URL = uri else { guard let uri:URL = uri else {
return nil return nil
@ -378,6 +368,18 @@ open class RTMPConnection: EventDispatcher {
return RTMPChunk(message: message) return RTMPChunk(message: message)
} }
@objc private func on(timer:Timer) {
let totalBytesIn:Int64 = self.totalBytesIn
let totalBytesOut:Int64 = self.totalBytesOut
currentBytesInPerSecond = Int32(totalBytesIn - previousTotalBytesIn)
currentBytesOutPerSecond = Int32(totalBytesOut - previousTotalBytesOut)
previousTotalBytesIn = totalBytesIn
previousTotalBytesOut = totalBytesOut
for (_, stream) in streams {
stream.on(timer: timer)
}
}
} }
extension RTMPConnection: RTMPSocketDelegate { extension RTMPConnection: RTMPSocketDelegate {

View File

@ -345,7 +345,7 @@ final class RTMPCommandMessage: RTMPMessage {
case "close": case "close":
connection.close() connection.close()
default: default:
connection.dispatch(type: Event.RTMP_STATUS, bubbles: false, data: arguments.isEmpty ? nil : arguments[0]) connection.dispatch(Event.RTMP_STATUS, bubbles: false, data: arguments.isEmpty ? nil : arguments[0])
} }
return return
} }
@ -371,7 +371,7 @@ final class RTMPDataMessage: RTMPMessage {
var handlerName:String = "" var handlerName:String = ""
var arguments:[Any?] = [] var arguments:[Any?] = []
fileprivate var serializer:AMFSerializer = AMF0Serializer() private var serializer:AMFSerializer = AMF0Serializer()
override var payload:[UInt8] { override var payload:[UInt8] {
get { get {
@ -533,10 +533,10 @@ final class RTMPSharedObjectMessage: RTMPMessage {
final class RTMPAudioMessage: RTMPMessage { final class RTMPAudioMessage: RTMPMessage {
var config:AudioSpecificConfig? var config:AudioSpecificConfig?
fileprivate(set) var codec:FLVAudioCodec = .unknown private(set) var codec:FLVAudioCodec = .unknown
fileprivate(set) var soundRate:FLVSoundRate = .kHz44 private(set) var soundRate:FLVSoundRate = .kHz44
fileprivate(set) var soundSize:FLVSoundSize = .snd8bit private(set) var soundSize:FLVSoundSize = .snd8bit
fileprivate(set) var soundType:FLVSoundType = .stereo private(set) var soundType:FLVSoundType = .stereo
var soundData:[UInt8] { var soundData:[UInt8] {
let data:[UInt8] = payload.isEmpty ? [] : Array(payload[codec.headerSize..<payload.count]) let data:[UInt8] = payload.isEmpty ? [] : Array(payload[codec.headerSize..<payload.count])
@ -618,8 +618,8 @@ final class RTMPAudioMessage: RTMPMessage {
7.1.5. Video Message (9) 7.1.5. Video Message (9)
*/ */
final class RTMPVideoMessage: RTMPMessage { final class RTMPVideoMessage: RTMPMessage {
fileprivate(set) var codec:FLVVideoCodec = .unknown private(set) var codec:FLVVideoCodec = .unknown
fileprivate(set) var status:OSStatus = noErr private(set) var status:OSStatus = noErr
init() { init() {
super.init(type: .video) super.init(type: .video)
@ -638,7 +638,7 @@ final class RTMPVideoMessage: RTMPMessage {
return return
} }
OSAtomicAdd64(Int64(payload.count), &stream.info.byteCount) OSAtomicAdd64(Int64(payload.count), &stream.info.byteCount)
guard FLVTag.TagType.video.headerSize < payload.count else { guard FLVTagType.video.headerSize < payload.count else {
return return
} }
switch payload[1] { switch payload[1] {
@ -661,7 +661,7 @@ final class RTMPVideoMessage: RTMPMessage {
decodeTimeStamp: kCMTimeInvalid decodeTimeStamp: kCMTimeInvalid
) )
let bytes:[UInt8] = Array(payload[FLVTag.TagType.video.headerSize..<payload.count]) let bytes:[UInt8] = Array(payload[FLVTagType.video.headerSize..<payload.count])
var sample:[UInt8] = bytes var sample:[UInt8] = bytes
let sampleSize:Int = bytes.count let sampleSize:Int = bytes.count
var blockBuffer:CMBlockBuffer? var blockBuffer:CMBlockBuffer?
@ -680,7 +680,7 @@ final class RTMPVideoMessage: RTMPMessage {
func createFormatDescription(_ stream: RTMPStream) -> OSStatus { func createFormatDescription(_ stream: RTMPStream) -> OSStatus {
var config:AVCConfigurationRecord = AVCConfigurationRecord() var config:AVCConfigurationRecord = AVCConfigurationRecord()
config.bytes = Array(payload[FLVTag.TagType.video.headerSize..<payload.count]) config.bytes = Array(payload[FLVTagType.video.headerSize..<payload.count])
return config.createFormatDescription(&stream.mixer.videoIO.formatDescription) return config.createFormatDescription(&stream.mixer.videoIO.formatDescription)
} }
} }
@ -764,7 +764,7 @@ final class RTMPUserControlMessage: RTMPMessage {
message: RTMPUserControlMessage(event: .pong, value: value) message: RTMPUserControlMessage(event: .pong, value: value)
)) ))
case .bufferEmpty, .bufferFull: case .bufferEmpty, .bufferFull:
connection.streams[UInt32(value)]?.dispatch(type: "rtmpStatus", bubbles: false, data: [ connection.streams[UInt32(value)]?.dispatch("rtmpStatus", bubbles: false, data: [
"level": "status", "level": "status",
"code": description, "code": description,
"description": "" "description": ""

View File

@ -8,8 +8,12 @@ protocol RTMPMuxerDelegate: class {
// MARK: - // MARK: -
final class RTMPMuxer { final class RTMPMuxer {
weak var delegate:RTMPMuxerDelegate? = nil static let aac:UInt8 = FLVAudioCodec.aac.rawValue << 4 | FLVSoundRate.kHz44.rawValue << 2 | FLVSoundSize.snd16bit.rawValue << 1 | FLVSoundType.stereo.rawValue
weak var delegate:RTMPMuxerDelegate? = nil
fileprivate var avcC:Data?
fileprivate var audioDecorderSpecificConfig:Data?
fileprivate var timestamps:[Int:Double] = [:]
fileprivate var audioTimestamp:CMTime = kCMTimeZero fileprivate var audioTimestamp:CMTime = kCMTimeZero
fileprivate var videoTimestamp:CMTime = kCMTimeZero fileprivate var videoTimestamp:CMTime = kCMTimeZero
@ -21,7 +25,6 @@ final class RTMPMuxer {
extension RTMPMuxer: AudioEncoderDelegate { extension RTMPMuxer: AudioEncoderDelegate {
// MARK: AudioEncoderDelegate // MARK: AudioEncoderDelegate
private static let aac:UInt8 = FLVAudioCodec.aac.rawValue << 4 | FLVSoundRate.kHz44.rawValue << 2 | FLVSoundSize.snd16bit.rawValue << 1 | FLVSoundType.stereo.rawValue
func didSetFormatDescription(audio formatDescription: CMFormatDescription?) { func didSetFormatDescription(audio formatDescription: CMFormatDescription?) {
guard let formatDescription:CMFormatDescription = formatDescription else { guard let formatDescription:CMFormatDescription = formatDescription else {
@ -95,3 +98,49 @@ extension RTMPMuxer: VideoEncoderDelegate {
videoTimestamp = decodeTimeStamp videoTimestamp = decodeTimeStamp
} }
} }
extension RTMPMuxer: MP4SamplerDelegate {
// MP4SampleDelegate
func didSet(avcC: Data, withType:Int) {
if (avcC == self.avcC) {
return
}
var buffer:Data = Data([FLVFrameType.key.rawValue << 4 | FLVVideoCodec.avc.rawValue, FLVAVCPacketType.seq.rawValue, 0, 0, 0])
buffer.append(avcC)
delegate?.sampleOutput(video: buffer, withTimestamp: 0, muxer: self)
self.avcC = avcC
}
func didSet(audioDecorderSpecificConfig: Data, withType:Int) {
if (withType == 2) {
return
}
if (audioDecorderSpecificConfig == self.audioDecorderSpecificConfig) {
return
}
var buffer:Data = Data([RTMPMuxer.aac, FLVAACPacketType.raw.rawValue])
buffer.append(audioDecorderSpecificConfig)
delegate?.sampleOutput(audio: buffer, withTimestamp: 0, muxer: self)
self.audioDecorderSpecificConfig = audioDecorderSpecificConfig
}
func output(data:Data, withType:Int, currentTime:Double, keyframe:Bool) {
let delta:Double = (timestamps[withType] == nil) ? 0 : timestamps[withType]!
switch withType {
case 0:
print(delta)
let compositionTime:Int32 = 0
var buffer:Data = Data([((keyframe ? FLVFrameType.key.rawValue : FLVFrameType.inter.rawValue) << 4) | FLVVideoCodec.avc.rawValue, FLVAVCPacketType.nal.rawValue])
buffer.append(contentsOf: compositionTime.bigEndian.bytes[1..<4])
buffer.append(data)
delegate?.sampleOutput(video: buffer, withTimestamp: delta, muxer: self)
case 1:
var buffer:Data = Data([RTMPMuxer.aac, FLVAACPacketType.raw.rawValue])
buffer.append(data)
delegate?.sampleOutput(audio: buffer, withTimestamp: delta, muxer: self)
default:
break
}
timestamps[withType] = currentTime
}
}

View File

@ -192,7 +192,7 @@ open class RTMPSharedObject: EventDispatcher {
} }
changeList.append(change) changeList.append(change)
} }
dispatch(type: Event.SYNC, bubbles: false, data: changeList) dispatch(Event.SYNC, bubbles: false, data: changeList)
} }
func createChunk(_ events:[RTMPSharedObjectEvent]) -> RTMPChunk { func createChunk(_ events:[RTMPSharedObjectEvent]) -> RTMPChunk {

View File

@ -150,7 +150,7 @@ final class RTMPSocket: NetSocket, RTMPSocketCompatible {
override func didTimeout() { override func didTimeout() {
deinitConnection(isDisconnected: false) deinitConnection(isDisconnected: false)
delegate?.dispatch(type: Event.IO_ERROR, bubbles: false, data: nil) delegate?.dispatch(Event.IO_ERROR, bubbles: false, data: nil)
logger.warning("connection timedout") logger.warning("connection timedout")
} }
} }

View File

@ -224,7 +224,7 @@ open class RTMPStream: NetStream {
open static let defaultVideoBitrate:UInt32 = AVCEncoder.defaultBitrate open static let defaultVideoBitrate:UInt32 = AVCEncoder.defaultBitrate
open internal(set) var info:RTMPStreamInfo = RTMPStreamInfo() open internal(set) var info:RTMPStreamInfo = RTMPStreamInfo()
open fileprivate(set) var objectEncoding:UInt8 = RTMPConnection.defaultObjectEncoding open fileprivate(set) var objectEncoding:UInt8 = RTMPConnection.defaultObjectEncoding
open fileprivate(set) dynamic var currentFPS:UInt8 = 0 open fileprivate(set) dynamic var currentFPS:UInt16 = 0
open var soundTransform:SoundTransform { open var soundTransform:SoundTransform {
get { return audioPlayback.soundTransform } get { return audioPlayback.soundTransform }
set { audioPlayback.soundTransform = newValue } set { audioPlayback.soundTransform = newValue }
@ -242,6 +242,7 @@ open class RTMPStream: NetStream {
send(handlerName: "@setDataFrame", arguments: "onMetaData", createMetaData()) send(handlerName: "@setDataFrame", arguments: "onMetaData", createMetaData())
mixer.audioIO.encoder.startRunning() mixer.audioIO.encoder.startRunning()
mixer.videoIO.encoder.startRunning() mixer.videoIO.encoder.startRunning()
sampler.startRunning()
if (howToPublish == .localRecord) { if (howToPublish == .localRecord) {
mixer.recorder.fileName = info.resourceName mixer.recorder.fileName = info.resourceName
mixer.recorder.startRunning() mixer.recorder.startRunning()
@ -256,8 +257,9 @@ open class RTMPStream: NetStream {
var videoTimestamp:Double = 0 var videoTimestamp:Double = 0
fileprivate(set) var audioPlayback:RTMPAudioPlayback = RTMPAudioPlayback() fileprivate(set) var audioPlayback:RTMPAudioPlayback = RTMPAudioPlayback()
fileprivate var muxer:RTMPMuxer = RTMPMuxer() fileprivate var muxer:RTMPMuxer = RTMPMuxer()
fileprivate var frameCount:UInt8 = 0 fileprivate var sampler:MP4Sampler = MP4Sampler()
fileprivate var chunkTypes:[FLVTag.TagType:Bool] = [:] fileprivate var frameCount:UInt16 = 0
fileprivate var chunkTypes:[FLVTagType:Bool] = [:]
fileprivate var dispatcher:IEventDispatcher! fileprivate var dispatcher:IEventDispatcher!
fileprivate var howToPublish:RTMPStream.HowToPublish = .live fileprivate var howToPublish:RTMPStream.HowToPublish = .live
fileprivate var rtmpConnection:RTMPConnection fileprivate var rtmpConnection:RTMPConnection
@ -390,6 +392,7 @@ open class RTMPStream: NetStream {
self.mixer.videoIO.encoder.delegate = nil self.mixer.videoIO.encoder.delegate = nil
self.mixer.audioIO.encoder.stopRunning() self.mixer.audioIO.encoder.stopRunning()
self.mixer.videoIO.encoder.stopRunning() self.mixer.videoIO.encoder.stopRunning()
self.sampler.stopRunning()
self.mixer.recorder.stopRunning() self.mixer.recorder.stopRunning()
self.FCUnpublish() self.FCUnpublish()
self.rtmpConnection.socket.doOutput(chunk: RTMPChunk( self.rtmpConnection.socket.doOutput(chunk: RTMPChunk(
@ -419,6 +422,7 @@ open class RTMPStream: NetStream {
#endif #endif
self.mixer.audioIO.encoder.delegate = self.muxer self.mixer.audioIO.encoder.delegate = self.muxer
self.mixer.videoIO.encoder.delegate = self.muxer self.mixer.videoIO.encoder.delegate = self.muxer
self.sampler.delegate = self.muxer
self.mixer.startRunning() self.mixer.startRunning()
self.chunkTypes.removeAll() self.chunkTypes.removeAll()
self.FCPublish() self.FCPublish()
@ -491,6 +495,10 @@ open class RTMPStream: NetStream {
return metadata return metadata
} }
func append(url:URL, completionHandler: MP4Sampler.Handler? = nil) {
sampler.append(file: url, completionHandler: completionHandler)
}
func on(timer:Timer) { func on(timer:Timer) {
currentFPS = frameCount currentFPS = frameCount
frameCount = 0 frameCount = 0
@ -541,8 +549,8 @@ extension RTMPStream: IEventDispatcher {
public func dispatch(event:Event) { public func dispatch(event:Event) {
dispatcher.dispatch(event: event) dispatcher.dispatch(event: event)
} }
public func dispatch(type:String, bubbles:Bool, data:Any?) { public func dispatch(_ type:String, bubbles:Bool, data:Any?) {
dispatcher.dispatch(type: type, bubbles: bubbles, data: data) dispatcher.dispatch(type, bubbles: bubbles, data: data)
} }
} }
@ -552,11 +560,11 @@ extension RTMPStream: RTMPMuxerDelegate {
guard readyState == .publishing else { guard readyState == .publishing else {
return return
} }
let type:FLVTag.TagType = .audio let type:FLVTagType = .audio
let length:Int = rtmpConnection.socket.doOutput(chunk: RTMPChunk( let length:Int = rtmpConnection.socket.doOutput(chunk: RTMPChunk(
type: chunkTypes[type] == nil ? .zero : .one, type: chunkTypes[type] == nil ? .zero : .one,
streamId: type.streamId, streamId: type.streamId,
message: type.createMessage(id, timestamp: UInt32(audioTimestamp), buffer: buffer) message: type.message(with: id, timestamp: UInt32(audioTimestamp), buffer: buffer)
)) ))
chunkTypes[type] = true chunkTypes[type] = true
OSAtomicAdd64(Int64(length), &info.byteCount) OSAtomicAdd64(Int64(length), &info.byteCount)
@ -567,15 +575,15 @@ extension RTMPStream: RTMPMuxerDelegate {
guard readyState == .publishing else { guard readyState == .publishing else {
return return
} }
let type:FLVTag.TagType = .video let type:FLVTagType = .video
let length:Int = rtmpConnection.socket.doOutput(chunk: RTMPChunk( let length:Int = rtmpConnection.socket.doOutput(chunk: RTMPChunk(
type: chunkTypes[type] == nil ? .zero : .one, type: chunkTypes[type] == nil ? .zero : .one,
streamId: type.streamId, streamId: type.streamId,
message: type.createMessage(id, timestamp: UInt32(videoTimestamp), buffer: buffer) message: type.message(with: id, timestamp: UInt32(videoTimestamp), buffer: buffer)
)) ))
chunkTypes[type] = true chunkTypes[type] = true
OSAtomicAdd64(Int64(length), &info.byteCount) OSAtomicAdd64(Int64(length), &info.byteCount)
videoTimestamp = withTimestamp + (videoTimestamp - floor(videoTimestamp)) videoTimestamp = withTimestamp + (videoTimestamp - floor(videoTimestamp))
frameCount = (frameCount + 1) & 0xFF frameCount += 1
} }
} }

View File

@ -43,15 +43,16 @@ final class RTMPTSocket: NSObject, RTMPSocketCompatible {
} }
private var delay:UInt8 = 1 private var delay:UInt8 = 1
private var mutex:Mutex = Mutex()
private var index:Int64 = 0 private var index:Int64 = 0
private var events:[Event] = [] private var events:[Event] = []
private var baseURL:URL! private var baseURL:URL!
private var session:URLSession! private var session:URLSession!
private var c2packet:[UInt8] = [] private var c2packet:[UInt8] = []
private var isPending:Bool = false private var isPending:Bool = false
private let outputQueue:DispatchQueue = DispatchQueue(label: "com.github.shgoo4405.lf.RTMPTSocket.output")
private var connectionID:String! private var connectionID:String!
private var outputBuffer:[UInt8] = [] private var outputBuffer:[UInt8] = []
private var lastResponse:Date = Date()
override init() { override init() {
super.init() super.init()
@ -77,20 +78,13 @@ final class RTMPTSocket: NSObject, RTMPSocketCompatible {
for chunk in chunks { for chunk in chunks {
bytes.append(contentsOf: chunk) bytes.append(contentsOf: chunk)
} }
do { outputQueue.sync {
try mutex.lock() self.outputBuffer.append(contentsOf: bytes)
outputBuffer.append(contentsOf: bytes) if (!self.isPending) {
if (!isPending) { self.isPending = true
isPending = true self.doOutput(bytes: self.outputBuffer)
doOutput(bytes: outputBuffer) self.outputBuffer.removeAll()
outputBuffer.removeAll()
} }
mutex.unlock()
} catch {
logger.warning("")
}
if (logger.isEnabledForLogLevel(.verbose)) {
logger.verbose("\(chunk)")
} }
return bytes.count return bytes.count
} }
@ -121,17 +115,14 @@ final class RTMPTSocket: NSObject, RTMPSocketCompatible {
return return
} }
do { lastResponse = Date()
try mutex.lock() outputQueue.sync {
if (outputBuffer.isEmpty) { if (self.outputBuffer.isEmpty) {
isPending = false self.isPending = false
} else { } else {
doOutput(bytes: outputBuffer) self.doOutput(bytes: outputBuffer)
outputBuffer.removeAll() self.outputBuffer.removeAll()
} }
mutex.unlock()
} catch {
logger.warning()
} }
guard guard

View File

@ -7,7 +7,7 @@ public protocol IEventDispatcher: class {
func addEventListener(_ type:String, selector:Selector, observer:AnyObject?, useCapture:Bool) func addEventListener(_ type:String, selector:Selector, observer:AnyObject?, useCapture:Bool)
func removeEventListener(_ type:String, selector:Selector, observer:AnyObject?, useCapture:Bool) func removeEventListener(_ type:String, selector:Selector, observer:AnyObject?, useCapture:Bool)
func dispatch(event:Event) func dispatch(event:Event)
func dispatch(type:String, bubbles:Bool, data:Any?) func dispatch(_ type:String, bubbles:Bool, data:Any?)
} }
public enum EventPhase: UInt8 { public enum EventPhase: UInt8 {
@ -92,7 +92,7 @@ open class EventDispatcher: NSObject, IEventDispatcher {
event.target = nil event.target = nil
} }
public final func dispatch(type:String, bubbles:Bool, data:Any?) { public final func dispatch(_ type:String, bubbles:Bool, data:Any?) {
dispatch(event: Event(type: type, bubbles: bubbles, data: data)) dispatch(event: Event(type: type, bubbles: bubbles, data: data))
} }
} }

View File

@ -26,6 +26,9 @@
29245AEE1D32347E00AFFB9A /* VideoGravityUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29245AEC1D3233EB00AFFB9A /* VideoGravityUtil.swift */; }; 29245AEE1D32347E00AFFB9A /* VideoGravityUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29245AEC1D3233EB00AFFB9A /* VideoGravityUtil.swift */; };
29245AEF1D32348400AFFB9A /* VideoGravityUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29245AEC1D3233EB00AFFB9A /* VideoGravityUtil.swift */; }; 29245AEF1D32348400AFFB9A /* VideoGravityUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29245AEC1D3233EB00AFFB9A /* VideoGravityUtil.swift */; };
292AC17C1CF4C871004F5730 /* MD5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2942424C1CF4C01300D65DCB /* MD5.swift */; }; 292AC17C1CF4C871004F5730 /* MD5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2942424C1CF4C01300D65DCB /* MD5.swift */; };
292D8A331D8B293300DBECE2 /* MP4Sampler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292D8A321D8B293300DBECE2 /* MP4Sampler.swift */; };
292D8A341D8B294900DBECE2 /* MP4Sampler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292D8A321D8B293300DBECE2 /* MP4Sampler.swift */; };
292D8A351D8B294E00DBECE2 /* MP4Reader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29798E511CE5DF1900F5CBD0 /* MP4Reader.swift */; };
2931204C1D4522CF00B14211 /* RTSPRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2931204B1D4522CF00B14211 /* RTSPRequest.swift */; }; 2931204C1D4522CF00B14211 /* RTSPRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2931204B1D4522CF00B14211 /* RTSPRequest.swift */; };
2931204E1D4522E400B14211 /* RTSPResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2931204D1D4522E400B14211 /* RTSPResponse.swift */; }; 2931204E1D4522E400B14211 /* RTSPResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2931204D1D4522E400B14211 /* RTSPResponse.swift */; };
2931204F1D4529F900B14211 /* RTSPRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2931204B1D4522CF00B14211 /* RTSPRequest.swift */; }; 2931204F1D4529F900B14211 /* RTSPRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2931204B1D4522CF00B14211 /* RTSPRequest.swift */; };
@ -36,6 +39,11 @@
2942424F1CF4C02300D65DCB /* MD5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2942424E1CF4C02300D65DCB /* MD5Tests.swift */; }; 2942424F1CF4C02300D65DCB /* MD5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2942424E1CF4C02300D65DCB /* MD5Tests.swift */; };
294852571D852499002DE492 /* RTMPTSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 294852551D84BFAD002DE492 /* RTMPTSocket.swift */; }; 294852571D852499002DE492 /* RTMPTSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 294852551D84BFAD002DE492 /* RTMPTSocket.swift */; };
2955F51F1D09EBAD004CC995 /* VisualEffect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296897461CDB01D20074D5F0 /* VisualEffect.swift */; }; 2955F51F1D09EBAD004CC995 /* VisualEffect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296897461CDB01D20074D5F0 /* VisualEffect.swift */; };
2962425E1D8BFC7B00C451A3 /* MovieClipHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292D8A301D8B233C00DBECE2 /* MovieClipHandler.swift */; };
296242611D8DB86500C451A3 /* TSReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2962425F1D8DB86500C451A3 /* TSReader.swift */; };
296242621D8DB86500C451A3 /* TSWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296242601D8DB86500C451A3 /* TSWriter.swift */; };
296242631D8DBA8C00C451A3 /* TSReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2962425F1D8DB86500C451A3 /* TSReader.swift */; };
296242641D8DBA9000C451A3 /* TSWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296242601D8DB86500C451A3 /* TSWriter.swift */; };
2965434F1D62FAED00734698 /* RTMPConnectionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2965434E1D62FAED00734698 /* RTMPConnectionTests.m */; }; 2965434F1D62FAED00734698 /* RTMPConnectionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2965434E1D62FAED00734698 /* RTMPConnectionTests.m */; };
296543601D62FE6A00734698 /* AudioUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296543561D62FE6200734698 /* AudioUtil.swift */; }; 296543601D62FE6A00734698 /* AudioUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296543561D62FE6200734698 /* AudioUtil.swift */; };
296543611D62FE7100734698 /* GLLFView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296543571D62FE6200734698 /* GLLFView.swift */; }; 296543611D62FE7100734698 /* GLLFView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296543571D62FE6200734698 /* GLLFView.swift */; };
@ -54,7 +62,7 @@
2976A4821D4902CE00B53EF2 /* IOComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2976A4801D49025B00B53EF2 /* IOComponent.swift */; }; 2976A4821D4902CE00B53EF2 /* IOComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2976A4801D49025B00B53EF2 /* IOComponent.swift */; };
2976A4861D4903C300B53EF2 /* DeviceUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2976A4851D4903C300B53EF2 /* DeviceUtil.swift */; }; 2976A4861D4903C300B53EF2 /* DeviceUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2976A4851D4903C300B53EF2 /* DeviceUtil.swift */; };
2976A4871D49045700B53EF2 /* DeviceUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2976A4851D4903C300B53EF2 /* DeviceUtil.swift */; }; 2976A4871D49045700B53EF2 /* DeviceUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2976A4851D4903C300B53EF2 /* DeviceUtil.swift */; };
29798E521CE5DF1A00F5CBD0 /* MP4File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29798E511CE5DF1900F5CBD0 /* MP4File.swift */; }; 29798E521CE5DF1A00F5CBD0 /* MP4Reader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29798E511CE5DF1900F5CBD0 /* MP4Reader.swift */; };
29798E671CE610F500F5CBD0 /* lf.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29B8761B1CD701F900FC07DA /* lf.framework */; }; 29798E671CE610F500F5CBD0 /* lf.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29B8761B1CD701F900FC07DA /* lf.framework */; };
29798E691CE6110F00F5CBD0 /* ASClassTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876CE1CD70CE700FC07DA /* ASClassTests.swift */; }; 29798E691CE6110F00F5CBD0 /* ASClassTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876CE1CD70CE700FC07DA /* ASClassTests.swift */; };
29798E6A1CE6110F00F5CBD0 /* ByteArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876CF1CD70CE700FC07DA /* ByteArrayTests.swift */; }; 29798E6A1CE6110F00F5CBD0 /* ByteArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876CF1CD70CE700FC07DA /* ByteArrayTests.swift */; };
@ -81,7 +89,6 @@
299B131D1D35272D00A1E8F5 /* ScreenCaptureSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 299B131C1D35272D00A1E8F5 /* ScreenCaptureSession.swift */; }; 299B131D1D35272D00A1E8F5 /* ScreenCaptureSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 299B131C1D35272D00A1E8F5 /* ScreenCaptureSession.swift */; };
299B13271D3B751400A1E8F5 /* LFView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 299B13261D3B751400A1E8F5 /* LFView.swift */; }; 299B13271D3B751400A1E8F5 /* LFView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 299B13261D3B751400A1E8F5 /* LFView.swift */; };
29A39C8E1D85BF6F007C27E9 /* BroadcastViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29A39C831D85BF21007C27E9 /* BroadcastViewController.swift */; }; 29A39C8E1D85BF6F007C27E9 /* BroadcastViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29A39C831D85BF21007C27E9 /* BroadcastViewController.swift */; };
29A39C901D85BF9C007C27E9 /* SampleHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29A39C8A1D85BF30007C27E9 /* SampleHandler.swift */; };
29A39C921D85CF5F007C27E9 /* RTMPSampleHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29A39C911D85CF5E007C27E9 /* RTMPSampleHandler.swift */; }; 29A39C921D85CF5F007C27E9 /* RTMPSampleHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29A39C911D85CF5E007C27E9 /* RTMPSampleHandler.swift */; };
29AF3FCF1D7C744C00E41212 /* NetStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29AF3FCE1D7C744C00E41212 /* NetStream.swift */; }; 29AF3FCF1D7C744C00E41212 /* NetStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29AF3FCE1D7C744C00E41212 /* NetStream.swift */; };
29AF3FD01D7C745200E41212 /* NetStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29AF3FCE1D7C744C00E41212 /* NetStream.swift */; }; 29AF3FD01D7C745200E41212 /* NetStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29AF3FCE1D7C744C00E41212 /* NetStream.swift */; };
@ -98,8 +105,6 @@
29B876781CD70ACE00FC07DA /* HTTPService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876711CD70ACE00FC07DA /* HTTPService.swift */; }; 29B876781CD70ACE00FC07DA /* HTTPService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876711CD70ACE00FC07DA /* HTTPService.swift */; };
29B876791CD70ACE00FC07DA /* HTTPStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876721CD70ACE00FC07DA /* HTTPStream.swift */; }; 29B876791CD70ACE00FC07DA /* HTTPStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876721CD70ACE00FC07DA /* HTTPStream.swift */; };
29B8767A1CD70ACE00FC07DA /* M3U.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876731CD70ACE00FC07DA /* M3U.swift */; }; 29B8767A1CD70ACE00FC07DA /* M3U.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876731CD70ACE00FC07DA /* M3U.swift */; };
29B8767B1CD70ACE00FC07DA /* TSReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876741CD70ACE00FC07DA /* TSReader.swift */; };
29B8767C1CD70ACE00FC07DA /* TSWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876751CD70ACE00FC07DA /* TSWriter.swift */; };
29B876831CD70AE800FC07DA /* AudioSpecificConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B8767D1CD70AE800FC07DA /* AudioSpecificConfig.swift */; }; 29B876831CD70AE800FC07DA /* AudioSpecificConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B8767D1CD70AE800FC07DA /* AudioSpecificConfig.swift */; };
29B876841CD70AE800FC07DA /* H264+AVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B8767E1CD70AE800FC07DA /* H264+AVC.swift */; }; 29B876841CD70AE800FC07DA /* H264+AVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B8767E1CD70AE800FC07DA /* H264+AVC.swift */; };
29B876851CD70AE800FC07DA /* NALUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B8767F1CD70AE800FC07DA /* NALUnit.swift */; }; 29B876851CD70AE800FC07DA /* NALUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B8767F1CD70AE800FC07DA /* NALUnit.swift */; };
@ -143,8 +148,6 @@
29B876F81CD70D5900FC07DA /* HTTPService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876711CD70ACE00FC07DA /* HTTPService.swift */; }; 29B876F81CD70D5900FC07DA /* HTTPService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876711CD70ACE00FC07DA /* HTTPService.swift */; };
29B876F91CD70D5900FC07DA /* HTTPStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876721CD70ACE00FC07DA /* HTTPStream.swift */; }; 29B876F91CD70D5900FC07DA /* HTTPStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876721CD70ACE00FC07DA /* HTTPStream.swift */; };
29B876FA1CD70D5900FC07DA /* M3U.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876731CD70ACE00FC07DA /* M3U.swift */; }; 29B876FA1CD70D5900FC07DA /* M3U.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876731CD70ACE00FC07DA /* M3U.swift */; };
29B876FB1CD70D5A00FC07DA /* TSReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876741CD70ACE00FC07DA /* TSReader.swift */; };
29B876FC1CD70D5A00FC07DA /* TSWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876751CD70ACE00FC07DA /* TSWriter.swift */; };
29B876FD1CD70D5A00FC07DA /* AudioSpecificConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B8767D1CD70AE800FC07DA /* AudioSpecificConfig.swift */; }; 29B876FD1CD70D5A00FC07DA /* AudioSpecificConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B8767D1CD70AE800FC07DA /* AudioSpecificConfig.swift */; };
29B876FE1CD70D5A00FC07DA /* H264+AVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B8767E1CD70AE800FC07DA /* H264+AVC.swift */; }; 29B876FE1CD70D5A00FC07DA /* H264+AVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B8767E1CD70AE800FC07DA /* H264+AVC.swift */; };
29B876FF1CD70D5A00FC07DA /* NALUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B8767F1CD70AE800FC07DA /* NALUnit.swift */; }; 29B876FF1CD70D5A00FC07DA /* NALUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B8767F1CD70AE800FC07DA /* NALUnit.swift */; };
@ -282,6 +285,8 @@
291F4E361CF206E200F59C51 /* Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Icon.png; path = Examples/iOS/Icon.png; sourceTree = "<group>"; }; 291F4E361CF206E200F59C51 /* Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Icon.png; path = Examples/iOS/Icon.png; sourceTree = "<group>"; };
2923A1FA1D63011E0019FBCD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Examples/macOS/Base.lproj/MainMenu.xib; sourceTree = "<group>"; }; 2923A1FA1D63011E0019FBCD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Examples/macOS/Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
29245AEC1D3233EB00AFFB9A /* VideoGravityUtil.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = VideoGravityUtil.swift; path = Sources/Util/VideoGravityUtil.swift; sourceTree = SOURCE_ROOT; }; 29245AEC1D3233EB00AFFB9A /* VideoGravityUtil.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = VideoGravityUtil.swift; path = Sources/Util/VideoGravityUtil.swift; sourceTree = SOURCE_ROOT; };
292D8A301D8B233C00DBECE2 /* MovieClipHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MovieClipHandler.swift; path = Examples/iOS/Screencast/MovieClipHandler.swift; sourceTree = "<group>"; };
292D8A321D8B293300DBECE2 /* MP4Sampler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MP4Sampler.swift; path = Sources/ISO/MP4Sampler.swift; sourceTree = SOURCE_ROOT; };
2931204B1D4522CF00B14211 /* RTSPRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RTSPRequest.swift; path = Sources/RTSP/RTSPRequest.swift; sourceTree = SOURCE_ROOT; }; 2931204B1D4522CF00B14211 /* RTSPRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RTSPRequest.swift; path = Sources/RTSP/RTSPRequest.swift; sourceTree = SOURCE_ROOT; };
2931204D1D4522E400B14211 /* RTSPResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RTSPResponse.swift; path = Sources/RTSP/RTSPResponse.swift; sourceTree = SOURCE_ROOT; }; 2931204D1D4522E400B14211 /* RTSPResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RTSPResponse.swift; path = Sources/RTSP/RTSPResponse.swift; sourceTree = SOURCE_ROOT; };
293C74361D85D56D001ED43C /* MainInterface.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = MainInterface.storyboard; path = Examples/iOS/ScreencastUI/Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; }; 293C74361D85D56D001ED43C /* MainInterface.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = MainInterface.storyboard; path = Examples/iOS/ScreencastUI/Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
@ -289,6 +294,8 @@
2942424E1CF4C02300D65DCB /* MD5Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MD5Tests.swift; sourceTree = "<group>"; }; 2942424E1CF4C02300D65DCB /* MD5Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MD5Tests.swift; sourceTree = "<group>"; };
2945CBBD1B4BE66000104112 /* lf.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = lf.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 2945CBBD1B4BE66000104112 /* lf.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = lf.framework; sourceTree = BUILT_PRODUCTS_DIR; };
294852551D84BFAD002DE492 /* RTMPTSocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RTMPTSocket.swift; path = Sources/RTMP/RTMPTSocket.swift; sourceTree = SOURCE_ROOT; }; 294852551D84BFAD002DE492 /* RTMPTSocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RTMPTSocket.swift; path = Sources/RTMP/RTMPTSocket.swift; sourceTree = SOURCE_ROOT; };
2962425F1D8DB86500C451A3 /* TSReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TSReader.swift; path = Sources/ISO/TSReader.swift; sourceTree = SOURCE_ROOT; };
296242601D8DB86500C451A3 /* TSWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TSWriter.swift; path = Sources/ISO/TSWriter.swift; sourceTree = SOURCE_ROOT; };
2965434E1D62FAED00734698 /* RTMPConnectionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RTMPConnectionTests.m; sourceTree = "<group>"; }; 2965434E1D62FAED00734698 /* RTMPConnectionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RTMPConnectionTests.m; sourceTree = "<group>"; };
296543561D62FE6200734698 /* AudioUtil.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AudioUtil.swift; path = Platforms/macOS/AudioUtil.swift; sourceTree = "<group>"; }; 296543561D62FE6200734698 /* AudioUtil.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AudioUtil.swift; path = Platforms/macOS/AudioUtil.swift; sourceTree = "<group>"; };
296543571D62FE6200734698 /* GLLFView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GLLFView.swift; path = Platforms/macOS/GLLFView.swift; sourceTree = "<group>"; }; 296543571D62FE6200734698 /* GLLFView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GLLFView.swift; path = Platforms/macOS/GLLFView.swift; sourceTree = "<group>"; };
@ -312,7 +319,7 @@
2976A47D1D48C5C700B53EF2 /* AVMixerRecorder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AVMixerRecorder.swift; path = Sources/Media/AVMixerRecorder.swift; sourceTree = SOURCE_ROOT; }; 2976A47D1D48C5C700B53EF2 /* AVMixerRecorder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AVMixerRecorder.swift; path = Sources/Media/AVMixerRecorder.swift; sourceTree = SOURCE_ROOT; };
2976A4801D49025B00B53EF2 /* IOComponent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = IOComponent.swift; path = Sources/Media/IOComponent.swift; sourceTree = SOURCE_ROOT; }; 2976A4801D49025B00B53EF2 /* IOComponent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = IOComponent.swift; path = Sources/Media/IOComponent.swift; sourceTree = SOURCE_ROOT; };
2976A4851D4903C300B53EF2 /* DeviceUtil.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DeviceUtil.swift; path = Sources/Util/DeviceUtil.swift; sourceTree = SOURCE_ROOT; }; 2976A4851D4903C300B53EF2 /* DeviceUtil.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DeviceUtil.swift; path = Sources/Util/DeviceUtil.swift; sourceTree = SOURCE_ROOT; };
29798E511CE5DF1900F5CBD0 /* MP4File.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MP4File.swift; path = Sources/ISO/MP4File.swift; sourceTree = SOURCE_ROOT; }; 29798E511CE5DF1900F5CBD0 /* MP4Reader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MP4Reader.swift; path = Sources/ISO/MP4Reader.swift; sourceTree = SOURCE_ROOT; };
29798E591CE60E5300F5CBD0 /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 29798E591CE60E5300F5CBD0 /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
29798E5D1CE60E5300F5CBD0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 29798E5D1CE60E5300F5CBD0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
2981B7F31D7345D5002FA821 /* SessionDescription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SessionDescription.swift; path = Sources/RTSP/SessionDescription.swift; sourceTree = SOURCE_ROOT; }; 2981B7F31D7345D5002FA821 /* SessionDescription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SessionDescription.swift; path = Sources/RTSP/SessionDescription.swift; sourceTree = SOURCE_ROOT; };
@ -331,7 +338,6 @@
29A39C831D85BF21007C27E9 /* BroadcastViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BroadcastViewController.swift; path = Examples/iOS/ScreencastUI/BroadcastViewController.swift; sourceTree = "<group>"; }; 29A39C831D85BF21007C27E9 /* BroadcastViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BroadcastViewController.swift; path = Examples/iOS/ScreencastUI/BroadcastViewController.swift; sourceTree = "<group>"; };
29A39C841D85BF21007C27E9 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Examples/iOS/ScreencastUI/Info.plist; sourceTree = "<group>"; }; 29A39C841D85BF21007C27E9 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Examples/iOS/ScreencastUI/Info.plist; sourceTree = "<group>"; };
29A39C881D85BF30007C27E9 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Examples/iOS/Screencast/Info.plist; sourceTree = "<group>"; }; 29A39C881D85BF30007C27E9 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Examples/iOS/Screencast/Info.plist; sourceTree = "<group>"; };
29A39C8A1D85BF30007C27E9 /* SampleHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SampleHandler.swift; path = Examples/iOS/Screencast/SampleHandler.swift; sourceTree = "<group>"; };
29A39C911D85CF5E007C27E9 /* RTMPSampleHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RTMPSampleHandler.swift; path = Platforms/iOS/RTMPSampleHandler.swift; sourceTree = "<group>"; }; 29A39C911D85CF5E007C27E9 /* RTMPSampleHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RTMPSampleHandler.swift; path = Platforms/iOS/RTMPSampleHandler.swift; sourceTree = "<group>"; };
29AF3FCE1D7C744C00E41212 /* NetStream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NetStream.swift; path = Sources/Net/NetStream.swift; sourceTree = SOURCE_ROOT; }; 29AF3FCE1D7C744C00E41212 /* NetStream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NetStream.swift; path = Sources/Net/NetStream.swift; sourceTree = SOURCE_ROOT; };
29B8761B1CD701F900FC07DA /* lf.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = lf.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 29B8761B1CD701F900FC07DA /* lf.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = lf.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@ -348,8 +354,6 @@
29B876711CD70ACE00FC07DA /* HTTPService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = HTTPService.swift; path = Sources/HTTP/HTTPService.swift; sourceTree = SOURCE_ROOT; }; 29B876711CD70ACE00FC07DA /* HTTPService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = HTTPService.swift; path = Sources/HTTP/HTTPService.swift; sourceTree = SOURCE_ROOT; };
29B876721CD70ACE00FC07DA /* HTTPStream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = HTTPStream.swift; path = Sources/HTTP/HTTPStream.swift; sourceTree = SOURCE_ROOT; }; 29B876721CD70ACE00FC07DA /* HTTPStream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = HTTPStream.swift; path = Sources/HTTP/HTTPStream.swift; sourceTree = SOURCE_ROOT; };
29B876731CD70ACE00FC07DA /* M3U.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = M3U.swift; path = Sources/HTTP/M3U.swift; sourceTree = SOURCE_ROOT; }; 29B876731CD70ACE00FC07DA /* M3U.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = M3U.swift; path = Sources/HTTP/M3U.swift; sourceTree = SOURCE_ROOT; };
29B876741CD70ACE00FC07DA /* TSReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TSReader.swift; path = Sources/HTTP/TSReader.swift; sourceTree = SOURCE_ROOT; };
29B876751CD70ACE00FC07DA /* TSWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TSWriter.swift; path = Sources/HTTP/TSWriter.swift; sourceTree = SOURCE_ROOT; };
29B8767D1CD70AE800FC07DA /* AudioSpecificConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AudioSpecificConfig.swift; path = Sources/ISO/AudioSpecificConfig.swift; sourceTree = SOURCE_ROOT; }; 29B8767D1CD70AE800FC07DA /* AudioSpecificConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AudioSpecificConfig.swift; path = Sources/ISO/AudioSpecificConfig.swift; sourceTree = SOURCE_ROOT; };
29B8767E1CD70AE800FC07DA /* H264+AVC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "H264+AVC.swift"; path = "Sources/ISO/H264+AVC.swift"; sourceTree = SOURCE_ROOT; }; 29B8767E1CD70AE800FC07DA /* H264+AVC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "H264+AVC.swift"; path = "Sources/ISO/H264+AVC.swift"; sourceTree = SOURCE_ROOT; };
29B8767F1CD70AE800FC07DA /* NALUnit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NALUnit.swift; path = Sources/ISO/NALUnit.swift; sourceTree = SOURCE_ROOT; }; 29B8767F1CD70AE800FC07DA /* NALUnit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NALUnit.swift; path = Sources/ISO/NALUnit.swift; sourceTree = SOURCE_ROOT; };
@ -666,11 +670,14 @@
children = ( children = (
29B8767D1CD70AE800FC07DA /* AudioSpecificConfig.swift */, 29B8767D1CD70AE800FC07DA /* AudioSpecificConfig.swift */,
29B8767E1CD70AE800FC07DA /* H264+AVC.swift */, 29B8767E1CD70AE800FC07DA /* H264+AVC.swift */,
29798E511CE5DF1900F5CBD0 /* MP4File.swift */, 29798E511CE5DF1900F5CBD0 /* MP4Reader.swift */,
292D8A321D8B293300DBECE2 /* MP4Sampler.swift */,
29B8767F1CD70AE800FC07DA /* NALUnit.swift */, 29B8767F1CD70AE800FC07DA /* NALUnit.swift */,
29B876801CD70AE800FC07DA /* PacketizedElementaryStream.swift */, 29B876801CD70AE800FC07DA /* PacketizedElementaryStream.swift */,
29B876811CD70AE800FC07DA /* ProgramSpecific.swift */, 29B876811CD70AE800FC07DA /* ProgramSpecific.swift */,
29B876821CD70AE800FC07DA /* TransportStream.swift */, 29B876821CD70AE800FC07DA /* TransportStream.swift */,
2962425F1D8DB86500C451A3 /* TSReader.swift */,
296242601D8DB86500C451A3 /* TSWriter.swift */,
); );
name = ISO; name = ISO;
sourceTree = "<group>"; sourceTree = "<group>";
@ -683,8 +690,6 @@
29B876711CD70ACE00FC07DA /* HTTPService.swift */, 29B876711CD70ACE00FC07DA /* HTTPService.swift */,
29B876721CD70ACE00FC07DA /* HTTPStream.swift */, 29B876721CD70ACE00FC07DA /* HTTPStream.swift */,
29B876731CD70ACE00FC07DA /* M3U.swift */, 29B876731CD70ACE00FC07DA /* M3U.swift */,
29B876741CD70ACE00FC07DA /* TSReader.swift */,
29B876751CD70ACE00FC07DA /* TSWriter.swift */,
); );
name = HTTP; name = HTTP;
sourceTree = "<group>"; sourceTree = "<group>";
@ -823,7 +828,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
29A39C881D85BF30007C27E9 /* Info.plist */, 29A39C881D85BF30007C27E9 /* Info.plist */,
29A39C8A1D85BF30007C27E9 /* SampleHandler.swift */, 292D8A301D8B233C00DBECE2 /* MovieClipHandler.swift */,
); );
name = Screencast; name = Screencast;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1485,7 +1490,7 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
29A39C901D85BF9C007C27E9 /* SampleHandler.swift in Sources */, 2962425E1D8BFC7B00C451A3 /* MovieClipHandler.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -1511,7 +1516,6 @@
29B8766E1CD70AB300FC07DA /* SiwftCore+Extension.swift in Sources */, 29B8766E1CD70AB300FC07DA /* SiwftCore+Extension.swift in Sources */,
2901A4EE1D437170002BBD23 /* ClockedQueue.swift in Sources */, 2901A4EE1D437170002BBD23 /* ClockedQueue.swift in Sources */,
29B876941CD70AFE00FC07DA /* SoundTransform.swift in Sources */, 29B876941CD70AFE00FC07DA /* SoundTransform.swift in Sources */,
29B8767B1CD70ACE00FC07DA /* TSReader.swift in Sources */,
2931204C1D4522CF00B14211 /* RTSPRequest.swift in Sources */, 2931204C1D4522CF00B14211 /* RTSPRequest.swift in Sources */,
29B876861CD70AE800FC07DA /* PacketizedElementaryStream.swift in Sources */, 29B876861CD70AE800FC07DA /* PacketizedElementaryStream.swift in Sources */,
29B876761CD70ACE00FC07DA /* HTTPRequest.swift in Sources */, 29B876761CD70ACE00FC07DA /* HTTPRequest.swift in Sources */,
@ -1519,6 +1523,7 @@
299AE0E51D44EC7800D26A49 /* RTSPSocket.swift in Sources */, 299AE0E51D44EC7800D26A49 /* RTSPSocket.swift in Sources */,
29DD70E61D68CF020021904A /* RTPPacket.swift in Sources */, 29DD70E61D68CF020021904A /* RTPPacket.swift in Sources */,
29B876AD1CD70B2800FC07DA /* ASClass.swift in Sources */, 29B876AD1CD70B2800FC07DA /* ASClass.swift in Sources */,
296242611D8DB86500C451A3 /* TSReader.swift in Sources */,
29B8766B1CD70AB300FC07DA /* Foundation+Extension.swift in Sources */, 29B8766B1CD70AB300FC07DA /* Foundation+Extension.swift in Sources */,
2981B7F41D7345D5002FA821 /* SessionDescription.swift in Sources */, 2981B7F41D7345D5002FA821 /* SessionDescription.swift in Sources */,
2931204E1D4522E400B14211 /* RTSPResponse.swift in Sources */, 2931204E1D4522E400B14211 /* RTSPResponse.swift in Sources */,
@ -1529,10 +1534,10 @@
299B13271D3B751400A1E8F5 /* LFView.swift in Sources */, 299B13271D3B751400A1E8F5 /* LFView.swift in Sources */,
29B876AF1CD70B2800FC07DA /* RTMPChunk.swift in Sources */, 29B876AF1CD70B2800FC07DA /* RTMPChunk.swift in Sources */,
29B876841CD70AE800FC07DA /* H264+AVC.swift in Sources */, 29B876841CD70AE800FC07DA /* H264+AVC.swift in Sources */,
296242621D8DB86500C451A3 /* TSWriter.swift in Sources */,
29B8769B1CD70B1100FC07DA /* MIME.swift in Sources */, 29B8769B1CD70B1100FC07DA /* MIME.swift in Sources */,
29A39C921D85CF5F007C27E9 /* RTMPSampleHandler.swift in Sources */, 29A39C921D85CF5F007C27E9 /* RTMPSampleHandler.swift in Sources */,
29B8769C1CD70B1100FC07DA /* NetClient.swift in Sources */, 29B8769C1CD70B1100FC07DA /* NetClient.swift in Sources */,
29B8767C1CD70ACE00FC07DA /* TSWriter.swift in Sources */,
29B876871CD70AE800FC07DA /* ProgramSpecific.swift in Sources */, 29B876871CD70AE800FC07DA /* ProgramSpecific.swift in Sources */,
29B876B01CD70B2800FC07DA /* RTMPConnection.swift in Sources */, 29B876B01CD70B2800FC07DA /* RTMPConnection.swift in Sources */,
29B876B61CD70B2800FC07DA /* RTMPStream.swift in Sources */, 29B876B61CD70B2800FC07DA /* RTMPStream.swift in Sources */,
@ -1558,7 +1563,8 @@
29B8769D1CD70B1100FC07DA /* NetService.swift in Sources */, 29B8769D1CD70B1100FC07DA /* NetService.swift in Sources */,
29B8769E1CD70B1100FC07DA /* NetSocket.swift in Sources */, 29B8769E1CD70B1100FC07DA /* NetSocket.swift in Sources */,
29B876791CD70ACE00FC07DA /* HTTPStream.swift in Sources */, 29B876791CD70ACE00FC07DA /* HTTPStream.swift in Sources */,
29798E521CE5DF1A00F5CBD0 /* MP4File.swift in Sources */, 292D8A331D8B293300DBECE2 /* MP4Sampler.swift in Sources */,
29798E521CE5DF1A00F5CBD0 /* MP4Reader.swift in Sources */,
29B876AC1CD70B2800FC07DA /* AMF3Serializer.swift in Sources */, 29B876AC1CD70B2800FC07DA /* AMF3Serializer.swift in Sources */,
29B876921CD70AFE00FC07DA /* AVMixer.swift in Sources */, 29B876921CD70AFE00FC07DA /* AVMixer.swift in Sources */,
29B876911CD70AFE00FC07DA /* AudioStreamPlayback.swift in Sources */, 29B876911CD70AFE00FC07DA /* AudioStreamPlayback.swift in Sources */,
@ -1616,16 +1622,18 @@
29B876F91CD70D5900FC07DA /* HTTPStream.swift in Sources */, 29B876F91CD70D5900FC07DA /* HTTPStream.swift in Sources */,
296543631D62FE9000734698 /* LFView.swift in Sources */, 296543631D62FE9000734698 /* LFView.swift in Sources */,
29B876FA1CD70D5900FC07DA /* M3U.swift in Sources */, 29B876FA1CD70D5900FC07DA /* M3U.swift in Sources */,
29B876FB1CD70D5A00FC07DA /* TSReader.swift in Sources */, 292D8A341D8B294900DBECE2 /* MP4Sampler.swift in Sources */,
29B876FC1CD70D5A00FC07DA /* TSWriter.swift in Sources */,
29B876FD1CD70D5A00FC07DA /* AudioSpecificConfig.swift in Sources */, 29B876FD1CD70D5A00FC07DA /* AudioSpecificConfig.swift in Sources */,
296242631D8DBA8C00C451A3 /* TSReader.swift in Sources */,
29B876FE1CD70D5A00FC07DA /* H264+AVC.swift in Sources */, 29B876FE1CD70D5A00FC07DA /* H264+AVC.swift in Sources */,
294852571D852499002DE492 /* RTMPTSocket.swift in Sources */, 294852571D852499002DE492 /* RTMPTSocket.swift in Sources */,
29245AEE1D32347E00AFFB9A /* VideoGravityUtil.swift in Sources */, 29245AEE1D32347E00AFFB9A /* VideoGravityUtil.swift in Sources */,
292D8A351D8B294E00DBECE2 /* MP4Reader.swift in Sources */,
29B876FF1CD70D5A00FC07DA /* NALUnit.swift in Sources */, 29B876FF1CD70D5A00FC07DA /* NALUnit.swift in Sources */,
29B877001CD70D5A00FC07DA /* PacketizedElementaryStream.swift in Sources */, 29B877001CD70D5A00FC07DA /* PacketizedElementaryStream.swift in Sources */,
29B877011CD70D5A00FC07DA /* ProgramSpecific.swift in Sources */, 29B877011CD70D5A00FC07DA /* ProgramSpecific.swift in Sources */,
29B877021CD70D5A00FC07DA /* TransportStream.swift in Sources */, 29B877021CD70D5A00FC07DA /* TransportStream.swift in Sources */,
296242641D8DBA9000C451A3 /* TSWriter.swift in Sources */,
29B877031CD70D5A00FC07DA /* AudioIOComponent.swift in Sources */, 29B877031CD70D5A00FC07DA /* AudioIOComponent.swift in Sources */,
29B877041CD70D5A00FC07DA /* AudioStreamPlayback.swift in Sources */, 29B877041CD70D5A00FC07DA /* AudioStreamPlayback.swift in Sources */,
2931204F1D4529F900B14211 /* RTSPRequest.swift in Sources */, 2931204F1D4529F900B14211 /* RTSPRequest.swift in Sources */,