BBMetalImage
BBMetalImage copied to clipboard
Support dynamic overlay view
Hi! Is it possible to add to the camera, for example, a UILabel with dynamic text that changes text every second and record this?
Yes.
- Use
BBMetalUISourceto captureUIViewsnapshot as the overlay. Or draw an image with core graphics and useBBMetalStaticImageSourceto output the overlay. - Blend camera and the overlay with blend filter.
Thanks for your reply!
I tried to write, but it does not look very good, although it worked. Can you please tell me if there is an obvious error?
Thanks in advance for your reply!
class BBMetalViewController: UIViewController {
private var camera: BBMetalCamera!
private var filter: BBMetalBaseFilter!
private var metalView: BBMetalView!
private var videoWriter: BBMetalVideoWriter!
private var filePath: String!
private var uiSource: BBMetalUISource!
private let label = UILabel()
private var overlayView: UIView!
private var playButton: UIButton!
private var isRecording = false
private var displayLink: CADisplayLink!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .gray
camera = BBMetalCamera(sessionPreset: .hd1920x1080)
filter = BBMetalAddBlendFilter()
metalView = BBMetalView(frame: view.frame)
view.addSubview(metalView)
self.overlayView = UIView(frame: view.frame)
overlayView.backgroundColor = .clear
view.addSubview(overlayView)
label.textColor = .white
label.text = "OVERLAY"
label.font = .systemFont(ofSize: 20)
overlayView.addSubview(label)
label.snp.makeConstraints { make in
make.center.equalToSuperview()
}
var i = 0
func generateButton(title: String, selectedTitle: String? = nil) -> UIButton {
let button = UIButton()
self.view.addSubview(button)
button.snp.makeConstraints { make in
make.height.equalTo(40)
make.leading.trailing.equalToSuperview().inset(50)
make.bottom.equalToSuperview().inset(i % 2 == 0 ? 30 : 80)
}
button.backgroundColor = (i % 2 == 0 ? .blue : .red)
button.setTitle(title, for: .normal)
button.setTitle(selectedTitle, for: .selected)
i += 1
return button
}
let recordButton = generateButton(title: "Start recording", selectedTitle: "Finish recording")
recordButton.addTarget(self, action: #selector(clickRecordButton(_:)), for: .touchUpInside)
playButton = generateButton(title: "Play")
playButton.addTarget(self, action: #selector(clickPlayButton(_:)), for: .touchUpInside)
filePath = NSTemporaryDirectory() + "test.mp4"
let outputUrl = URL(fileURLWithPath: filePath)
videoWriter = BBMetalVideoWriter(url: outputUrl, frameSize: camera.textureSize)
camera.audioConsumer = videoWriter
filter.add(consumer: videoWriter)
camera.add(consumer: metalView)
camera.add(consumer: filter)
self.uiSource = BBMetalUISource(view: overlayView)
uiSource.add(consumer: filter)
.add(consumer: metalView)
let countDown = 100
Observable<Int>.timer(.seconds(0), period: .seconds(1), scheduler: MainScheduler.instance)
.filter { _ in self.isRecording }
.take(countDown + 1)
.subscribe(onNext: { timePassed in
guard self.isRecording else { return }
self.label.text = "\(timePassed)"
}, onCompleted: {
print("count down complete")
}).disposed(by: disposeBag)
}
private let disposeBag = DisposeBag()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
camera.start()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
camera.stop()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if displayLink != nil {
displayLink.invalidate()
displayLink = nil
}
}
@objc private func refreshDisplayLink(_ link: CADisplayLink) {
uiSource.transmitTexture(with: CMTime(value: 0, timescale: 60))
}
@objc private func clickRecordButton(_ button: UIButton) {
button.isSelected = !button.isSelected
if displayLink == nil {
displayLink = CADisplayLink(target: self, selector: #selector(refreshDisplayLink(_:)))
displayLink.add(to: .main, forMode: .common)
}
if button.isSelected {
isRecording = true
playButton.isHidden = true
try? FileManager.default.removeItem(at: videoWriter.url)
displayLink.isPaused = false
videoWriter.start()
} else {
isRecording = false
displayLink.isPaused = true
videoWriter.finish { [weak self] in
DispatchQueue.main.async {
guard let self = self else { return }
self.playButton.isHidden = false
}
}
}
}
@objc private func clickPlayButton(_ button: UIButton) {
if FileManager.default.fileExists(atPath: filePath) {
navigationController?.pushViewController(VideoPlayerVC(url: videoWriter.url), animated: true)
}
}
}
extension BBMetalStaticImageSource {
public convenience init (view: UIView) {
self.init(image: view.asImage())
}
}
extension UIView {
func asImage() -> UIImage {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { rendererContext in
layer.render(in: rendererContext.cgContext)
}
}
}
Two concerns:
- Since you use a blend filter, remove unnecessary
camera.add(consumer: metalView). - The camera resolution is 1920x1080, but the overlay view size is dynamic. So the overlay view will stretch.
If I delete camera.add(consumer: metalView) videoWriter will not finish recording the video file and will not be able to save it after that
Maybe you know how to adjust the size of the overlay view correctly?