استفاده از تبلیغات میانی HLS سمت کلاینت برای پخش زنده

مشخصات HLS Interstitials روشی انعطاف‌پذیر برای زمان‌بندی و درج تبلیغات در یک جریان ویدیویی یا صوتی معرفی می‌کند. با رویکرد سمت کلاینت، برنامه شما با ایجاد کلاس AVPlayerInterstitialEvent کنترل کامل زمان درخواست و پخش وقفه‌های تبلیغاتی را به دست می‌گیرد. این رویکرد نیازی به برچسب‌های EXT-X-DATERANGE در مانیفست‌های جریان محتوا ندارد. HLS interstitials سمت کلاینت به شما امکان می‌دهد بدون نیاز به تغییر مانیفست جریان یا فایل‌های رسانه‌ای، تبلیغات را به صورت پویا در هر محتوایی درج کنید.

این راهنما، ادغام SDK تبلیغات رسانه‌ای تعاملی (IMA) را در یک برنامه پخش ویدیو پوشش می‌دهد که یک جلسه پخش زنده با هدایت سرور (SGAI) ایجاد می‌کند و تبلیغات بینابینی را در سمت کلاینت زمان‌بندی می‌کند. برای اطلاعات بیشتر، به DAI هدایت‌شده توسط سرور مراجعه کنید.

پیش‌نیازها

قبل از شروع، به موارد زیر نیاز دارید:

  • یک پروژه Xcode جدید، با استفاده از Storyboard برای رابط کاربری. برای اطلاعات بیشتر، به ایجاد یک پروژه Xcode برای یک برنامه مراجعه کنید.

  • کیت توسعه نرم‌افزار IMA گوگل. برای اطلاعات بیشتر، به «راه‌اندازی کیت توسعه نرم‌افزار IMA برای DAI» مراجعه کنید.

  • پارامترهای زیر برای درخواست پخش زنده DAI شما:

    • NETWORK_CODE : کد شبکه مدیریت تبلیغات گوگل شما.
    • CUSTOM_ASSET_KEY : رشته سفارشی شما که رویداد پخش زنده DAI را مشخص می‌کند. رویداد پخش زنده باید نوع DAI مانیفست Pod serve را داشته باشد.

پیکربندی استوری‌بورد

در فایل iPhone.storyboard خود، موارد زیر را انجام دهید:

  1. یک شیء UIView به عنوان ظرفی برای پخش‌کننده ویدیو و رابط کاربری تبلیغ ایجاد کنید.
  2. یک ویژگی adUIView از کلاس ViewController ایجاد کنید تا با شیء UIView ارتباط برقرار کند.
  3. در شیء adUIView ، یک UIButton ایجاد کنید تا به عنوان دکمه پخش عمل کند.
  4. یک ویژگی playButton از کلاس ViewController ایجاد کنید تا با شیء UIButton ارتباط برقرار کند و یک تابع onPlayButtonTouch برای مدیریت لمس‌های کاربر ایجاد کنید.

مقداردهی اولیه یک بارگذاری‌کننده تبلیغات

در رویداد viewDidLoad کنترلر اصلی view، موارد زیر را انجام دهید:

  1. با استفاده از کلاس‌های AVPlayer و AVPlayerLayer یک پخش‌کننده ویدیو راه‌اندازی کنید.
  2. اشیاء IMAAdDisplayContainer و IMAAVPlayerVideoDisplay را ایجاد کنید. کانتینر نمایش تبلیغات، adUIView برای IMA DAI SDK مشخص می‌کند تا زیرنماهای رابط کاربری تبلیغات را درج کند. شیء نمایش ویدیو به عنوان پلی بین منطق تبلیغ IMA DAI SDK و سیستم پخش AVFoundation عمل می‌کند و پخش تبلیغات ویدیویی را ردیابی می‌کند.
  3. شیء IMAAdsLoader را با تنظیمات پخش تبلیغات و محلی‌سازی رابط کاربری تبلیغات، مقداردهی اولیه کنید.

مثال زیر یک بارگذار تبلیغات را با یک شیء خالی IMASettings مقداردهی اولیه می‌کند:

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
  }

درخواست پخش جریانی بدهید

برای درخواست تبلیغات برای یک جریان محتوا، یک شیء IMAPodStreamRequest ایجاد کنید و آن را به نمونه IMAAdsLoader خود منتقل کنید. به صورت اختیاری، می‌توانید ویژگی adTagParameters را طوری تنظیم کنید که گزینه‌های DAI و پارامترهای هدف‌گیری را برای جریان شما ارائه دهد.

این مثال متد loadAdStream را در رویداد viewDidAppear فراخوانی می‌کند:

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

در برنامه‌ی کاربردی خود، پس از انتخاب یک جریان محتوا توسط کاربر، متد loadAdStream را فراخوانی کنید.

مدیریت رویدادهای بارگذاری جریان

پروتکل IMAAdsLoaderDelegate را برای مدیریت موفقیت یا شکست درخواست استریم پیاده‌سازی کنید:

  • در صورت موفقیت، یک شیء IMAAdsLoadedData حاوی IMAStreamManager دریافت می‌کنید. مقدار streamManager.streamId را برای جلسه DAI فعلی ذخیره کنید.
  • در صورت عدم موفقیت، خطا را ثبت کنید.

مثال زیر رویداد بارگذاری استریم را مدیریت می‌کند و رویداد بارگذاری ناموفق استریم را ثبت می‌کند:

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

زمان‌بندی درج آگهی

برای زمان‌بندی پخش تبلیغات، یک شیء AVPlayerInterstitialEvent ایجاد کنید. ویژگی templateItems شیء رویداد را روی آرایه‌ای از اشیاء AVPlayerItem تنظیم کنید، که در آن هر شیء آیتم حاوی یک URL مانیفست ad pod است.

برای ساخت URL مانیفست پاد تبلیغاتی، از روش زیر استفاده کنید: مستندات مانیفست پاد HLS .

برای اهداف نمایشی، مثال زیر با استفاده از زمان فعلی پخش زنده محتوا، یک رشته شناسه پاد تولید می‌کند. تابع generatePodIdentifier شناسه پاد را به صورت ad_break_id/mid-roll-{minute} برمی‌گرداند.

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

در برنامه‌ی کاربردی خود، شناسه‌ی پاد را از منبعی بازیابی کنید که مقادیر منحصر به فردی برای هر پخش تبلیغ ارائه می‌دهد و برای همه بینندگان پخش زنده همگام‌سازی شده است.

مثال زیر زمان‌بندی می‌کند که یک تبلیغ کوتاه ظرف دو دقیقه پس از کلیک کاربر روی دکمه پخش، شروع شود:

/// 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 زمان شروع پخش تبلیغات را محاسبه کرده و یک URL مانیفست برای Ad pod می‌سازد. از این URL برای ایجاد یک شیء AVPlayerInterstitialEvent استفاده کنید.

به صورت اختیاری، از ساختار AVPlayerInterstitialEvent.Restrictions برای محدود کردن پرش یا عقب رفتن کاربر در حین پخش تبلیغ استفاده کنید.

مدیریت رویدادهای تبلیغاتی

برای مدیریت رویدادهای تبلیغاتی، پروتکل IMAStreamManagerDelegate را پیاده‌سازی کنید. این رویکرد به شما امکان می‌دهد زمان شروع و پایان وقفه‌های تبلیغاتی را پیگیری کنید و اطلاعات مربوط به هر تبلیغ را به دست آورید.

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

برنامه خود را اجرا کنید. در صورت موفقیت، می‌توانید با استفاده از یک جریان مانیفستِ Pod، درخواست نمایش بینابینی (interstitials) بدهید و آن را پخش کنید.