يتناول هذا الدليل كيفية دمج حزمة تطوير البرامج (SDK) لتقنية IMA DAI في تطبيق مشغّل فيديو. إذا أردت الاطّلاع على نموذج عملية دمج مكتملة أو اتّباع خطواتها، يمكنك تنزيل BasicExample من GitHub.
نظرة عامة على IMA DAI
يتضمّن تنفيذ ميزة "إدراج الإعلان الديناميكي" في "حزمة تطوير البرامج لإعلانات الوسائط التفاعلية" ثلاثة مكوّنات رئيسية لحزمة تطوير البرامج، كما يوضّح هذا الدليل:
IMAAdDisplayContainer: هو عنصر حاوية يقع أعلى عنصر تشغيل الفيديو ويضم عناصر في واجهة المستخدم للإعلان.-
IMAAdsLoader: عنصر يطلب أحداثًا ويتعامل مع الأحداث التي يتم تشغيلها بواسطة عناصر استجابة طلب البث. يجب إنشاء مثيل واحد فقط من أداة تحميل الإعلانات، والذي يمكن إعادة استخدامه طوال مدة تشغيل التطبيق. -
IMAStreamRequest: إماIMAVODStreamRequestأوIMALiveStreamRequest: عنصر يحدّد طلب بث يمكن أن تكون طلبات البث لفيديوهات عند الطلب أو أحداث بث مباشر. تحدّد طلبات البث المباشر مفتاح مادة عرض، بينما تحدّد طلبات الفيديو عند الطلب معرّف "نظام إدارة المحتوى" ومعرّف الفيديو. يمكن أن يتضمّن كلا نوعَي الطلبات اختياريًا مفتاح واجهة برمجة التطبيقات اللازم للوصول إلى أحداث بث معيّنة، ورمز شبكة "إدارة إعلانات Google" لكي تتعامل حزمة IMA SDK مع معرّفات الإعلانات على النحو المحدّد في إعدادات "إدارة إعلانات Google". -
IMAStreamManager: عنصر يعالج أحداث بث "إدراج الإعلانات الديناميكية" والتفاعلات مع الخلفية الخاصة بهذه الميزة. يتولّى مدير البث أيضًا تتبُّع إشعارات الاتصال وإعادة توجيه أحداث البث والإعلانات إلى الناشر.
المتطلبات الأساسية
قبل البدء، تحتاج إلى ما يلي:
- الإصدار 13 من Xcode أو إصدار أحدث
- طريقة تثبيت حزمة تطوير البرامج للإعلانات التفاعلية (IMA SDK):
- Swift Package Manager (الخيار المفضّل)
- نسخة قابلة للتنزيل من حزمة تطوير البرامج للإعلانات التفاعلية (IMA SDK) لنظام التشغيل tvOS
إنشاء مشروع Xcode جديد
في Xcode، أنشِئ مشروع tvOS جديدًا باستخدام Objective-C. استخدِم BasicExample كاسم للمشروع.
إضافة حزمة تطوير البرامج (SDK) لتقنية IMA DAI إلى مشروع Xcode
لتثبيت حزمة تطوير البرامج للإعلانات التفاعلية، اختَر الطريقة المفضّلة لديك.
الإجراء المقترَح: تثبيت حزمة تطوير البرامج (SDK) باستخدام Swift Package Manager
تتوافق حزمة تطوير البرامج لإعلانات الوسائط التفاعلية مع Swift Package Manager بدءًا من الإصدار 4.8.2. اتّبِع هذه الخطوات لاستيراد حزمة Swift.
في Xcode، ثبِّت حزمة GoogleInteractiveMediaAds Swift من خلال الانتقال إلى ملف > إضافة حِزم.
في الطلب الذي يظهر، ابحث عن مستودع GoogleInteractiveMediaAds Swift Package على GitHub:
https://github.com/googleads/swift-package-manager-google-interactive-media-ads-tvosاختَر إصدار حزمة GoogleInteractiveMediaAds Swift Package الذي تريد استخدامه. بالنسبة إلى المشاريع الجديدة، ننصحك باستخدام الخيار حتى رقم الإصدار الرئيسي التالي.
بعد الانتهاء، يحلّ Xcode التبعيات المرتبطة بحِزمك وينزّلها في الخلفية. لمزيد من التفاصيل حول كيفية إضافة تبعيات الحزمة، يُرجى الاطّلاع على مقالة Apple.
تنزيل حزمة SDK وتثبيتها يدويًا
إذا كنت لا تريد استخدام Swift Package Manager، يمكنك تنزيل حزمة تطوير البرامج للإعلانات التفاعلية (IMA SDK) وإضافتها يدويًا إلى مشروعك.
استيراد حزمة تطوير البرامج للإعلانات التفاعلية
أضِف إطار عمل IMA باستخدام عبارة استيراد:
Objective-C
#import "ViewController.h"
#import <AVKit/AVKit.h>
@import GoogleInteractiveMediaAds;
Swift
import AVFoundation
import GoogleInteractiveMediaAds
import UIKit
إنشاء مشغّل فيديو ودمج حزمة تطوير البرامج للإعلانات التفاعلية (IMA SDK)
يعمل المثال التالي على إعداد حزمة تطوير البرامج (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() الحصول على بث "إدراج الإعلان ديناميكيًا".
لتحديد مَعلمات طلب البث، يستخدم هذا المثال ثوابت، مثل
asset key لأحداث البث المباشر، أو content source ID وvideo ID لأحداث البث
عند الطلب. يستخدم المثال أيضًا المكوّنات التالية لإدارة حزمة تطوير البرامج للإعلانات التفاعلية (IMA SDK):
-
adsLoader: يعالج طلبات البث إلى "إدارة إعلانات Google". ننصح باستخدام مثيل واحد لدورة حياة التطبيق. -
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")")
}
هذا كل شيء! يمكنك الآن طلب الإعلانات وعرضها باستخدام حزمة تطوير البرامج (SDK) لإدراج الإعلانات الديناميكية في إعلانات الوسائط التفاعلية (IMA DAI). لمزيد من المعلومات حول ميزات حزمة تطوير البرامج (SDK) الأكثر تقدّمًا، يُرجى الاطّلاع على الأدلة الأخرى أو النماذج على GitHub.