چگونه تاخیر در نمایش تبلیغات را به کمتر از یک ثانیه رساندیم؟

همیشه برای من و احتمالاً همه‌ی ما دیدن تبلیغات اجباری، آزاردهنده و سخت بوده؛ این‌که برای دیدن همین تبلیغات باید کلی صبر کنیم و زمان زیادی رو منتظر بمونیم هم می‌تونه موضوع رو سخت‌تر و آزاردهنده‌تر کنه؛ زمستان ۹۷ وقتی تازه به تیم لحظه‌نگار اضافه‌ شده‌بودم، یکی از اولین و مهم‌ترین کارهایی که به من سپرده‌ شده‌بود، تغییر UI، ‌UX و پرفورمنس سیستم تبلیغاتی ویدیوهای لحظه‌نگار بود، تا کمی تجربه پخش تبلیغات روی محتوای لحظه‌نگار رو برای کاربر‌هامون بهتر و قابل‌تحمل‌تر کنیم؛ در اون زمان سیستم تبلیغاتی لحظه‌نگار به‌تازگی شکل‌ گرفته‌بود و به دلیل محدودیت‌های زمانی، فرصت توسعه‌ی یک سیستم اختصاصی برای نمایش تبلیغات در کلاینت‌ها رو نداشتیم (باوجود این‌که نیازش احساس می‌شد و می‌دونستیم که در آینده بهش نیاز داریم) و تا چند ماه از سیستم گوگل IMA (یا Interactive Media Ads) برای نمایش تبلیغات در چهارچوب VAST (یا Video Ad Serving Template) استفاده می‌کردیم. در این نوشته قصد دارم کمی درباره این‌که چرا از IMA استفاده نکردیم و خودمون سیستم اختصاصی برای نمایش تبلیغات در کلاینت‌ها رو توسعه دادیم بنویسم.

چالش‌ها و مشکلاتی که با IMA داشتیم

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

مشکلات ظاهری

یکی از مهم‌ترین چالش‌هایی که در ابتدا با IMA داشتیم، عدم امکان ویرایش UI استاندارد این سیستم بود. البته که در چیدمان و Layout کلی این سیستم کمتر ایراد ساختاری‌ای وارد بود، اما وقتی قراره از IMA در یک پروژه RTL استفاده کنیم، همه‌چیز فرق خواهد کرد. گوگل برای نمایش کلید‌های کنترلی و امکان مدیریت روی تبلیغات، از یک Iframe روی پلیر خودش استفاده می‌کنه که اطلاعات تبلیغ و کلید Skip رو در اون قرار می‌ده؛ طبیعتاً در این شرایط نه امکان تغییر قلم، نه Stylesheet و نه حتی متن‌های لیبل‌ها برای ما فراهم بود. برای حل این چالش در ابتدا تصمیم گرفتیم تمام کنترلر‌های ویدیو رو به‌صورت مجزا از IMA روی پلیر قرار بدیدم و Iframe گوگل رو از صفحه حذف کنیم. نتیجه این کار از دید UI قابل‌قبول بود، ولی با توجه به این‌که اسکریپت IMA از سرور‌های گوگل لود می‌شد و امکان Dynamic import اون رو در پروژه نداشتیم، همچنان Iframe گوگل در صفحه لود می‌شد و مرورگر‌ها بدون توجه به وضعیت Visibility اون رو لود می‌کردند و همیشه یک ریکوئست اضافه و بی ‌کاربرد ایجاد می‌شد.

سرعت دریافت

چالش اصلی ما در لحظه‌نگار، در لحظه بودنه؛ این اصل مهم ما، با لود یک فایل ۲۵۰ کیلوبایتی در ابتدای لود صفحات از سرور گوگل می‌تونست با مشکل مواجه بشه؛ هرچند SDK های گوگل به‌صورت GZip شده ارسال می‌شدند و در اون حالت حجمی حدود ۸۴ کیلوبایت داشتند، ولی مدت‌زمان لود شدن همین حجم کم هم روی تجربه‌ی کاربر‌های ما تأثیر داشت. مخصوصاً زمانی که صحبت از Pre-roll بودن تبلیغات باشه و پخش شدن ویدیوی تبلیغاتی، لازمه‌ی پخش ویدیوی اصلی باشه؛ در این حالت کاربر ابتدا درخواست IMA رو از سرور گوگل می‌کنه و بعد از اون نوبت دریافت VAST می‌رسه و بعد از دریافت و Parse کردن اون، محتوای تبلیغاتی لود و نمایش داده می‌شه که باعث اتلاف زمان نسبتاً زیادی می‌شد. حالا اگر دریافت پاسخ ریکوئست‌ها بیش‌ازحد طول بکشه، تجربه کاربر‌ها تلخ‌تر هم خواهد شد؛ پس در این میان وجود یک محدودیت زمانی برای دریافت این فایل‌ها هم احساس می‌شد، به‌طوری‌که مثلاً اگر مجموع این ریکوئست‌ها بیش‌تر از ۵ ثانیه طول بکشه، ویدیو اصلی پخش بشه و نمایش اون تبلیغات منتفی بشه که IMA چنین امکانی رو برای ما فراهم نمی‌کرد. همچنین تلاش‌های ما برای Dynamic import کردن فایل IMA هم به دلیل این‌که گوگل اجازه لود شدن SDK های خودش رو از آدرسی به‌غیراز googleapis.com نمی‌ده هم بی‌نتیجه موند.

سرعت نمایش

در حال حاضر تمام محتوا‌های VOD و زنده‌ی لحظه‌نگار در فرمت فایل‌های ویدیویی HLS دریافت و نمایش داده می‌شه که برای هندل کردنش در کلاینت‌ها از HLS.js استفاده می‌کنیم؛ همیشه ترجیح ما برای نمایش تبلیغات هم استفاده از فرمت HLS بود که در مقایسه با MP4، سرعت نمایش خیلی بالاتری رو به ما می‌داد که چنین امکانی در IMA بدون استفاده از HLS.js به‌صورت جداگانه وجود نداشت. هرچند برطرف کردن این چالش سخت نبود، ولی باعث دوباره‌نویسی خیلی از متد‌هایی که یک‌بار در پلیر لحظه‌نگار توسعه داده بودیم، می‌شد.

AdBlocker ها

دردسر بعدی کار با IMA، از دست دادن کاربرانی بود که با Ad Blocker از لحظه‌نگار استفاده می‌کنن. این‌گونه Extension ها علاوه بر تغییر در DOM (که در اینجا برای ما مطرح نبود)، ریکوئست‌های HTTP رو هم برسی می‌کنن تا درصورتی‌که ازنظر اون‌ها، اون ریکوئست حاوی محتوای تبلیغاتی باشه بلاک بشه؛ متأسفانه یا خوشبختانه این Extension ها SDK مربوط به IMA رو از سرور گوگل بلاک می‌کردند و به ادامه‌ی روند تبلیغات و دریافت VAST نمی‌رسیدیم.

(خطای ERR_BLOCKED_BY_CLIENT)
const {AdEvent: {Type: {CONTENT_PAUSE_REQUESTED, CONTENT_RESUME_REQUESTED, ALL_ADS_COMPLETED, LOADED, STARTED, COMPLETE}}} = google.ima,
    {AdErrorEvent: {Type: {AD_ERROR}}} = google.ima;

this.adsManager.addEventListener(AD_ERROR, event => this.onAdError(event));
this.adsManager.addEventListener(CONTENT_PAUSE_REQUESTED, event => this.onContentPauseRequested(event));
this.adsManager.addEventListener(CONTENT_RESUME_REQUESTED, event => this.onContentResumeRequested(event));
this.adsManager.addEventListener(ALL_ADS_COMPLETED, event => this.onAdEvent(event));
this.adsManager.addEventListener(LOADED, event => this.onAdEvent(event));
this.adsManager.addEventListener(STARTED, event => this.onAdEvent(event));
this.adsManager.addEventListener(COMPLETE, event => this.onAdEvent(event));
// other methods.

برای استفاده از APIهای این Eventها باید برای هر کدوم یک EventListener تعریف می‌کردیم که خودش موجب پیچیدگی و تودرتو شدن بی‌جهت متد هامون می‌شد و یک API کلی برای هندل کردن این Event ها وجود نداشت. برای همین تصمیم گرفتیم سیستم Tracking و متریک‌های مورد نیازمون رو هم کلی کاستومایز کنیم و در نهایت این اطلاعات رو به Google Analytics به‌عنوان Event و Custom Metrics ارسال کنیم؛ اما نکته‌ی جالب موضوع این بود که تقریباً تمام این متریک‌ها و Event هارو ما یک‌بار برای پلیر لحظه‌نگار نوشته بودیم و اگر سیستم تبلیغاتی ما در همون چهارچوب کار می‌کرد، عملاً می‌تونستیم با تغییرات خیلی جزئی، از اون متد‌ها برای Tracking تبلیغات هم استفاده کنیم.

تولد سیستم تبلیغاتی جدید لحظه‌نگار

وجود تمام چالش‌هایی که گفته شد باعث شد تا به فکر توسعه یک سیستم تبلیغاتی برای کلاینت‌ با توجه به نیازها و خواسته‌هامون بیافتیم. در همون اول کار، تصمیم ما این بود تا تمام اجزاء این سیستم تبلیغاتی (شامل دریافت و خواندن VAST، نمایش محتوای ویدیویی، محاسبه متریک‌ها و ارسال آمار هر تبلیغ) رو در سورس پلیر لحظه‌نگار جا بدیم تا هرچه بیشتر و تا حد ممکن سرعت نمایش تبلیغات بالا رفته و باعث ایجاد یک تجربه‌ی خوش‌آیند برای کاربرانمون باشه. در ادامه کمی درباره روند پیاده‌سازی این سیستم می‌نویسم.

چهارچوب VAST

ما در لحظه‌نگار از ورژن سوم چهارچوب VAST برای مشخص کردن تبلیغات روی محتوای ویدیو‌یی استفاده می‌کنیم؛ یک فایل XML که مشخص کننده نوع، زمان، محل قرار گیری و Track آمار و اعداد تبلیغات است (اطلاعات بیشتر درباره VAST 3.0). به عنوان مثال نمونه زیر یک نمونه تبلیغ Pre-roll شاتل موبایل با مدت‌زمان ۲۹ ثانیه و امکان Skip بعد از ۵ ثانیه رو تعیین می‌کنه.

<?xml version="1.0" encoding="UTF-8"?>
<VAST version="3.0">
    <Ad id="9f32ede9fc5842d5c47cf196bb990974">
        <InLine>
            <AdSystem>Lahzenegar</AdSystem>
            <AdTitle>ShatelMobile</AdTitle>
            <Creatives>
                <Creative id="164" AdID="164">
                    <Linear skipoffset="00:00:05">
                        <Duration>00:00:29</Duration>
                        <MediaFiles>
                            <MediaFile type="application/x-mpegURL" delivery="progressive" width="640" height=“360”><![CDATA[/hls/shatel-mobile.m3u8]]></MediaFile>
                        </MediaFiles>
                        <VideoClicks>
                            <ClickThrough><![CDATA[https://shatelmobile.ir/]]></ClickThrough>
                            <ClickTracking><![CDATA[]]></ClickTracking>
                        </VideoClicks>
                        <TrackingEvents>
                            <Tracking event="start"><![CDATA[]]></Tracking>
                        </TrackingEvents>
                    </Linear>
                </Creative>
            </Creatives>
        </InLine>
    </Ad>
</VAST>

همون‌طور که احتمالاً متوجه شده باشید، Creatives دربرگیرنده‌ی انواع تبلیغات موردنظر ما هست که در این مثال، از نوع Linear برای تبلیغات Pre-roll استفاده می‌شه؛ انواع دیگری مثل Companion, Nonlinear و Ad Pods هم در این استاندارد قابل‌تعریف هستند که با توجه به سیاست‌های مارکتینگ لحظه‌نگار، ما درحال‌حاضر فقط از تبلیغات Linear یا Pre-roll استفاده می‌کنیم. بخش MediaFiles هم تعیین‌کننده فایل و یا فایل‌های تبلیغ است که قراره در فرمت مشخص‌شده نمایش داده بشه. همان‌طور که گفته بودیم، ما آمار و اعداد تبلیغات رو در Google Analytics ذخیره می‌کنیم، هرچند اگر لازم باشه می‌تونیم یک یا چند Event تبلیغ رو برای Data Store و یا Analytics Service دیگری هم ارسال کنیم، ما می‌تونیم در بخش‌های TrackingEvents, ClickTracking,  Impression و Error یک لینک HTTP رو قرار بدیم تا هنگام رخ دادن اون Event، لینک موردنظر از طرف مرورگر کاربر اجرا شه.

نمایش تبلیغات در پلیر لحظه‌نگار

ما در پلیر لحظه‌نگار (که در NPM برای همه در دسترس قراردادیم) یک لایه تبلیغات ایجاد کردیم که وظایف زیر رو به عهده گرفته:

  • بررسی وجود تبلیغات.
  • دریافت فایل VAST با Timeout مشخص (۵ ثانیه).
  • Parse کردن XML فایل VAST در قالب آبجکت.
  • برسی و Validate آبجکت و مقادیر تگ‌های VAST و تبدیل اون به یک فرمت مشخص و استاندارد برای کلاینت.
  • هندل کردن تمام ارورهای مراحل بالا.
  • برسی پخش شدن تبلیغ در لحظه جاری.
  • تغییر Source ویدیو اصلی در زمانی که تبلیغات Pre-roll باشد.
  • ارسال Eventها و متریک‌ها به لایه بالاتر.
  • هندل کردن ارورهای هنگام Play تبلیغات و نادیده گرفتن آن هنگامی که پخش تبلیغ به دلیل خرابی فایل، قطعی اینترنت و… با مشکل مواجه شود.
  • حذف تبلیغات از صفحه پس از به اتمام رسیدن.

همون‌طور که در دیاگرام بالا دیدید، این لایه تبلیغاتی در تعامل مستقیم با متد‌های اصلی DOM پلیر و طبعاً HLS.js قرارگرفته و به همین دلیل، علاوه بر سرعت نمایش بالا، کار ما رو هم در قرار دادن تبلیغات ویدیویی به‌شدت ساده‌تر و تمیزتر کرده.

متریک‌های تبلیغات

ما در لحظه‌نگار متریک‌هایی برای محاسبه‌ی میزان مشاهده هر تبلیغ، تعداد درخواست‌های Play در ابتدای تبلیغ، تعداد خطا‌های تبلیغ هنگام اولین Play شدن، مدت زمان لود شدن فایل VAST، مدت زمان شروع اولین فریم تبلیغ، تعداد Skipهای هر تبلیغ، تعداد Play و Pause شدن هر تبلیغ، تعداد دفعاتی که تبلیغ به انتها می‌رسد، تعداد کلیک‌های روی لینک تبلیغ، تعداد خطا‌های تبلیغ و… رو تعریف کردیم و به صورت Custom Metrics و Event در Google Analytics با Custom Dimensions دریافت می‌کنیم و از اون‌ها برای اندازه‌گیری کیفیت تبلیغ‌ها و کیفیت سرویس‌های خودمون استفاده می‌کنیم. به عنوان مثال Error Rate تبلیغات رو با تقسیم عدد متریک‌های ارسالی تعداد خطا‌ها در ابتدای تبلیغ بر تعداد Playها در ابتدای تبلیغ محاسبه می‌کنیم؛ این متریک‌ها تقریبا برای ویدیو‌های لحظه‌نگار هم استفاده می‌شدند که حالا Callbackهای مربوط به Event ها و متریک‌ها رو خیلی ساده‌تر دریافت می‌کنیم و می‌تونیم مستقیماً برای Google Analytics ارسال کنیم.

adEvent(event) {
  const { sendAdsEvent } = this.props,
    { ad: { name } } = this.state;
  if (sendAdsEvent) {
    sendAdsEvent(name, event);
  }
}

adPlayEvent() {
  const { ad: { source, isPlaying } } = this.state;
  if (source) {
    this.adEvent(isPlaying ? 'play' : 'resume');
    this.setState(({ ad }) => ({
      ad: {
        ...ad,
        isPlaying: true,
      }
    }));
    this.isAdPlaying(true);
  }
}

انتخاب و دریافت تبلیغ

ما درحال‌حاضر نوع تبلیغی که باید روی محتوا‌های لحظه‌نگار قرار بگیره رو به‌صورت تصادفی و با یک ضریب نمایش مشخص‌شده برای هر تبلیغ تعیین می‌کنیم که هنگام درخواست اطلاعات یک Event از API، آدرس فایل VAST موردنظرمون هم برای کاربر‌ها ارسال می‌شه؛ ولی در آینده‌ی نزدیک و با افزایش تنوع تبلیغات لحظه‌نگار، قصد داریم انتخاب تبلیغ رو بر اساس علایق کاربرانمون با کمک هوش مصنوعی (Artificial Intelligence) انتخاب کنیم تا لذت مشاهده یک محتوای خوب رو با دیدن یک تبلیغ بی‌ربط به کام کاربرانمون تلخ نکنیم.

تیر خلاص به سرعت!

ما برای هرچه سریع‌تر شدن پخش تبلیغات ویدیویی خودمون، با کمک موتور استریمینگ‌ نگاربن، فایل‌های ویدیویی تبلیغات رو به فرمت HLS تبدیل می‌کنیم و با CDN به دست کلاینت می‌رسونیم (در انتهای این مقاله یک مقایسه درباره میزان تفاوت پخش تبلیغات در قالب MP4 و HLS رو هم خواهیم دید).

UI و UX تبلیغات

گوگل برای IMA، ساده‌ترین UI ممکن رو درنظر گرفته تا شاید این‌طوری هماهنگی بیش‌تری با UI نرم‌افزارها و وبسایت‌های کاربرانش رو فراهم کنه، اما این رابط کاربری روی لحظه‌نگاری که تمامش رو محتواهای ویدیویی در برگرفتن، نمی‌تونست موفق باشه. ما ترجیح دادیم تا امکانات مدیریتی بیش‌تری رو روی تبلیغات ویدیوییمون قرار بدیم تا احساس کاربرها نسبت به تبلیغات کمی تغییر کنه؛ ما تقریباً تمام کلیدهای کنترلی پلیر لحظه‌نگار رو برای تبلیغات هم فراهم کردیم تا کاربر بتونه نهایت تعامل رو با تبلیغات برقرار کنه و به‌خوبی از زمان شروع و پایان تبلیغ به کمک Seek Bar پلیر اطلاع پیدا کنه.

(تفاوت‌های رابط کاربری سیستم تبلیغاتی لحظه‌نگار و IMA)

نتیجه نهایی

در حال حاضر که بیش از یک ماه از اولین ریلیز این سیستم تبلیغاتی گذشته‌ و من و دوستانم همچنان درحال‌ توسعه و بهتر کردن این سیستم هستیم، بد نیست نتایج این تغییر رو با استفاده از مرورگر گوگل کروم (ورژن 72.0.3626.121)، در شبکه‌ی Fast 3G و بدون استفاده از کش، برای یک تبلیغ Pre-roll با زمان ۱۰ ثانیه رو باهم مرور کنیم. البته که تمام این سه آزمایش در محیط آزمایشی یکسانی تهیه‌شده‌اند.

۱. در زمان استفاده از IMA، با توجه به این‌که نیاز به دریافت SDK و VAST به‌صورت جداگانه مطرح بود، نمایش این تبلیغ در قالب MP4 زمانی حدود ۲۱ ثانیه طول می‌کشید.

۲. با استفاده از سیستم جدیدمون، دریافت VAST و نمایش این تبلیغ در قالب MP4 زمانی حدود ۱۳ ثانیه طول می‌کشید.

۳. با استفاده از سیستم جدیدمون، دریافت VAST و نمایش این تبلیغ در قالب HLS زمانی حدود ۱۱ ثانیه طول می‌کشید که بهترین حالت ممکن برای نمایش این تبلیغ ۱۰ ثانیه‌ای، در این حالت به دست اومد.

ممنون از این‌که تا انتهای این نوشته همراه ما بودید، ما همیشه خوشحال می‌شیم نظرات و پیشنهاداتتون رو در مورد لحظه‌نگار بشنویم؛ اگر در این زمینه نظر، پیشنهاد، انتقاد یا صحبتی دارید لطفاً در بخش نظرات این وبلاگ و یا در توییتر برامون بنویسید.

نویسنده رامین محمدی