Canlı yayın için istemci tarafı HLS geçiş reklamları kullanma

HLS geçiş reklamları spesifikasyonu, reklamları bir video veya ses akışına planlamak ve eklemek için esnek bir yöntem sunar. İstemci tarafı yaklaşımında, uygulamanız AVPlayerInterstitialEvent sınıfını oluşturarak reklam aralarının ne zaman isteneceği ve oynatılacağı konusunda tam kontrol sahibi olur. Bu yaklaşımda, içerik akışı manifestlerinde EXT-X-DATERANGE etiketleri gerekmez. İstemci tarafı HLS geçiş reklamları, akış manifestini veya medya dosyalarını değiştirmenize gerek kalmadan herhangi bir içeriğe dinamik olarak reklam eklemenizi sağlar.

Bu kılavuzda, Interactive Media Ads (IMA) SDK'nın sunucu tarafından yönlendirilen reklam ekleme (SGAI) canlı yayın oturumu oluşturan ve istemci tarafında geçiş reklamları planlayan bir video oynatıcı uygulamasına entegre edilmesi ele alınmaktadır. Daha fazla bilgi için Sunucu yönlendirmeli DAI başlıklı makaleyi inceleyin.

Ön koşullar

Başlamadan önce aşağıdakilere ihtiyacınız vardır:

  • Kullanıcı arayüzü için Storyboard kullanılan yeni bir Xcode projesi. Daha fazla bilgi için Uygulama için Xcode projesi oluşturma başlıklı makaleyi inceleyin.

  • Google IMA SDK'sı. Daha fazla bilgi için DAI için IMA SDK'yı ayarlama başlıklı makaleyi inceleyin.

  • DAI canlı yayın isteğiniz için aşağıdaki parametreler:

    • NETWORK_CODE: Google Ad Manager ağ kodunuz.
    • CUSTOM_ASSET_KEY: DAI canlı yayın etkinliğini tanımlayan özel dizeniz. Canlı yayın etkinliğinin kapsül yayınlama manifesti DAI türü olmalıdır.

Resimli taslak yapılandırma

iPhone.storyboard dosyanızda aşağıdakileri yapın:

  1. Video oynatıcı ve reklam kullanıcı arayüzü için kapsayıcı olarak bir UIView nesnesi oluşturun.
  2. UIView nesnesiyle bağlantı kurmak için ViewController sınıfının adUIView özelliğini oluşturun.
  3. adUIView nesnesinde, oynatma düğmesi işlevi görecek bir UIButton oluşturun.
  4. UIButton nesnesiyle bağlantı kurmak için playButton sınıfından bir ViewController özelliği ve kullanıcı dokunmalarını işlemek için bir onPlayButtonTouch işlevi oluşturun.

Reklam yükleyiciyi başlatma

Ana görünüm denetleyicisinin viewDidLoad etkinliğinde aşağıdakileri yapın:

  1. AVPlayer ve AVPlayerLayer sınıflarını kullanarak video oynatıcı oluşturun.
  2. IMAAdDisplayContainer ve IMAAVPlayerVideoDisplay nesneleri oluşturun. Reklam görüntüleme kapsayıcısı, IMA DAI SDK'nın reklam kullanıcı arayüzü alt görünümlerini ekleyeceği adUIView değerini belirtir. Video görüntüleme nesnesi, IMA DAI SDK'sının reklam mantığı ile AVFoundation oynatma sistemi arasında bir köprü görevi görerek video reklamların oynatılmasını izler.
  3. IMAAdsLoader nesnesini reklam oynatma ve reklam kullanıcı arayüzü yerelleştirme ayarlarıyla başlatın.

Aşağıdaki örnekte, boş bir IMASettings nesnesiyle bir reklam yükleyici başlatılmaktadır:

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
  }

Akış isteğinde bulunma

Bir içerik akışı için reklam isteğinde bulunmak üzere bir IMAPodStreamRequest nesnesi oluşturun ve bunu IMAAdsLoader örneğinize iletin. İsteğe bağlı olarak, akışınız için DAI seçenekleri ve hedefleme parametreleri sağlamak üzere adTagParameters özelliğini ayarlayın.

Bu örnekte, viewDidAppear etkinliğinde loadAdStream yöntemi çağrılıyor:

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)
}

Üretim uygulamanızda, kullanıcı bir içerik akışı seçtikten sonra loadAdStream yöntemini çağırın.

Akış yükleme etkinliklerini işleme

Akış isteğinin başarılı veya başarısız olmasını işlemek için IMAAdsLoaderDelegate protokolünü uygulayın:

  • Başarılı olursa IMAStreamManager içeren bir IMAAdsLoadedData nesnesi alırsınız. Mevcut DAI oturumu için streamManager.streamId değerini saklayın.
  • Başarısız olursa hatayı günlüğe kaydedin.

Aşağıdaki örnekte, akış yüklendi etkinliği işlenir ve akışın yüklenemediği etkinliği günlüğe kaydedilir:

// 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)")
}

Reklam eklemelerini planlama

Reklam arası planlamak için bir AVPlayerInterstitialEvent nesnesi oluşturun. Etkinlik nesnesinin templateItems özelliğini bir AVPlayerItem nesneleri dizisi olarak ayarlayın. Her öğe nesnesi bir reklam kapsülü manifest URL'si içerir.

Reklam kapsülü manifest URL'si oluşturmak için Yöntem: HLS kapsülü manifesti dokümanını inceleyin.

Aşağıdaki örnek, gösterim amacıyla içerik canlı yayınının geçerli saatini kullanarak bir pod tanımlayıcı dizesi oluşturur. generatePodIdentifier işlevi, pod tanımlayıcısını ad_break_id/mid-roll-{minute} olarak döndürür.

/// 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)"
}

Üretim uygulamanızda, her reklam arası için benzersiz değerler sağlayan ve canlı yayının tüm izleyicileri için senkronize edilmiş bir kaynaktan pod tanımlayıcısını alın.

Aşağıdaki örnekte, kullanıcı oynatma düğmesini tıkladıktan sonraki iki dakika içinde başlayacak şekilde bir reklam arası planlanmaktadır:

/// 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)."
  )
}

scheduleAdInsertion yöntemi, reklam arasının başlangıç zamanını hesaplar ve bir reklam kapsülü manifest URL'si oluşturur. AVPlayerInterstitialEvent nesnesi oluşturmak için bu URL'yi kullanın.

İsteğe bağlı olarak, reklam oynatma sırasında kullanıcıların reklamı atlamasını veya geri sarmasını kısıtlamak için AVPlayerInterstitialEvent.Restrictions yapısını kullanın.

Reklam etkinliklerini işleme

Reklam etkinliklerini işlemek için IMAStreamManagerDelegate protokolünü uygulayın. Bu yaklaşım, reklam aralarının ne zaman başlayıp ne zaman bittiğini izlemenize ve tek tek reklamlar hakkında bilgi edinmenize olanak tanır.

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
  }
}

Uygulamanızı çalıştırın. İşlem başarılı olursa bir Pod yayınlama manifest akışı kullanarak geçiş reklamları isteyebilir ve oynatabilirsiniz.