"๋ณธ ํ๋ก์ ํธ๋ ๊ฐ์ธํ๋ก์ ํธ์ด๋ฉฐ ๋ณธ์ธ์ด ํฌ๊ฒ ์ฝ์งํ ๋ถ๋ถ์ ์์ฃผ๋ก ์ ๋ฆฌ, ๊ธฐ๋ก์ฉ์ผ๋ก ์์ฑ๋ฉ๋๋ค."
์์ฒญ์ ํ๋ฉด์์ Live List๋ฅผ ๋ณผ ๋ ์๊ฐ์ ์ธ ์ ๋ณด์ธ ์ธ๋ค์ผ์ด ํ์ํ์.
์ผ๋จ ๋ ์ค๋ฅด๋ ๋ฐฉ๋ฒ์ผ๋ก URL(.m3u8)์์ ๋ฐ์ค๋ ๋ฐฉ๋ฒ์ด ์์ ๊ฒ ๊ฐ์ ์ฌ๋ฌ ์๋๋ฅผ ํด๋ณด์์.
Apple ๊ณต์๋ฌธ์์์ ์ ๊ณตํ๋ HTTP Live Streaming (HLS) ํ์์ cgImage ํ์์ผ๋ก ๋ณํํ๋ ๋ฐฉ์์ ์ฌ์ฉ.
AVPlayer์์ ์ ์ฒด ์๊ฐ์ด ๋จ๋ m3u8 ํ์์ URL์ ์ ์๋ํจ.
๊ทธ๋ฐ๋ฐ ๋ด๊ฐ ์ํ๋ Live ํ์์ image๋ฅผ ๊ฐ์ ธ์ค์ง ๋ชปํ์. (๋ด๊ฐ ๋ชปํ๊ฑธ์๋)
์คํจ.
๊ฐ Cell์ ์๋ AVPlayerLayer์ URL์ ์ฃผ๊ณ ์ฌ์ ์ํ๋ฅผ ์ ์ง ํด๋์ผ๋ฉด ์ธ๋ค์ผ ๋๋์ ๋ผ ์ ์์์.
๊ทผ๋ฐ ๋งค์ฐ ๋๋ฆฌ๊ฑฐ๋ ๊ฐ ์์๋ง๋ค ๋๋ ์ด๊ฐ ๋ฌ๋ผ ๋ฐ๋๋ ์๊ณ ์๋ฐ๋๋ ์์์.
๊ทธ๋ฆฌ๊ณ Cell์ ๋ค์ ๋ก๋ํ ๋ ๋ค๋ฅธ ์์์ ์ธ๋ค์ผ์ด ํ์๋๋ ๋ฌธ์ ์ ์ด ์์์.
์ธ๋ค์ผ์ ํ์ํ๋ค๋ ๋ชฉ์ ์ ๋ฌ์ฑํ์ง๋ง, ๋ง์์ ์๋ค์์.
์คํจ?
์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ HLS/.m3u8๋ฅผ UIImage๋ก ์ถ์ถํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์.
UIImage๋ก ์ถ์ถํ๋ ์๊ฐ์ด ๋ค์ ์๊ธฐ์ Image Cache ์ฒ๋ฆฌ๋ก ๋ค์ ๋ก๋ํ ๋ ์๊ฐ์ ์ค์์.
๊ทผ๋ฐ ์ฒซ Image ์ถ์ถ์ด ์ฝ 0.5 ~ 1์ด ์ ๋ ๊ฑธ๋ฆฌ๊ธฐ ๋๋ฌธ์ ์ฌ๋ฌ Cell์ ์๋ Image๋ฅผ ์ฒ๋ฆฌํ๋๋ฐ์๋ ๋ถ์ ํฉํ๋ค๊ณ ํ๋จ. (๋ด๊ฐ ์ฌ์ฉ์์์ ์ํฐ์ก์๋ฏ)
์คํจ.
์ผ๋จ ์ ๊ทผ๋ฐฉ์์ด ์๋ชป๋๊ฒ ๊ฐ์ ๋ค๋ฅธ ๋ฐฉ์์ผ๋ก ์ ๊ทผํจ.
์์ฒญ์์ชฝ์์ ์ธ๋ค์ผ์ ๋ง๋๋๊ฒ์ ๋๋ฌด ๋นํจ์จ์ ์ด๋ผ ํ๋จ.
๊ทธ๋ผ ์๋ฒ์์ ์ธ๋ค์ผ์ ๋ง๋ค์ด ๋ณด๋ด์ฃผ๋ ๋ฐฉ๋ฒ, Host์ชฝ์์ ์ธ๋ค์ผ์ ์ ์ 2๊ฐ์ง ๋ฐฉ๋ฒ์ด ๋ ์ค๋ฆ.
์๋ฒ์์ ์ธ๋ค์ผ์ ๋ง๋๋ ๋ฐฉ๋ฒ์ RTMP๋ฅผ ๋ฐ๋ Nginx์์ ์ฒ๋ฆฌํด์ผ ํ๋๋ฐ ๋ ์์ฒญ๋ ์ฝ์ง์ด ์์๋ ๊ฒ ๊ฐ์ ์์ ์ฝ์ง๋ก ๋ชธ๊ณผ ๋ง์์ง ์ง์ฒ ํฌ๊ธฐ.
Host์ชฝ์์ ์ธ๋ค์ผ์ ์ ์ํ๋ ๋ฐฉ์์ ์ด๋์ ๋ ๊ฐ์ด ์กํ ์งํํ๊ธฐ๋ก ํจ.
๋จผ์ ์ก์ถ ๋ถ๋ถ์ HaishinKit ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํจ.
class StreamCam: UIViewController {
...
func startStream() {
rtmpConnection.connect("rtmp://diddbstjr55.shop/hls") // ์ก์ถํ ๋ฏธ๋์ด์๋ฒ ์ฃผ์
rtmpStream.publish(userEmailSplit) // Host Key
rtmpConnection.addEventListener(.rtmpStatus, selector: #selector(rtmpStatusHandler), observer: self) // ์ก์ถ๋ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ํด addEventListener
if rtmpStream.receiveVideo { // ์ก์ถ๋ ์์์ ๊ฐ์ ธ์ฌ ์ ์๋๊ฐ?
var count = 0
timer = Timer(timeInterval: 1, repeats: true, block: { _ in
count += 1
if count > 4 {
self.createThumbnail() // 4์ด๋ง๋ค ์ธ๋ค์ผ ์์ฑ ์๋
count = 0
}
})
RunLoop.current.add(timer!, forMode: .common)
}
}
func createThumbnail() {
let liveResult = LiveResult()
let url = liveResult.hlsURL(hls: userEmailSplit ) // ์ด ์ฝ๋๋ ์์์ ์ ์ฅ๋ ์ ์ Email์ ๊ฐ์ ธ์ค๋ ๋จ๊ณ์
๋๋ค.
generator = ACThumbnailGenerator(streamUrl: url) // ๊ฐ์ ธ์จ ์ ์ Email์ ๊ณ ์ Key๋ก ์ค์ ํด ACThumbnailGeneratorDelegat์์ ์์ฑ๋ ์ธ๋ค์ผ์ Cache ์ฒ๋ฆฌ
generator.delegate = self
generator.captureImage(at: 30)
let cacheKey = NSString(string: url.description)
if let cacheImage = ImageCachManager.shared.object(forKey: cacheKey) { // Cache Image๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์ FirebaseStorage์ ์
๋ก๋ ํ๊ณ ํ์ด๋จธ ์ค์ง
timer?.invalidate()
FirebaseStorageManager.uploadImage(image: cacheImage, imageName: userEmailSplit) { URL in
if let url = URL {
self.uploadFirebase(imageURL: url.description)
}
}
}
}
}
...
extension StreamCam: ACThumbnailGeneratorDelegate {
func generator(_ generator: ACThumbnailGenerator, didCapture image: UIImage, at position: Double) {
let url = generator.streamUrl
print("image to",url)
let cacheKey = NSString(string: url.description)
ImageCachManager.shared.setObject(image, forKey: cacheKey) // ์ฌ๊ธฐ์ Cache ์ฒ๋ฆฌํ image๋ FirebaseStorage์ ์ฌ๋ฆด ๋ ์ฌ์ฉ
}
}
๋จผ์ startStream() ์ค์ ๋ ์ก์ถ์ ์๋.
์ค์ ๋ ํ์ด๋จธ๋ 4์ด๋ง๋ค createThumbnail() ํจ์๋ฅผ ํธ์ถ.
createThumbnail() ํจ์๋ 4์ด๋ง๋ค ์ก์ถ๋ ์์์ ๊ฐ์ ธ์ฌ ์ ์์ผ๋ฉด ํ์ด๋จธ๋ฅผ ์ค์งํ๊ณ URL์์ ์ด๋ฏธ์ง๋ฅผ ์ถ์ถ ํ FirebaseStorage์ ์ ๋ก๋.
์ก์ถ(RTMP) -> Nginx -> HLS(.m3u8) -> HLS Thumbmail ์ถ์ถ -> Firebase์ ๋ฐฉ์ก์ ๋ณด( Title, Category, HLS URL...) ์ ๋ก๋ -> ์ฌ์ฉ์์๊ฒ ๋ ธ์ถ
๋จ์ ์ RTMP์ ๋๋ ์ด๊ฐ ์ด๋์ ๋ ์๊ธฐ์ Host๊ฐ ๋ฐฉ์ก์ ์์ํ๊ณ ์ฝ 4~8์ด ์ดํ ์์ฒญ์์๊ฒ ๊ณต๊ฐ๋๋ค๋ ๋จ์ ์ด ์๋ค.
๋ด๊ฐ ๊ฒฝํํ ์ค์ ์ธํฐ๋ท ๋ฐฉ์ก์ Host๊ฐ ๋ฐฉ์ก์ ์์ํ๊ณ ๋ณธ ๋ด์ฉ์ผ๋ก ๋ค์ด๊ฐ๊ธฐ์ ์ฝ 5~10๋ถ ์ ๋์ ๋๊ธฐ์๊ฐ์ ๊ฐ์ง๋๊ฒ์ผ๋ก ๋ชฉ๊ฒฉ๋์๋ค.
๊ทธ๋ ๊ธฐ์ ์ค์ฌ์ฉ์ผ๋ก ํฐ ๋ฌธ์ ๊ฐ ๋์ง๋ ์์ง๋ง ๊ตฌ์กฐ์ ์ผ๋ก๋ ํจ์จ์ ์ด์ง๋ ์๋ค.
๊ฐ์ธ์ ์ผ๋ก๋ ์ด ๋ฐฉ๋ฒ์ด ๋ง์์ ๋ค์ง๋ ์์ง๋ง ์์ง ๋ ๋์ ๋ฐฉ๋ฒ์ ์ฐพ์ง ๋ชปํด ์ฌ์ฉํ๊ณ ์๋ค.

์๋ ํ์ธ์ ๊ธ ์์ฝ์์ต๋๋ค...
ํน์ ํ์์ด์ ๊ฑด๊ฐ์...???