تسهّل حِزم تطوير البرامج (SDK) لإعلانات الوسائط التفاعلية دمج إعلانات الوسائط المتعددة في مواقعك الإلكترونية وتطبيقاتك. يمكن لحِزم تطوير البرامج لإعلانات الوسائط التفاعلية طلب الإعلانات من أي خادم إعلانات متوافق مع VAST وإدارة تشغيل الإعلانات في تطبيقاتك. باستخدام حِزم تطوير البرامج لإعلانات الوسائط التفاعلية من جهة العميل، يمكنك التحكّم في تشغيل فيديوهات المحتوى، بينما تتولّى حزمة تطوير البرامج تشغيل الإعلانات. يتم تشغيل الإعلانات في مشغّل فيديو منفصل يظهر فوق مشغّل فيديو المحتوى في التطبيق.
يوضّح هذا الدليل كيفية دمج حزمة تطوير البرامج للإعلانات التفاعلية في مشروع فارغ في "استوديو Android" باستخدام VideoView في Android لعرض المحتوى والإعلانات. للاطّلاع على نموذج متكامل لعملية الدمج، نزِّل BasicExample من GitHub.
نظرة عامة على IMA من جهة العميل
يتضمّن تنفيذ "إعلانات الوسائط التفاعلية" من جهة العميل أربعة مكوّنات رئيسية من حزمة تطوير البرامج (SDK)، كما هو موضّح في هذا الدليل:
AdDisplayContainer
: عنصر حاوٍ يحدّد المكان الذي تعرض فيه IMA عناصر واجهة مستخدم الإعلان وتقيس إمكانية العرض، بما في ذلك العرض النشط و Open Measurement.AdsLoader
: هو عنصر يطلب الإعلانات ويتعامل مع الأحداث من ردود طلبات الإعلانات. يجب إنشاء أداة تحميل إعلانات واحدة فقط، ويمكن إعادة استخدامها طوال مدة تشغيل التطبيق.-
AdsRequest
: كائن يعرّف طلب إعلانات. تحدّد طلبات الإعلانات عنوان URL لعلامة إعلان VAST، بالإضافة إلى مَعلمات إضافية، مثل أبعاد الإعلان. -
AdsManager
: كائن يحتوي على الردّ على طلب الإعلانات، ويتحكّم في تشغيل الإعلانات، ويستمع إلى أحداث الإعلانات التي يتم تشغيلها بواسطة حزمة SDK.
المتطلبات الأساسية
1. إنشاء مشروع جديد في "استوديو Android"
لإنشاء مشروع Android Studio، أكمِل الخطوات التالية:
- ابدأ تشغيل "استوديو Android".
- اختَر بدء مشروع جديد في "استوديو Android".
- في صفحة اختيار مشروعك، اختَر نموذج نشاط فارغ.
- انقر على التالي.
- في صفحة إعداد مشروعك، حدِّد اسمًا لمشروعك واختَر Java كلغة.
- انقر على إنهاء.
2. إضافة "حزمة تطوير البرامج لإعلانات الوسائط التفاعلية" إلى مشروعك
أولاً، في ملف build.gradle على مستوى التطبيق، أضِف عمليات استيراد لحزمة IMA SDK إلى قسم التبعيات. نظرًا لحجم حزمة تطوير البرامج لإعلانات الوسائط التفاعلية، يجب تنفيذ وتفعيل multidex هنا. وهذا الإجراء ضروري للتطبيقات التي تم ضبط قيمة minSdkVersion
فيها على 20 أو أقل. أضِف أيضًا السمة الجديدة
compileOptions
لتحديد معلومات التوافق مع إصدار Java وتفعيل
إزالة التشويش من المكتبة.
تتطلّب حزمة تطوير البرامج لإعلانات الوسائط التفاعلية تفعيل ميزة إزالة التشويش في المكتبة، ويجب إجراء ذلك من خلال ضبط
coreLibraryDesugaringEnabled true
وإضافة
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
كملحَق في ملف
build.gradle. لمزيد من التفاصيل، يُرجى الاطّلاع على
واجهات برمجة التطبيقات المتوافقة مع الإصدار 11 من Java والإصدارات الأحدث والمتاحة من خلال إزالة التشفير باستخدام مواصفات nio.
apply plugin: 'com.android.application' android { namespace 'com.google.ads.interactivemedia.v3.samples.videoplayerapp' compileSdk 36 // Java 17 required by Gradle 8+ compileOptions { // Required by IMA SDK v3.37.0+ coreLibraryDesugaringEnabled true // Java 17 required by Gradle 8+ sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 } defaultConfig { applicationId "com.google.ads.interactivemedia.v3.samples.videoplayerapp" minSdkVersion 21 targetSdkVersion 36 multiDexEnabled true versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } repositories { google() mavenCentral() } dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5' implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0")) implementation 'androidx.appcompat:appcompat:1.7.1' implementation 'androidx.browser:browser:1.9.0' implementation 'androidx.media:media:1.7.0' implementation 'com.google.ads.interactivemedia.v3:interactivemedia:3.37.0' }
3- تعديل تصميم التطبيق
عدِّل تخطيط التطبيق ليتضمّن VideoView
لتشغيل كلّ من المحتوى والإعلانات:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MyActivity" tools:ignore="MergeRootFrame"> <RelativeLayout android:background="#000000" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="0.4" android:orientation="vertical" android:id="@+id/videoPlayerContainer" > <VideoView android:id="@+id/videoView" android:layout_width="match_parent" android:layout_height="match_parent" /> <ImageButton android:id="@+id/playButton" android:contentDescription="@string/play_description" android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/ic_action_play_over_video" android:background="@null" /> </RelativeLayout> <FrameLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="0.6" android:id="@+id/videoDescription" > <TextView android:id="@+id/playerDescription" android:text="@string/app_name" android:textAlignment="center" android:gravity="center_horizontal" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingVertical="@dimen/font_size" android:textSize="@dimen/font_size" /> </FrameLayout> </LinearLayout>
4. استيراد IMA إلى النشاط الرئيسي
أضِف عبارات الاستيراد الخاصة بحزمة تطوير البرامج لإعلانات الوسائط التفاعلية:
import android.content.Context; import android.content.res.Configuration; import android.media.AudioManager; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.MediaController; import android.widget.VideoView; import androidx.appcompat.app.AppCompatActivity; import com.google.ads.interactivemedia.v3.api.AdDisplayContainer; import com.google.ads.interactivemedia.v3.api.AdErrorEvent; import com.google.ads.interactivemedia.v3.api.AdEvent; import com.google.ads.interactivemedia.v3.api.AdsLoader; import com.google.ads.interactivemedia.v3.api.AdsManager; import com.google.ads.interactivemedia.v3.api.AdsRenderingSettings; import com.google.ads.interactivemedia.v3.api.AdsRequest; import com.google.ads.interactivemedia.v3.api.ImaSdkFactory; import com.google.ads.interactivemedia.v3.api.ImaSdkSettings; import com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate; import java.util.Arrays;
عدِّل فئة MyActivity
لتوسيع AppCompatActivity
. تتيح الفئة AppCompatActivity
إمكانية استخدام ميزات المنصّة الأحدث على أجهزة Android القديمة. بعد ذلك، أضِف مجموعة من المتغيّرات الخاصة التي سيتم استخدامها في التطبيق:
/** Main activity. */ public class MyActivity extends AppCompatActivity { private static final String LOGTAG = "IMABasicSample"; private static final String SAMPLE_VIDEO_URL = "https://storage.googleapis.com/gvabox/media/samples/stock.mp4"; /** * IMA sample tag for a single skippable inline video ad. See more IMA sample tags at * https://developers.google.com/interactive-media-ads/docs/sdks/html5/client-side/tags */ private static final String SAMPLE_VAST_TAG_URL = "https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/" + "single_preroll_skippable&sz=640x480&ciu_szs=300x250%2C728x90&gdfp_req=1&output=vast" + "&unviewed_position_start=1&env=vp&correlator="; // Factory class for creating SDK objects. private ImaSdkFactory sdkFactory; // The AdsLoader instance exposes the requestAds method. private AdsLoader adsLoader; // AdsManager exposes methods to control ad playback and listen to ad events. private AdsManager adsManager; // The saved content position, used to resumed content following an ad break. private int savedPosition = 0; // This sample uses a VideoView for content and ad playback. For production // apps, Android's Exoplayer offers a more fully featured player compared to // the VideoView. private VideoView videoPlayer; private MediaController mediaController; private VideoAdPlayerAdapter videoAdPlayerAdapter; private ImaSdkSettings imaSdkSettings;
5- إنشاء فئة VideoAdPlayerAdapter
أنشئ فئة VideoAdPlayerAdapter
باستخدام VideoView
،
وكيِّفها مع واجهة
VideoAdPlayer
الخاصة بـ "إعلانات الوسائط التفاعلية". سيتولّى هذا الصف معالجة تشغيل المحتوى والإعلانات، وسيتضمّن مجموعة من الطرق التي يجب أن ينفّذها مشغّل الفيديو ليتم استخدامه من خلال حزمة IMA SDK:
import android.media.AudioManager; import android.media.MediaPlayer; import android.net.Uri; import android.util.Log; import android.widget.VideoView; import com.google.ads.interactivemedia.v3.api.AdPodInfo; import com.google.ads.interactivemedia.v3.api.player.AdMediaInfo; import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer; import com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate; import java.util.ArrayList; import java.util.List; import java.util.Timer; import java.util.TimerTask; /** Example implementation of IMA's VideoAdPlayer interface. */ public class VideoAdPlayerAdapter implements VideoAdPlayer { private static final String LOGTAG = "IMABasicSample"; private static final long POLLING_TIME_MS = 250; private static final long INITIAL_DELAY_MS = 250; private final VideoView videoPlayer; private final AudioManager audioManager; private final List<VideoAdPlayerCallback> videoAdPlayerCallbacks = new ArrayList<>(); private Timer timer; private int adDuration; // The saved ad position, used to resumed ad playback following an ad click-through. private int savedAdPosition; private AdMediaInfo loadedAdMediaInfo; public VideoAdPlayerAdapter(VideoView videoPlayer, AudioManager audioManager) { this.videoPlayer = videoPlayer; this.videoPlayer.setOnCompletionListener( (MediaPlayer mediaPlayer) -> notifyImaOnContentCompleted()); this.audioManager = audioManager; }
6. تجاوز طرق VideoAdPlayer
تجاوز طرق VideoAdPlayer
التالية:
تضبط الطريقة playAd()
عنوان URL للمحتوى أو الإعلان، وتضبط أداة معالجة لبدء التشغيل بعد تحميل الوسائط.
@Override public void addCallback(VideoAdPlayerCallback videoAdPlayerCallback) { videoAdPlayerCallbacks.add(videoAdPlayerCallback); } @Override public void loadAd(AdMediaInfo adMediaInfo, AdPodInfo adPodInfo) { // This simple ad loading logic works because preloading is disabled. To support // preloading ads your app must maintain state for the currently playing ad // while handling upcoming ad downloading and buffering at the same time. // See the IMA Android preloading guide for more info: // https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/preload loadedAdMediaInfo = adMediaInfo; } @Override public void pauseAd(AdMediaInfo adMediaInfo) { Log.i(LOGTAG, "pauseAd"); savedAdPosition = videoPlayer.getCurrentPosition(); stopAdTracking(); } @Override public void playAd(AdMediaInfo adMediaInfo) { videoPlayer.setVideoURI(Uri.parse(adMediaInfo.getUrl())); videoPlayer.setOnPreparedListener( mediaPlayer -> { adDuration = mediaPlayer.getDuration(); if (savedAdPosition > 0) { mediaPlayer.seekTo(savedAdPosition); } mediaPlayer.start(); startAdTracking(); }); videoPlayer.setOnErrorListener( (mediaPlayer, errorType, extra) -> notifyImaSdkAboutAdError(errorType)); videoPlayer.setOnCompletionListener( mediaPlayer -> { savedAdPosition = 0; notifyImaSdkAboutAdEnded(); }); } @Override public void release() { // any clean up that needs to be done. } @Override public void removeCallback(VideoAdPlayerCallback videoAdPlayerCallback) { videoAdPlayerCallbacks.remove(videoAdPlayerCallback); } @Override public void stopAd(AdMediaInfo adMediaInfo) { Log.i(LOGTAG, "stopAd"); stopAdTracking(); } /** Returns current volume as a percent of max volume. */ @Override public int getVolume() { return audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) / audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); }
7. إعداد تتبُّع الإعلانات
لكي يتم تسجيل أحداث الإعلانات، يجب استدعاء VideoAdPlayerCallback.onAdProgress
مع تقدّم المحتوى والإعلانات. لإجراء ذلك، اضبط موقّتًا للاتصال بـ onAdProgress()
في فواصل زمنية محدّدة.
private void startAdTracking() { Log.i(LOGTAG, "startAdTracking"); if (timer != null) { return; } timer = new Timer(); TimerTask updateTimerTask = new TimerTask() { @Override public void run() { VideoProgressUpdate progressUpdate = getAdProgress(); notifyImaSdkAboutAdProgress(progressUpdate); } }; timer.schedule(updateTimerTask, POLLING_TIME_MS, INITIAL_DELAY_MS); } private void notifyImaSdkAboutAdEnded() { Log.i(LOGTAG, "notifyImaSdkAboutAdEnded"); savedAdPosition = 0; for (VideoAdPlayer.VideoAdPlayerCallback callback : videoAdPlayerCallbacks) { callback.onEnded(loadedAdMediaInfo); } } private void notifyImaSdkAboutAdProgress(VideoProgressUpdate adProgress) { for (VideoAdPlayer.VideoAdPlayerCallback callback : videoAdPlayerCallbacks) { callback.onAdProgress(loadedAdMediaInfo, adProgress); } } /** * @param errorType Media player's error type as defined at * https://cs.android.com/android/platform/superproject/+/master:frameworks/base/media/java/android/media/MediaPlayer.java;l=4335 * @return True to stop the current ad playback. */ private boolean notifyImaSdkAboutAdError(int errorType) { Log.i(LOGTAG, "notifyImaSdkAboutAdError"); switch (errorType) { case MediaPlayer.MEDIA_ERROR_UNSUPPORTED: Log.e(LOGTAG, "notifyImaSdkAboutAdError: MEDIA_ERROR_UNSUPPORTED"); break; case MediaPlayer.MEDIA_ERROR_TIMED_OUT: Log.e(LOGTAG, "notifyImaSdkAboutAdError: MEDIA_ERROR_TIMED_OUT"); break; default: break; } for (VideoAdPlayer.VideoAdPlayerCallback callback : videoAdPlayerCallbacks) { callback.onError(loadedAdMediaInfo); } return true; } public void notifyImaOnContentCompleted() { Log.i(LOGTAG, "notifyImaOnContentCompleted"); for (VideoAdPlayer.VideoAdPlayerCallback callback : videoAdPlayerCallbacks) { callback.onContentComplete(); } } private void stopAdTracking() { Log.i(LOGTAG, "stopAdTracking"); if (timer != null) { timer.cancel(); timer = null; } } @Override public VideoProgressUpdate getAdProgress() { long adPosition = videoPlayer.getCurrentPosition(); return new VideoProgressUpdate(adPosition, adDuration); }
8. بدء IMA في طريقة onCreate
استبدِل طريقة onCreate
وأضِف عمليات تعيين المتغيرات المطلوبة لبدء IMA. في هذه الخطوة، أنشئ مثيلات لما يلي:
ImaSdkSettings
AdsLoader
VideoAdPlayerAdapter
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_my); // Initialize the IMA SDK as early as possible when the app starts. If your app already // overrides Application.onCreate(), call this method inside the onCreate() method. // https://developer.android.com/topic/performance/vitals/launch-time#app-creation sdkFactory = ImaSdkFactory.getInstance(); sdkFactory.initialize(this, getImaSdkSettings()); // Create the UI for controlling the video view. mediaController = new MediaController(this); videoPlayer = findViewById(R.id.videoView); mediaController.setAnchorView(videoPlayer); videoPlayer.setMediaController(mediaController); // Create an ad display container that uses a ViewGroup to listen to taps. AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); videoAdPlayerAdapter = new VideoAdPlayerAdapter(videoPlayer, audioManager); AdDisplayContainer adDisplayContainer = ImaSdkFactory.createAdDisplayContainer( findViewById(R.id.videoPlayerContainer), videoAdPlayerAdapter); // Create an AdsLoader. adsLoader = sdkFactory.createAdsLoader(this, getImaSdkSettings(), adDisplayContainer);
إعداد زر التشغيل لطلب الإعلانات، ثم إخفاؤه عند النقر عليه:
// When the play button is clicked, request ads and hide the button. View playButton = findViewById(R.id.playButton); playButton.setOnClickListener( view -> { videoPlayer.setVideoPath(SAMPLE_VIDEO_URL); requestAds(SAMPLE_VAST_TAG_URL); view.setVisibility(View.GONE); });
ImaSdkSettings
:
private ImaSdkSettings getImaSdkSettings() { if (imaSdkSettings == null) { imaSdkSettings = ImaSdkFactory.getInstance().createImaSdkSettings(); // Set any IMA SDK settings here. } return imaSdkSettings; }
9- إضافة أدوات معالجة الأحداث AdsLoader
أضِف أدوات معالجة الأحداث للسمتَين addAdErrorListener
وaddAdsLoadedListener
. في AdsLoadedListener
، أنشئ AdsManager
، وأعِدّ مستمع أخطاء AdsManager
:
// Add listeners for when ads are loaded and for errors. adsLoader.addAdErrorListener( new AdErrorEvent.AdErrorListener() { /** An event raised when there is an error loading or playing ads. */ @Override public void onAdError(AdErrorEvent adErrorEvent) { Log.i(LOGTAG, "Ad Error: " + adErrorEvent.getError().getMessage()); resumeContent(); } }); adsLoader.addAdsLoadedListener( adsManagerLoadedEvent -> { // Ads were successfully loaded, so get the AdsManager instance. AdsManager has // events for ad playback and errors. adsManager = adsManagerLoadedEvent.getAdsManager(); // Attach event and error event listeners. adsManager.addAdErrorListener( new AdErrorEvent.AdErrorListener() { /** An event raised when there is an error loading or playing ads. */ @Override public void onAdError(AdErrorEvent adErrorEvent) { Log.e(LOGTAG, "Ad Error: " + adErrorEvent.getError().getMessage()); String universalAdIds = Arrays.toString(adsManager.getCurrentAd().getUniversalAdIds()); Log.i( LOGTAG, "Discarding the current ad break with universal " + "ad Ids: " + universalAdIds); adsManager.discardAdBreak(); } });
10. التعامل مع أحداث إعلانات IMA
استمِع إلى أحداث إعلانات IMA باستخدام
AdsManager.addAdEventListener
. باستخدام عبارة switch، يمكنك إعداد إجراءات لأحداث IMA التالية:
يتضمّن مقتطف الرمز البرمجي تعليقات تتضمّن مزيدًا من المعلومات حول كيفية استخدام الأحداث. بعد إعداد الأحداث، استخدِم الرمز AdsManager.init()
:
adsManager.addAdEventListener( new AdEvent.AdEventListener() { /** Responds to AdEvents. */ @Override public void onAdEvent(AdEvent adEvent) { if (adEvent.getType() != AdEvent.AdEventType.AD_PROGRESS) { Log.i(LOGTAG, "Event: " + adEvent.getType()); } // These are the suggested event types to handle. For full list of // all ad event types, see AdEvent.AdEventType documentation. switch (adEvent.getType()) { case LOADED -> // AdEventType.LOADED is fired when ads are ready to play. // This sample app uses the sample tag // single_preroll_skippable_ad_tag_url that requires calling // AdsManager.start() to start ad playback. // If you use a different ad tag URL that returns a VMAP or // an ad rules playlist, the adsManager.init() function will // trigger ad playback automatically and the IMA SDK will // ignore the adsManager.start(). // It is safe to always call adsManager.start() in the // LOADED event. adsManager.start(); case CONTENT_PAUSE_REQUESTED -> // AdEventType.CONTENT_PAUSE_REQUESTED is fired when you // should pause your content and start playing an ad. pauseContentForAds(); case CONTENT_RESUME_REQUESTED -> // AdEventType.CONTENT_RESUME_REQUESTED is fired when the ad // you should play your content. resumeContent(); case ALL_ADS_COMPLETED -> { // Calling adsManager.destroy() triggers the function // VideoAdPlayer.release(). adsManager.destroy(); adsManager = null; } case CLICKED -> { // When the user clicks on the Learn More button, the IMA SDK fires // this event, pauses the ad, and opens the ad's click-through URL. // When the user returns to the app, the IMA SDK calls the // VideoAdPlayer.playAd() function automatically. } default -> {} } } }); AdsRenderingSettings adsRenderingSettings = ImaSdkFactory.getInstance().createAdsRenderingSettings(); // Add any ads rendering settings here. // This init() only loads the UI rendering settings locally. adsManager.init(adsRenderingSettings); });
11. التعامل مع التبديل بين الإعلانات والمحتوى
في هذا القسم، أنشئ الطريقتَين pauseContentForAds
وresumeContent
المشار إليهما في الخطوات السابقة. ستعيد هذه الطرق استخدام المشغّل لتشغيل المحتوى والإعلانات. عليك تتبُّع موضع المحتوى لاستئناف التشغيل بعد فاصل الإعلانات.
private void pauseContentForAds() { Log.i(LOGTAG, "pauseContentForAds"); savedPosition = videoPlayer.getCurrentPosition(); videoPlayer.stopPlayback(); // Hide the buttons and seek bar controlling the video view. videoPlayer.setMediaController(null); } private void resumeContent() { Log.i(LOGTAG, "resumeContent"); // Show the buttons and seek bar controlling the video view. videoPlayer.setVideoPath(SAMPLE_VIDEO_URL); videoPlayer.setMediaController(mediaController); videoPlayer.setOnPreparedListener( mediaPlayer -> { if (savedPosition > 0) { mediaPlayer.seekTo(savedPosition); } mediaPlayer.start(); }); videoPlayer.setOnCompletionListener( mediaPlayer -> videoAdPlayerAdapter.notifyImaOnContentCompleted()); }
12. طلب إدراج الإعلانات
الآن، أضِف طريقة requestAds
لإنشاء
AdsRequest
واستخدِمها لاستدعاء AdsLoader.requestAds()
:
private void requestAds(String adTagUrl) { // Create the ads request. AdsRequest request = sdkFactory.createAdsRequest(); request.setAdTagUrl(adTagUrl); request.setContentProgressProvider( () -> { if (videoPlayer.getDuration() <= 0) { return VideoProgressUpdate.VIDEO_TIME_NOT_READY; } return new VideoProgressUpdate( videoPlayer.getCurrentPosition(), videoPlayer.getDuration()); }); // Request the ad. After the ad is loaded, onAdsManagerLoaded() will be called. adsLoader.requestAds(request); }
يمكنك الآن طلب الإعلانات وعرضها بنجاح باستخدام "حزمة تطوير البرامج للإعلانات التفاعلية". للاطّلاع على المزيد من الميزات المتقدّمة، يمكنك استكشاف الأدلة الأخرى أو الأمثلة على GitHub.