เล่นสื่อขณะล็อกหน้าจอหรือขณะใช้แอปอื่น

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

โดยคุณฝัง MediaPlayer ในบริการ MediaBrowserServiceCompat และทําให้โต้ตอบกับ MediaBrowserCompat ในกิจกรรมอื่น

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

หน้านี้จะอธิบายวิธีการพิเศษในการจัดการ MediaPlayer เมื่อคุณติดตั้งใช้งานภายในบริการ

ทำงานแบบไม่พร้อมกัน

เช่นเดียวกับ Activity งานทั้งหมดใน Service จะทําในชุดข้อความเดียวโดยค่าเริ่มต้น อันที่จริงแล้ว เมื่อคุณเรียกใช้กิจกรรมและบริการจากแอปพลิเคชันเดียวกัน กิจกรรมและบริการเหล่านั้นจะใช้เธรดเดียวกัน ("เธรดหลัก") โดยค่าเริ่มต้น

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

เช่น เมื่อใช้ MediaPlayer จากชุดข้อความหลัก คุณควรทำดังนี้

  • เรียก prepareAsync() แทน prepare() และ
  • ใช้ MediaPlayer.OnPreparedListener เพื่อรับการแจ้งเตือนเมื่อการเตรียมพร้อมเสร็จสมบูรณ์และคุณเริ่มเล่นได้

เช่น

Kotlin

private const val ACTION_PLAY: String = "com.example.action.PLAY"

class MyService: Service(), MediaPlayer.OnPreparedListener {

    private var mMediaPlayer: MediaPlayer? = null

    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        ...
        val action: String = intent.action
        when(action) {
            ACTION_PLAY -> {
                mMediaPlayer = ... // initialize it here
                mMediaPlayer?.apply {
                    setOnPreparedListener(this@MyService)
                    prepareAsync() // prepare async to not block main thread
                }

            }
        }
        ...
    }

    /** Called when MediaPlayer is ready */
    override fun onPrepared(mediaPlayer: MediaPlayer) {
        mediaPlayer.start()
    }
}

Java

public class MyService extends Service implements MediaPlayer.OnPreparedListener {
    private static final String ACTION_PLAY = "com.example.action.PLAY";
    MediaPlayer mediaPlayer = null;

    public int onStartCommand(Intent intent, int flags, int startId) {
        ...
        if (intent.getAction().equals(ACTION_PLAY)) {
            mediaPlayer = ... // initialize it here
            mediaPlayer.setOnPreparedListener(this);
            mediaPlayer.prepareAsync(); // prepare async to not block main thread
        }
    }

    /** Called when MediaPlayer is ready */
    public void onPrepared(MediaPlayer player) {
        player.start();
    }
}

จัดการข้อผิดพลาดแบบไม่พร้อมกัน

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

Kotlin

class MyService : Service(), MediaPlayer.OnErrorListener {

    private var mediaPlayer: MediaPlayer? = null

    fun initMediaPlayer() {
        // ...initialize the MediaPlayer here...
        mediaPlayer?.setOnErrorListener(this)
    }

    override fun onError(mp: MediaPlayer, what: Int, extra: Int): Boolean {
        // ... react appropriately ...
        // The MediaPlayer has moved to the Error state, must be reset!
    }
}

Java

public class MyService extends Service implements MediaPlayer.OnErrorListener {
    MediaPlayer mediaPlayer;

    public void initMediaPlayer() {
        // ...initialize the MediaPlayer here...
        mediaPlayer.setOnErrorListener(this);
    }

    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        // ... react appropriately ...
        // The MediaPlayer has moved to the Error state, must be reset!
    }
}

เมื่อเกิดข้อผิดพลาด MediaPlayer จะเปลี่ยนเป็นสถานะข้อผิดพลาด คุณต้องรีเซ็ตก่อนจึงจะใช้ได้อีกครั้ง โปรดดูรายละเอียดในแผนภาพสถานะแบบเต็มสำหรับคลาส MediaPlayer

ใช้ Wake Lock

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

การล็อกให้ตื่นคือสัญญาณที่บอกระบบว่าแอปพลิเคชันของคุณกำลังใช้ฟีเจอร์ที่ควรพร้อมใช้งานแม้ในขณะที่โทรศัพท์ไม่ได้ทำงาน

เรียกใช้เมธอด setWakeMode() เมื่อเริ่มต้นMediaPlayerเพื่อให้ CPU ทำงานต่อไปขณะที่ MediaPlayer เล่นอยู่ MediaPlayer จะยึดล็อกที่ระบุไว้ขณะเล่นและปล่อยล็อกเมื่อหยุดชั่วคราวหรือหยุดเล่น

Kotlin

mediaPlayer = MediaPlayer().apply {
    // ... other initialization here ...
    setWakeMode(applicationContext, PowerManager.PARTIAL_WAKE_LOCK)
}

Java

mediaPlayer = new MediaPlayer();
// ... other initialization here ...
mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);

อย่างไรก็ตาม Wake Lock ที่ได้ในตัวอย่างนี้จะตรวจสอบเฉพาะว่า CPU ยังคงทำงานอยู่ หากคุณสตรีมสื่อผ่านเครือข่ายและใช้ Wi-Fi คุณอาจต้องถือครอง WifiLock ด้วย ซึ่งจะต้องรับและปล่อยด้วยตนเอง ดังนั้น เมื่อเริ่มเตรียม MediaPlayer ด้วย URL ระยะไกล คุณควรสร้างและรับล็อก Wi-Fi

เช่น

Kotlin

val wifiManager = getSystemService(Context.WIFI_SERVICE) as WifiManager
val wifiLock: WifiManager.WifiLock =
    wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock")

wifiLock.acquire()

Java

WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
    .createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");

wifiLock.acquire();

เมื่อหยุดสื่อชั่วคราวหรือหยุดเล่น หรือเมื่อคุณไม่จำเป็นต้องใช้เครือข่ายอีกต่อไป คุณควรปลดล็อกโดยทำดังนี้

Kotlin

wifiLock.release()

Java

wifiLock.release();

ดำเนินการล้างข้อมูล

ดังที่ได้กล่าวไว้ก่อนหน้านี้ ออบเจ็กต์ MediaPlayer อาจใช้ทรัพยากรของระบบเป็นจำนวนมาก คุณจึงควรเก็บไว้เฉพาะเท่าที่จําเป็นเท่านั้น และเรียกใช้ release() เมื่อเสร็จสิ้น คุณควรเรียกใช้เมธอดล้างข้อมูลนี้อย่างชัดเจนแทนที่จะใช้การเก็บขยะของระบบ เนื่องจากอาจใช้เวลาสักครู่ก่อนที่เครื่องมือเก็บขยะจะเรียกใช้MediaPlayer อีกครั้ง เนื่องจากเครื่องมือนี้คำนึงถึงความต้องการหน่วยความจำเท่านั้น และไม่คำนึงถึงทรัพยากรอื่นๆ ที่เกี่ยวข้องกับสื่อ ดังนั้นในกรณีที่คุณใช้บริการ คุณควรลบล้างเมธอด onDestroy() เสมอเพื่อให้แน่ใจว่าคุณกำลังเผยแพร่ MediaPlayer

Kotlin

class MyService : Service() {

    private var mediaPlayer: MediaPlayer? = null
    // ...

    override fun onDestroy() {
        super.onDestroy()
        mediaPlayer?.release()
    }
}

Java

public class MyService extends Service {
   MediaPlayer mediaPlayer;
   // ...

   @Override
   public void onDestroy() {
       super.onDestroy();
       if (mediaPlayer != null) mediaPlayer.release();
   }
}

คุณควรมองหาโอกาสอื่นๆ ในการเผยแพร่MediaPlayer เสมอ นอกเหนือจากการเผยแพร่เมื่อมีการปิด ตัวอย่างเช่น หากคาดว่าจะเล่นสื่อไม่ได้เป็นเวลานาน (เช่น หลังจากเสียโฟกัสเสียง) คุณควรปล่อย MediaPlayer ที่มีอยู่และสร้างใหม่ในภายหลัง ในทางกลับกัน หากคุณคาดว่าจะหยุดเล่นเพียงระยะเวลาสั้นๆ ก็ควรเก็บMediaPlayer ไว้เพื่อหลีกเลี่ยงการสร้างและเตรียมMediaPlayer อีกครั้ง

ดูข้อมูลเพิ่มเติม

Jetpack Media3 เป็นโซลูชันที่แนะนำสำหรับการเล่นสื่อในแอป อ่านข้อมูลเพิ่มเติมเกี่ยวกับโซลูชันนี้

หน้าเหล่านี้ครอบคลุมหัวข้อเกี่ยวกับการบันทึก การจัดเก็บ และการเล่นเสียงและวิดีโอ