본문 바로가기

Project/MovieClip

TMDB에서 특정 조건의 video 정보 받아오기

1. 접근방식 검토

 적절한 접근 방식

  • 영화/TV의 비디오 정보를 가져와서 헤더뷰 및 상세 페이지에서 활용
  • 공식 트레일러(Official Trailer) 또는 티저(Official Tease)를 우선적으로 가져오기 → Trailer 버튼을 통해 WebView에서 재생
  • 나머지 비디오 정보는 테이블뷰의 미디어 섹션에서 컬렉션뷰로 표시 → 사용자가 더 많은 미디어 탐색 가능 (추후)

 

2. 개선해야 할 부분

1️⃣ compactMap을 사용하여 원하는 key 값 가져오기
2️⃣ 비디오 정보가 없는 경우 예외 처리 (default 값 또는 nil 체크) (중요)
3️⃣ 추가적인 영상 정보를 미디어 섹션(컬렉션뷰)으로 전달하도록 설계 (나중에)

 

 

 

업데이트된 fetchContentDetail 메서드

HomeViewController 클래스 내에서 실행되는 fetchContentDetail 내에 영화_id 또는 tv_id를 통해 비디오 정보를 받아오는 로직 추가

private func fetchContentDetail() {
    Task {
        do {
            var fetchedDetail: ContentDetail?
            var fetchedGenres: [String] = []  // ✅ 장르 저장 변수
            var videoPath: String? = nil      // ✅ 유튜브 영상 URL 저장
            var allVideoResults: [VideoInfoResult] = [] // ✅ 전체 비디오 데이터 저장
            
            switch contentType {
            case .movie:
                let movieDetail = try await NetworkManager.shared.getMovieDetailInfo(movieID: contentID)
                fetchedDetail = .movie(movieDetail)
                fetchedGenres = getGenresFromHomeSection(for: contentID)
                
                // ✅ 영화 비디오 정보 가져오기
                let videoInfo = try await NetworkManager.shared.getMovieVideoInfo(contentID: contentID)
                
                // ✅ "Official Trailer" 또는 "Official Tease"에 해당하는 key 가져오기
                videoPath = videoInfo.results
                    .first(where: { $0.name == "Official Trailer" || $0.name == "Official Tease" })?.key
                
                // ✅ 전체 비디오 정보 저장
                allVideoResults = videoInfo.results

            case .tv:
                let tvDetail = try await NetworkManager.shared.getTVDetailInfo(tvID: contentID)
                fetchedDetail = .tv(tvDetail)
                fetchedGenres = getGenresFromHomeSection(for: contentID)
                
                // ✅ TV 비디오 정보 가져오기
                let videoInfo = try await NetworkManager.shared.getTVVideoInfo(contentID: contentID)
                
                // ✅ "Official Trailer" 또는 "Official Tease"에 해당하는 key 가져오기
                videoPath = videoInfo.results
                    .first(where: { $0.name == "Official Trailer" || $0.name == "Official Tease" })?.key
                
                // ✅ 전체 비디오 정보 저장
                allVideoResults = videoInfo.results

            case .people:
                let peopleDetail = try await NetworkManager.shared.getPeopleDetailInfo(peopleID: contentID)
                fetchedDetail = .people(peopleDetail)
            }

            DispatchQueue.main.async {
                guard let contentDetail = fetchedDetail else { return }
                
                self.contentDetail = contentDetail
                self.detailTableHeaderView()   // ✅ 헤더뷰 설정
                
                switch contentDetail {
                case .movie(let movieDetail):
                    self.detailHeaderView?.configure(movieDetail, genres: fetchedGenres, videoPath: videoPath)
                case .tv(let tvDetail):
                    self.detailHeaderView?.configure(tvDetail, genres: fetchedGenres, videoPath: videoPath)
                case .people(let peopleDetail):
                    self.detailHeaderView?.configure(peopleDetail)
                }
                
                // ✅ 테이블뷰 미디어 섹션을 위한 전체 비디오 데이터 전달
                self.allVideos = allVideoResults
                self.detailTableView.reloadData()
            }
        } catch {
            print("❌ 데이터 로드 실패: \(error)")
        }
    }
}

 

비디오 데이터 활용 (DetailHeaderView)

1️⃣ DetailHeaderView에서 버튼 액션을 DetailViewController로 전달

// DetailHeaderView.swift

import UIKit


// ✅ Protocol 생성 (데이터 전달_)
protocol DetailHeaderViewDelegate: AnyObject {
    func didTapTrailerButton(videoKey: String)
}

class DetailHeaderView: UIView {
    
    // ✅ 대리자 선언 
    weak var delegate: DetailHeaderViewDelegate?
    private var trailerVideoKey: String?
    
    private let trailerButton: UIButton = {
        let button = UIButton()
        button.setTitle("▶️ 예고편 보기", for: .normal)
        button.backgroundColor = .red
        button.addTarget(self, action: #selector(didTapTrailerButton), for: .touchUpInside)
        return button
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        addSubview(trailerButton)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func configure(_ movie: MovieDetailInfoWelcome, genres: [String], videoPath: String?) {
        trailerVideoKey = videoPath
        trailerButton.isHidden = (videoPath == nil) // ✅ 영상 없으면 버튼 숨기기
    }

    @objc private func didTapTrailerButton() {
        guard let videoKey = trailerVideoKey else { return }
        delegate?.didTapTrailerButton(videoKey: videoKey) // ✅ 뷰컨트롤러로 전달
    }
}

 

 

✅ YouTube WebView (트레일러 재생)

// TrailerViewController.swift

import UIKit
import WebKit

class TrailerViewController: UIViewController {
    
    private let videoKey: String
    
    init(videoKey: String) {
        self.videoKey = videoKey
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private let webView: WKWebView = {
        let webView = WKWebView()
        return webView
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(webView)
        webView.frame = view.bounds
        
        if let url = URL(string: "https://www.youtube.com/watch?v=\(videoKey)") {
            webView.load(URLRequest(url: url))
        }
    }
}

 

 

✅ HomeViewController 내에 delegate 설정 

        private func detailTableHeaderView() {
            detailHeaderView = DetailHeaderView(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: 350))
            detailHeaderView?.delegate = self // ✅ 여기서 delegate 설정
            detailTableView.tableHeaderView = detailHeaderView
        }


// Protocol 채택: didTapTrailerButton 메서드 구체화
extension DetailViewController: DetailHeaderViewDelegate {
    func didTapTrailerButton(videoKey: String) {
        let trailerVC = TrailerViewController(videoKey: videoKey)
        present(trailerVC, animated: true)
    }
}

 

✅ 🚀 최종 동작 흐름

1️⃣ DetailViewController에서 API 요청 → DetailHeaderView에 비디오 데이터 전달
2️⃣ 트레일러 버튼 클릭 시, DetailHeaderView가 DetailViewController로 이벤트 전달
3️⃣ DetailViewController가 TrailerViewController를 present하여 WebView에서 유튜브 영상 재생

 


🚗 개선

"Official Trailer" 또는 "Official Teaser"가 있으면 그 key를 사용하고, 없으면 key가 있는 첫 번째 항목을 선택하는 방법"

 

🔹 해결 방법

first(where:)를 사용하면 특정 조건에 맞는 첫 번째 요소만 가져오기 때문에, 조건을 여러 개 설정하고 fallback(대체값)을 추가해야 해.

📌 우선순위 설정:

  1. "Official Trailer" 또는 "Official Tease"가 있으면 해당 key 사용
  2. 만약 없으면 key가 있는 첫 번째 요소를 사용
  3. 그래도 없으면 nil 반환

 

🔹수정 코드

// ✅ "Official Trailer" 또는 "Official Tease"가 있으면 그 key를 사용하고, 없으면 key가 있는 첫 번째 항목을 선택
videoPath = videoInfo.results.first(where: { 
    $0.name == "Official Trailer" || $0.name == "Official Teaser" 
})?.key ?? videoInfo.results.first(where: { 
    $0.key != nil 
})?.key

 

📌 설명

  • first(where:)를 사용해서 "Official Trailer" 또는 "Official Teaser"를 찾음.
  • 만약 위에서 찾지 못했다면, key가 nil이 아닌 첫 번째 요소를 찾음.
  • 이렇게 하면 트레일러가 없더라도 다른 영상이 있으면 그 key를 사용할 수 있음.

 

🔹 전체 적용 코드

case .movie:
    let movieDetail = try await NetworkManager.shared.getMovieDetailInfo(movieID: contentID)
    fetchedDetail = .movie(movieDetail)
    fetchedGenres = getGenresFromHomeSection(for: contentID)

    // ✅ 영화 비디오 정보 가져오기
    let videoInfo = try await NetworkManager.shared.getVideoInfo(contentID: contentID)
    
    // ✅ "Official Trailer" 또는 "Official Teaser"가 있으면 그 key를 사용하고, 없으면 key가 있는 첫 번째 항목을 선택
    videoPath = videoInfo.results.first(where: { 
        $0.name == "Official Trailer" || $0.name == "Official Teaser" 
    })?.key ?? videoInfo.results.first(where: { 
        $0.key != nil 
    })?.key

    allVideoResults = videoInfo.results