สร้างแอปมีเดียเพลเยอร์พื้นฐานโดยใช้ Media3 ExoPlayer

Jetpack Media3 กำหนดPlayerอินเทอร์เฟซที่ระบุฟังก์ชันพื้นฐานสำหรับการเล่นไฟล์วิดีโอและไฟล์เสียง ExoPlayer เป็นการใช้งานเริ่มต้นของอินเทอร์เฟซนี้ใน Media3 เราขอแนะนำให้ใช้ ExoPlayer เนื่องจากมีชุดฟีเจอร์ที่ครอบคลุมซึ่งครอบคลุม Use Case การเล่นส่วนใหญ่ และปรับแต่งได้เพื่อจัดการกับ Use Case เพิ่มเติมที่คุณอาจมี นอกจากนี้ ExoPlayer ยังแยกความแตกต่างของอุปกรณ์และระบบปฏิบัติการออกเพื่อให้โค้ดทำงานได้อย่างสอดคล้องกันทั่วทั้งระบบนิเวศ Android ExoPlayer ประกอบด้วย

หน้านี้จะแนะนำขั้นตอนสำคัญในการสร้างแอปการเล่น และดูรายละเอียดเพิ่มเติมได้ที่คู่มือฉบับเต็มเกี่ยวกับ Media3 ExoPlayer

เริ่มต้นใช้งาน

ในการเริ่มต้นใช้งาน ให้เพิ่มการพึ่งพาโมดูล ExoPlayer, UI และ Common ของ Jetpack Media3

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

คุณอาจต้องใช้โมดูลเพิ่มเติมจาก Media3 เช่น exoplayer-dash เพื่อเล่นสตรีมในรูปแบบ DASH ด้วย ทั้งนี้ขึ้นอยู่กับกรณีการใช้งาน

โปรดตรวจสอบว่าได้แทนที่ 1.5.0 ด้วยไลบรารีเวอร์ชันที่ต้องการ คุณสามารถดูบันทึกประจำรุ่นเพื่อดูเวอร์ชันล่าสุด

การสร้างโปรแกรมเล่นสื่อ

เมื่อใช้ Media3 คุณจะใช้การติดตั้งใช้งานPlayer อินเทอร์เฟซ ExoPlayer ที่รวมอยู่ด้วย หรือจะสร้างการติดตั้งใช้งานที่กําหนดเองก็ได้

การสร้าง ExoPlayer

วิธีที่ง่ายที่สุดในการสร้างอินสแตนซ์ ExoPlayer มีดังนี้

Kotlin

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

Java

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

คุณสร้างโปรแกรมเล่นสื่อได้ในonCreate()เมธอดวงจรชีวิตของ Activity, Fragment หรือ Service ที่ใช้งานอยู่

Builder มีตัวเลือกการปรับแต่งมากมายที่คุณอาจสนใจ เช่น

Media3 มีPlayerViewคอมโพเนนต์ UI ที่คุณรวมไว้ในไฟล์เลย์เอาต์ของแอปได้ คอมโพเนนต์นี้รวม PlayerControlView สำหรับการควบคุมการเล่น SubtitleView สำหรับการแสดงคำบรรยาย และ Surface สำหรับการแสดงผลวิดีโอ

กำลังเตรียมโปรแกรมเล่น

เพิ่มรายการสื่อลงในเพลย์ลิสต์เพื่อเล่นด้วยวิธีการต่างๆ เช่น setMediaItem() และ addMediaItem() จากนั้นเรียกใช้ prepare() เพื่อเริ่มโหลดสื่อและรับทรัพยากรที่จำเป็น

คุณไม่ควรทำตามขั้นตอนเหล่านี้ก่อนที่แอปจะทำงานอยู่เบื้องหน้า หากโปรแกรมเล่นอยู่ใน Activity หรือ Fragment หมายความว่าคุณกำลังเตรียมโปรแกรมเล่นในonStart() lifecycle method ใน API ระดับ 24 ขึ้นไป หรือ onResume() lifecycle method ใน API ระดับ 23 และต่ำกว่า สำหรับผู้เล่นที่อยู่ใน Service คุณสามารถเตรียมความพร้อมใน onCreate()

ควบคุมเพลเยอร์

หลังจากเตรียมเพลเยอร์แล้ว คุณจะควบคุมการเล่นได้โดยเรียกใช้เมธอดในเพลเยอร์ เช่น

  • play() และ pause() เพื่อเริ่มและหยุดเล่นชั่วคราว
  • seekTo() เพื่อกรอไปยังตำแหน่งภายในรายการสื่อปัจจุบัน
  • seekToNextMediaItem() และ seekToPreviousMediaItem() เพื่อไปยังส่วนต่างๆ ของเพลย์ลิสต์

คอมโพเนนต์ UI เช่น PlayerView หรือ PlayerControlView จะอัปเดตตามนั้นเมื่อเชื่อมโยงกับวิดีโอเพลเยอร์

ปล่อยผู้เล่น

การเล่นอาจต้องใช้ทรัพยากรที่มีอย่างจำกัด เช่น ตัวถอดรหัสวิดีโอ คุณจึงควรเรียกใช้ release() ในโปรแกรมเล่นเพื่อเพิ่มพื้นที่ว่างสำหรับทรัพยากรเมื่อไม่จำเป็นต้องใช้โปรแกรมเล่นอีกต่อไป

หากเพลเยอร์อยู่ใน Activity หรือ Fragment ให้ปล่อยเพลเยอร์ในวิธีวงจรชีวิตของ onStop() ใน API ระดับ 24 ขึ้นไป หรือวิธี onPause() ใน API ระดับ 23 และต่ำกว่า สำหรับผู้เล่นที่อยู่ใน Service คุณจะเผยแพร่ได้ใน onDestroy()

การจัดการการเล่นด้วยเซสชันสื่อ

ใน Android เซสชันสื่อเป็นวิธีที่เป็นมาตรฐานในการโต้ตอบกับโปรแกรมเล่นสื่อในขอบเขตของกระบวนการต่างๆ การเชื่อมต่อเซสชันสื่อกับโปรแกรมเล่นช่วยให้คุณโฆษณาการเล่นสื่อภายนอกและรับคำสั่งการเล่นจากแหล่งที่มาภายนอกได้ เช่น เพื่อผสานรวมกับการควบคุมสื่อของระบบในอุปกรณ์เคลื่อนที่และอุปกรณ์หน้าจอขนาดใหญ่

หากต้องการใช้เซสชันสื่อ ให้เพิ่มการพึ่งพาในโมดูลเซสชัน Media3 โดยทำดังนี้

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

สร้างเซสชันสื่อ

คุณสามารถสร้าง 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 หรือการติดตั้งใช้งานที่กําหนดเอง

มอบสิทธิ์ควบคุมให้กับไคลเอ็นต์รายอื่น

แอปไคลเอ็นต์สามารถใช้ตัวควบคุมสื่อเพื่อควบคุมการเล่นเซสชันสื่อ หากต้องการรับคําขอเหล่านี้ ให้ตั้งค่าออบเจ็กต์การเรียกกลับเมื่อสร้าง MediaSession

เมื่อตัวควบคุมกำลังจะเชื่อมต่อกับเซสชันสื่อ ระบบจะเรียกใช้เมธอด onConnect() คุณสามารถใช้ ControllerInfo ที่ระบุไว้เพื่อตัดสินใจว่าจะยอมรับหรือปฏิเสธคําขอ ดูตัวอย่างได้ในแอปเดโมเซสชัน Media3

เมื่อเชื่อมต่อแล้ว ตัวควบคุมจะส่งคำสั่งการเล่นไปยังเซสชันได้ จากนั้นเซสชันจะมอบสิทธิ์คำสั่งเหล่านั้นไปยังโปรแกรมเล่น เซสชันจะจัดการคำสั่งการเล่นและเพลย์ลิสต์ที่กําหนดไว้ในอินเทอร์เฟซ Player โดยอัตโนมัติ

วิธีการเรียกกลับอื่นๆ ช่วยให้คุณจัดการกับคำขอต่างๆ ได้ เช่น คำขอคำสั่งการเล่นที่กำหนดเองและการแก้ไขเพลย์ลิสต์ แคล็กแบ็กเหล่านี้จะมีออบเจ็กต์ 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();
    }
}

ในไฟล์ Manifest ให้คลาส 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;
}

การเชื่อมต่อกับ UI

เมื่อเซสชันสื่ออยู่ใน Service ที่แยกจาก Activity หรือ Fragment ที่แสดง UI ของโปรแกรมเล่นแล้ว คุณจะใช้ MediaController เพื่อลิงก์เซสชันเหล่านั้นเข้าด้วยกันได้ ในเมธอด onStart() ของ Activity หรือ Fragment ที่มี UI ให้สร้าง 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 จะมีวิธีการเพิ่มเติมที่ช่วยให้ไคลเอ็นต์เบราว์เซอร์ไปยังส่วนต่างๆ ของเนื้อหาที่บริการคลังของคุณนำเสนอได้

ประกาศ MediaLibraryService ในไฟล์ Manifest และขอสิทธิ์ FOREGROUND_SERVICE เพื่อเรียกใช้บริการที่ทำงานอยู่เบื้องหน้า เช่นเดียวกับ MediaSessionService

<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 เพิ่มเติมช่วยให้แอปไคลเอ็นต์ที่ใช้ MediaBrowserCompat API จดจํา Service ได้

MediaLibrarySession ช่วยให้คุณแสดงคลังเนื้อหาในรูปแบบโครงสร้างต้นไม้โดยมี MediaItem รูทเดียว MediaItem แต่ละรายการในต้นไม้จะมีโหนด MediaItem ย่อยได้เท่าใดก็ได้ คุณสามารถแสดงรูทหรือต้นไม้อื่นตามคำขอของแอปไคลเอ็นต์ได้ ตัวอย่างเช่น ต้นไม้ที่คุณแสดงผลต่อไคลเอ็นต์ที่ค้นหารายการสื่อที่แนะนำอาจมีเฉพาะโหนดรูท MediaItem และโหนดย่อย MediaItem ระดับเดียวเท่านั้น ขณะที่ต้นไม้ที่คุณแสดงผลต่อแอปไคลเอ็นต์อื่นอาจแสดงคลังเนื้อหาที่สมบูรณ์กว่า

การสร้าง MediaLibrarySession

MediaLibrarySession ขยาย MediaSession API เพื่อเพิ่ม API การเรียกดูเนื้อหา เมื่อเทียบกับ MediaSession callback แล้ว MediaLibrarySession callback จะมีวิธีการเพิ่มเติม เช่น

  • onGetLibraryRoot() สำหรับกรณีที่ลูกค้าขอรูท MediaItem ของต้นไม้เนื้อหา
  • onGetChildren() สำหรับกรณีที่ไคลเอ็นต์ขอรายการย่อยของ MediaItem ในโครงสร้างเนื้อหา
  • onGetSearchResult() สำหรับเมื่อไคลเอ็นต์ขอผลการค้นหาจากต้นไม้เนื้อหาสำหรับข้อความค้นหาหนึ่งๆ

เมธอดการเรียกกลับที่เกี่ยวข้องจะมีออบเจ็กต์ LibraryParams ที่มีสัญญาณเพิ่มเติมเกี่ยวกับประเภทของต้นไม้เนื้อหาที่แอปไคลเอ็นต์สนใจ