คุณจึงควรเล่นสื่อขณะที่แอปไม่ได้ทำงานอยู่เบื้องหน้า ตัวอย่างเช่น โดยทั่วไปโปรแกรมเล่นเพลงจะเล่นเพลงต่อไปเมื่อผู้ใช้ล็อกอุปกรณ์หรือกำลังใช้แอปอื่นอยู่ ไลบรารี Media3 มีชุดอินเทอร์เฟซที่ช่วยให้คุณรองรับการเล่นขณะล็อกหน้าจอหรือขณะใช้แอปอื่น
ใช้ MediaSessionService
หากต้องการเปิดใช้การเล่นขณะล็อกหน้าจอหรือขณะใช้แอปอื่น คุณควรใส่ Player
และ
MediaSession
ไว้ในบริการแยกต่างหาก
ซึ่งจะช่วยให้อุปกรณ์แสดงสื่อต่อไปได้แม้ว่าแอปของคุณจะไม่อยู่เบื้องหน้า
เมื่อโฮสต์โปรแกรมเล่นภายในบริการ คุณควรใช้ MediaSessionService
โดยสร้างคลาสที่ขยาย MediaSessionService
และสร้างเซสชันสื่อภายในคลาส
การใช้ MediaSessionService
จะช่วยให้ไคลเอ็นต์ภายนอก เช่น Google Assistant, การควบคุมสื่อของระบบ หรืออุปกรณ์เสริม เช่น Wear OS สามารถค้นพบบริการของคุณ เชื่อมต่อกับบริการ และควบคุมการเล่นได้โดยไม่ต้องเข้าถึงกิจกรรม UI ของแอปเลย ความจริงแล้ว แอปไคลเอ็นต์หลายแอปอาจเชื่อมต่อกับ MediaSessionService
เดียวกันได้ในเวลาเดียวกัน โดยแต่ละแอปจะมี MediaController
เป็นของตัวเอง
ใช้วงจรบริการ
คุณต้องใช้วิธีวงจร 3 วิธีสําหรับบริการ ได้แก่
onCreate()
จะเรียกใช้เมื่อตัวควบคุมตัวแรกกำลังเชื่อมต่อ และระบบจะสร้างอินสแตนซ์และเริ่มบริการ เพราะเป็นแพลตฟอร์มที่เหมาะสําหรับการสร้างPlayer
และMediaSession
onTaskRemoved(Intent)
จะเรียกใช้เมื่อผู้ใช้ปิดแอปจากงานล่าสุด หากการเล่นดำเนินอยู่ แอปสามารถเลือกที่จะให้บริการทำงานอยู่เบื้องหน้าต่อไปได้ หากหยุดเล่นชั่วคราว แสดงว่าบริการไม่ได้ทำงานอยู่เบื้องหน้าและจำเป็นต้องหยุดonDestroy()
จะเรียกใช้เมื่อมีการหยุดบริการ คุณต้องปล่อยทรัพยากรทั้งหมด รวมถึงผู้เล่นและเซสชัน
Kotlin
class PlaybackService : MediaSessionService() { private var mediaSession: MediaSession? = null // Create your player and media session in the onCreate lifecycle event override fun onCreate() { super.onCreate() val player = ExoPlayer.Builder(this).build() mediaSession = MediaSession.Builder(this, player).build() } // The user dismissed the app from the recent tasks override fun onTaskRemoved(rootIntent: Intent?) { val player = mediaSession?.player!! if (!player.playWhenReady || player.mediaItemCount == 0 || player.playbackState == Player.STATE_ENDED) { // Stop the service if not playing, continue playing in the background // otherwise. stopSelf() } } // 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; // Create your Player and MediaSession in the onCreate lifecycle event @Override public void onCreate() { super.onCreate(); ExoPlayer player = new ExoPlayer.Builder(this).build(); mediaSession = new MediaSession.Builder(this, player).build(); } // The user dismissed the app from the recent tasks @Override public void onTaskRemoved(@Nullable Intent rootIntent) { Player player = mediaSession.getPlayer(); if (!player.getPlayWhenReady() || player.getMediaItemCount() == 0 || player.getPlaybackState() == Player.STATE_ENDED) { // Stop the service if not playing, continue playing in the background // otherwise. stopSelf(); } } // Remember to release the player and media session in onDestroy @Override public void onDestroy() { mediaSession.getPlayer().release(); mediaSession.release(); mediaSession = null; super.onDestroy(); } }
นอกเหนือจากการเล่นอย่างต่อเนื่องในเบื้องหลังแล้ว แอปสามารถหยุดบริการได้ทุกเมื่อที่ผู้ใช้ปิดแอป ดังนี้
Kotlin
override fun onTaskRemoved(rootIntent: Intent?) { val player = mediaSession.player if (player.playWhenReady) { // Make sure the service is not in foreground. player.pause() } stopSelf() }
Java
@Override public void onTaskRemoved(@Nullable Intent rootIntent) { Player player = mediaSession.getPlayer(); if (player.getPlayWhenReady()) { // Make sure the service is not in foreground. player.pause(); } stopSelf(); }
ให้สิทธิ์เข้าถึงเซสชันสื่อ
ลบล้างเมธอด onGetSession()
เพื่อให้ไคลเอ็นต์รายอื่นเข้าถึงเซสชันสื่อที่สร้างขึ้นเมื่อสร้างบริการ
Kotlin
class PlaybackService : MediaSessionService() { private var mediaSession: MediaSession? = null // [...] lifecycle methods omitted override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? = mediaSession }
Java
public class PlaybackService extends MediaSessionService { private MediaSession mediaSession = null; // [...] lifecycle methods omitted @Override public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) { return mediaSession; } }
ประกาศบริการในไฟล์ Manifest
แอปต้องมีสิทธิ์เพื่อเรียกใช้บริการที่ทำงานอยู่เบื้องหน้า เพิ่มสิทธิ์ FOREGROUND_SERVICE
ลงในไฟล์ Manifest และหากกำหนดเป้าหมายเป็น API 34 ขึ้นไปด้วย FOREGROUND_SERVICE_MEDIA_PLAYBACK
ให้ทำดังนี้
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
นอกจากนี้ คุณต้องประกาศคลาส Service
ในไฟล์ Manifest ด้วยตัวกรอง Intent ของ MediaSessionService
<service
android:name=".PlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
</intent-filter>
</service>
คุณต้องกำหนด foregroundServiceType
ที่รวม mediaPlayback
เมื่อแอปทำงานในอุปกรณ์ที่ใช้ Android 10 (API ระดับ 29) ขึ้นไป
ควบคุมการเล่นโดยใช้ MediaController
ในกิจกรรมหรือส่วนที่ประกอบด้วย UI ของโปรแกรมเล่น คุณสามารถลิงก์ระหว่าง UI กับเซสชันสื่อได้โดยใช้ MediaController
UI ของคุณใช้ตัวควบคุมสื่อเพื่อส่งคําสั่งจาก UI ไปยังเพลเยอร์ภายในเซสชัน โปรดดูรายละเอียดการสร้างและใช้ MediaController
ในคำแนะนำการสร้าง MediaController
จัดการคําสั่ง UI
MediaSession
รับคำสั่งจากตัวควบคุมผ่าน MediaSession.Callback
การกำหนดค่าเริ่มต้น MediaSession
จะสร้างการใช้งาน MediaSession.Callback
เริ่มต้นที่จะจัดการคําสั่งทั้งหมดที่ MediaController
ส่งไปยังโปรแกรมเล่นโดยอัตโนมัติ
การแจ้งเตือน
MediaSessionService
จะสร้าง MediaNotification
ให้คุณโดยอัตโนมัติ ซึ่งควรใช้งานได้ในกรณีส่วนใหญ่ โดยค่าเริ่มต้น การแจ้งเตือนที่เผยแพร่จะเป็นการแจ้งเตือน MediaStyle
ที่อัปเดตข้อมูลล่าสุดจากเซสชันสื่อและแสดงตัวควบคุมการเล่น MediaNotification
จะรับรู้เซสชันของคุณและสามารถใช้เพื่อควบคุมการเล่นสำหรับแอปอื่นๆ ที่เชื่อมต่อกับเซสชันเดียวกัน
ตัวอย่างเช่น แอปสตรีมมิงเพลงที่ใช้ MediaSessionService
จะสร้าง MediaNotification
ที่แสดงชื่อ ศิลปิน และปกอัลบั้มของรายการสื่อปัจจุบันที่เล่นควบคู่กับส่วนควบคุมการเล่นตามการกำหนดค่า MediaSession
ของคุณ
คุณสามารถระบุข้อมูลเมตาที่จำเป็นในสื่อหรือประกาศเป็นส่วนหนึ่งของรายการสื่อได้ ดังตัวอย่างข้อมูลต่อไปนี้
Kotlin
val mediaItem = MediaItem.Builder() .setMediaId("media-1") .setUri(mediaUri) .setMediaMetadata( MediaMetadata.Builder() .setArtist("David Bowie") .setTitle("Heroes") .setArtworkUri(artworkUri) .build() ) .build() mediaController.setMediaItem(mediaItem) mediaController.prepare() mediaController.play()
Java
MediaItem mediaItem = new MediaItem.Builder() .setMediaId("media-1") .setUri(mediaUri) .setMediaMetadata( new MediaMetadata.Builder() .setArtist("David Bowie") .setTitle("Heroes") .setArtworkUri(artworkUri) .build()) .build(); mediaController.setMediaItem(mediaItem); mediaController.prepare(); mediaController.play();
แอปสามารถปรับแต่งปุ่มคำสั่งของการควบคุมสื่อของ Android อ่านเพิ่มเติมเกี่ยวกับการปรับแต่งการควบคุม Android Media
การปรับแต่งการแจ้งเตือน
หากต้องการปรับแต่งการแจ้งเตือน ให้สร้าง MediaNotification.Provider
ที่มี DefaultMediaNotificationProvider.Builder
หรือสร้างการติดตั้งใช้งานที่กำหนดเองสำหรับอินเทอร์เฟซผู้ให้บริการ เพิ่มผู้ให้บริการลงใน MediaSessionService
ด้วย setMediaNotificationProvider
การกลับมาเล่นต่อ
ปุ่มสื่อคือปุ่มฮาร์ดแวร์ที่มีอยู่ในอุปกรณ์ Android และอุปกรณ์ต่อพ่วงอื่นๆ เช่น ปุ่มเล่นหรือหยุดชั่วคราวในชุดหูฟังบลูทูธ Media3 จัดการอินพุตปุ่มสื่อให้คุณเมื่อบริการทำงานอยู่
ประกาศตัวรับปุ่มสื่อ Media3
Media3 มี API ที่ช่วยให้ผู้ใช้กลับมาเล่นต่อได้
หลังจากแอปสิ้นสุดลงหรือแม้กระทั่งหลังจากอุปกรณ์รีสตาร์ท โดยค่าเริ่มต้น ระบบจะปิดการกลับมาเล่นต่อ ซึ่งหมายความว่าผู้ใช้จะเล่นต่อไม่ได้เมื่อบริการไม่ทำงาน หากต้องการเลือกใช้ ให้เริ่มด้วยการประกาศ MediaButtonReceiver
ในไฟล์ Manifest
<receiver android:name="androidx.media3.session.MediaButtonReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
ใช้การเรียกกลับเพื่อเล่นต่อ
เมื่ออุปกรณ์บลูทูธหรือฟีเจอร์การกลับมาเล่นต่อของ UI ระบบ Android ขอให้เล่นต่อ ระบบจะเรียกใช้เมธอดการเรียกกลับ onPlaybackResumption()
Kotlin
override fun onPlaybackResumption( mediaSession: MediaSession, controller: ControllerInfo ): ListenableFuture<MediaItemsWithStartPosition> { val settable = SettableFuture.create<MediaItemsWithStartPosition>() scope.launch { // Your app is responsible for storing the playlist and the start position // to use here val resumptionPlaylist = restorePlaylist() settable.set(resumptionPlaylist) } return settable }
Java
@Override public ListenableFuture<MediaItemsWithStartPosition> onPlaybackResumption( MediaSession mediaSession, ControllerInfo controller ) { SettableFuture<MediaItemsWithStartPosition> settableFuture = SettableFuture.create(); settableFuture.addListener(() -> { // Your app is responsible for storing the playlist and the start position // to use here MediaItemsWithStartPosition resumptionPlaylist = restorePlaylist(); settableFuture.set(resumptionPlaylist); }, MoreExecutors.directExecutor()); return settableFuture; }
หากคุณจัดเก็บพารามิเตอร์อื่นๆ เช่น ความเร็วในการเล่น โหมดเล่นซ้ำ หรือโหมดสุ่ม onPlaybackResumption()
จะเป็นตําแหน่งที่ดีในการกําหนดค่าโปรแกรมเล่นด้วยพารามิเตอร์เหล่านี้ก่อนที่ Media3 จะเตรียมโปรแกรมเล่นและเริ่มเล่นเมื่อการเรียกกลับเสร็จสมบูรณ์
การกำหนดค่าตัวควบคุมขั้นสูงและการทำงานร่วมกันแบบย้อนหลัง
สถานการณ์ทั่วไปคือการใช้ MediaController
ใน UI ของแอปเพื่อควบคุมการเล่นและการแสดงเพลย์ลิสต์ ในขณะเดียวกัน เซสชันจะแสดงต่อไคลเอ็นต์ภายนอก เช่น ตัวควบคุมสื่อและ Assistant ของ Android บนอุปกรณ์เคลื่อนที่หรือทีวี, Wear OS สำหรับนาฬิกา และ Android Auto ในรถยนต์ แอปเดโมเซสชันของ Media3 เป็นตัวอย่างของแอปที่ใช้สถานการณ์ดังกล่าว
ไคลเอ็นต์ภายนอกเหล่านี้อาจใช้ API เช่น MediaControllerCompat
ของไลบรารี AndroidX รุ่นเดิมหรือ android.media.session.MediaController
ของเฟรมเวิร์ก Android Media3 เข้ากันได้แบบย้อนหลังกับไลบรารีเดิมอย่างเต็มรูปแบบและสามารถทำงานร่วมกันกับ API ของเฟรมเวิร์ก Android
ใช้ตัวควบคุมการแจ้งเตือนสื่อ
คุณต้องเข้าใจว่าตัวควบคุมเดิมหรือตัวควบคุมเฟรมเวิร์กเหล่านี้จะอ่านค่าเดียวกันจากเฟรมเวิร์ก PlaybackState.getActions()
และ PlaybackState.getCustomActions()
หากต้องการกำหนดการดำเนินการและการดําเนินการที่กำหนดเองของเซสชันเฟรมเวิร์ก แอปสามารถใช้ตัวควบคุมการแจ้งเตือนสื่อและตั้งค่าคำสั่งและเลย์เอาต์ที่กำหนดเองได้ บริการจะเชื่อมต่อตัวควบคุมการแจ้งเตือนสื่อกับเซสชันของคุณ และเซสชันจะใช้ ConnectionResult
ที่แสดงผลโดย onConnect()
ของ Callback เพื่อกำหนดค่าการดำเนินการและการดำเนินการที่กำหนดเองของเซสชันเฟรมเวิร์ก
ในกรณีสําหรับอุปกรณ์เคลื่อนที่เท่านั้น แอปสามารถใช้งาน MediaSession.Callback.onConnect()
เพื่อตั้งค่าคําสั่งที่ใช้ได้และเลย์เอาต์ที่กําหนดเองสําหรับเซสชันเฟรมเวิร์กโดยเฉพาะ ดังนี้
Kotlin
override fun onConnect( session: MediaSession, controller: MediaSession.ControllerInfo ): ConnectionResult { if (session.isMediaNotificationController(controller)) { val sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon() .add(customCommandSeekBackward) .add(customCommandSeekForward) .build() val playerCommands = ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon() .remove(COMMAND_SEEK_TO_PREVIOUS) .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM) .remove(COMMAND_SEEK_TO_NEXT) .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM) .build() // Custom layout and available commands to configure the legacy/framework session. return AcceptedResultBuilder(session) .setCustomLayout( ImmutableList.of( createSeekBackwardButton(customCommandSeekBackward), createSeekForwardButton(customCommandSeekForward)) ) .setAvailablePlayerCommands(playerCommands) .setAvailableSessionCommands(sessionCommands) .build() } // Default commands with default custom layout for all other controllers. return AcceptedResultBuilder(session).build() }
Java
@Override public ConnectionResult onConnect( MediaSession session, MediaSession.ControllerInfo controller) { if (session.isMediaNotificationController(controller)) { SessionCommands sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS .buildUpon() .add(customCommandSeekBackward) .add(customCommandSeekForward) .build(); Player.Commands playerCommands = ConnectionResult.DEFAULT_PLAYER_COMMANDS .buildUpon() .remove(COMMAND_SEEK_TO_PREVIOUS) .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM) .remove(COMMAND_SEEK_TO_NEXT) .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM) .build(); // Custom layout and available commands to configure the legacy/framework session. return new AcceptedResultBuilder(session) .setCustomLayout( ImmutableList.of( createSeekBackwardButton(customCommandSeekBackward), createSeekForwardButton(customCommandSeekForward))) .setAvailablePlayerCommands(playerCommands) .setAvailableSessionCommands(sessionCommands) .build(); } // Default commands without default custom layout for all other controllers. return new AcceptedResultBuilder(session).build(); }
ให้สิทธิ์ Android Auto ส่งคําสั่งที่กําหนดเอง
เมื่อใช้ MediaLibraryService
และเพื่อรองรับ Android Auto ด้วยแอปบนอุปกรณ์เคลื่อนที่ ตัวควบคุม Android Auto ต้องใช้คำสั่งที่เหมาะสม มิเช่นนั้น Media3 จะปฏิเสธคำสั่งที่กำหนดเองขาเข้าจากตัวควบคุมนั้น
Kotlin
override fun onConnect( session: MediaSession, controller: MediaSession.ControllerInfo ): ConnectionResult { val sessionCommands = ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS.buildUpon() .add(customCommandSeekBackward) .add(customCommandSeekForward) .build() if (session.isMediaNotificationController(controller)) { // [...] See above. } else if (session.isAutoCompanionController(controller)) { // Available session commands to accept incoming custom commands from Auto. return AcceptedResultBuilder(session) .setAvailableSessionCommands(sessionCommands) .build() } // Default commands with default custom layout for all other controllers. return AcceptedResultBuilder(session).build() }
Java
@Override public ConnectionResult onConnect( MediaSession session, MediaSession.ControllerInfo controller) { SessionCommands sessionCommands = ConnectionResult.DEFAULT_SESSION_COMMANDS .buildUpon() .add(customCommandSeekBackward) .add(customCommandSeekForward) .build(); if (session.isMediaNotificationController(controller)) { // [...] See above. } else if (session.isAutoCompanionController(controller)) { // Available commands to accept incoming custom commands from Auto. return new AcceptedResultBuilder(session) .setAvailableSessionCommands(sessionCommands) .build(); } // Default commands without default custom layout for all other controllers. return new AcceptedResultBuilder(session).build(); }
แอปสาธิตเซสชันมีโมดูลยานยนต์ซึ่งแสดงการรองรับ Automotive OS ที่ต้องแยก APK ต่างหาก