[UIKit] NetflixClone: Video

Junyoung Park·2022년 11월 1일
0

UIKit

목록 보기
74/142
post-thumbnail

Building Netflix App in Swift 5 and UIKit - Episode 11 - YouTube API

Building Netflix App in Swift 5 and UIKit - Episode 12 - TitlePreviewController

NetflixClone: Video

구현 목표

  • 유튜브 API 서비스를 통해 비동기 동영상 데이터를 핸들링
  • 웹킷 뷰를 통해 비디오 재생
  • 스크롤 감지 이벤트를 통한 자동 전환

구현 태스크

  • 유튜브 검색을 위한 API 함수 등록
  • 컨텐츠 제목을 통한 동영상 정보 패치
  • 클릭 이벤트를 통한 동영상 자동 로드
  • 스크롤 감지를 통한 동영상 자동 멈춤

핵심 코드

func getVideoMetaData(with query: String) -> AnyPublisher<MetaIdModel, Error> {   
        var urlComponents = URLComponents()
        urlComponents.scheme = "https"
        urlComponents.host = "www.googleapis.com"
        urlComponents.path = "/youtube/v3/search"
        urlComponents.queryItems = [
            URLQueryItem(name: "q", value: query),
            URLQueryItem(name: "type", value: "video"),
            URLQueryItem(name: "key", value: Constants.Youtube_API_KEY.rawValue)
        ]
        guard let url = urlComponents.url else { return Fail(error: URLError(.badURL)).eraseToAnyPublisher()}
        return URLSession
            .shared
            .dataTaskPublisher(for: url)
            .tryMap { data, response in
                guard let response = response as? HTTPURLResponse,
                      response.statusCode == 200 else {
                    print("response error")
                    throw URLError(.badServerResponse)
                }
                return data
            }
            .decode(type: MetaDataContentResponse.self, decoder: JSONDecoder())
            .map({ $0.items[0].id})
            .eraseToAnyPublisher()
    }
  • 구글 API에 등록한 뒤 API 키와 함께 쿼리문을 통해 유튜브 컨텐츠를 검색 가능
import Foundation

struct MetaIdModel: Codable {
    let kind: String
    let videoId: String
}
  • API 검색 결과를 통해 다음과 같은 비디오 아이디를 리턴
func getHeaderVideoId() -> AnyPublisher<MetaIdModel, Error> {
        guard let model = headerModel else {
            return Fail(error: URLError(.badURL)).eraseToAnyPublisher()
        }
        var title: String?
        if model.title != nil {
            title = model.title
        } else if model.original_title != nil {
            title = model.original_title
        }
        guard let title = title else {
            return Fail(error: URLError(.badURL)).eraseToAnyPublisher()
        }
        
        return APICaller
            .shared
            .getVideoMetaData(with: title + " trailer")
            .eraseToAnyPublisher()
    }
  • TMDB API 서비스를 통해 얻어낸 각 컨텐츠의 제목 데이터를 통해 유튜브 검색 쿼리를 실행
  • 리턴받은 비디오 아이디 값을 통해 재생할 비디오 영상을 확보 가능
private func bind() {
        playButton
            .tapPublisher
            .receive(on: DispatchQueue.main)
            .sink { [weak self] _ in
                if let id = self?.videoId {
                    print("id: \(id)")
                    let playerVars = [
                        "playsinline" : 1,
                        "showinfo" : 0,
                        "rel" : 0,
                        "modestbranding" : 1,
                        "controls" : 1
                    ]
                    self?.playVideo()
                    self?.playerView.load(withVideoId: id, playerVars: playerVars)
                }
            }
            .store(in: &cancellabels)
    }
  • 홈 뷰 헤더의 재생 버튼 클릭 이벤트를 감지
  • 해당 비디오 아이디를 사용한 재생
 private func playVideo() {
        imageView.isHidden = true
        playerView.isHidden = false
    }
    
    func stopVideo() {
        if !playerView.isHidden {
            playerView.stopVideo()
            imageView.isHidden = false
            playerView.isHidden = true
        }
    }
  • 기존의 이미지 뷰를 교체하기 때문에 isHidden을 통해 일종의 토글링 실행
extension HomeHeaderView: YTPlayerViewDelegate {
    func playerViewDidBecomeReady(_ playerView: YTPlayerView) {
        print("Player view did Become ready")
        playerView.playVideo()
    }
}
  • 재생 버튼 클릭 시 유튜브 클릭 컨트롤이 아닌 자동 재생을 위한 델리게이트 함수
func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if let tableHeaderView = tableView.tableHeaderView as? HomeHeaderView {
            let defaultY = tableHeaderView.frame.size.height
            let contentOffset = scrollView.contentOffset.y
            if defaultY <= contentOffset {
                tableHeaderView.stopVideo()
            }
        }
    }
  • 테이블 뷰의 헤더 뷰가 보이지 않을 경우 비디오 영상을 중지하고 기존의 이미지 뷰를 띄우기 위한 스크롤 감지 함수
  • 테이블 뷰의 각 셀에 존재하는 컬렉션 뷰의 경우 prepareForReuse를 오버라이드해 해당 셀이 재사용될 경우를 감지

구현 화면

넷플릭스 어플의 특징은 스크롤 감지를 통한 동영상 자동 재생/멈춤이라 생각하기 때문에 강의에서 구현한 바에 추가해 이상을 구현했다.

profile
JUST DO IT

0개의 댓글