התאמה אישית

הממשק בליבה של ספריית 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 מגיב לשגיאות טעינה. לדוגמה, יכול להיות שאפליקציה מסוימת רוצה להיכשל במהירות במקום לנסות שוב הרבה פעמים, או להתאים אישית את לוגיקת ההשהיה לפני ניסיון חוזר (backoff) המדיניות הזו קובעת כמה זמן השחקן ימתין בין כל ניסיון חוזר. קטע הקוד הבא מראים איך להטמיע לוגיקה מותאמת אישית של השהיה לפני ניסיון חוזר (backoff):

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 methods כמו שמתואר למעלה. שפת תרגום לאפשר חיפוש בקצב העברת נתונים קבוע לכל המחלצים שתומכים בכך, 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 אל MediaCodecVideoRenderer ו MediaCodecAudioRenderer בנאים.

הפרדת קריאות בשיטת יירוט באמצעות ForwardingPlayer

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

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

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

  • אם רוצים לשנות כל 'הפעלה' צריך לבטל את שניהם ForwardingPlayer.play ו-ForwardingPlayer.setPlayWhenReady, כי יצפה שהתנהגות שיטות אלה תהיה זהה כאשר playWhenReady = true.
  • אם רוצים לשנות את מרווח ההרצה קדימה, צריך לבטל את שניהם. ForwardingPlayer.seekForward כדי לבצע דילוג עם טקסט מותאם אישית וגם ForwardingPlayer.getSeekForwardIncrement כדי לדווח את המרווח הנכון בהתאמה אישית חזרה למתקשר.
  • כדי לקבוע איזה תוכן שחקן מפרסם Player.Commands צריך לשנות גם את Player.getAvailableCommands() וגם Player.isCommandAvailable(), וגם להאזין התקשרות חזרה 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 מותאמת אישית או מתוך מאגר תגים בהתאמה אישית של Google.

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

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