Deep Dive: MediaPlayer En İyi Uygulamaları

Unsplash'ta Marcela Laskoski tarafından fotoğraf

MediaPlayer'ın kullanımı aldatıcı derecede basit gibi görünüyor, ancak karmaşıklık yüzeyin hemen altında yaşıyor. Örneğin, böyle bir şey yazmak cazip gelebilir:

MediaPlayer.create (içerik, R.raw.cowbell) .start ()

Bu, ilk ve muhtemelen ikinci, üçüncü veya daha fazla kez iyi çalışır. Ancak, her yeni MediaPlayer, bellek ve codec bileşenleri gibi sistem kaynaklarını tüketir. Bu, uygulamanızın ve muhtemelen tüm cihazın performansını düşürebilir.

Neyse ki, MediaPlayer'ı birkaç basit kurala uyarak hem basit hem de güvenli bir şekilde kullanmak mümkündür.

Basit Dava

En temel örnek, sadece oynamak istediğimiz ses dosyası, belki de ham bir kaynak olduğumuzdur. Bu durumda, her ses çalmamız gerektiğinde tekrar kullanacağımız bir oyuncu oluşturacağız. Oyuncu şöyle bir şeyle yaratılmalıdır:

private val mediaPlayer = MediaPlayer ().
    setOnPreparedListener {start ()}
    setOnCompletionListener {reset ()}
}

Oyuncu iki dinleyici ile yaratılmıştır:

  • Oynatıcı hazırlandıktan sonra oynatmayı otomatik olarak başlatacak OnPreparedListener.
  • Oynatma bittiğinde kaynakları otomatik olarak temizleyen OnCompletionListener.

Oluşturulan oynatıcı ile bir sonraki adım, kaynak kimliğini alan ve oynatmak için bu MediaPlayer'ı kullanan bir işlev yapmaktır:

eğlenceli playSound (@RawRes rawResId: Int) geçersiz kıl {
    val assetFileDescriptor = context.resources.openRawResourceFd (rawResId)?: dönüş
    mediaPlayer.run {
        ) (Reset
        setDataSource (assetFileDescriptor.fileDescriptor, assetFileDescriptor.startOffset, assetFileDescriptor.declaredLength)
        prepareAsync ()
    }
}

Bu kısa yöntemde biraz oluyor:

  • Kaynak kimliği, AssetFileDescriptor'a dönüştürülmelidir, çünkü bu, MediaPlayer'ın ham kaynakları oynatmak için kullandığı şeydir. Boş denetim, kaynağın var olmasını sağlar.
  • Reset () işlevinin çağrılması oynatıcının Başlatılmış durumda olmasını sağlar. Bu, oyuncunun hangi durumda olduğu önemli değil.
  • Müzikçalar için veri kaynağını ayarlayın.
  • prepareAsync, oynatıcıyı anında oynamaya hazırlar ve kullanıcı arayüzünü duyarlı tutar. Bu çalışır çünkü eklenmiş OnPreparedListener kaynak hazırlandıktan sonra oynamaya başlar.

Oynatıcımızdaki release () kodunu aramadığımızı veya null değerine ayarlamadığımızı not etmek önemlidir. Tekrar kullanmak istiyoruz! Bunun yerine, onu kullanan belleği ve kodekleri serbest bırakan reset () işlevini çağırırız.

Bir ses çalmak, arama yapmak kadar kolaydır:

PlaySound (R.raw.cowbell)

Basit!

Daha fazla Cowbells

Bir seferde bir ses çalmak kolaydır, ancak ilki çalarken başka bir sese başlamak istiyorsanız ne olur? PlaySound () öğesine bunun gibi birçok kez çağrı yapılması işe yaramaz:

PlaySound (R.raw.big_cowbell)
PlaySound (R.raw.small_cowbell)

Bu durumda, R.raw.big_cowbell hazırlanmaya başlar, ancak ikinci çağrı bir şey olmadan önce oynatıcıyı sıfırlar, bu yüzden sadece siz sadece R.raw.small_cowbell'i duyarsınız.

Ve aynı anda birden fazla sesi birlikte çalmak istiyorsak ne olur? Her biri için bir MediaPlayer oluşturmamız gerekecek. Bunu yapmanın en basit yolu, aktif oyuncuların bir listesine sahip olmaktır. Belki böyle bir şey:

sınıf MediaPlayers (içerik: Bağlam) {
    private val bağlamı: İçerik = içerik.applicationContext
    private val playersInUse = mutableListOf  ()

    private fun buildPlayer () = MediaPlayer ().
        setOnPreparedListener {start ()}
        setOnCompletionListener {
            it.release ()
            playersInUse - = bu
        }
    }

    eğlenceli playSound (@RawRes rawResId: Int) geçersiz kıl {
        val assetFileDescriptor = context.resources.openRawResourceFd (rawResId)?: dönüş
        val mediaPlayer = buildPlayer ()

        mediaPlayer.run {
            playersInUse + = bu
            setDataSource (assetFileDescriptor.fileDescriptor, assetFileDescriptor.startOffset,
                    assetFileDescriptor.declaredLength)
            prepareAsync ()
        }
    }
}

Artık her sesin kendi oynatıcısı olduğu için, hem R.raw.big_cowbell hem de R.raw.small_cowbell'i birlikte çalmak mümkün! Mükemmel!

… Eh, neredeyse mükemmel. Kodumuzda bir kerede çalabilecek ses sayısını sınırlayan hiçbir şey yoktur ve MediaPlayer'ın çalışacak hafızaya ve codec bileşenine sahip olması gerekir. Bittiğinde, MediaPlayer sessizce başarısız oluyor, sadece logcat'taki “E / MediaPlayer: Error (1, -19)” yazısını gösteriyor.

MediaPlayerPool'a girin

Aynı anda birden fazla ses çalmayı desteklemek istiyoruz, ancak bellek veya kod çözücüler tükenmek istemiyoruz. Bunları yönetmenin en iyi yolu, bir oyuncu havuzuna sahip olmak ve sonra bir ses çalmak istediğimizde kullanmak için birini seçmek. Bu şekilde olması için kodumuzu güncelleyebiliriz:

MediaPlayerPool sınıfı (içerik: Bağlam, maxStreams: Int) {
    private val bağlamı: İçerik = içerik.applicationContext

    private val mediaPlayerPool = mutableListOf  () .also {
        (i. 0.maxStreams içindeki) it + = buildPlayer ()
    }
    private val playersInUse = mutableListOf  ()

    private fun buildPlayer () = MediaPlayer ().
        setOnPreparedListener {start ()}
        setOnCompletionListener {recyclePlayer (it)}
    }

    / **
     * Varsa, bir [MediaPlayer] döndürür,
     * aksi takdirde boş.
     * /
    özel eğlence requestPlayer (): MediaPlayer? {
        if if (! mediaPlayerPool.isEmpty ()) {
            mediaPlayerPool.removeAt (0) .also {
                playersInUse + = bu
            }
        } başka null
    }

    özel eğlence geri dönüşümPlayer (mediaPlayer: MediaPlayer) {
        mediaPlayer.reset ()
        playersInUse - = mediaPlayer
        mediaPlayerPool + = mediaPlayer
    }

    eğlenceli playSound (@RawRes rawResId: Int) {
        val assetFileDescriptor = context.resources.openRawResourceFd (rawResId)?: dönüş
        val mediaPlayer = requestPlayer ()?: dönüş

        mediaPlayer.run {
            setDataSource (assetFileDescriptor.fileDescriptor, assetFileDescriptor.startOffset,
                    assetFileDescriptor.declaredLength)
            prepareAsync ()
        }
    }
}

Artık aynı anda birden fazla ses çalabiliyor ve çok fazla bellek veya çok fazla kodek kullanmaktan kaçınmak için maksimum eşzamanlı oynatıcı sayısını kontrol edebiliyoruz. Ayrıca, örnekleri geri dönüştürdüğümüz için, çöp toplayıcı, oynatmayı bitiren tüm eski örnekleri temizlemek için koşmak zorunda kalmayacak.

Bu yaklaşımın birkaç dezavantajı vardır:

  • MaxStreams sesleri çalındıktan sonra, playSound'a yapılan diğer çağrılar bir oyuncu serbest bırakılıncaya kadar dikkate alınmaz. Bu sorunu, yeni bir ses çalmak için zaten kullanımda olan bir oynatıcıyı “çalarak” çözebilirsiniz.
  • PlaySound'u çağırmakla aslında ses çalmak arasında önemli bir gecikme olabilir. MediaPlayer yeniden kullanılsa bile, temelde C ++ yerel nesnesini JNI üzerinden kontrol eden ince bir sargıdır. Yerel oyuncu, MediaPlayer.reset () öğesini her aradığınızda yok edilir ve MediaPlayer hazırlandığı zaman yeniden oluşturulmalıdır.

Oyuncuları tekrar kullanabilme özelliğini korurken gecikmeyi arttırmak daha zordur. Neyse ki, düşük gecikmenin gerekli olduğu bazı ses türleri ve uygulamalar için bir dahaki sefere bakacağımız başka bir seçenek daha var: SoundPool.