התאמה אישית

הממשק בליבה של ספריית ExoPlayer נמצא ב-Player. Player חושף פונקציונליות מסורתית של נגן מדיה ברמה גבוהה, כמו האפשרות לאחסן מדיה במאגר, להפעיל, להשהות ולחפש מדיה. הטמעת ברירת המחדל ExoPlayer תוכננה כך שתהיה בה מעט הנחות לגבי סוג המדיה שמופעלת, האופן שבו היא מאוחסנת והאופן שבו היא מוצגת (ולכן היא מטילה מעט הגבלות עליהם). במקום להטמיע את הטעינה והרינדור של מדיה ישירות, הטמעות ExoPlayer מאצילות את העבודה הזו לרכיבים שמוחדרים כאשר נוצר נגן או כשמקורות מדיה חדשים מועברים לנגן. הרכיבים המשותפים לכל ההטמעות של ExoPlayer הם:

  • מכונות MediaSource שמגדירות את המדיה שרוצים להפעיל, טוענות את המדיה ומאפשרות לקרוא את המדיה הטעונה. מופע MediaSource נוצר מ-MediaItem על ידי MediaSource.Factory בתוך הנגן. אפשר גם להעביר אותם ישירות לנגן באמצעות ממשק ה-API של פלייליסט שמבוסס על מקור מדיה.
  • מכונות MediaSource.Factory שממירות MediaItem ל-MediaSource. ה-MediaSource.Factory מוזרק כשיוצרים את הנגן.
  • מכונות Renderer שמרינדרות רכיבים נפרדים של המדיה. הם מוחדרים בזמן יצירת הנגן.
  • TrackSelector שבוחר טראקים שסופקו על ידי MediaSource לשימוש בכל Renderer זמין. ה-TrackSelector מוזרק כשהנגן נוצר.
  • LoadControl שקובע מתי MediaSource יאגר יותר מדיה, וכמה מדיה ייאגר. ה-LoadControl מוזרק כשיוצרים את השחקן.
  • LivePlaybackSpeedControl ששולט במהירות ההפעלה במהלך הפעלות בשידור חי, כדי לאפשר לנגן להישאר קרוב להיסט מוגדר בשידור חי. החדר LivePlaybackSpeedControl מועבר כשהנגן נוצר.

הקונספט של החדרת רכיבים שמטמיעים חלקים מהפונקציונליות של הנגן קיים בספרייה. הטמעות ברירת המחדל של חלק מהרכיבים מעבירות את העבודה לרכיבים נוספים שהוזנו. כך אפשר להחליף רכיבי משנה רבים בנפרד בהטמעות שמוגדרות באופן מותאם אישית.

התאמה אישית של הנגן

מתוארות כמה דוגמאות נפוצות להתאמה אישית של הנגן על ידי החדרת רכיבים.

הגדרת ערימת הרשת

יש לנו דף בנושא התאמה אישית של מקבץ הרשת שמשמש את ExoPlayer.

שמירת נתונים במטמון שנטענו מהרשת

עיינו במדריכים בנושא שמירה זמנית במטמון תוך כדי תנועה והורדת מדיה.

התאמה אישית של אינטראקציות עם השרת

יכול להיות שחלק מהאפליקציות ירצו ליירט בקשות ותגובות של HTTP. יכול להיות שתרצו להחדיר כותרות בקשה בהתאמה אישית, לקרוא את כותרות התשובה של השרת, לשנות את מזהי ה-URI של הבקשות וכו'. לדוגמה, האפליקציה עשויה לאמת את עצמה על ידי הוספת אסימון ככותרת כשמבקשים את פלחי המדיה.

הדוגמה הבאה ממחישה איך להטמיע את ההתנהגויות האלה על ידי החדרת DataSource.Factory בהתאמה אישית ל-DefaultMediaSourceFactory:

Kotlin

val dataSourceFactory =
  DataSource.Factory {
    val dataSource = httpDataSourceFactory.createDataSource()
    // Set a custom authentication request header.
    dataSource.setRequestProperty("Header", "Value")
    dataSource
  }
val player =
  ExoPlayer.Builder(context)
    .setMediaSourceFactory(
      DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory)
    )
    .build()

Java

DataSource.Factory dataSourceFactory =
    () -> {
      HttpDataSource dataSource = httpDataSourceFactory.createDataSource();
      // Set a custom authentication request header.
      dataSource.setRequestProperty("Header", "Value");
      return dataSource;
    };

ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(
            new DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory))
        .build();

בקטע הקוד שלמעלה, ה-HttpDataSource שהוחדר כולל את הכותרת "Header: Value" בכל בקשת HTTP. ההתנהגות הזו קבועה לכל אינטראקציה עם מקור HTTP.

לגישה מפורטת יותר, אפשר להחדיר התנהגות בזמן אמת באמצעות ResolvingDataSource. קטע הקוד הבא מראה איך להחדיר כותרות של בקשות ממש לפני האינטראקציה עם מקור HTTP:

Kotlin

val dataSourceFactory: DataSource.Factory =
  ResolvingDataSource.Factory(httpDataSourceFactory) { dataSpec: DataSpec ->
    // Provide just-in-time request headers.
    dataSpec.withRequestHeaders(getCustomHeaders(dataSpec.uri))
  }

Java

    DataSource.Factory dataSourceFactory =
        new ResolvingDataSource.Factory(
            httpDataSourceFactory,
            // Provide just-in-time request headers.
            dataSpec -> dataSpec.withRequestHeaders(getCustomHeaders(dataSpec.uri)));

אפשר גם להשתמש ב-ResolvingDataSource כדי לבצע שינויים ב-URI בזמן אמת, כפי שמוצג בקטע הקוד הבא:

Kotlin

val dataSourceFactory: DataSource.Factory =
  ResolvingDataSource.Factory(httpDataSourceFactory) { dataSpec: DataSpec ->
    // Provide just-in-time URI resolution logic.
    dataSpec.withUri(resolveUri(dataSpec.uri))
  }

Java

DataSource.Factory dataSourceFactory =
    new ResolvingDataSource.Factory(
        httpDataSourceFactory,
        // Provide just-in-time URI resolution logic.
        dataSpec -> dataSpec.withUri(resolveUri(dataSpec.uri)));

התאמה אישית של הטיפול בשגיאות

הטמעה של LoadErrorHandlingPolicy בהתאמה אישית מאפשרת לאפליקציות להתאים אישית את הדרך שבה ExoPlayer מגיב לשגיאות בטעינה. לדוגמה, יכול להיות שאפליקציה רוצה להיכשל במהירות במקום לנסות שוב הרבה פעמים, או להתאים אישית את לוגיקת ההשהיה לפני ניסיון חוזר שקובעת כמה זמן השחקן ימתין בין כל ניסיון חוזר. קטע הקוד הבא מראה איך ליישם לוגיקה מותאמת אישית של השהיה לפני ניסיון חוזר:

Kotlin

val loadErrorHandlingPolicy: LoadErrorHandlingPolicy =
  object : DefaultLoadErrorHandlingPolicy() {
    override fun getRetryDelayMsFor(loadErrorInfo: LoadErrorInfo): Long {
      // Implement custom back-off logic here.
      return 0
    }
  }
val player =
  ExoPlayer.Builder(context)
    .setMediaSourceFactory(
      DefaultMediaSourceFactory(context).setLoadErrorHandlingPolicy(loadErrorHandlingPolicy)
    )
    .build()

Java

LoadErrorHandlingPolicy loadErrorHandlingPolicy =
    new DefaultLoadErrorHandlingPolicy() {
      @Override
      public long getRetryDelayMsFor(LoadErrorInfo loadErrorInfo) {
        // Implement custom back-off logic here.
        return 0;
      }
    };

ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(
            new DefaultMediaSourceFactory(context)
                .setLoadErrorHandlingPolicy(loadErrorHandlingPolicy))
        .build();

הארגומנט LoadErrorInfo מכיל מידע נוסף על הטעינה שנכשלה כדי להתאים אישית את הלוגיקה על סמך סוג השגיאה או הבקשה שנכשלה.

התאמה אישית של דגלים של חילוץ

אפשר להשתמש בדגלים של חילוץ כדי להתאים אישית את האופן שבו פורמטים ספציפיים מחלצים ממדיה פרוגרסיבית. אפשר להגדיר אותם ב-DefaultExtractorsFactory שסופק ל-DefaultMediaSourceFactory. הדוגמה הבאה מעבירה דגל שמאפשר דילוג מבוסס אינדקס לסטרימינג של MP3.

Kotlin

val extractorsFactory =
  DefaultExtractorsFactory().setMp3ExtractorFlags(Mp3Extractor.FLAG_ENABLE_INDEX_SEEKING)
val player =
  ExoPlayer.Builder(context)
    .setMediaSourceFactory(DefaultMediaSourceFactory(context, extractorsFactory))
    .build()

Java

DefaultExtractorsFactory extractorsFactory =
    new DefaultExtractorsFactory().setMp3ExtractorFlags(Mp3Extractor.FLAG_ENABLE_INDEX_SEEKING);

ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(new DefaultMediaSourceFactory(context, extractorsFactory))
        .build();

הפעלת דילוג קבוע על קצב נתונים

להפעלת דילוג משוער בסטרימינג של MP3, ‏ ADTS ו-AMR, אפשר להשתמש בהנחה של קצב נתונים קבוע באמצעות הדגלים FLAG_ENABLE_CONSTANT_BITRATE_SEEKING. אפשר להגדיר את הדגלים האלה למחוללים נפרדים באמצעות השיטות הנפרדות של DefaultExtractorsFactory.setXyzExtractorFlags, כפי שמתואר למעלה. כדי להפעיל חיפוש בקצב נתונים קבוע בכל ה-extractors שתומכים בכך, משתמשים ב-DefaultExtractorsFactory.setConstantBitrateSeekingEnabled.

Kotlin

val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)

Java

DefaultExtractorsFactory extractorsFactory =
    new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true);

לאחר מכן אפשר להזריק את ה-ExtractorsFactory דרך DefaultMediaSourceFactory, כפי שמתואר למעלה להתאמה אישית של דגלים לחילוץ נתונים.

הפעלת תורים של מאגרים אסינכרונים

תורים של מאגרים אסינכרונים הם שיפור בצינור עיבוד הנתונים של ExoPlayer, שמפעיל מכונות MediaCodec במצב אסינכרוני ומשתמש בשרשורים נוספים לתזמון פענוח ועיבוד נתונים. כשמפעילים אותה, יכול להיות שהפריימים מושמטים ופחות פריימים שמקורם באודיו.

התכונה 'אוסף בקשות אסינכררוני במאגר' מופעלת כברירת מחדל במכשירים עם Android 12 (רמת API 31) ואילך, וניתן להפעיל אותה באופן ידני החל מ-Android 6.0 (רמת API 23). מומלץ להפעיל את התכונה במכשירים ספציפיים שבהם נתקלתם בפריימים שנשמטו או בבעיות של אודיו חסר, במיוחד כשאתם מפעילים תוכן מוגן DRM או תוכן עם קצב פריימים גבוה.

במקרה הפשוט ביותר, צריך להחדיר DefaultRenderersFactory לנגן באופן הבא:

Kotlin

val renderersFactory = 
  DefaultRenderersFactory(context).forceEnableMediaCodecAsynchronousQueueing()
val exoPlayer = ExoPlayer.Builder(context, renderersFactory).build()

Java

DefaultRenderersFactory renderersFactory =
    new DefaultRenderersFactory(context).forceEnableMediaCodecAsynchronousQueueing();
ExoPlayer exoPlayer = new ExoPlayer.Builder(context, renderersFactory).build();

אם אתם יוצרים ישירות מרינדור, צריך להעביר AsynchronousMediaCodecAdapter.Factory ל-constructor של MediaCodecVideoRenderer ו-MediaCodecAudioRenderer.

תיעוד קריאות ל-method באמצעות ForwardingPlayer

אפשר להתאים אישית חלק מההתנהגות של מופע Player על ידי עטיפה שלו בתת-סוג של ForwardingPlayer ושינוי מברירת המחדל של שיטות כדי לבצע אחת מהפעולות הבאות:

  • פרמטרים של גישה לפני העברתם למקבל הגישה Player.
  • צריך לגשת לערך המוחזר מהגורם המבצע Player לפני שמחזירים אותו.
  • מטמיעים מחדש את השיטה.

כשמבטלים את הגדרת ברירת המחדל של שיטות ForwardingPlayer, חשוב לוודא שההטמעה נשארת עקבית ותואמת לממשק Player, במיוחד כשמדובר בשיטות שאמורות להיות בעלות התנהגות זהה או קשורה. לדוגמה:

  • אם רוצים לשנות כל פעולת 'הפעלה', צריך לבטל את ForwardingPlayer.play ואת ForwardingPlayer.setPlayWhenReady, כי ה-caller יצפה שההתנהגות ב-methods האלה תהיה זהה כאשר playWhenReady = true.
  • אם רוצים לשנות את הגדלת ההתקדמות, צריך לשנות את הערך של ForwardingPlayer.seekForward כדי לבצע התקדמות עם הגדלה בהתאמה אישית, ואת הערך של ForwardingPlayer.getSeekForwardIncrement כדי לדווח למבצע הקריאה החוזרת על הגדלה בהתאמה אישית נכונה.
  • אם רוצים לקבוע אילו Player.Commands יוצגו על ידי מכשיר הנגן, צריך לשנות את הגדרות Player.getAvailableCommands() ו-Player.isCommandAvailable(), וגם להאזין להודעת החזרה (callback) של Player.Listener.onAvailableCommandsChanged() כדי לקבל התראות על שינויים שמגיעים מהנגן הבסיסי.

התאמה אישית של MediaSource

הדוגמאות שלמעלה מחדירות רכיבים מותאמים אישית לשימוש במהלך ההפעלה של כל אובייקטי ה-MediaItem שמועברים לנגן. במקרים שבהם נדרשת התאמה אישית פרטנית, אפשר גם להוסיף רכיבים מותאמים אישית למכונות MediaSource נפרדות, שאותן אפשר להעביר ישירות לנגן. בדוגמה הבאה מוסבר איך להתאים אישית את ProgressiveMediaSource כך שישתמש ב-DataSource.Factory, ב-ExtractorsFactory וב-LoadErrorHandlingPolicy בהתאמה אישית:

Kotlin

val mediaSource =
  ProgressiveMediaSource.Factory(customDataSourceFactory, customExtractorsFactory)
    .setLoadErrorHandlingPolicy(customLoadErrorHandlingPolicy)
    .createMediaSource(MediaItem.fromUri(streamUri))

Java

ProgressiveMediaSource mediaSource =
    new ProgressiveMediaSource.Factory(customDataSourceFactory, customExtractorsFactory)
        .setLoadErrorHandlingPolicy(customLoadErrorHandlingPolicy)
        .createMediaSource(MediaItem.fromUri(streamUri));

יצירת רכיבים מותאמים אישית

הספרייה מספקת הטמעות ברירת מחדל של הרכיבים שמפורטים בחלק העליון של הדף הזה, למקרים נפוצים של שימוש. ExoPlayer יכול להשתמש ברכיבים האלו, אבל הוא עשוי גם להיווצר לשימוש בהטמעות מותאמות אישית אם נדרשות התנהגויות לא סטנדרטיות. לפניכם מספר תרחישים לדוגמה של הטמעות בהתאמה אישית:

  • Renderer – יכול להיות שתרצו להטמיע Renderer בהתאמה אישית כדי לטפל בסוג מדיה שלא נתמך בהטמעות ברירת המחדל שסופקו על ידי הספרייה.
  • TrackSelector – הטמעת TrackSelector בהתאמה אישית מאפשרת למפתחי האפליקציות לשנות את האופן שבו הטראקים שמוצגים על ידי MediaSource נבחרים לשימוש בכל אחד מה-Renderer הזמינים.
  • LoadControl – הטמעת LoadControl בהתאמה אישית מאפשרת למפתחי האפליקציה לשנות את מדיניות האחסון במטמון של הנגן.
  • Extractor – אם אתם צריכים לתמוך בפורמט מאגר שאינו נתמך כרגע בספרייה, כדאי להטמיע את הכיתה Extractor בהתאמה אישית.
  • MediaSource – כדאי להטמיע סוג MediaSource מותאם אישית אם רוצים לקבל דגימות מדיה כדי להעביר למעבד גרפיקה באופן מותאם אישית, או אם רוצים להטמיע התנהגות מותאמת אישית של MediaSource ליצירת קומפוזיציה.
  • MediaSource.Factory – הטמעה של MediaSource.Factory בהתאמה אישית מאפשרת לאפליקציה להתאים אישית את האופן שבו נוצר MediaSource מ-MediaItem.
  • DataSource – חבילת ה-upstream של ExoPlayer כבר מכילה מספר הטמעות של DataSource לתרחישים לדוגמה שונים. יכול להיות שתרצו להטמיע את הכיתה DataSource שלכם כדי לטעון נתונים בדרך אחרת, למשל באמצעות פרוטוקול מותאם אישית, באמצעות סטאק HTTP מותאם אישית או ממאגר מטמון מתמיד מותאם אישית.

כשאתם יוצרים רכיבים מותאמים אישית, מומלץ:

  • אם רכיב מותאם אישית צריך לדווח על אירועים לאפליקציה, מומלץ לעשות זאת באמצעות אותו מודל שבו נעשה שימוש ברכיבי ExoPlayer קיימים. לדוגמה, אפשר להשתמש ב-classes‏ EventDispatcher או להעביר Handler יחד עם מאזין ל-constructor של הרכיב.
  • מומלץ להשתמש באותו מודל ברכיבים מותאמים אישית כמו ברכיבים הקיימים של ExoPlayer, כדי לאפשר לאפליקציה לבצע שינוי תצורה במהלך ההפעלה. לשם כך, צריך להטמיע את PlayerMessage.Target ברכיבים מותאמים אישית ולקבל שינויים בהגדרות בשיטה handleMessage. קוד האפליקציה צריך להעביר את שינויי ההגדרה באמצעות קריאה ל-method‏ createMessage של ExoPlayer, הגדרת ההודעה ושליחתה לרכיב באמצעות PlayerMessage.send. שליחת הודעות שיועברו בשרשור של ההפעלה מבטיחה שהן יבוצעו כדי לבצע פעולות אחרות בנגן.