BrashUp Replay Kit Live #81
This commit is contained in:
parent
7eacded159
commit
bc708b6dae
|
@ -30,9 +30,9 @@
|
|||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.broadcast-services</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).SampleHandler</string>
|
||||
<string>$(PRODUCT_MODULE_NAME).MovieClipHandler</string>
|
||||
<key>RPBroadcastProcessMode</key>
|
||||
<string>RPBroadcastProcessModeSampleBuffer</string>
|
||||
<string>RPBroadcastProcessModeMP4Clip</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
import lf
|
||||
import Foundation
|
||||
|
||||
class MovieClipHandler: RTMPMP4ClipHandler {
|
||||
}
|
|
@ -22,6 +22,10 @@ class BroadcastViewController: UIViewController {
|
|||
]
|
||||
|
||||
let broadcastConfiguration:RPBroadcastConfiguration = RPBroadcastConfiguration()
|
||||
broadcastConfiguration.clipDuration = 2
|
||||
broadcastConfiguration.videoCompressionProperties = [
|
||||
AVVideoProfileLevelKey: AVVideoProfileLevelH264BaselineAutoLevel as NSSecureCoding & NSObjectProtocol,
|
||||
]
|
||||
|
||||
self.extensionContext?.completeRequest(
|
||||
withBroadcast: broadcastURL,
|
||||
|
|
|
@ -158,12 +158,14 @@ final class LiveViewController: NSViewController {
|
|||
httpStream.attach(camera: nil)
|
||||
rtmpStream.attach(audio: DeviceUtil.device(withLocalizedName: audioPopUpButton.itemTitles[audioPopUpButton.indexOfSelectedItem], mediaType: AVMediaTypeAudio))
|
||||
rtmpStream.attach(camera: DeviceUtil.device(withLocalizedName: cameraPopUpButton.itemTitles[cameraPopUpButton.indexOfSelectedItem], mediaType: AVMediaTypeVideo))
|
||||
lfView.attach(stream: rtmpStream)
|
||||
urlField.stringValue = LiveViewController.defaultURL
|
||||
case 1:
|
||||
rtmpStream.attach(audio: nil)
|
||||
rtmpStream.attach(camera: nil)
|
||||
httpStream.attach(audio: DeviceUtil.device(withLocalizedName: audioPopUpButton.itemTitles[audioPopUpButton.indexOfSelectedItem], mediaType: AVMediaTypeAudio))
|
||||
httpStream.attach(camera: DeviceUtil.device(withLocalizedName: cameraPopUpButton.itemTitles[cameraPopUpButton.indexOfSelectedItem], mediaType: AVMediaTypeVideo))
|
||||
lfView.attach(stream: httpStream)
|
||||
urlField.stringValue = "http://{ipAddress}:8080/hello/playlist.m3u8"
|
||||
default:
|
||||
break
|
||||
|
|
|
@ -3,20 +3,39 @@ import Foundation
|
|||
|
||||
@available(iOS 10.0, *)
|
||||
public class RTMPBroadcaster: RTMPConnection {
|
||||
private var stream:RTMPStream?
|
||||
static let `default`:RTMPBroadcaster = RTMPBroadcaster()
|
||||
|
||||
func process(sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) {
|
||||
guard let stream:RTMPStream = stream, stream.readyState == .publishing else {
|
||||
private var stream:RTMPStream!
|
||||
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
|
||||
}
|
||||
switch sampleBufferType {
|
||||
case .video:
|
||||
stream.mixer.videoIO.captureOutput(nil, didOutputSampleBuffer: sampleBuffer, from: nil)
|
||||
case .audioApp:
|
||||
break
|
||||
case .audioMic:
|
||||
stream.mixer.audioIO.captureOutput(nil, didOutputSampleBuffer: sampleBuffer, from: nil)
|
||||
}
|
||||
stream.append(url: url)
|
||||
completionHandler()
|
||||
}
|
||||
|
||||
override func on(status:Notification) {
|
||||
|
@ -27,8 +46,7 @@ public class RTMPBroadcaster: RTMPConnection {
|
|||
}
|
||||
switch code {
|
||||
case RTMPConnection.Code.connectSuccess.rawValue:
|
||||
stream = RTMPStream(connection: self)
|
||||
stream?.publish("live")
|
||||
stream.publish("live")
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
@ -36,34 +54,21 @@ public class RTMPBroadcaster: RTMPConnection {
|
|||
}
|
||||
|
||||
@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]?) {
|
||||
logger.info("broadcastStarted:\(setupInfo)")
|
||||
override open func processMP4Clip(with mp4ClipURL: URL?, setupInfo: [String : NSObject]?, finished: Bool) {
|
||||
guard let endpointURL:String = setupInfo?["endpointURL"] as? String else {
|
||||
return
|
||||
}
|
||||
RTMPSampleHandler.broadcaster = RTMPBroadcaster()
|
||||
RTMPSampleHandler.broadcaster?.connect(endpointURL, arguments: nil)
|
||||
}
|
||||
|
||||
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)
|
||||
RTMPBroadcaster.default.connect(endpointURL, arguments: nil)
|
||||
RTMPBroadcaster.default.processMP4Clip(mp4ClipURL: mp4ClipURL) {_ in
|
||||
if (finished) {
|
||||
RTMPBroadcaster.default.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,13 +37,16 @@ class MP4Box {
|
|||
}
|
||||
}
|
||||
|
||||
var leafNode:Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
fileprivate(set) var type:String = "undf"
|
||||
fileprivate(set) var size:UInt32 = 0
|
||||
fileprivate(set) var offset:UInt64 = 0
|
||||
fileprivate(set) var parent:MP4Box? = nil
|
||||
|
||||
var leafNode:Bool {
|
||||
return false
|
||||
init() {
|
||||
}
|
||||
|
||||
init(size: UInt32, type:String) {
|
||||
|
@ -51,7 +54,7 @@ class MP4Box {
|
|||
self.type = type
|
||||
}
|
||||
|
||||
func loadFile(_ fileHandle: FileHandle) throws -> UInt32 {
|
||||
func load(_ fileHandle:FileHandle) throws -> UInt32 {
|
||||
if (size == 0) {
|
||||
size = UInt32(fileHandle.seekToEndOfFile() - offset)
|
||||
return size
|
||||
|
@ -60,7 +63,7 @@ class MP4Box {
|
|||
return size
|
||||
}
|
||||
|
||||
func getBoxesByName(_ name:String) -> [MP4Box] {
|
||||
func getBoxes(byName:String) -> [MP4Box] {
|
||||
return []
|
||||
}
|
||||
|
||||
|
@ -76,8 +79,8 @@ class MP4Box {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: CustomStringConvertible
|
||||
extension MP4Box: CustomStringConvertible {
|
||||
// MARK: CustomStringConvertible
|
||||
var description:String {
|
||||
return Mirror(reflecting: self).description
|
||||
}
|
||||
|
@ -92,29 +95,29 @@ class MP4ContainerBox: MP4Box {
|
|||
return false
|
||||
}
|
||||
|
||||
override func loadFile(_ fileHandle: FileHandle) throws -> UInt32 {
|
||||
override func load(_ file:FileHandle) throws -> UInt32 {
|
||||
children.removeAll(keepingCapacity: false)
|
||||
|
||||
var offset:UInt32 = parent == nil ? 0 : 8
|
||||
fileHandle.seek(toFileOffset: self.offset + UInt64(offset))
|
||||
file.seek(toFileOffset: self.offset + UInt64(offset))
|
||||
|
||||
while (size != offset) {
|
||||
let child:MP4Box = try create(fileHandle.readData(ofLength: 8), offset: offset)
|
||||
offset += try child.loadFile(fileHandle)
|
||||
let child:MP4Box = try create(file.readData(ofLength: 8), offset: offset)
|
||||
offset += try child.load(file)
|
||||
children.append(child)
|
||||
}
|
||||
|
||||
return offset
|
||||
}
|
||||
|
||||
override func getBoxesByName(_ name:String) -> [MP4Box] {
|
||||
override func getBoxes(byName:String) -> [MP4Box] {
|
||||
var list:[MP4Box] = []
|
||||
for child in children {
|
||||
if (name == child.type || name == "*" ) {
|
||||
if (byName == child.type || byName == "*" ) {
|
||||
list.append(child)
|
||||
}
|
||||
if (!child.leafNode) {
|
||||
list += child.getBoxesByName(name)
|
||||
list += child.getBoxes(byName: byName)
|
||||
}
|
||||
}
|
||||
return list
|
||||
|
@ -139,7 +142,7 @@ final class MP4MediaHeaderBox: MP4Box {
|
|||
var language: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))
|
||||
version = try buffer.readUInt8()
|
||||
buffer.position += 3
|
||||
|
@ -158,7 +161,7 @@ final class MP4MediaHeaderBox: MP4Box {
|
|||
final class MP4ChunkOffsetBox: MP4Box {
|
||||
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))
|
||||
buffer.position += 4
|
||||
|
||||
|
@ -176,7 +179,7 @@ final class MP4ChunkOffsetBox: MP4Box {
|
|||
final class MP4SyncSampleBox: MP4Box {
|
||||
var entries:[UInt32] = []
|
||||
|
||||
override func loadFile(_ fileHandle: FileHandle) throws -> UInt32 {
|
||||
override func load(_ fileHandle: FileHandle) throws -> UInt32 {
|
||||
entries.removeAll(keepingCapacity: false)
|
||||
|
||||
let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: Int(size) - 8))
|
||||
|
@ -209,7 +212,7 @@ final class MP4TimeToSampleBox: MP4Box {
|
|||
|
||||
var entries:[Entry] = []
|
||||
|
||||
override func loadFile(_ fileHandle: FileHandle) throws -> UInt32 {
|
||||
override func load(_ fileHandle: FileHandle) throws -> UInt32 {
|
||||
entries.removeAll(keepingCapacity: false)
|
||||
|
||||
let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: Int(size) - 8))
|
||||
|
@ -231,7 +234,7 @@ final class MP4TimeToSampleBox: MP4Box {
|
|||
final class MP4SampleSizeBox: MP4Box {
|
||||
var entries:[UInt32] = []
|
||||
|
||||
override func loadFile(_ fileHandle: FileHandle) throws -> UInt32 {
|
||||
override func load(_ fileHandle: FileHandle) throws -> UInt32 {
|
||||
entries.removeAll(keepingCapacity: false)
|
||||
|
||||
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 streamPriority:UInt8 = 0
|
||||
|
||||
override func loadFile(_ fileHandle: FileHandle) throws -> UInt32 {
|
||||
override func load(_ fileHandle: FileHandle) throws -> UInt32 {
|
||||
var tagSize:UInt8 = 0
|
||||
let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: Int(self.size) - 8))
|
||||
buffer.position += 4
|
||||
|
@ -329,7 +332,7 @@ final class MP4AudioSampleEntryBox: MP4ContainerBox {
|
|||
|
||||
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))
|
||||
buffer.position += 8
|
||||
|
||||
|
@ -361,7 +364,7 @@ final class MP4AudioSampleEntryBox: MP4ContainerBox {
|
|||
fileHandle.seek(toFileOffset: self.offset + UInt64(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)
|
||||
|
||||
// skip
|
||||
|
@ -383,7 +386,7 @@ final class MP4VisualSampleEntryBox: MP4ContainerBox {
|
|||
var compressorname:String = ""
|
||||
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))
|
||||
|
||||
buffer.position += 24
|
||||
|
@ -402,7 +405,7 @@ final class MP4VisualSampleEntryBox: MP4ContainerBox {
|
|||
let child:MP4Box = try MP4Box.create(fileHandle.readData(ofLength: 8))
|
||||
child.parent = self
|
||||
child.offset = self.offset + UInt64(offset) + 8
|
||||
offset += try child.loadFile(fileHandle)
|
||||
offset += try child.load(fileHandle)
|
||||
children.append(child)
|
||||
|
||||
// skip
|
||||
|
@ -414,7 +417,7 @@ final class MP4VisualSampleEntryBox: MP4ContainerBox {
|
|||
|
||||
// MARK: -
|
||||
final class MP4SampleDescriptionBox: MP4ContainerBox {
|
||||
override func loadFile(_ fileHandle: FileHandle) throws -> UInt32 {
|
||||
override func load(_ fileHandle: FileHandle) throws -> UInt32 {
|
||||
children.removeAll(keepingCapacity: false)
|
||||
|
||||
let buffer:ByteArray = ByteArray(data: fileHandle.readData(ofLength: 8))
|
||||
|
@ -424,7 +427,7 @@ final class MP4SampleDescriptionBox: MP4ContainerBox {
|
|||
let numberOfEntries:UInt32 = try buffer.readUInt32()
|
||||
for _ in 0..<numberOfEntries {
|
||||
let child:MP4Box = try create(fileHandle.readData(ofLength: 8), offset: offset)
|
||||
offset += try child.loadFile(fileHandle)
|
||||
offset += try child.load(fileHandle)
|
||||
children.append(child)
|
||||
}
|
||||
|
||||
|
@ -452,7 +455,7 @@ final class MP4SampleToChunkBox: MP4Box {
|
|||
|
||||
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))
|
||||
buffer.position += 4
|
||||
|
||||
|
@ -491,7 +494,7 @@ final class MP4EditListBox: MP4Box {
|
|||
var version:UInt32 = 0
|
||||
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))
|
||||
|
||||
version = try buffer.readUInt32()
|
||||
|
@ -511,7 +514,7 @@ final class MP4EditListBox: MP4Box {
|
|||
}
|
||||
|
||||
// MARK: -
|
||||
final class MP4File: MP4ContainerBox {
|
||||
final class MP4Reader: MP4ContainerBox {
|
||||
var url:URL? = nil {
|
||||
didSet {
|
||||
if (url == nil) {
|
||||
|
@ -525,46 +528,46 @@ final class MP4File: MP4ContainerBox {
|
|||
}
|
||||
}
|
||||
|
||||
fileprivate var fileHandle:FileHandle? = nil
|
||||
|
||||
func isEmpty() -> Bool {
|
||||
return getBoxesByName("mdhd").isEmpty
|
||||
var isEmpty:Bool {
|
||||
return getBoxes(byName: "mdhd").isEmpty
|
||||
}
|
||||
|
||||
func readDataOfLength(_ length: Int) -> Data {
|
||||
return fileHandle!.readData(ofLength: length)
|
||||
private var fileHandle:FileHandle? = nil
|
||||
|
||||
func seek(toFileOffset: UInt64) {
|
||||
return fileHandle!.seek(toFileOffset: toFileOffset)
|
||||
}
|
||||
|
||||
func seekToFileOffset(_ offset: UInt64) {
|
||||
return fileHandle!.seek(toFileOffset: offset)
|
||||
func readData(ofLength: Int) -> Data {
|
||||
return fileHandle!.readData(ofLength: ofLength)
|
||||
}
|
||||
|
||||
func readDataOfBox(_ box:MP4Box) -> Data {
|
||||
if (fileHandle == nil) {
|
||||
func readData(ofBox:MP4Box) -> Data {
|
||||
guard let fileHandle:FileHandle = fileHandle else {
|
||||
return Data()
|
||||
}
|
||||
let currentOffsetInFile:UInt64 = fileHandle!.offsetInFile
|
||||
fileHandle!.seek(toFileOffset: box.offset + 8)
|
||||
let data:Data = fileHandle!.readData(ofLength: Int(box.size) - 8)
|
||||
fileHandle!.seek(toFileOffset: currentOffsetInFile)
|
||||
let currentOffsetInFile:UInt64 = fileHandle.offsetInFile
|
||||
fileHandle.seek(toFileOffset: ofBox.offset + 8)
|
||||
let data:Data = fileHandle.readData(ofLength: Int(ofBox.size) - 8)
|
||||
fileHandle.seek(toFileOffset: currentOffsetInFile)
|
||||
return data
|
||||
}
|
||||
|
||||
func loadFile() throws -> UInt32 {
|
||||
if (fileHandle == nil) {
|
||||
func load() throws -> UInt32 {
|
||||
guard let fileHandle:FileHandle = self.fileHandle else {
|
||||
return 0
|
||||
}
|
||||
return try self.loadFile(fileHandle!)
|
||||
return try load(fileHandle)
|
||||
}
|
||||
|
||||
func closeFile() {
|
||||
func close() {
|
||||
fileHandle?.closeFile()
|
||||
}
|
||||
|
||||
override func loadFile(_ fileHandle: FileHandle) throws -> UInt32 {
|
||||
override func load(_ fileHandle: FileHandle) throws -> UInt32 {
|
||||
let size:UInt64 = fileHandle.seekToEndOfFile()
|
||||
fileHandle.seek(toFileOffset: 0)
|
||||
self.size = UInt32(size)
|
||||
return try super.loadFile(fileHandle)
|
||||
return try super.load(fileHandle)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -194,7 +194,7 @@ struct PacketizedElementaryStream: PESPacketHeader {
|
|||
packet.adaptationFieldFlag = true
|
||||
packet.adaptationField = TSAdaptationField()
|
||||
packet.adaptationField?.PCRFlag = true
|
||||
packet.adaptationField?.PCR = TSProgramClockReference.encode(0, UInt16(PCR))
|
||||
packet.adaptationField?.PCR = TSProgramClockReference.encode(PCR, 0)
|
||||
packet.adaptationField?.compute()
|
||||
}
|
||||
packet.payloadUnitStartIndicator = true
|
||||
|
|
|
@ -58,10 +58,11 @@ class TSWriter {
|
|||
}
|
||||
|
||||
func writeSampleBuffer(_ PID:UInt16, streamID:UInt8, sampleBuffer:CMSampleBuffer) {
|
||||
let presentationTimeStamp:CMTime = sampleBuffer.presentationTimeStamp
|
||||
if (timestamps[PID] == nil) {
|
||||
timestamps[PID] = sampleBuffer.presentationTimeStamp
|
||||
timestamps[PID] = presentationTimeStamp
|
||||
if (PCRPID == PID) {
|
||||
PCRTimestamp = sampleBuffer.presentationTimeStamp
|
||||
PCRTimestamp = presentationTimeStamp
|
||||
}
|
||||
}
|
||||
let config:Any? = streamID == 192 ? audioConfig : videoConfig
|
||||
|
@ -71,9 +72,14 @@ class TSWriter {
|
|||
return
|
||||
}
|
||||
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)
|
||||
if (PCRPID == PID && rorateFileHandle(decodeTimeStamp, next: sampleBuffer.decodeTimeStamp)) {
|
||||
if (PCRPID == PID && rorateFileHandle(decodeTimeStamp)) {
|
||||
packets[0].adaptationField?.randomAccessIndicator = true
|
||||
packets[0].adaptationField?.discontinuityIndicator = true
|
||||
}
|
||||
|
@ -106,7 +112,7 @@ class TSWriter {
|
|||
return packets
|
||||
}
|
||||
|
||||
func rorateFileHandle(_ timestamp:CMTime, next:CMTime) -> Bool {
|
||||
func rorateFileHandle(_ timestamp:CMTime) -> Bool {
|
||||
let duration:Double = timestamp.seconds - rotatedTimestamp.seconds
|
||||
if (duration <= segmentDuration) {
|
||||
return false
|
|
@ -10,7 +10,7 @@ final class NetClient: NetSocket {
|
|||
static let defaultBufferSize:Int = 8192
|
||||
|
||||
weak var delegate:NetClientDelegate?
|
||||
fileprivate(set) var service:Foundation.NetService?
|
||||
private(set) var service:Foundation.NetService?
|
||||
|
||||
init(service:Foundation.NetService, inputStream:InputStream, outputStream:OutputStream) {
|
||||
super.init()
|
||||
|
|
|
@ -153,50 +153,51 @@ enum FLVAudioCodec:UInt8 {
|
|||
}
|
||||
|
||||
// MARK: -
|
||||
struct FLVTag {
|
||||
enum FLVTagType:UInt8 {
|
||||
case audio = 8
|
||||
case video = 9
|
||||
case data = 18
|
||||
|
||||
enum TagType: UInt8 {
|
||||
case audio = 8
|
||||
case video = 9
|
||||
case data = 18
|
||||
|
||||
var streamId:UInt16 {
|
||||
switch self {
|
||||
case .audio:
|
||||
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 streamId:UInt16 {
|
||||
switch self {
|
||||
case .audio:
|
||||
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 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
|
||||
|
||||
var tagType:TagType = .data
|
||||
var tagType:FLVTagType = .data
|
||||
var dataSize:UInt32 = 0
|
||||
var timestamp:UInt32 = 0
|
||||
var timestampExtended:UInt8 = 0
|
||||
|
|
|
@ -5,10 +5,12 @@ import Foundation
|
|||
*/
|
||||
open class Responder: NSObject {
|
||||
|
||||
private var result:(_ data:[Any?]) -> Void
|
||||
private var status:((_ data:[Any?]) -> Void)?
|
||||
public typealias Handler = (_ 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.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? {
|
||||
guard let uri:URL = uri else {
|
||||
return nil
|
||||
|
@ -378,6 +368,18 @@ open class RTMPConnection: EventDispatcher {
|
|||
|
||||
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 {
|
||||
|
|
|
@ -345,7 +345,7 @@ final class RTMPCommandMessage: RTMPMessage {
|
|||
case "close":
|
||||
connection.close()
|
||||
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
|
||||
}
|
||||
|
@ -371,7 +371,7 @@ final class RTMPDataMessage: RTMPMessage {
|
|||
var handlerName:String = ""
|
||||
var arguments:[Any?] = []
|
||||
|
||||
fileprivate var serializer:AMFSerializer = AMF0Serializer()
|
||||
private var serializer:AMFSerializer = AMF0Serializer()
|
||||
|
||||
override var payload:[UInt8] {
|
||||
get {
|
||||
|
@ -533,10 +533,10 @@ final class RTMPSharedObjectMessage: RTMPMessage {
|
|||
final class RTMPAudioMessage: RTMPMessage {
|
||||
var config:AudioSpecificConfig?
|
||||
|
||||
fileprivate(set) var codec:FLVAudioCodec = .unknown
|
||||
fileprivate(set) var soundRate:FLVSoundRate = .kHz44
|
||||
fileprivate(set) var soundSize:FLVSoundSize = .snd8bit
|
||||
fileprivate(set) var soundType:FLVSoundType = .stereo
|
||||
private(set) var codec:FLVAudioCodec = .unknown
|
||||
private(set) var soundRate:FLVSoundRate = .kHz44
|
||||
private(set) var soundSize:FLVSoundSize = .snd8bit
|
||||
private(set) var soundType:FLVSoundType = .stereo
|
||||
|
||||
var soundData:[UInt8] {
|
||||
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)
|
||||
*/
|
||||
final class RTMPVideoMessage: RTMPMessage {
|
||||
fileprivate(set) var codec:FLVVideoCodec = .unknown
|
||||
fileprivate(set) var status:OSStatus = noErr
|
||||
private(set) var codec:FLVVideoCodec = .unknown
|
||||
private(set) var status:OSStatus = noErr
|
||||
|
||||
init() {
|
||||
super.init(type: .video)
|
||||
|
@ -638,7 +638,7 @@ final class RTMPVideoMessage: RTMPMessage {
|
|||
return
|
||||
}
|
||||
OSAtomicAdd64(Int64(payload.count), &stream.info.byteCount)
|
||||
guard FLVTag.TagType.video.headerSize < payload.count else {
|
||||
guard FLVTagType.video.headerSize < payload.count else {
|
||||
return
|
||||
}
|
||||
switch payload[1] {
|
||||
|
@ -661,7 +661,7 @@ final class RTMPVideoMessage: RTMPMessage {
|
|||
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
|
||||
let sampleSize:Int = bytes.count
|
||||
var blockBuffer:CMBlockBuffer?
|
||||
|
@ -680,7 +680,7 @@ final class RTMPVideoMessage: RTMPMessage {
|
|||
|
||||
func createFormatDescription(_ stream: RTMPStream) -> OSStatus {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -764,7 +764,7 @@ final class RTMPUserControlMessage: RTMPMessage {
|
|||
message: RTMPUserControlMessage(event: .pong, value: value)
|
||||
))
|
||||
case .bufferEmpty, .bufferFull:
|
||||
connection.streams[UInt32(value)]?.dispatch(type: "rtmpStatus", bubbles: false, data: [
|
||||
connection.streams[UInt32(value)]?.dispatch("rtmpStatus", bubbles: false, data: [
|
||||
"level": "status",
|
||||
"code": description,
|
||||
"description": ""
|
||||
|
|
|
@ -8,8 +8,12 @@ protocol RTMPMuxerDelegate: class {
|
|||
|
||||
// MARK: -
|
||||
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 videoTimestamp:CMTime = kCMTimeZero
|
||||
|
||||
|
@ -21,7 +25,6 @@ final class RTMPMuxer {
|
|||
|
||||
extension RTMPMuxer: 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?) {
|
||||
guard let formatDescription:CMFormatDescription = formatDescription else {
|
||||
|
@ -95,3 +98,49 @@ extension RTMPMuxer: VideoEncoderDelegate {
|
|||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -192,7 +192,7 @@ open class RTMPSharedObject: EventDispatcher {
|
|||
}
|
||||
changeList.append(change)
|
||||
}
|
||||
dispatch(type: Event.SYNC, bubbles: false, data: changeList)
|
||||
dispatch(Event.SYNC, bubbles: false, data: changeList)
|
||||
}
|
||||
|
||||
func createChunk(_ events:[RTMPSharedObjectEvent]) -> RTMPChunk {
|
||||
|
|
|
@ -150,7 +150,7 @@ final class RTMPSocket: NetSocket, RTMPSocketCompatible {
|
|||
|
||||
override func didTimeout() {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -224,7 +224,7 @@ open class RTMPStream: NetStream {
|
|||
open static let defaultVideoBitrate:UInt32 = AVCEncoder.defaultBitrate
|
||||
open internal(set) var info:RTMPStreamInfo = RTMPStreamInfo()
|
||||
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 {
|
||||
get { return audioPlayback.soundTransform }
|
||||
set { audioPlayback.soundTransform = newValue }
|
||||
|
@ -242,6 +242,7 @@ open class RTMPStream: NetStream {
|
|||
send(handlerName: "@setDataFrame", arguments: "onMetaData", createMetaData())
|
||||
mixer.audioIO.encoder.startRunning()
|
||||
mixer.videoIO.encoder.startRunning()
|
||||
sampler.startRunning()
|
||||
if (howToPublish == .localRecord) {
|
||||
mixer.recorder.fileName = info.resourceName
|
||||
mixer.recorder.startRunning()
|
||||
|
@ -256,8 +257,9 @@ open class RTMPStream: NetStream {
|
|||
var videoTimestamp:Double = 0
|
||||
fileprivate(set) var audioPlayback:RTMPAudioPlayback = RTMPAudioPlayback()
|
||||
fileprivate var muxer:RTMPMuxer = RTMPMuxer()
|
||||
fileprivate var frameCount:UInt8 = 0
|
||||
fileprivate var chunkTypes:[FLVTag.TagType:Bool] = [:]
|
||||
fileprivate var sampler:MP4Sampler = MP4Sampler()
|
||||
fileprivate var frameCount:UInt16 = 0
|
||||
fileprivate var chunkTypes:[FLVTagType:Bool] = [:]
|
||||
fileprivate var dispatcher:IEventDispatcher!
|
||||
fileprivate var howToPublish:RTMPStream.HowToPublish = .live
|
||||
fileprivate var rtmpConnection:RTMPConnection
|
||||
|
@ -390,6 +392,7 @@ open class RTMPStream: NetStream {
|
|||
self.mixer.videoIO.encoder.delegate = nil
|
||||
self.mixer.audioIO.encoder.stopRunning()
|
||||
self.mixer.videoIO.encoder.stopRunning()
|
||||
self.sampler.stopRunning()
|
||||
self.mixer.recorder.stopRunning()
|
||||
self.FCUnpublish()
|
||||
self.rtmpConnection.socket.doOutput(chunk: RTMPChunk(
|
||||
|
@ -419,6 +422,7 @@ open class RTMPStream: NetStream {
|
|||
#endif
|
||||
self.mixer.audioIO.encoder.delegate = self.muxer
|
||||
self.mixer.videoIO.encoder.delegate = self.muxer
|
||||
self.sampler.delegate = self.muxer
|
||||
self.mixer.startRunning()
|
||||
self.chunkTypes.removeAll()
|
||||
self.FCPublish()
|
||||
|
@ -491,6 +495,10 @@ open class RTMPStream: NetStream {
|
|||
return metadata
|
||||
}
|
||||
|
||||
func append(url:URL, completionHandler: MP4Sampler.Handler? = nil) {
|
||||
sampler.append(file: url, completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
func on(timer:Timer) {
|
||||
currentFPS = frameCount
|
||||
frameCount = 0
|
||||
|
@ -541,8 +549,8 @@ extension RTMPStream: IEventDispatcher {
|
|||
public func dispatch(event:Event) {
|
||||
dispatcher.dispatch(event: event)
|
||||
}
|
||||
public func dispatch(type:String, bubbles:Bool, data:Any?) {
|
||||
dispatcher.dispatch(type: type, bubbles: bubbles, data: data)
|
||||
public func dispatch(_ type:String, bubbles:Bool, data:Any?) {
|
||||
dispatcher.dispatch(type, bubbles: bubbles, data: data)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -552,11 +560,11 @@ extension RTMPStream: RTMPMuxerDelegate {
|
|||
guard readyState == .publishing else {
|
||||
return
|
||||
}
|
||||
let type:FLVTag.TagType = .audio
|
||||
let type:FLVTagType = .audio
|
||||
let length:Int = rtmpConnection.socket.doOutput(chunk: RTMPChunk(
|
||||
type: chunkTypes[type] == nil ? .zero : .one,
|
||||
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
|
||||
OSAtomicAdd64(Int64(length), &info.byteCount)
|
||||
|
@ -567,15 +575,15 @@ extension RTMPStream: RTMPMuxerDelegate {
|
|||
guard readyState == .publishing else {
|
||||
return
|
||||
}
|
||||
let type:FLVTag.TagType = .video
|
||||
let type:FLVTagType = .video
|
||||
let length:Int = rtmpConnection.socket.doOutput(chunk: RTMPChunk(
|
||||
type: chunkTypes[type] == nil ? .zero : .one,
|
||||
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
|
||||
OSAtomicAdd64(Int64(length), &info.byteCount)
|
||||
videoTimestamp = withTimestamp + (videoTimestamp - floor(videoTimestamp))
|
||||
frameCount = (frameCount + 1) & 0xFF
|
||||
frameCount += 1
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,15 +43,16 @@ final class RTMPTSocket: NSObject, RTMPSocketCompatible {
|
|||
}
|
||||
|
||||
private var delay:UInt8 = 1
|
||||
private var mutex:Mutex = Mutex()
|
||||
private var index:Int64 = 0
|
||||
private var events:[Event] = []
|
||||
private var baseURL:URL!
|
||||
private var session:URLSession!
|
||||
private var c2packet:[UInt8] = []
|
||||
private var isPending:Bool = false
|
||||
private let outputQueue:DispatchQueue = DispatchQueue(label: "com.github.shgoo4405.lf.RTMPTSocket.output")
|
||||
private var connectionID:String!
|
||||
private var outputBuffer:[UInt8] = []
|
||||
private var lastResponse:Date = Date()
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
|
@ -77,20 +78,13 @@ final class RTMPTSocket: NSObject, RTMPSocketCompatible {
|
|||
for chunk in chunks {
|
||||
bytes.append(contentsOf: chunk)
|
||||
}
|
||||
do {
|
||||
try mutex.lock()
|
||||
outputBuffer.append(contentsOf: bytes)
|
||||
if (!isPending) {
|
||||
isPending = true
|
||||
doOutput(bytes: outputBuffer)
|
||||
outputBuffer.removeAll()
|
||||
outputQueue.sync {
|
||||
self.outputBuffer.append(contentsOf: bytes)
|
||||
if (!self.isPending) {
|
||||
self.isPending = true
|
||||
self.doOutput(bytes: self.outputBuffer)
|
||||
self.outputBuffer.removeAll()
|
||||
}
|
||||
mutex.unlock()
|
||||
} catch {
|
||||
logger.warning("")
|
||||
}
|
||||
if (logger.isEnabledForLogLevel(.verbose)) {
|
||||
logger.verbose("\(chunk)")
|
||||
}
|
||||
return bytes.count
|
||||
}
|
||||
|
@ -121,17 +115,14 @@ final class RTMPTSocket: NSObject, RTMPSocketCompatible {
|
|||
return
|
||||
}
|
||||
|
||||
do {
|
||||
try mutex.lock()
|
||||
if (outputBuffer.isEmpty) {
|
||||
isPending = false
|
||||
lastResponse = Date()
|
||||
outputQueue.sync {
|
||||
if (self.outputBuffer.isEmpty) {
|
||||
self.isPending = false
|
||||
} else {
|
||||
doOutput(bytes: outputBuffer)
|
||||
outputBuffer.removeAll()
|
||||
self.doOutput(bytes: outputBuffer)
|
||||
self.outputBuffer.removeAll()
|
||||
}
|
||||
mutex.unlock()
|
||||
} catch {
|
||||
logger.warning()
|
||||
}
|
||||
|
||||
guard
|
||||
|
|
|
@ -7,7 +7,7 @@ public protocol IEventDispatcher: class {
|
|||
func addEventListener(_ type:String, selector:Selector, observer:AnyObject?, useCapture:Bool)
|
||||
func removeEventListener(_ type:String, selector:Selector, observer:AnyObject?, useCapture:Bool)
|
||||
func dispatch(event:Event)
|
||||
func dispatch(type:String, bubbles:Bool, data:Any?)
|
||||
func dispatch(_ type:String, bubbles:Bool, data:Any?)
|
||||
}
|
||||
|
||||
public enum EventPhase: UInt8 {
|
||||
|
@ -92,7 +92,7 @@ open class EventDispatcher: NSObject, IEventDispatcher {
|
|||
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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,9 @@
|
|||
29245AEE1D32347E00AFFB9A /* 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 */; };
|
||||
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 */; };
|
||||
2931204E1D4522E400B14211 /* RTSPResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2931204D1D4522E400B14211 /* RTSPResponse.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 */; };
|
||||
294852571D852499002DE492 /* RTMPTSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 294852551D84BFAD002DE492 /* RTMPTSocket.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 */; };
|
||||
296543601D62FE6A00734698 /* AudioUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296543561D62FE6200734698 /* AudioUtil.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 */; };
|
||||
2976A4861D4903C300B53EF2 /* 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 */; };
|
||||
29798E691CE6110F00F5CBD0 /* ASClassTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876CE1CD70CE700FC07DA /* ASClassTests.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 */; };
|
||||
299B13271D3B751400A1E8F5 /* LFView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 299B13261D3B751400A1E8F5 /* LFView.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 */; };
|
||||
29AF3FCF1D7C744C00E41212 /* 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 */; };
|
||||
29B876791CD70ACE00FC07DA /* HTTPStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876721CD70ACE00FC07DA /* HTTPStream.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 */; };
|
||||
29B876841CD70AE800FC07DA /* H264+AVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B8767E1CD70AE800FC07DA /* H264+AVC.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 */; };
|
||||
29B876F91CD70D5900FC07DA /* HTTPStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B876721CD70ACE00FC07DA /* HTTPStream.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 */; };
|
||||
29B876FE1CD70D5A00FC07DA /* H264+AVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29B8767E1CD70AE800FC07DA /* H264+AVC.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>"; };
|
||||
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; };
|
||||
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; };
|
||||
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>"; };
|
||||
|
@ -289,6 +294,8 @@
|
|||
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; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -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; };
|
||||
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; };
|
||||
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; };
|
||||
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; };
|
||||
|
@ -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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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; };
|
||||
|
@ -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; };
|
||||
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; };
|
||||
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; };
|
||||
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; };
|
||||
|
@ -666,11 +670,14 @@
|
|||
children = (
|
||||
29B8767D1CD70AE800FC07DA /* AudioSpecificConfig.swift */,
|
||||
29B8767E1CD70AE800FC07DA /* H264+AVC.swift */,
|
||||
29798E511CE5DF1900F5CBD0 /* MP4File.swift */,
|
||||
29798E511CE5DF1900F5CBD0 /* MP4Reader.swift */,
|
||||
292D8A321D8B293300DBECE2 /* MP4Sampler.swift */,
|
||||
29B8767F1CD70AE800FC07DA /* NALUnit.swift */,
|
||||
29B876801CD70AE800FC07DA /* PacketizedElementaryStream.swift */,
|
||||
29B876811CD70AE800FC07DA /* ProgramSpecific.swift */,
|
||||
29B876821CD70AE800FC07DA /* TransportStream.swift */,
|
||||
2962425F1D8DB86500C451A3 /* TSReader.swift */,
|
||||
296242601D8DB86500C451A3 /* TSWriter.swift */,
|
||||
);
|
||||
name = ISO;
|
||||
sourceTree = "<group>";
|
||||
|
@ -683,8 +690,6 @@
|
|||
29B876711CD70ACE00FC07DA /* HTTPService.swift */,
|
||||
29B876721CD70ACE00FC07DA /* HTTPStream.swift */,
|
||||
29B876731CD70ACE00FC07DA /* M3U.swift */,
|
||||
29B876741CD70ACE00FC07DA /* TSReader.swift */,
|
||||
29B876751CD70ACE00FC07DA /* TSWriter.swift */,
|
||||
);
|
||||
name = HTTP;
|
||||
sourceTree = "<group>";
|
||||
|
@ -823,7 +828,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
29A39C881D85BF30007C27E9 /* Info.plist */,
|
||||
29A39C8A1D85BF30007C27E9 /* SampleHandler.swift */,
|
||||
292D8A301D8B233C00DBECE2 /* MovieClipHandler.swift */,
|
||||
);
|
||||
name = Screencast;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1485,7 +1490,7 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
29A39C901D85BF9C007C27E9 /* SampleHandler.swift in Sources */,
|
||||
2962425E1D8BFC7B00C451A3 /* MovieClipHandler.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -1511,7 +1516,6 @@
|
|||
29B8766E1CD70AB300FC07DA /* SiwftCore+Extension.swift in Sources */,
|
||||
2901A4EE1D437170002BBD23 /* ClockedQueue.swift in Sources */,
|
||||
29B876941CD70AFE00FC07DA /* SoundTransform.swift in Sources */,
|
||||
29B8767B1CD70ACE00FC07DA /* TSReader.swift in Sources */,
|
||||
2931204C1D4522CF00B14211 /* RTSPRequest.swift in Sources */,
|
||||
29B876861CD70AE800FC07DA /* PacketizedElementaryStream.swift in Sources */,
|
||||
29B876761CD70ACE00FC07DA /* HTTPRequest.swift in Sources */,
|
||||
|
@ -1519,6 +1523,7 @@
|
|||
299AE0E51D44EC7800D26A49 /* RTSPSocket.swift in Sources */,
|
||||
29DD70E61D68CF020021904A /* RTPPacket.swift in Sources */,
|
||||
29B876AD1CD70B2800FC07DA /* ASClass.swift in Sources */,
|
||||
296242611D8DB86500C451A3 /* TSReader.swift in Sources */,
|
||||
29B8766B1CD70AB300FC07DA /* Foundation+Extension.swift in Sources */,
|
||||
2981B7F41D7345D5002FA821 /* SessionDescription.swift in Sources */,
|
||||
2931204E1D4522E400B14211 /* RTSPResponse.swift in Sources */,
|
||||
|
@ -1529,10 +1534,10 @@
|
|||
299B13271D3B751400A1E8F5 /* LFView.swift in Sources */,
|
||||
29B876AF1CD70B2800FC07DA /* RTMPChunk.swift in Sources */,
|
||||
29B876841CD70AE800FC07DA /* H264+AVC.swift in Sources */,
|
||||
296242621D8DB86500C451A3 /* TSWriter.swift in Sources */,
|
||||
29B8769B1CD70B1100FC07DA /* MIME.swift in Sources */,
|
||||
29A39C921D85CF5F007C27E9 /* RTMPSampleHandler.swift in Sources */,
|
||||
29B8769C1CD70B1100FC07DA /* NetClient.swift in Sources */,
|
||||
29B8767C1CD70ACE00FC07DA /* TSWriter.swift in Sources */,
|
||||
29B876871CD70AE800FC07DA /* ProgramSpecific.swift in Sources */,
|
||||
29B876B01CD70B2800FC07DA /* RTMPConnection.swift in Sources */,
|
||||
29B876B61CD70B2800FC07DA /* RTMPStream.swift in Sources */,
|
||||
|
@ -1558,7 +1563,8 @@
|
|||
29B8769D1CD70B1100FC07DA /* NetService.swift in Sources */,
|
||||
29B8769E1CD70B1100FC07DA /* NetSocket.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 */,
|
||||
29B876921CD70AFE00FC07DA /* AVMixer.swift in Sources */,
|
||||
29B876911CD70AFE00FC07DA /* AudioStreamPlayback.swift in Sources */,
|
||||
|
@ -1616,16 +1622,18 @@
|
|||
29B876F91CD70D5900FC07DA /* HTTPStream.swift in Sources */,
|
||||
296543631D62FE9000734698 /* LFView.swift in Sources */,
|
||||
29B876FA1CD70D5900FC07DA /* M3U.swift in Sources */,
|
||||
29B876FB1CD70D5A00FC07DA /* TSReader.swift in Sources */,
|
||||
29B876FC1CD70D5A00FC07DA /* TSWriter.swift in Sources */,
|
||||
292D8A341D8B294900DBECE2 /* MP4Sampler.swift in Sources */,
|
||||
29B876FD1CD70D5A00FC07DA /* AudioSpecificConfig.swift in Sources */,
|
||||
296242631D8DBA8C00C451A3 /* TSReader.swift in Sources */,
|
||||
29B876FE1CD70D5A00FC07DA /* H264+AVC.swift in Sources */,
|
||||
294852571D852499002DE492 /* RTMPTSocket.swift in Sources */,
|
||||
29245AEE1D32347E00AFFB9A /* VideoGravityUtil.swift in Sources */,
|
||||
292D8A351D8B294E00DBECE2 /* MP4Reader.swift in Sources */,
|
||||
29B876FF1CD70D5A00FC07DA /* NALUnit.swift in Sources */,
|
||||
29B877001CD70D5A00FC07DA /* PacketizedElementaryStream.swift in Sources */,
|
||||
29B877011CD70D5A00FC07DA /* ProgramSpecific.swift in Sources */,
|
||||
29B877021CD70D5A00FC07DA /* TransportStream.swift in Sources */,
|
||||
296242641D8DBA9000C451A3 /* TSWriter.swift in Sources */,
|
||||
29B877031CD70D5A00FC07DA /* AudioIOComponent.swift in Sources */,
|
||||
29B877041CD70D5A00FC07DA /* AudioStreamPlayback.swift in Sources */,
|
||||
2931204F1D4529F900B14211 /* RTSPRequest.swift in Sources */,
|
||||
|
|
Loading…
Reference in New Issue