Hướng dẫn này trình bày cách tích hợp SDK IMA DAI vào một ứng dụng trình phát video. Nếu bạn muốn xem hoặc làm theo một mẫu tích hợp hoàn chỉnh, hãy tải BasicExample xuống từ GitHub.
Tổng quan về IMA DAI
Việc triển khai IMA DAI bao gồm 3 thành phần SDK chính, như hướng dẫn này minh hoạ:
IMAAdDisplayContainer: Một đối tượng vùng chứa nằm trên phần tử phát video và chứa các phần tử trên giao diện người dùng của quảng cáo.IMAAdsLoader: Một đối tượng yêu cầu luồng và xử lý các sự kiện được kích hoạt bởi đối tượng phản hồi yêu cầu luồng. Bạn chỉ nên tạo thực thể một trình tải quảng cáo và có thể sử dụng lại trình tải này trong suốt thời gian hoạt động của ứng dụng.IMAStreamRequest–IMAVODStreamRequesthoặcIMALiveStreamRequest: Một đối tượng xác định yêu cầu về luồng. Yêu cầu về luồng có thể là video theo yêu cầu hoặc sự kiện phát trực tiếp. Yêu cầu phát trực tiếp chỉ định khoá thành phần, trong khi yêu cầu VOD chỉ định mã CMS và mã video. Cả hai loại yêu cầu đều có thể bao gồm khoá API cần thiết để truy cập vào các luồng được chỉ định và mã mạng Google Ad Manager để IMA SDK xử lý giá trị nhận dạng quảng cáo như được chỉ định trong phần cài đặt Google Ad Manager.IMAStreamManager: Một đối tượng xử lý các luồng chèn quảng cáo động và hoạt động tương tác với phần phụ trợ DAI. Trình quản lý luồng cũng xử lý các ping theo dõi và chuyển tiếp các sự kiện quảng cáo và luồng đến nhà xuất bản.
Điều kiện tiên quyết
Trước khi bắt đầu, bạn cần có:
- Xcode 13 trở lên
- Phương thức cài đặt IMA SDK:
- Trình quản lý gói Swift (ưu tiên)
- Bản sao có thể tải xuống của IMA SDK cho tvOS
Tạo dự án Xcode mới
Trong Xcode, hãy tạo một dự án tvOS mới bằng Objective-C. Sử dụng BasicExample làm tên dự án.
Thêm SDK IMA DAI vào dự án Xcode
Để cài đặt IMA SDK, hãy chọn một phương thức bạn muốn.
Nên dùng: Cài đặt SDK bằng Trình quản lý gói Swift
SDK quảng cáo trên phương tiện truyền thông tương tác hỗ trợ Trình quản lý gói Swift phiên bản 4.8.2 trở lên. Hãy làm theo các bước sau để nhập gói Swift.
Trong Xcode, hãy cài đặt Gói Swift GoogleInteractiveMediaAds bằng cách chuyển đến File (Tệp) > Add Packages (Thêm gói).
Trong lời nhắc xuất hiện, hãy tìm kho lưu trữ GitHub cho Gói Swift GoogleInteractiveMediaAds:
https://github.com/googleads/swift-package-manager-google-interactive-media-ads-tvosChọn phiên bản của Gói Swift GoogleInteractiveMediaAds mà bạn muốn sử dụng. Đối với các dự án mới, bạn nên sử dụng Phiên bản lớn tiếp theo.
Khi bạn hoàn tất, Xcode sẽ phân giải các phần phụ thuộc của gói và tải các phần phụ thuộc đó xuống ở chế độ nền. Để biết thêm thông tin chi tiết về cách thêm các phần phụ thuộc của gói, hãy đọc bài viết của Apple.
Tải xuống và cài đặt SDK theo cách thủ công
Nếu bạn không muốn sử dụng Trình quản lý gói Swift, hãy tải xuống và thêm IMA SDK vào dự án theo cách thủ công.
Nhập IMA SDK
Thêm khung IMA bằng câu lệnh nhập:
Objective-C
#import "ViewController.h"
#import <AVKit/AVKit.h>
@import GoogleInteractiveMediaAds;
Swift
import AVFoundation
import GoogleInteractiveMediaAds
import UIKit
Tạo trình phát video và tích hợp SDK IMA
Ví dụ sau đây khởi chạy 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
}
Trong viewDidLoad(), setupAdsLoader() tạo IMAAdsLoader, setupPlayer() tạo AVPlayerViewController và setupAdContainer() chuẩn bị UIView để hiển thị quảng cáo. Khi khung hiển thị xuất hiện, viewDidAppear() sẽ gọi requestStream() để yêu cầu luồng DAI.
Để xác định các tham số yêu cầu luồng, ví dụ này sử dụng hằng số, chẳng hạn như asset key cho sự kiện phát trực tiếp hoặc content source ID và video ID cho luồng video theo yêu cầu (VOD). Ví dụ này cũng sử dụng các thành phần sau để quản lý IMA SDK:
adsLoader: Xử lý các yêu cầu về luồng đến Google Ad Manager. Bạn nên sử dụng một thực thể duy nhất cho vòng đời của ứng dụng.videoDisplay: Một cách triển khaiIMAVideoDisplaycho phép IMA kiểm soát việc phát video và theo dõi các sự kiện phát bằng AVPlayer.adDisplayContainer: Quản lý chế độ xem dùng để kết xuất các phần tử trên giao diện người dùng của quảng cáo và xử lý tiêu điểm giao diện người dùng trong thời gian nghỉ để quảng cáo.streamManager: Quản lý việc phát luồng nội dung và quảng cáo kết hợp, đồng thời gửi các sự kiện trong vòng đời quảng cáo bằng cách sử dụng trình uỷ nhiệm.playerViewController: Trình phát tvOS dùng để trình bày luồng video do SDK IMA quản lý.adBreakActive: Cờ boolean cho biết liệu một điểm chèn quảng cáo có đang phát hay không, được dùng để ngăn việc tua qua quảng cáo và quản lý tiêu điểm giao diện người dùng.
Triển khai IMAAdsLoader
Tiếp theo, hãy tạo thực thể cho IMAAdsLoader và đính kèm chế độ xem vùng chứa quảng cáo vào hệ phân cấp view.
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
}
Tạo yêu cầu phát trực tiếp
Tạo một vài hằng số để lưu giữ thông tin về luồng, sau đó triển khai hàm yêu cầu luồng để đưa ra yêu cầu.
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.
switch (self.streamType) {
case StreamTypeLive: {
IMALiveStreamRequest *request =
[[IMALiveStreamRequest alloc] initWithAssetKey:kAssetKey
networkCode:kNetworkCode
adDisplayContainer:self.adDisplayContainer
videoDisplay:self.videoDisplay
userContext:nil];
NSLog(@"IMA: Requesting Live Stream with Asset Key: %@.", kAssetKey);
request.useHLSInterstitials = YES;
[self.adsLoader requestStreamWithRequest:request];
break;
}
case StreamTypeVOD: {
IMAVODStreamRequest *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);
[self.adsLoader requestStreamWithRequest:request];
break;
}
}
}
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
switch self.currentStreamType {
case .live:
// Create a live stream request.
let 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)")
request.useHLSInterstitials = true
adsLoader.requestStream(with: request)
case .vod:
// Create a VOD stream request.
let 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)
}
}
Xử lý sự kiện luồng
IMAAdsLoader và IMAStreamManager kích hoạt các sự kiện xử lý quá trình khởi chạy, lỗi và thay đổi trong trạng thái luồng. Các sự kiện này kích hoạt qua giao thức IMAAdsLoaderDelegate và IMAStreamManagerDelegate. Lắng nghe sự kiện đã tải quảng cáo và khởi động luồng. Nếu không tải được quảng cáo, hãy phát
luồng dự phòng.
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()
}
Xử lý sự kiện ghi nhật ký và lỗi
Đại diện trình quản lý luồng có thể xử lý một số sự kiện, nhưng đối với các hoạt động triển khai cơ bản, mục đích sử dụng quan trọng nhất là ghi nhật ký sự kiện, ngăn chặn hành động tua trong khi quảng cáo đang phát và xử lý lỗi.
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")")
}
Vậy là xong! Giờ đây, bạn đang yêu cầu và hiển thị quảng cáo bằng SDK IMA DAI. Để tìm hiểu thêm về các tính năng nâng cao của SDK, hãy xem các hướng dẫn khác hoặc mẫu trên GitHub.