הגדרה עם DAI בשירות מלא

בחירת פלטפורמה: HTML5 Android iOS tvOS Cast Roku

במדריך הזה מוסבר איך לשלב את IMA DAI SDK באפליקציה של נגן וידאו. אם אתם רוצים לראות או לעקוב אחרי שילוב לדוגמה, אתם יכולים להוריד את BasicExample מ-GitHub.

סקירה כללית על IMA DAI

הטמעת IMA DAI כוללת שלושה רכיבי SDK עיקריים, כפי שמוצג במדריך הזה:

  • IMAAdDisplayContainer: אובייקט מאגר שמוצב מעל לרכיב ההפעלה של הסרטון ומכיל את רכיבי ממשק המשתמש של המודעה.
  • ‫ IMAAdsLoader: אובייקט שמבקש שידורים ומטפל באירועים שמופעלים על ידי אובייקטים של תגובה לבקשת שידור. צריך ליצור מופע של טוען מודעות אחד בלבד, שאפשר לעשות בו שימוש חוזר לאורך כל חיי האפליקציה.
  • ‫ IMAStreamRequest – IMAVODStreamRequest או IMALiveStreamRequest: אובייקט שמגדיר בקשה לשידור. הבקשות לשידור יכולות להיות לווידאו על פי דרישה (VOD) או לשידורים חיים. בבקשות לשידור חי מציינים מפתח נכס, ובבקשות ל-VOD מציינים מזהה CMS ומזהה סרטון. שני סוגי הבקשות יכולים לכלול מפתח API שנדרש כדי לגשת לסטרימים שצוינו, וקוד רשת של Google Ad Manager כדי ש-IMA SDK יטפל במזהי מודעות כפי שצוין בהגדרות של Google Ad Manager.
  • IMAStreamManager: אובייקט שמטפל בסטרימינג של הטמעת מודעות דינמיות ובאינטראקציות עם הקצה העורפי של DAI. מנהל השידור גם מטפל בפינגים של מעקב ומעביר אירועים של שידורים ומודעות לבעל האפליקציה.

דרישות מוקדמות

לפני שמתחילים, צריך:

יצירת פרויקט Xcode חדש

ב-Xcode, יוצרים פרויקט tvOS חדש באמצעות Objective-C. בשם הפרויקט, משתמשים ב-BasicExample.

הוספת IMA DAI SDK לפרויקט Xcode

כדי להתקין את IMA SDK, בוחרים את השיטה המועדפת.

מומלץ: להתקין את ה-SDK באמצעות Swift Package Manager

החל מגרסה 4.8.2, ערכת Interactive Media Ads SDK תומכת ב-Swift Package Manager. כדי לייבא את חבילת Swift, פועלים לפי השלבים הבאים:

  1. ב-Xcode, מתקינים את חבילת GoogleInteractiveMediaAds Swift Package: עוברים אל File > Add Packages (קובץ > הוספת חבילות).

  2. בתיבת הדו-שיח שמופיעה, מחפשים את מאגר ה-GitHub של חבילת Swift‏ GoogleInteractiveMediaAds:

    https://github.com/googleads/swift-package-manager-google-interactive-media-ads-tvos
    
  3. בוחרים את הגרסה של GoogleInteractiveMediaAds Swift Package שבה רוצים להשתמש. בפרויקטים חדשים, מומלץ להשתמש באפשרות Up to Next Major Version.

אחרי שתסיימו, פלטפורמת Xcode תטפל ביחסי התלות שבחבילה ותוריד אותם ברקע. לפרטים נוספים על הוספת יחסי תלות של חבילות, קראו את המאמר של אפל.

הורדה והתקנה של ה-SDK באופן ידני

אם אתם לא רוצים להשתמש ב-Swift Package Manager, אתם יכולים להוריד את IMA SDK ולהוסיף אותו לפרויקט באופן ידני.

ייבוא של IMA SDK

מוסיפים את ה-framework של 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 לשידורים חיים, או content source ID ו-video ID לשידורי וידאו על פי דרישה (VOD). בדוגמה נעשה שימוש גם ברכיבים הבאים כדי לנהל את IMA SDK:

  • ‫adsLoader: מטפל בבקשות לסטרימינג ב-Google Ad Manager. מומלץ להשתמש במופע יחיד למחזור החיים של האפליקציה.
  • ‫videoDisplay: הטמעה של IMAVideoDisplay שמאפשרת ל-IMA לשלוט בהפעלת הסרטונים ולעקוב אחרי אירועי הפעלה באמצעות AVPlayer.
  • ‫adDisplayContainer: מנהל את התצוגה שמשמשת לעיבוד של רכיבי ממשק המשתמש של המודעה ולטיפול במיקוד של ממשק המשתמש במהלך הפסקות למודעות.
  • streamManager: מנהל את ההפעלה של הסטרימינג המשולב של המודעות והתוכן, ושולח אירועים במחזור החיים של המודעות באמצעות הנציג שלו.
  • ‫playerViewController: נגן tvOS שמשמש להצגת שידור הווידאו שמנוהל על ידי IMA SDK.
  • ‫adBreakActive: דגל בוליאני שמציין אם מופעלת הפסקה למודעה, ומשמש למניעת דילוג על מודעות ולניהול המיקוד של ממשק המשתמש.

הטמעת 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.
  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)
  }
}

טיפול באירועי סטרימינג

האובייקטים 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.