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

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

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

תחילת העבודה

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

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

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

חשוב להחליף את 1.4.1 בגרסה המועדפת של הספרייה. אפשר לעיין בנתוני הגרסה כדי לראות מהי הגרסה האחרונה.

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

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

יצירת ExoPlayer

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

Kotlin

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

Java

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

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

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

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

הכנת הנגן

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

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

שליטה בנגן

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

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

שחרור הנגן

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

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

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

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

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

implementation "androidx.media3:media3-session:1.4.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.

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

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

שיטות אחרות של קריאה חוזרת מאפשרות לכם לטפל, למשל, בבקשות לפקודות הפעלה בהתאמה אישית ובשינוי הפלייליסט. באופן דומה, פונקציות ה-callbacks האלה כוללות אובייקט 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 עם מסנן Intent מסוג MediaSessionService ומבקשים את ההרשאה 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 כדי לקשר ביניהם. ב-method onStart() של 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" />

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

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

יצירת MediaLibrarySession

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

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

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