-
쿠키로 안전하게 HLS m3u8 영상 접근하기iOS 2023. 3. 30. 16:14
안녕하세요. 그린입니다🍏
이번 포스팅에서는 HLS와 m3u8파일이 뭔지 그리고 이러한 영상들에 쿠키를 통해 어떻게 안전하게 리소스에 접근할 수 있는지 학습해보고 구현해보겠습니다🙋🏻
우선 쿠키로 안전하게 영상에 접근하기전 HLS와 m3u8이라는 개념에 대해 알아보시죠🕺🏻
HLS (HTTP Live Streaming)
표준 HTTP 기반 스트리밍 프로토콜로 HLS라고 흔히 불립니다.
즉, 쉽게 말해 가장 흔히 사용되는 비디오 스트리밍 프로토콜이라고 하죠.
HTTP 파일 조각들로 비디오 파일을 나누고 이를 HTTP 프로토콜을 이용해 전송합니다.
클라이언트 단에서는 이런 HTTP 파일을 로드한 후 비디오를 재생시킬 수 있죠!
그럼 왜 HLS를 사용하느냐?
이에 대한 물음에는 HLS 장점 중 하나로 모든 인터넷 연결 장치가 HTTP를 지원하는 기본 전제가 있기에 전용 서버가 필요한 스트리밍 프로토콜보다 간단하게 실행할 수 있다는 점입니다.
또한, HLS 스트리밍은 재생에 지장을 주지 않고 네트워크 상태에 따라 비디오 품질을 조정할 수 있습니다.
유투브를 보시다보면 흔히 네트워크가 좋지 않을때는 화질이 낮다가 네트워크 상태가 좋아지면 화질이 정상적으로 좋아진 경험을 다들 하셨을거에요.
비디오 품질이 조정된다는 말이 이 뜻입니다👍
이러한 HLS는 누가 만들었을까?
바로 애플!
애플이 iOS를 위해 개발했지만 현재는 애플 제품 외에도 다양한 장치에서 사용되고 있습니다.
HLS의 작동 방식
먼저 서버단을 볼 수 있습니다.
HLS 스트리밍은 미디어 파일이 저장된 서버 혹은 스트리밍이 제작된 서버에서 시작됩니다.
HLS는 HTTP 기반이기에 모든 일반 웹 서버에서 스트리밍을 할 수 있습니다.
이때, 서버는 가장 중요한 두가지 프로세스를 진행합니다.
1️⃣ 인코딩
비디오 데이터 포맷을 다시 설정해 모든 장치가 데이터를 인식하고 해석할 수 있게 인코딩 합니다.
HLS는 H.264나 H.265 인코딩을 사용해야 합니다.
2️⃣ 조각화
대개 비디오는 몇 초 길이 분량의 세그먼트로 나눠집니다.
기본 길이는 6초 정도이고 더 다양하긴 합니다.
결국 이 세그먼트로 나눠진것을 HLS는 세그먼트의 인덱스 파일을 생성하여 그 세그먼트 순서를 기록합니다.
또한 HLS는 480p, 720p, 1080p 등 다양한 품질로 여러 세트의 세그먼트를 복제합니다.
그 다음으로 배포 단계입니다.
인코딩된 비디오 세그먼트는 클라가 스트리밍을 요청하면 인터넷을 통해 클라로 전송하게 됩니다.
CDN(콘텐츠 전송 네트워크)이 여러 지역으로 스트리밍을 배포하는데 도움을 주죠.
또한 CDN이 스트리밍을 캐싱하여 클라에 더 빠르게 전송해줄 수 있습니다.
이 단계가 배포 단계입니다.
마지막으로 클라 단계입니다.
클라이언트에서는 스트리밍을 받아 비디오를 재생시켜줍니다.
여기서 세그먼트의 인덱스 파일을 참조하여 비디오를 순서에 맞게 조합하고 필요에 따라 품질을 조정합니다.
그럼 HLS는 TCP와 UDP중 어느것을 사용할까?
HLS의 전송 프로토콜
흔히 알고 있는 TCP와 UDP라는 전송 프로토콜이 있습니다.
TCP는 응답을 받고 다음 패킷을 넘기는 작업이 있기에 UDP보다는 느리지만 안전하죠.
반면 UDP는 응답과 무관하게 우선 패킷을 빠르게 넘기기에 속도는 향상되지만 TCP보단 안전하지 않습니다.
즉, 데이터 손실이 될 수 있습니다.
그럼 HLS는 어떤 방식일 취해야할까요?
보통 대개는 UDP가 빠르기에 다양한 많은 스트리밍 프로토콜로는 속도를 더 신경써 UDP를 사용합니다.
다만 아래와 같은 이유로 HLS는 TCP를 사용합니다😲
1️⃣ HLS는 HTTP를 사용하면 HTTP 프로토콜은 TCP를 사용하도록 구현되었음
2️⃣ 요즘의 인터넷은 신뢰성과 효율성이 높아졌고 많은 모바일 연결을 중심으로 사용자 연결이 크게 개선되었습니다.
이에 사용자는 모든 비디오 프레임 전송을 충분히 지원할 수 있는 대역을 갖고 있습니다.3️⃣ HLS의 적응 비트 전송률 스트리밍이 TCP의 데이터 전송이 느려질 경우 보상에 도움을 줍니다.
4️⃣ HLS는 실시간이지 않아도 됩니다. 그렇기에 몇 초 정도 지연되어도 사용자 경험에 큰 영향을 주지 않습니다.
이렇게 좀 더 안전성에 중점을 두며 현재 스트리밍 정도는 TCP로도 충분히 많이 한번에 보낼 수 있기에 TCP를 사용하는것 같습니다.
자.. 후.. 많이 달려왔는데요.
이렇게 HLS를 알아봤습니다🙌
그럼 이어서 m3u8 파일 형식이 어떤건지 알아보겠습니다🙌
m3u8
HLS를 통해 스트리밍 비디오를 서비스할 수 있다면, 이 영상 스트리밍용 파일이 있어야 하는데요.
보통 m3u8이라는 파일 형식이 스트리밍용 파일이라고 생각하면 됩니다.
즉, 내부 정보로는 플레이리스트의 경로 및 재생시간등을 구조적으로 갖습니다.
보통 웹 사이트들에서 m3u8이 재생되지 않도록 막는 경우가 많습니다.
그렇지만 우리는 클라이언트 개발자라는점에서 충분히 각자의 방식대로 m3u8을 지원하는 비디오 플레이어를 구현할 수 있습니다.
위에서 말했던 구조는 각 영상들이 세그먼트 단위로 쪼개져있고 이 쪼개진 영상파일을 ts파일이라고 불립니다.
#EXTM3U #EXT-X-VERSION:3 #EXT-X-INDEPENDENT-SEGMENTS #EXT-X-STREAM-INF:BANDWIDTH=940300,AVERAGE-BANDWIDTH=849459,CODECS="avc1.4d4028,mp4a.40.2",RESOLUTION=1920x1080,FRAME-RATE=30.000 file_example_MP4_1920_18MGprivate.m3u8
요런 형태를 띄게됩니다.
#EXTM3U은 m3u8 파일 형식이란걸 나타냅니다.
#EXT-X-VERSION: 3은 이 m3u8 버전이 3이라는걸 나타내죠.
자 그럼 이렇게 HLS와 m3u8을 알아봤으니 본격적으로 우리가 원래 하려던!? 쿠키를 통해 iOS에서 요 형식의 영상을 불러와볼께요!
내가 만든 쿠키~ 리소스 위해 구웠지🍪
우선 쿠키를 통해 사용하는 이유는 안전성입니다.
즉, 해당 m3u8 파일의 URL만 있으면 누구나 어디서든 접근할 수 있지 않게 하기 위해 암호화된 쿠키를 이용해 특정 자신의 앱에서만 혹은 특정 유저에게만 영상을 보일 수 있도록 하기 위함입니다.
우리가 해볼 쿠키를 받아오고 받아온 쿠키를 통해 영상을 요청하는 전체적인 서버와의 플로우는 이렇습니다🙋🏻
이렇게 클라와 백앤드 API 사이 쿠키를 요청하고 내려줍니다.
이 쿠키로 영상 리소스가 있는 CloudFront에 쿠키를 담아 리소스를 요청하고 제공 받습니다.
자..!! 그럼 대망의 구현시간ㅎㅎ⭐️
쿠키로 안전하게 m3u8 리소스 요청하기
쫄거없습니다! 코드는 아주아주 쉽습니다ㅎㅎ
여기서는 가상의 서버 API와 CloudFront가 있다고 가정하고 진행됩니다.
혹시 이전에 제 포스팅중 SwiftUI에서 AVPlayer를 구현한 코드들을 먼저 보고오시면 더 도움이 됩니다⭐️
https://green1229.tistory.com/342
해당 위 포스팅의 예제 코드에서 보완해보겠습니다.
final public class GreenPlayerViewModel: ObservableObject { @Published var pipStatus: PipStatus = .unowned @Published var media: Media? let player = AVPlayer() // 쿠키 요청을 위한 서버 API URL private var cookieURL: String = "https://green/api/cookie" // 제공된 쿠키 private var cookies = [HTTPCookie]() private var cancellable: AnyCancellable? public init() { setAudioSessionCategory(to: .playback) setCookiesAndReqeustMedia() } // MARK: - 쿠키 설정 및 영상 요청 func setCookiesAndReqeustMedia() { guard let cookieURL = URL(string: cookieURL) else { return } // 쿠키 요청 let task = URLSession.shared.dataTask(with: cookieURL) { data, response, _ in guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200, data != nil, let headerFields = httpResponse.allHeaderFields as? [String: String], let cookieHeader = headerFields["Set-Cookie"] else { return } // 제공 받은 쿠키 저장 self.cookies = HTTPCookie.cookies(withResponseHeaderFields: ["Set-Cookie": cookieHeader], for: cookieURL) // 미디어 요청 self.cancellable = self.$media .compactMap({ $0 }) .compactMap({ URL(string: $0.url) }) .map { url -> AVPlayerItem in let asset = AVURLAsset( url: url, options:[ "AVURLAssetHTTPHeaderFieldsKey": HTTPCookie.requestHeaderFields(with: self.cookies) ] ) return AVPlayerItem(asset: asset) } .sink(receiveValue: { [weak self] in guard let self = self else { return } self.player.replaceCurrentItem(with: $0) }) } task.resume() } ... }
자 구현 간단하죠?
추가된 영역은 쿠키쪽입니다.
우선 기본 URLSession 네트워크 통신을 통해 쿠키 요청 API를 호출합니다.
그리고 리스폰스 헤더필드중 쿠키를 가진 필드 Set-Cookie들을 cookieHeader로 받습니다.
(대개 쿠키가 여러개일 경우가 있습니다.)
받아온 cookieHeader에서 값을 프로퍼티로 만들어준 cookie로 저장합니다.
그럼 해당 쿠키에는 보통 요런식으로 값과 경로 그리고 만료일이 담겨있을거에요.
CloudFront=greenblabla123123; Path=/; Expires=Thu, 06 Apr 2023 06:36:59 GMT;
그런데 이 API와 CloudFront를 통해 리소스를 Get 요청하는 Path가 다를경우 해당 쿠키를 직접 그대로 넘겨주면 리소스 요청에 실패하게 됩니다.
즉, 도메인과 Path가 달라져 해당 쿠키를 그대로 사용할 수 없게되는데요.
그렇기에 만약 그럴 경우라면 위 코드처럼 AVURLAsset을 통해 HTTPCookie의 requestHeaderFields를 통해 아래 값만 가져와서 Path에 영향을 받지 않고 쿠키를 담아 요청할 수 있어요.
CloudFront=greenblabla123123;
해당 asset을 AVPlayerItem으로 담아 지정해주고 마지막으로 player의 아이템으로 대치해주면 끝입니다.
이렇게 안전하게 쿠키를 제공받아 해당 쿠키로 제한된 리소스에 안전하게 접근할 수 있습니다🙌
마무리
어떠신가요?
HLS가 무엇인지 m3u8 영상 리소스를 어떻게 쿠키로 안전하게 요청하고 제공받는지 감이 오시나요!?
사실 스트리밍 영상을 다루는 경우가 아니라면 많이 생소할 개념들인데 구현은 전혀 어렵지 않고 처음 개념을 접할때만 익숙해지면 되더라구요ㅎㅎ
저도 영상을 다뤄보지 않아서 요번에 개념 정리를 조금 해봤습니다!
제가 했으니 다른분들은 더 금새 잘하실 수 있을거에요🙌
p.s. 쿠키 깊은 개념 이해에 도움을 준 Tony에게 감사를🙏🏻
[참고 자료]
https://www.cloudflare.com/ko-kr/learning/video/what-is-http-live-streaming/
'iOS' 카테고리의 다른 글
identifierForVendor를 이용한 기기 식별하기 (14) 2023.05.30 Setting Bundle을 사용해 커스텀한 설정 추가하기 (6) 2023.05.01 Nib & Xib (feat. storyboard) (3) 2022.12.29 removeArrangedSubview(_:) VS removeFromSuperview() (0) 2022.11.14 dSYM (feat. Firebase Crashlytics) (0) 2022.08.29