mirror of
https://github.com/HaishinKit/HaishinKit.swift.git
synced 2026-05-07 20:12:28 +00:00
101 lines
4.1 KiB
Swift
101 lines
4.1 KiB
Swift
import HaishinKit
|
|
@preconcurrency import Logboard
|
|
import MediaPlayer
|
|
import ReplayKit
|
|
import RTCHaishinKit
|
|
import RTMPHaishinKit
|
|
import SRTHaishinKit
|
|
import VideoToolbox
|
|
|
|
nonisolated let logger = LBLogger.with("com.haishinkit.Screencast")
|
|
|
|
final class SampleHandler: RPBroadcastSampleHandler, @unchecked Sendable {
|
|
private var slider: UISlider?
|
|
private var session: StreamSession?
|
|
private var mixer = MediaMixer(captureSessionMode: .manual, multiTrackAudioMixingEnabled: true)
|
|
private var needVideoConfiguration = true
|
|
|
|
override init() {
|
|
Task {
|
|
await StreamSessionBuilderFactory.shared.register(RTMPSessionFactory())
|
|
await StreamSessionBuilderFactory.shared.register(SRTSessionFactory())
|
|
await StreamSessionBuilderFactory.shared.register(HTTPSessionFactory())
|
|
|
|
await SRTLogger.shared.setLevel(.debug)
|
|
await RTCLogger.shared.setLevel(.info)
|
|
}
|
|
}
|
|
|
|
override func broadcastStarted(withSetupInfo setupInfo: [String: NSObject]?) {
|
|
LBLogger.with(kHaishinKitIdentifier).level = .trace
|
|
LBLogger.with(kRTMPHaishinKitIdentifier).level = .trace
|
|
LBLogger.with(kSRTHaishinKitIdentifier).level = .trace
|
|
LBLogger.with(kRTCHaishinKitIdentifier).level = .trace
|
|
// mixer.audioMixerSettings.tracks[1] = .default
|
|
Task {
|
|
do {
|
|
session = try await StreamSessionBuilderFactory.shared.make(Preference.default.makeURL()).build()
|
|
// ReplayKit is sensitive to memory, so we limit the queue to a maximum of five items.
|
|
var videoSetting = await mixer.videoMixerSettings
|
|
videoSetting.mode = .passthrough
|
|
await session?.stream.setVideoInputBufferCounts(5)
|
|
await mixer.setVideoMixerSettings(videoSetting)
|
|
await mixer.startRunning()
|
|
if let session {
|
|
await mixer.addOutput(session.stream)
|
|
try? await session.connect {
|
|
}
|
|
}
|
|
} catch {
|
|
logger.error(error)
|
|
}
|
|
}
|
|
// The volume of the audioApp can be obtained even when muted. A hack to synchronize with the volume.
|
|
DispatchQueue.main.async {
|
|
let volumeView = MPVolumeView(frame: CGRect.zero)
|
|
if let slider = volumeView.subviews.compactMap({ $0 as? UISlider }).first {
|
|
self.slider = slider
|
|
}
|
|
}
|
|
}
|
|
|
|
override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) {
|
|
switch sampleBufferType {
|
|
case .video:
|
|
Task {
|
|
if needVideoConfiguration, let dimensions = sampleBuffer.formatDescription?.dimensions {
|
|
var videoSettings = await session?.stream.videoSettings
|
|
videoSettings?.videoSize = .init(
|
|
width: CGFloat(dimensions.width),
|
|
height: CGFloat(dimensions.height)
|
|
)
|
|
videoSettings?.profileLevel = kVTProfileLevel_H264_Baseline_AutoLevel as String
|
|
if let videoSettings {
|
|
try? await session?.stream.setVideoSettings(videoSettings)
|
|
}
|
|
needVideoConfiguration = false
|
|
}
|
|
}
|
|
Task { await mixer.append(sampleBuffer) }
|
|
case .audioMic:
|
|
if sampleBuffer.dataReadiness == .ready {
|
|
Task { await mixer.append(sampleBuffer, track: 0) }
|
|
}
|
|
case .audioApp:
|
|
Task { @MainActor in
|
|
if let volume = slider?.value {
|
|
var audioMixerSettings = await mixer.audioMixerSettings
|
|
audioMixerSettings.tracks[1] = .default
|
|
audioMixerSettings.tracks[1]?.volume = volume * 0.5
|
|
await mixer.setAudioMixerSettings(audioMixerSettings)
|
|
}
|
|
}
|
|
if sampleBuffer.dataReadiness == .ready {
|
|
Task { await mixer.append(sampleBuffer, track: 1) }
|
|
}
|
|
@unknown default:
|
|
break
|
|
}
|
|
}
|
|
}
|