IMA SDK를 사용하면 멀티미디어 광고를 웹사이트와 앱에 쉽게 통합할 수 있습니다. IMA SDK는 VAST 호환 광고 서버에서 광고를 요청하고 앱에서 광고 재생을 관리할 수 있습니다. IMA DAI SDK를 사용하면 앱에서 광고 및 콘텐츠 동영상(VOD 또는 라이브 콘텐츠)의 스트림을 요청합니다. 그러면 SDK가 결합된 동영상 스트림을 반환하므로 앱 내에서 광고와 콘텐츠 동영상 간 전환을 관리하지 않아도 됩니다.
관심 있는 DAI 솔루션 선택
종합 서비스 DAI
이 가이드에서는 IMA DAI SDK를 간단한 동영상 플레이어 앱에 통합하는 방법을 보여줍니다. 완료된 샘플 통합을 확인하거나 따라 하려면 GitHub에서 BasicExample을 다운로드하세요.
IMA DAI 개요
IMA DAI를 구현하려면 이 가이드에 설명된 대로 세 가지 주요 SDK 구성요소가 필요합니다.
IMAAdDisplayContainer: 동영상 재생 요소 위에 있으며 광고 UI 요소를 포함하는 컨테이너 객체입니다.IMAAdsLoader: 스트림을 요청하고 스트림 요청 응답 객체에 의해 트리거된 이벤트를 처리하는 객체입니다. 애플리케이션 수명 동안 재사용할 수 있는 광고 로더를 하나만 인스턴스화해야 합니다.IMAStreamRequest–IMAVODStreamRequest또는IMALiveStreamRequest: 스트림 요청을 정의하는 객체입니다. 스트림 요청은 주문형 비디오 또는 라이브 스트림용일 수 있습니다. 라이브 스트림 요청은 애셋 키를 지정하고 VOD 요청은 CMS ID와 동영상 ID를 지정합니다. 두 요청 유형 모두 지정된 스트림에 액세스하는 데 필요한 API 키와 Google Ad Manager 설정에 지정된 대로 IMA SDK가 광고 식별자를 처리하는 데 필요한 Google Ad Manager 네트워크 코드를 선택적으로 포함할 수 있습니다.IMAStreamManager: 동적 광고 삽입 스트림과 DAI 백엔드와의 상호작용을 처리하는 객체입니다. 스트림 관리자는 추적 핑을 처리하고 스트림 및 광고 이벤트를 게시자에게 전달합니다.
기본 요건
시작하기 전에 다음이 필요합니다.
- Xcode 13 이상
- Swift Package Manager, CocoaPods 또는 다운로드한 tvOS용 IMA DAI SDK 사본
새 Xcode 프로젝트 만들기
Xcode에서 Objective-C를 사용하여 새 tvOS 프로젝트를 만듭니다. BasicExample을 프로젝트 이름으로 사용합니다.
Xcode 프로젝트에 IMA DAI SDK 추가
다음 세 가지 방법 중 하나를 사용하여 IMA DAI SDK를 설치합니다.
Swift Package Manager를 사용하여 SDK 설치
양방향 미디어 광고 SDK는 버전 4.8.2부터 Swift Package Manager를 지원합니다. Swift 패키지를 가져오려면 다음 단계를 따르세요.
Xcode에서 File(파일) > Add Packages(패키지 추가)로 이동하여 GoogleInteractiveMediaAds Swift 패키지를 설치합니다.
표시되는 메시지에서 GoogleInteractiveMediaAds Swift 패키지 GitHub 저장소를 검색합니다.
https://github.com/googleads/swift-package-manager-google-interactive-media-ads-tvos사용할 GoogleInteractiveMediaAds Swift 패키지의 버전을 선택합니다. 새 프로젝트의 경우 Up to Next Major Version을 사용하는 것이 좋습니다.
작업이 완료되면 Xcode에서 패키지 종속 항목을 확인하고 백그라운드에서 다운로드합니다. 패키지 종속 항목을 추가하는 방법에 대한 자세한 내용은 Apple 도움말을 참고하세요.
CocoaPods를 사용하여 SDK 설치
CocoaPods는 Xcode 프로젝트의 종속 항목 관리자이며 IMA DAI SDK를 설치하는 데 권장되는 방법입니다. CocoaPods 설치 또는 사용에 대한 자세한 내용은 CocoaPods 문서를 참고하세요. CocoaPods를 설치한 후 다음 안내에 따라 IMA DAI SDK를 설치합니다.
BasicExample.xcodeproj 파일과 동일한 디렉터리에 Podfile이라는 텍스트 파일을 만들고 다음 구성을 추가합니다.
source 'https://github.com/CocoaPods/Specs.git' platform :tvos, '15' target "BasicExample" do pod 'GoogleAds-IMA-tvOS-SDK', '~> 4.16.0' endPodfile이 포함된 디렉터리에서 다음을 실행합니다.
pod install --repo-updateBasicExample.xcworkspace 파일을 열고 BasicExample 및 Pods (CocoaPods로 설치된 종속 항목)라는 두 프로젝트가 포함되어 있는지 확인하여 설치가 성공했는지 확인합니다.
SDK 수동 다운로드 및 설치
Swift Package Manager 또는 CocoaPods를 사용하지 않으려면 IMA DAI SDK를 다운로드하여 프로젝트에 수동으로 추가하면 됩니다.
IMA SDK 가져오기
import 문을 사용하여 IMA 프레임워크를 추가합니다.
Objective-C
#import "ViewController.h"
#import <AVKit/AVKit.h>
@import GoogleInteractiveMediaAds;
Swift
import AVFoundation
import GoogleInteractiveMediaAds
import UIKit
동영상 플레이어를 만들고 IMA SDK 통합
다음 예에서는 IMA SDK를 초기화합니다.
Objective-C
// Live stream asset key, VOD content source and video IDs, and backup content URL.
static NSString *const kAssetKey = @"c-rArva4ShKVIAkNfy6HUQ";
static NSString *const kContentSourceID = @"2548831";
static NSString *const kVideoID = @"tears-of-steel";
static NSString *const kNetworkCode = @"21775744923";
static NSString *const kBackupStreamURLString =
@"http://googleimadev-vh.akamaihd.net/i/big_buck_bunny/bbb-,480p,720p,1080p,.mov.csmil/"
@"master.m3u8";
static const StreamType kDefaultStreamType = StreamTypeLive;
@interface ViewController () <IMAAdsLoaderDelegate,
IMAStreamManagerDelegate,
AVPlayerViewControllerDelegate>
@property(nonatomic) IMAAdsLoader *adsLoader;
@property(nonatomic) IMAAdDisplayContainer *adDisplayContainer;
@property(nonatomic) UIView *adContainerView;
@property(nonatomic) id<IMAVideoDisplay> videoDisplay;
@property(nonatomic) IMAStreamManager *streamManager;
@property(nonatomic) AVPlayerViewController *playerViewController;
@property(nonatomic, getter=isAdBreakActive) BOOL adBreakActive;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor blackColor];
self.streamType = kDefaultStreamType;
[self setupAdsLoader];
[self setupPlayer];
[self setupAdContainer];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self requestStream];
}
- (void)setupPlayer {
// Create a stream video player.
AVPlayer *player = [[AVPlayer alloc] init];
self.playerViewController = [[AVPlayerViewController alloc] init];
self.playerViewController.player = player;
// Attach video player to view hierarchy.
[self addChildViewController:self.playerViewController];
[self.view addSubview:self.playerViewController.view];
self.playerViewController.view.frame = self.view.bounds;
[self.playerViewController didMoveToParentViewController:self];
}
Swift
class ViewController:
UIViewController,
IMAAdsLoaderDelegate,
IMAStreamManagerDelegate,
AVPlayerViewControllerDelegate
{
// Live stream asset key, VOD content source and video IDs, Google Ad Manager network code, and
// backup content URL.
static let assetKey = "c-rArva4ShKVIAkNfy6HUQ"
static let contentSourceID = "2548831"
static let videoID = "tears-of-steel"
static let networkCode = "21775744923"
static let backupStreamURLString =
"http://googleimadev-vh.akamaihd.net/i/big_buck_bunny/bbb-,480p,720p,1080p,.mov.csmil/master.m3u8"
var adsLoader: IMAAdsLoader?
var videoDisplay: IMAAVPlayerVideoDisplay!
var adDisplayContainer: IMAAdDisplayContainer?
var adContainerView: UIView?
private var streamManager: IMAStreamManager?
private var contentPlayhead: IMAAVPlayerContentPlayhead?
private var playerViewController: AVPlayerViewController!
private var userSeekTime = 0.0
private var adBreakActive = false
private enum StreamType {
case live
/// Video on demand.
case vod
}
/// Set the stream type here.
private let currentStreamType: StreamType = .live
deinit {
NotificationCenter.default.removeObserver(self)
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.black
setupAdsLoader()
setupPlayer()
setupAdContainer()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
requestStream()
}
func setupPlayer() {
let player = AVPlayer()
let playerViewController = AVPlayerViewController()
playerViewController.delegate = self
playerViewController.player = player
// Set up our content playhead and contentComplete callback.
contentPlayhead = IMAAVPlayerContentPlayhead(avPlayer: player)
NotificationCenter.default.addObserver(
self,
selector: #selector(ViewController.contentDidFinishPlaying(_:)),
name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
object: player.currentItem)
self.addChild(playerViewController)
playerViewController.view.frame = self.view.bounds
self.view.insertSubview(playerViewController.view, at: 0)
playerViewController.didMove(toParent: self)
self.playerViewController = playerViewController
}
viewDidLoad()에서 setupAdsLoader()는 IMAAdsLoader를 만들고 setupPlayer()는 AVPlayerViewController를 만들며 setupAdContainer()는 광고 표시를 위해 UIView를 준비합니다. 뷰가 표시되면 viewDidAppear()가 requestStream()를 호출하여 DAI 스트림을 요청합니다.
스트림 요청 매개변수를 정의하기 위해 이 예에서는 라이브 스트림의 경우 asset key, VOD 스트림의 경우 content source ID 및 video ID와 같은 상수를 사용합니다. 이 예에서는 다음 구성요소를 사용하여 IMA SDK를 관리합니다.
adsLoader: Google Ad Manager에 대한 스트림 요청을 처리합니다. 앱의 수명 주기에 단일 인스턴스를 사용하는 것이 좋습니다.videoDisplay: IMA가 AVPlayer를 사용하여 동영상 재생을 제어하고 재생 이벤트를 추적할 수 있도록 하는IMAVideoDisplay구현입니다.adDisplayContainer: 광고 UI 요소를 렌더링하고 광고 시청 중에 UI 포커스를 처리하는 데 사용되는 뷰를 관리합니다.streamManager: 결합된 광고 및 콘텐츠 스트림의 재생을 관리하고 대리자를 사용하여 광고 수명 주기 이벤트를 전송합니다.playerViewController: IMA SDK로 관리되는 동영상 스트림을 표시하는 데 사용되는 tvOS 플레이어입니다.adBreakActive: 광고 시청 중인지 여부를 나타내는 불리언 플래그로, 광고 위로 탐색하는 것을 방지하고 UI 포커스를 관리하는 데 사용됩니다.
IMAAdsLoader 구현
그런 다음 IMAAdsLoader를 인스턴스화하고 광고 컨테이너 뷰를 뷰 계층 구조에 연결합니다.
Objective-C
- (void)setupAdsLoader {
self.adsLoader = [[IMAAdsLoader alloc] init];
self.adsLoader.delegate = self;
}
- (void)setupAdContainer {
// Attach the ad container to the view hierarchy on top of the player.
self.adContainerView = [[UIView alloc] init];
[self.view addSubview:self.adContainerView];
self.adContainerView.frame = self.view.bounds;
// Keep hidden initially, until an ad break.
self.adContainerView.hidden = YES;
}
Swift
func setupAdsLoader() {
let adsLoader = IMAAdsLoader(settings: nil)
adsLoader.delegate = self
self.adsLoader = adsLoader
}
func setupAdContainer() {
// Attach the ad container to the view hierarchy on top of the player.
let adContainerView = UIView()
self.view.addSubview(adContainerView)
adContainerView.frame = self.view.bounds
// Keep hidden initially, until an ad break.
adContainerView.isHidden = true
self.adContainerView = adContainerView
}
스트림 요청하기
스트림 정보를 보유할 상수를 몇 개 만들고 스트림 요청 함수를 구현하여 요청합니다.
Objective-C
- (void)requestStream {
self.videoDisplay =
[[IMAAVPlayerVideoDisplay alloc] initWithAVPlayer:self.playerViewController.player];
self.adDisplayContainer = [[IMAAdDisplayContainer alloc] initWithAdContainer:self.adContainerView
viewController:self];
// Use the streamType property to determine which request to create.
IMAStreamRequest *request;
switch (self.streamType) {
case StreamTypeLive: {
request = [[IMALiveStreamRequest alloc] initWithAssetKey:kAssetKey
networkCode:kNetworkCode
adDisplayContainer:self.adDisplayContainer
videoDisplay:self.videoDisplay
userContext:nil];
NSLog(@"IMA: Requesting Live Stream with Asset Key: %@.", kAssetKey);
break;
}
case StreamTypeVOD: {
request = [[IMAVODStreamRequest alloc] initWithContentSourceID:kContentSourceID
videoID:kVideoID
networkCode:kNetworkCode
adDisplayContainer:self.adDisplayContainer
videoDisplay:self.videoDisplay
userContext:nil];
NSLog(@"IMA: Requesting VOD Stream with Video ID: %@.", kVideoID);
break;
}
}
if (request) {
[self.adsLoader requestStreamWithRequest:request];
} else {
// Fallback or error handling if no request object was created
NSLog(@"IMA Error: Could not create stream request for unknown type.");
[self playBackupStream];
}
}
Swift
func requestStream() {
guard let playerViewController = self.playerViewController else { return }
guard let adContainerView = self.adContainerView else { return }
guard let adsLoader = self.adsLoader else { return }
self.videoDisplay = IMAAVPlayerVideoDisplay(avPlayer: playerViewController.player!)
let adDisplayContainer = IMAAdDisplayContainer(
adContainer: adContainerView, viewController: self)
self.adDisplayContainer = adDisplayContainer
// Variable to hold the specific stream request object.
let request: IMAStreamRequest
switch self.currentStreamType {
case .live:
// Create a live stream request.
request = IMALiveStreamRequest(
assetKey: ViewController.assetKey,
networkCode: ViewController.networkCode,
adDisplayContainer: adDisplayContainer,
videoDisplay: self.videoDisplay,
pictureInPictureProxy: nil,
userContext: nil)
print("IMA: Requesting Live Stream with asset key \(ViewController.assetKey)")
case .vod:
// Create a VOD stream request.
request = IMAVODStreamRequest(
contentSourceID: ViewController.contentSourceID,
videoID: ViewController.videoID,
networkCode: ViewController.networkCode,
adDisplayContainer: adDisplayContainer,
videoDisplay: self.videoDisplay,
pictureInPictureProxy: nil,
userContext: nil)
print(
"IMA: Requesting VOD Stream with content source ID \(ViewController.contentSourceID) and "
+ "video ID \(ViewController.videoID)")
}
adsLoader.requestStream(with: request)
}
스트림 이벤트 처리
IMAAdsLoader 및 IMAStreamManager은 초기화, 오류, 스트림 상태 변경을 처리하는 데 사용되는 이벤트를 발생시킵니다. 이러한 이벤트는 IMAAdsLoaderDelegate 및 IMAStreamManagerDelegate 프로토콜을 통해 실행됩니다. 광고 로드 이벤트를 수신하고 스트림을 초기화합니다. 광고가 로드되지 않으면 대신 백업 스트림을 재생합니다.
Objective-C
- (void)playBackupStream {
NSURL *backupStreamURL = [NSURL URLWithString:kBackupStreamURLString];
[self.videoDisplay loadStream:backupStreamURL withSubtitles:@[]];
[self.videoDisplay play];
[self startMediaSession];
}
- (void)startMediaSession {
[[AVAudioSession sharedInstance] setActive:YES error:nil];
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
}
#pragma mark - IMAAdsLoaderDelegate
- (void)adsLoader:(IMAAdsLoader *)loader adsLoadedWithData:(IMAAdsLoadedData *)adsLoadedData {
// Initialize and listen to stream manager's events.
self.streamManager = adsLoadedData.streamManager;
self.streamManager.delegate = self;
[self.streamManager initializeWithAdsRenderingSettings:nil];
NSLog(@"Stream created with: %@.", self.streamManager.streamId);
}
- (void)adsLoader:(IMAAdsLoader *)loader failedWithErrorData:(IMAAdLoadingErrorData *)adErrorData {
// Fall back to playing the backup stream.
NSLog(@"Error loading ads: %@", adErrorData.adError.message);
[self playBackupStream];
}
Swift
@objc func contentDidFinishPlaying(_ notification: Notification) {
guard let adsLoader = self.adsLoader else { return }
adsLoader.contentComplete()
}
func startMediaSession() {
try? AVAudioSession.sharedInstance().setActive(true, options: [])
try? AVAudioSession.sharedInstance().setCategory(.playback)
}
// MARK: - IMAAdsLoaderDelegate
func adsLoader(_ loader: IMAAdsLoader, adsLoadedWith adsLoadedData: IMAAdsLoadedData) {
let streamManager = adsLoadedData.streamManager!
streamManager.delegate = self
streamManager.initialize(with: nil)
self.streamManager = streamManager
}
func adsLoader(_ loader: IMAAdsLoader, failedWith adErrorData: IMAAdLoadingErrorData) {
print("Error loading ads: \(adErrorData.adError.message)")
let streamUrl = URL(string: ViewController.backupStreamURLString)
self.videoDisplay.loadStream(streamUrl!, withSubtitles: [])
self.videoDisplay.play()
playerViewController.player?.play()
}
로깅 및 오류 이벤트 처리
스트림 관리자 위임으로 처리할 수 있는 이벤트가 여러 개 있지만 기본 구현의 경우 가장 중요한 용도는 이벤트 로깅을 실행하고, 광고가 재생되는 동안 탐색 작업을 방지하고, 오류를 처리하는 것입니다.
Objective-C
#pragma mark - IMAStreamManagerDelegate
- (void)streamManager:(IMAStreamManager *)streamManager didReceiveAdEvent:(IMAAdEvent *)event {
NSLog(@"StreamManager event (%@).", event.typeString);
switch (event.type) {
case kIMAAdEvent_STREAM_STARTED: {
[self startMediaSession];
break;
}
case kIMAAdEvent_STARTED: {
// Log extended data.
NSString *extendedAdPodInfo = [[NSString alloc]
initWithFormat:@"Showing ad %zd/%zd, bumper: %@, title: %@, description: %@, contentType:"
@"%@, pod index: %zd, time offset: %lf, max duration: %lf.",
event.ad.adPodInfo.adPosition, event.ad.adPodInfo.totalAds,
event.ad.adPodInfo.isBumper ? @"YES" : @"NO", event.ad.adTitle,
event.ad.adDescription, event.ad.contentType, event.ad.adPodInfo.podIndex,
event.ad.adPodInfo.timeOffset, event.ad.adPodInfo.maxDuration];
NSLog(@"%@", extendedAdPodInfo);
break;
}
case kIMAAdEvent_AD_BREAK_STARTED: {
self.adContainerView.hidden = NO;
// Trigger an update to send focus to the ad display container.
self.adBreakActive = YES;
[self setNeedsFocusUpdate];
break;
}
case kIMAAdEvent_AD_BREAK_ENDED: {
self.adContainerView.hidden = YES;
// Trigger an update to send focus to the content player.
self.adBreakActive = NO;
[self setNeedsFocusUpdate];
break;
}
case kIMAAdEvent_ICON_FALLBACK_IMAGE_CLOSED: {
// Resume playback after the user has closed the dialog.
[self.videoDisplay play];
break;
}
default:
break;
}
}
- (void)streamManager:(IMAStreamManager *)streamManager didReceiveAdError:(IMAAdError *)error {
// Fall back to playing the backup stream.
NSLog(@"StreamManager error: %@", error.message);
[self playBackupStream];
}
Swift
// MARK: - IMAStreamManagerDelegate
func streamManager(_ streamManager: IMAStreamManager, didReceive event: IMAAdEvent) {
print("StreamManager event \(event.typeString).")
switch event.type {
case IMAAdEventType.STREAM_STARTED:
self.startMediaSession()
case IMAAdEventType.STARTED:
// Log extended data.
if let ad = event.ad {
let extendedAdPodInfo = String(
format: "Showing ad %zd/%zd, bumper: %@, title: %@, "
+ "description: %@, contentType:%@, pod index: %zd, "
+ "time offset: %lf, max duration: %lf.",
ad.adPodInfo.adPosition,
ad.adPodInfo.totalAds,
ad.adPodInfo.isBumper ? "YES" : "NO",
ad.adTitle,
ad.adDescription,
ad.contentType,
ad.adPodInfo.podIndex,
ad.adPodInfo.timeOffset,
ad.adPodInfo.maxDuration)
print("\(extendedAdPodInfo)")
}
break
case IMAAdEventType.AD_BREAK_STARTED:
if let adContainerView = self.adContainerView {
adContainerView.isHidden = false
}
// Trigger an update to send focus to the ad display container.
adBreakActive = true
setNeedsFocusUpdate()
break
case IMAAdEventType.AD_BREAK_ENDED:
if let adContainerView = self.adContainerView {
adContainerView.isHidden = true
}
// Trigger an update to send focus to the content player.
adBreakActive = false
setNeedsFocusUpdate()
break
case IMAAdEventType.ICON_FALLBACK_IMAGE_CLOSED:
// Resume playback after the user has closed the dialog.
self.videoDisplay.play()
break
default:
break
}
}
func streamManager(_ streamManager: IMAStreamManager, didReceive error: IMAAdError) {
print("StreamManager error: \(error.message ?? "Unknown Error")")
}
작업이 끝났습니다. 이제 IMA DAI SDK를 사용하여 광고를 요청하고 표시합니다. 고급 SDK 기능에 대해 자세히 알아보려면 다른 가이드나 GitHub의 샘플을 참고하세요.