יצירת אפליקציה בסיסית של נגן מדיה באמצעות Media3 ExoPlayer

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

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

תחילת העבודה

כדי להתחיל, מוסיפים תלות במודולים ExoPlayer,‏ UI ו-Common של Jetpack Media3:

Kotlin

implementation("androidx.media3:media3-exoplayer:1.10.1")
implementation("androidx.media3:media3-ui:1.10.1")
implementation("androidx.media3:media3-common:1.10.1")

מגניב

implementation "androidx.media3:media3-exoplayer:1.10.1"
implementation "androidx.media3:media3-ui:1.10.1"
implementation "androidx.media3:media3-common:1.10.1"

בהתאם לתרחיש השימוש, יכול להיות שתצטרכו גם מודולים נוספים מ-Media3, כמו exoplayer-dash כדי להפעיל סטרימינג בפורמט DASH.

חשוב להחליף את 1.10.1 בגרסה המועדפת של הספרייה. בהערות המוצר אפשר לראות את הגרסה האחרונה.

יצירת נגן מדיה

עם Media3, אפשר להשתמש בהטמעה הכלולה של הממשק Player ‏, ExoPlayer, או ליצור הטמעה מותאמת אישית משלכם.

יצירת ExoPlayer

הדרך הכי פשוטה ליצור מופע של ExoPlayer היא:

Kotlin

val player = ExoPlayer.Builder(context).build()

Java

ExoPlayer player = new ExoPlayer.Builder(context).build();

אפשר ליצור את נגן המדיה ב-lifecycle method onCreate() של Activity, Fragment או Service שבהם הוא נמצא.

Builder כולל מגוון אפשרויות להתאמה אישית שאולי יעניינו אתכם, כמו:

‫Media3 מספקת רכיב ממשק משתמש PlayerView שאפשר לכלול בקובץ הפריסה של האפליקציה. הרכיב הזה מכיל PlayerControlView לשליטה בהפעלה, SubtitleView להצגת כתוביות ו-Surface לעיבוד סרטונים.

הכנת הנגן

מוסיפים פריטי מדיה לפלייליסט להפעלה באמצעות שיטות כמו setMediaItem() ו-addMediaItem(). לאחר מכן, מתקשרים אל prepare() כדי להתחיל לטעון את המדיה ולרכוש את המשאבים הנדרשים.

לא מומלץ לבצע את השלבים האלה לפני שהאפליקציה עוברת לחזית. אם נגן המדיה נמצא במצב Activity או Fragment, צריך להכין את הנגן בשיטת מחזור החיים onStart() ברמת API‏ 24 ומעלה, או בשיטת מחזור החיים onResume() ברמת API‏ 23 ומטה. אם השחקן נמצא בService, אפשר להכין אותו בonCreate(). במאמר Exoplayer codelab יש דוגמה להטמעה של methods שקשורות למחזור החיים.

שליטה בנגן

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

רכיבי ממשק משתמש כמו PlayerView או PlayerControlView יתעדכנו בהתאם כשהם מקושרים לנגן.

הפעלת הנגן

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

אם נגן המדיה נמצא במצב Activity או Fragment, צריך לשחרר אותו בשיטת מחזור החיים onStop() ברמת API‏ 24 ומעלה, או בשיטה onPause() ברמת API‏ 23 ומטה. אם שחקן נמצא בService, אפשר לשחרר אותו ב-onDestroy(). בCodelab של ExoPlayer יש דוגמה להטמעה של שיטות מחזור חיים.

ניהול ההפעלה באמצעות סשן מדיה

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

כדי להשתמש בסשנים של מדיה, מוסיפים תלות במודול Session של Media3:

Kotlin

implementation("androidx.media3:media3-session:1.10.1")

מגניב

implementation "androidx.media3:media3-session:1.10.1"

יצירת סשן מדיה

אפשר ליצור MediaSession אחרי שמפעילים נגן באופן הבא:

Kotlin

val player = ExoPlayer.Builder(context).build()
val mediaSession = MediaSession.Builder(context, player).build()

Java

ExoPlayer player = new ExoPlayer.Builder(context).build();
MediaSession mediaSession = new MediaSession.Builder(context, player).build();

‫Media3 מסנכרן אוטומטית את המצב של Player עם המצב של MediaSession. השיטה הזו פועלת עם כל הטמעה של Player, כולל ExoPlayer,‏ CastPlayer או הטמעה מותאמת אישית.

מתן שליטה ללקוחות אחרים

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

כשבקר עומד להתחבר להפעלת המדיה, מתבצעת קריאה לשיטה onConnect(). אפשר להשתמש בנתונים שמופיעים בControllerInfo כדי להחליט אם לאשר או לדחות את הבקשה. אפשר לראות דוגמה לכך באפליקציית ההדגמה של Media3 Session.

אחרי החיבור, השלט יכול לשלוח פקודות הפעלה להפעלה של התוכן בסשן. הפקודות האלה מועברות מהסשן אל הנגן. פקודות הפעלה ופלייליסט שמוגדרות בממשק Player מטופלות באופן אוטומטי על ידי הסשן.

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

הפעלת מדיה ברקע

כדי להמשיך להפעיל מדיה כשהאפליקציה לא בחזית, למשל כדי להפעיל מוזיקה, ספרי אודיו או פודקאסטים גם כשהמשתמש לא פתח את האפליקציה, צריך להוסיף את Player וMediaSession לשירות בחזית. ‫Media3 מספקת את ממשק MediaSessionService למטרה הזו.

הטמעה של MediaSessionService

יוצרים מחלקה שמרחיבה את MediaSessionService ויוצרים מופע של MediaSession בשיטת מחזור החיים onCreate().

Kotlin

class PlaybackService : MediaSessionService() {
    private var mediaSession: MediaSession? = null

    // Create your Player and MediaSession in the onCreate lifecycle event
    override fun onCreate() {
        super.onCreate()
        val player = ExoPlayer.Builder(this).build()
        mediaSession = MediaSession.Builder(this, player).build()
    }

    // Remember to release the player and media session in onDestroy
    override fun onDestroy() {
        mediaSession?.run {
            player.release()
            release()
            mediaSession = null
        }
        super.onDestroy()
    }
}

Java

public class PlaybackService extends MediaSessionService {
    private MediaSession mediaSession = null;

    @Override
    public void onCreate() {
        super.onCreate();
        ExoPlayer player = new ExoPlayer.Builder(this).build();
        mediaSession = new MediaSession.Builder(this, player).build();
    }

    @Override
    public void onDestroy() {
        mediaSession.getPlayer().release();
        mediaSession.release();
        mediaSession = null;
        super.onDestroy();
    }
}

במניפסט, מצהירים על המחלקה Service עם MediaSessionService מסנן Intent ומבקשים את ההרשאה FOREGROUND_SERVICE להפעלת שירות שפועל בחזית:

<service
    android:name=".PlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="true">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaSessionService"/>
    </intent-filter>
</service>

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

לבסוף, בכיתה שיצרתם, מחליפים את השיטה onGetSession() כדי לשלוט בגישת הלקוח להפעלת המדיה. מחזירים MediaSession כדי לאשר את הבקשה לחיבור, או מחזירים null כדי לדחות את הבקשה.

Kotlin

// This example always accepts the connection request
override fun onGetSession(
    controllerInfo: MediaSession.ControllerInfo
): MediaSession? = mediaSession

Java

@Override
public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) {
  // This example always accepts the connection request
  return mediaSession;
}

מתחברים לממשק המשתמש

עכשיו סשן המדיה שלכם נמצא בService בנפרד מActivity או מFragment שבהם נמצא ממשק המשתמש של הנגן, ואתם יכולים להשתמש בMediaController כדי לקשר ביניהם. ב-onStart() method של Activity או Fragment עם ממשק המשתמש שלכם, יוצרים SessionToken בשביל MediaSession, ואז משתמשים ב-SessionToken כדי ליצור MediaController. התהליך של בניית MediaController מתבצע באופן אסינכרוני.

Kotlin

override fun onStart() {
  val sessionToken = SessionToken(this, ComponentName(this, PlaybackService::class.java))
  val controllerFuture = MediaController.Builder(this, sessionToken).buildAsync()
  controllerFuture.addListener(
    {
        // Call controllerFuture.get() to retrieve the MediaController.
        // MediaController implements the Player interface, so it can be
        // attached to the PlayerView UI component.
        playerView.setPlayer(controllerFuture.get())
      },
    MoreExecutors.directExecutor()
  )
}

Java

@Override
public void onStart() {
  SessionToken sessionToken =
    new SessionToken(this, new ComponentName(this, PlaybackService.class));
  ListenableFuture<MediaController> controllerFuture =
    new MediaController.Builder(this, sessionToken).buildAsync();
  controllerFuture.addListener(() -> {
    // Call controllerFuture.get() to retrieve the MediaController.
    // MediaController implements the Player interface, so it can be
    // attached to the PlayerView UI component.
    playerView.setPlayer(controllerFuture.get());
  }, MoreExecutors.directExecutor())
}

MediaController מטמיע את הממשק Player, כך שאפשר להשתמש באותן שיטות כמו play() ו-pause() כדי לשלוט בהפעלה. בדומה לרכיבים אחרים, חשוב לזכור לשחרר את MediaController כשכבר לא צריך אותו בשיטת מחזור החיים onStop() של Activity, על ידי קריאה ל-MediaController.releaseFuture().

פרסום התראה

כדי לפרסם התראה בזמן שהאפליקציה פעילה, צריך להשתמש בשירותים בחזית. ‫MediaSessionService תיצור באופן אוטומטי התראה ב-MediaStyle בצורה של MediaNotification. כדי לספק התראה בהתאמה אישית, יוצרים MediaNotification.Provider עם DefaultMediaNotificationProvider.Builder או יוצרים הטמעה מותאמת אישית של ממשק הספק. מוסיפים את הספק ל-MediaSession באמצעות setMediaNotificationProvider.

פרסום ספריית התוכן

MediaLibraryService מבוסס על MediaSessionService, ומאפשר לאפליקציות לקוח לעיין בתוכן המדיה שמסופק על ידי האפליקציה שלכם. אפליקציות לקוח מטמיעות MediaBrowser כדי ליצור אינטראקציה עם MediaLibraryService.

ההטמעה של MediaLibraryService דומה להטמעה של MediaSessionService, אבל ב-onGetSession() צריך להחזיר MediaLibrarySession במקום MediaSession. בהשוואה ל-MediaSession.Callback, ‏ MediaLibrarySession.Callback כולל שיטות נוספות שמאפשרות ללקוח בדפדפן לנווט בתוכן שמוצע על ידי שירות הספרייה שלכם.

בדומה ל-MediaSessionService, צריך להצהיר על MediaLibraryService במניפסט ולבקש את ההרשאה FOREGROUND_SERVICE כדי להריץ שירות שפועל בחזית:

<service
    android:name=".PlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="true">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaLibraryService"/>
        <action android:name="android.media.browse.MediaBrowserService"/>
    </intent-filter>
</service>

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

בדוגמה שלמעלה יש מסנן Intent גם ל-MediaLibraryService וגם ל-MediaBrowserService מדור קודם, לצורך תאימות לאחור. מסנן Intent הנוסף מאפשר לאפליקציות לקוח שמשתמשות ב-API‏ MediaBrowserCompat לזהות את Service.

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

יצירת MediaLibrarySession

MediaLibrarySession מרחיב את MediaSession API כדי להוסיף ממשקי API לעיון בתוכן. בהשוואה לMediaSession callback, ‏ MediaLibrarySession callback מוסיף שיטות כמו:

  • onGetLibraryRoot() כשלקוח מבקש את שורש MediaItem של עץ תוכן
  • onGetChildren() כשלקוח מבקש את צאצאי MediaItem בעץ התוכן
  • onGetSearchResult() כשלקוח מבקש תוצאות חיפוש מעץ התוכן עבור שאילתה מסוימת

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