Clientseitige HLS-Interstitials für Livestreams verwenden

Die HLS-Spezifikation für Interstitials bietet eine flexible Möglichkeit, Werbung in einen Video- oder Audiostream einzuplanen und einzufügen. Beim clientseitigen Ansatz übernimmt Ihre Anwendung die vollständige Kontrolle darüber, wann Werbeunterbrechungen angefordert und wiedergegeben werden. Dazu wird die Klasse AVPlayerInterstitialEvent erstellt. Bei dieser Methode sind die EXT-X-DATERANGE-Tags in den Manifesten des Content-Streams nicht erforderlich. Mit clientseitigen HLS-Interstitials können Sie Anzeigen dynamisch in beliebige Inhalte einfügen, ohne das Streammanifest oder die Media-Dateien ändern zu müssen.

In diesem Leitfaden wird beschrieben, wie Sie das IMA SDK (Interactive Media Ads) in eine Videoplayer-App einbinden, die eine SGAI-Livestreamsitzung (Server Guided Ad Insertion) erstellt und Interstitials clientseitig plant. Weitere Informationen zur servergesteuerten dynamischen Anzeigenbereitstellung

Vorbereitung

Für den Start ist Folgendes erforderlich:

  • Ein neues Xcode-Projekt, in dem Storyboard für die Benutzeroberfläche verwendet wird. Weitere Informationen finden Sie unter Xcode-Projekt für eine App erstellen.

  • Google IMA SDK Weitere Informationen finden Sie unter IMA SDK für DAI einrichten.

  • Die folgenden Parameter für Ihre DAI-Livestreamanfrage:

    • NETWORK_CODE: Ihr Google Ad Manager-Netzwerkcode.
    • CUSTOM_ASSET_KEY: Ihr benutzerdefinierter String zur Identifizierung des DAI-Livestream-Events. Für das Livestream-Ereignis muss der DAI-Typ „Manifest für die Pod-Auslieferung“ festgelegt sein.

Storyboard konfigurieren

Führen Sie in der Datei iPhone.storyboard folgende Schritte aus:

  1. Erstellen Sie ein UIView-Objekt als Container für den Videoplayer und die Anzeigen-UI.
  2. Erstellen Sie eine adUIView-Property der Klasse ViewController, um eine Verbindung zum UIView-Objekt herzustellen.
  3. Erstellen Sie im adUIView-Objekt ein UIButton, das als Schaltfläche zum Abspielen fungiert.
  4. Erstellen Sie eine playButton-Property der Klasse ViewController, um eine Verbindung zum UIButton-Objekt herzustellen, und eine onPlayButtonTouch-Funktion, um Nutzer-Taps zu verarbeiten.

Anzeigen-Loader initialisieren

Gehen Sie im viewDidLoad-Ereignis des Haupt-View-Controllers so vor:

  1. Richten Sie einen Videoplayer mit den Klassen AVPlayer und AVPlayerLayer ein.
  2. Erstellen Sie IMAAdDisplayContainer- und IMAAVPlayerVideoDisplay-Objekte. Der Container für die Anzeigendarstellung gibt die adUIView für das IMA DAI SDK an, in die die Unteransichten der Anzeigen-UI eingefügt werden sollen. Das Videoanzeigeobjekt fungiert als Brücke zwischen der Anzeigenlogik des IMA DAI SDK und dem AVFoundation-Wiedergabesystem und verfolgt die Wiedergabe von Videoanzeigen.
  3. Initialisieren Sie das IMAAdsLoader-Objekt mit den Einstellungen für die Anzeigenwiedergabe und die Lokalisierung der Anzeigen-UI.

Im folgenden Beispiel wird ein Ads Loader mit einem leeren IMASettings-Objekt initialisiert:

import AVFoundation
import GoogleInteractiveMediaAds
import UIKit

// The main view controller for the sample app.
class ViewController:
  UIViewController, IMAAdsLoaderDelegate, IMAStreamManagerDelegate
{

  private enum StreamParameters {
    static let contentStream =
      "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8"

    // Find your [Google Ad Manager network code](https://support.google.com/admanager/answer/7674889)
    // or use the test network code and custom asset key with the DAI type "Pod serving manifest"
    // from [DAI sample streams](https://developers.google.com/ad-manager/dynamic-ad-insertion/streams#pod_serving_dai).

    /// Google Ad Manager network code.
    static let networkCode = "21775744923"

    /// Google DAI livestream custom asset key.
    static let customAssetKey = "sgai-hls-live"

    // Set your ad break duration.
    static let adBreakDurationMs = 10000
  }

  /// The play button to start the stream.
  /// It is hidden when the stream starts playing.
  @IBOutlet private weak var playButton: UIButton!

  /// The view to display the ad UI elements: countdown, skip button, etc.
  /// It is hidden when the stream starts playing.
  @IBOutlet private weak var adUIView: UIView!

  /// The reference of your ad UI view for the IMA SDK to create the ad's user interface elements.
  private var adDisplayContainer: IMAAdDisplayContainer!

  /// The AVPlayer instance that plays the content and the ads.
  private var player: AVPlayer!

  /// The reference of your video player for the IMA SDK to play and monitor the ad breaks.
  private var videoDisplay: IMAAVPlayerVideoDisplay!

  /// The entry point of the IMA SDK to make stream requests to Google Ad Manager.
  private var adsLoader: IMAAdsLoader!

  /// The reference of the ad stream manager, set when the ad stream is loaded.
  /// The IMA SDK requires a strong reference to the stream manager for the entire duration of
  /// the ad break.
  private var streamManager: IMAStreamManager?

  /// The ad stream session ID, set when the ad stream is loaded.
  private var adStreamSessionId: String?

  override func viewDidLoad() {

    // Initialize the IMA SDK.
    let adLoaderSettings = IMASettings()
    adsLoader = IMAAdsLoader(settings: adLoaderSettings)

    // Set up the video player and the container view.
    player = AVPlayer()
    let playerLayer = AVPlayerLayer(player: player)
    playerLayer.frame = adUIView.bounds
    adUIView.layer.addSublayer(playerLayer)
    playButton.layer.zPosition = CGFloat.greatestFiniteMagnitude

    // Create an object to monitor the stream playback.
    videoDisplay = IMAAVPlayerVideoDisplay(avPlayer: player)

    super.viewDidLoad()

    // Create a container object for ad UI elements.
    // See [example in video ads](https://support.google.com/admanager/answer/2695279#zippy=%2Cexample-in-video-ads)
    adDisplayContainer = IMAAdDisplayContainer(
      adContainer: adUIView, viewController: self, companionSlots: nil)

    // Specify the delegate for hanlding ad events of the stream session.
    adsLoader.delegate = self
  }

Streaminganfrage stellen

Wenn Sie Anzeigen für einen Contentstream anfordern möchten, erstellen Sie ein IMAPodStreamRequest-Objekt und übergeben Sie es an Ihre IMAAdsLoader-Instanz. Optional können Sie das Attribut adTagParameters festlegen, um DAI-Optionen und Targeting-Parameter für Ihren Stream anzugeben.

In diesem Beispiel wird die Methode loadAdStream im Ereignis viewDidAppear aufgerufen:

override func viewDidAppear(_ animated: Bool) {
  super.viewDidAppear(animated)

  loadAdStream()
  loadContentStream()
}

private func loadContentStream() {
  guard let contentURL = URL(string: StreamParameters.contentStream) else {
    print("Failed to load content stream. The URL is invalid.")
    return
  }
  let item = AVPlayerItem(url: contentURL)
  player.replaceCurrentItem(with: item)
}

/// Makes a stream request to Google Ad Manager.
private func loadAdStream() {
  let streamRequest = IMAPodStreamRequest(
    networkCode: StreamParameters.networkCode,
    customAssetKey: StreamParameters.customAssetKey,
    adDisplayContainer: adDisplayContainer,
    videoDisplay: videoDisplay,
    pictureInPictureProxy: nil,
    userContext: nil)

  // Register a streaming session on Google Ad Manager DAI servers.
  adsLoader.requestStream(with: streamRequest)
}

Rufen Sie in Ihrer Produktions-App die Methode loadAdStream auf, nachdem der Nutzer einen Contentstream ausgewählt hat.

Stream-Ladeereignisse verarbeiten

Implementieren Sie das Protokoll IMAAdsLoaderDelegate, um den Erfolg oder Misserfolg der Streamanfrage zu verarbeiten:

  • Bei Erfolg erhalten Sie ein IMAAdsLoadedData-Objekt mit dem IMAStreamManager. Speichern Sie den streamManager.streamId-Wert für die aktuelle DAI-Sitzung.
  • Bei einem Fehler wird der Fehler protokolliert.

Im folgenden Beispiel wird das Ereignis „Stream geladen“ verarbeitet und das Ereignis „Stream konnte nicht geladen werden“ protokolliert:

// MARK: - IMAAdsLoaderDelegate
func adsLoader(_ loader: IMAAdsLoader, adsLoadedWith adsLoadedData: IMAAdsLoadedData) {
  guard let streamManager = adsLoadedData.streamManager else {
    // Report a bug on [IMA SDK forum](https://groups.google.com/g/ima-sdk).
    print("Failed to retrieve stream manager from ads loaded data.")
    return
  }
  // Save the stream manager to handle ad events of the stream session.
  self.streamManager = streamManager
  streamManager.delegate = self
  let adRenderingSettings = IMAAdsRenderingSettings()
  // Uncomment the next line to enable the current view controller to get notified of ad clicks.
  // adRenderingSettings.linkOpenerDelegate = self
  // Initialize the stream manager to create ad UI elements.
  streamManager.initialize(with: adRenderingSettings)

  guard streamManager.streamId != nil else {
    // Report a bug on [IMA SDK forum](https://groups.google.com/g/ima-sdk).
    print("Failed to retrieve stream ID from stream manager.")
    return
  }
  // Save the ad stream session ID to construct ad pod requests.
  adStreamSessionId = streamManager.streamId
}

func adsLoader(_ loader: IMAAdsLoader, failedWith adErrorData: IMAAdLoadingErrorData) {
  guard let errorMessage = adErrorData.adError.message else {
    print("Stream registration failed with unknown error.")
    return
  }
  print("Stream registration failed with error: \(errorMessage)")
}

// MARK: - IMAStreamManagerDelegate
func streamManager(_ streamManager: IMAStreamManager, didReceive error: IMAAdError) {
  guard let errorMessage = error.message else {
    print("Ad stream failed to load with unknown error.")
    return
  }
  print("Ad stream failed to load with error: \(errorMessage)")
}

Anzeigenunterbrechungen planen

Wenn Sie eine Werbeunterbrechung planen möchten, erstellen Sie ein AVPlayerInterstitialEvent-Objekt. Legen Sie die templateItems-Property des Ereignisobjekts auf ein Array von AVPlayerItem-Objekten fest, wobei jedes Elementobjekt eine Ad-Pod-Manifest-URL enthält.

Hier finden Sie eine Anleitung zum Erstellen einer URL für ein HLS-Pod-Manifest.

Im folgenden Beispiel wird zur Veranschaulichung ein Pod-Kennzeichnungsstring mit der aktuellen Zeit des Content-Livestreams generiert. Die Funktion generatePodIdentifier gibt die Pod-Kennung als ad_break_id/mid-roll-{minute} zurück.

/// Generates a pod identifier based on the current time.
///
/// See [HLS pod manifest parameters](https://developers.google.com/ad-manager/dynamic-ad-insertion/api/pod-serving/reference/live#path_parameters_3).
///
/// - Returns: The pod identifier in either the format of "pod/{integer}" or "ad_break_id/{string}".
private func generatePodIdentifier(from currentSeconds: Int) -> String {
  let minute = Int(currentSeconds / 60) + 1
  return "ad_break_id/mid-roll-\(minute)"
}

Rufen Sie in Ihrer Produktions-App die Pod-ID aus einer Quelle ab, die für jede Werbeunterbrechung eindeutige Werte bereitstellt, die für alle Zuschauer des Livestreams synchronisiert werden.

Im folgenden Beispiel wird eine Werbeunterbrechung geplant, die innerhalb der nächsten zwei Minuten nach dem Klicken des Nutzers auf die Schaltfläche „Wiedergabe“ beginnt:

/// Schedules ad insertion shortly before ad break starts.
private func scheduleAdInsertion() {

  guard let streamID = self.adStreamSessionId else {
    print("The ad stream ID is not set. Skipping all ad breaks of the current stream session.")
    return
  }

  let currentSeconds = Int(Date().timeIntervalSince1970)
  var secondsToAdBreakStart = 60 - currentSeconds % 60
  // If there is less than 30 seconds remaining in the current minute, schedule the ad insertion
  // for the next minute instead.
  if secondsToAdBreakStart < 30 {
    secondsToAdBreakStart += 60
  }

  guard let primaryPlayerCurrentItem = player.currentItem else {
    print(
      "Failed to get the player item of the content stream. Skipping an ad break in \(secondsToAdBreakStart) seconds."
    )
    return
  }

  let adBreakStartTime = CMTime(
    seconds: CMTimeGetSeconds(player.currentTime())
      + Double(secondsToAdBreakStart), preferredTimescale: 1)

  // Create an identifier to construct the ad pod request for the next ad break.
  let adPodIdentifier = generatePodIdentifier(from: currentSeconds)

  guard
    let adPodManifestUrl = URL(
      string:
        "https://dai.google.com/linear/pods/v1/hls/network/\(StreamParameters.networkCode)/custom_asset/\(StreamParameters.customAssetKey)/\(adPodIdentifier).m3u8?stream_id=\(streamID)&pd=\(StreamParameters.adBreakDurationMs)"
    )
  else {
    print("Failed to generate the ad pod manifest URL. Skipping insertion of \(adPodIdentifier).")
    return
  }

  let interstitialEvent = AVPlayerInterstitialEvent(
    primaryItem: primaryPlayerCurrentItem,
    identifier: adPodIdentifier,
    time: adBreakStartTime,
    templateItems: [AVPlayerItem(url: adPodManifestUrl)],
    restrictions: [],
    resumptionOffset: .zero)
  let interstitialEventController = AVPlayerInterstitialEventController(primaryPlayer: player)
  interstitialEventController.events = [interstitialEvent]
  print(
    "Ad break scheduled to start in \(secondsToAdBreakStart) seconds. Ad break manifest URL: \(adPodManifestUrl)."
  )
}

Mit der scheduleAdInsertion-Methode wird die Startzeit der Werbeunterbrechung berechnet und eine Manifest-URL für den Anzeigen-Pod erstellt. Verwenden Sie diese URL, um ein AVPlayerInterstitialEvent-Objekt zu erstellen.

Optional können Sie den AVPlayerInterstitialEvent.Restrictions-Struct verwenden, um das Überspringen oder Zurückspulen von Nutzern während der Anzeigenwiedergabe einzuschränken.

Anzeigenereignisse verarbeiten

Implementieren Sie das Protokoll IMAStreamManagerDelegate, um Anzeigenereignisse zu verarbeiten. So können Sie nachvollziehen, wann Werbeunterbrechungen beginnen und enden, und Informationen zu einzelnen Anzeigen abrufen.

func streamManager(_ streamManager: IMAStreamManager, didReceive event: IMAAdEvent) {
  switch event.type {
  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:
    print("Ad break started.")
    break
  case IMAAdEventType.AD_BREAK_ENDED:
    print("Ad break ended.")
    break
  case IMAAdEventType.AD_PERIOD_STARTED:
    print("Ad period started.")
    break
  case IMAAdEventType.AD_PERIOD_ENDED:
    print("Ad period ended.")
    break
  default:
    break
  }
}

Führen Sie Ihre App aus. Wenn das gelingt, können Sie Interstitials über einen Manifeststream für die Pod-Bereitstellung anfordern und abspielen.