Deep Dive: Najboljše prakse MediaPlayerja

Foto Marcela Laskoski na Unsplash

Zdi se, da je MediaPlayer zavajajoče preprost, vendar kompleksnost živi tik pod površjem. Na primer, napisati nekaj takega je lahko mamljivo:

MediaPlayer.create (kontekst, R.raw.cowbell) .start ()

To dobro deluje prvi in ​​verjetno drugi, tretji ali celo večkrat. Vendar vsak nov MediaPlayer porabi sistemske vire, kot sta pomnilnik in kodeki. To lahko poslabša uspešnost vaše aplikacije in morda celotne naprave.

Na srečo je MediaPlayer mogoče uporabljati na preprost in varen način z upoštevanjem nekaj preprostih pravil.

Preprost primer

Najosnovnejši primer je, da imamo zvočno datoteko, morda surov vir, ki jo samo želimo predvajati. V tem primeru bomo ustvarili enega igralca, da ga bomo ponovno uporabili vsakič, ko bomo morali predvajati zvok. Predvajalnik bi moral biti ustvarjen s takole:

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

Predvajalnik je ustvarjen z dvema poslušalcema:

  • OnPreparedListener, ki se bo samodejno začel predvajanje po pripravi predvajalnika.
  • OnCompletionListener, ki samodejno očisti vire, ko se predvajanje konča.

Z ustvarjenim predvajalnikom je naslednji korak izdelava funkcije, ki sprejme ID vira in ga za predvajanje uporablja MediaPlayer:

preglasi zabavno playSound (@RawRes rawResId: Int) {
    val sredstvoFileDescriptor = context.resources.openRawResourceFd (rawResId)?: vrnitev
    mediaPlayer.run {
        ponastaviti()
        setDataSource (sredstvoFileDescriptor.fileDescriptor, sredstvoFileDescriptor.startOffset, sredstvoFileDescriptor.declaredLength)
        pripraviAsync ()
    }
}

Pri tej kratki metodi se dogaja kar nekaj:

  • ID vira je treba pretvoriti v AssetFileDescriptor, ker to MediaPlayer uporablja za predvajanje surovih virov. Preverjanje nule zagotavlja, da vir obstaja.
  • Ponastavitev klica () zagotavlja, da je igralec v začetnem stanju. To deluje ne glede na to, v kakšnem stanju je igralec.
  • Nastavite vir podatkov za predvajalnik.
  • PrepaAsync pripravi igralca za igranje in se takoj vrne, pri čemer uporabniški vmesnik ostane odziven. To deluje, ker se priloženi OnPreparedListener začne predvajati po pripravi vira.

Pomembno je upoštevati, da na predvajalniku ne kličemo sprostitve () ali da ga nastavimo na nič. Želimo ga ponovno uporabiti! Zato namesto tega pokličemo reset (), ki sprosti pomnilnik in kodeke, ki jih je uporabljal.

Predvajanje zvoka je tako preprosto kot klicanje:

playSound (R.raw.cowbell)

Preprosto!

Več Cowbells

Predvajanje enega zvoka naenkrat je enostavno, a kaj, če želite začeti drug zvok, medtem ko prvi še vedno predvaja? Klicanje playSound () večkrat, kot je ta, ne bo delovalo:

playSound (R.raw.big_cowbell)
playSound (R.raw.small_cowbell)

V tem primeru se R.raw.big_cowbell začne pripravljati, drugi klic pa ponastavi igralca, preden se lahko zgodi karkoli, zato samo slišite R.raw.small_cowbell.

In kaj, če bi želeli hkrati predvajati več zvokov? Za vsako bi morali ustvariti MediaPlayer. Najpreprostejši način za to je imeti seznam aktivnih igralcev. Morda kaj takega:

razred MediaPlayers (kontekst: Kontekst) {
    zasebni val kontekst: Kontekst = kontekst.aplikacijaContext
    zasebni val igralciInUse = mutableListOf  ()

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

    preglasi zabavno playSound (@RawRes rawResId: Int) {
        val sredstvoFileDescriptor = context.resources.openRawResourceFd (rawResId)?: vrnitev
        val mediaPlayer = buildPlayer ()

        mediaPlayer.run {
            igralciUporaba + = it
            setDataSource (sredstvoFileDescriptor.fileDescriptor, sredstvoFileDescriptor.startOffset,
                    assetFileDescriptor.declaredLength)
            pripraviAsync ()
        }
    }
}

Zdaj, ko ima vsak zvok svoj predvajalnik, je mogoče skupaj igrati R.raw.big_cowbell in R.raw.small_cowbell! Popolno!

… No, skoraj popoln. V naši kodi ni ničesar, kar bi omejilo število zvokov, ki se lahko predvajajo hkrati, in MediaPlayer mora imeti še vedno pomnilnik in kodeke, s katerimi lahko sodelujete. Ko jih zmanjka, MediaPlayer tiho odpove, pri čemer v zapisu zabeleži le „E / MediaPlayer: Napaka (1, -19)“.

Vnesite MediaPlayerPool

Želimo podpreti predvajanje več zvokov hkrati, vendar nam ne zmanjka pomnilnika ali kodekov. Najboljši način za upravljanje teh stvari je, da imamo bazen igralcev in nato izberemo enega, ki ga bomo uporabili, kadar želimo predvajati zvok. Kodo lahko posodobimo tako, da je taka:

razred MediaPlayerPool (kontekst: Kontekst, maxStreams: Int) {
    zasebni val kontekst: Kontekst = kontekst.aplikacijaContext

    zasebni val mediaPlayerPool = mutableListOf  ().
        za (i v 0..maxStreams) je + = buildPlayer ()
    }
    zasebni val igralciInUse = mutableListOf  ()

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

    / **
     * Vrne [MediaPlayer], če je na voljo,
     * sicer nično.
     * /
    zasebna prošnja za zabavoPlayer (): MediaPlayer? {
        vrni, če (! mediaPlayerPool.isEmpty ()) {
            mediaPlayerPool.removeAt (0). tudi {
                igralciUporaba + = it
            }
        } else null
    }

    zasebna zabavna recyclePlayer (mediaPlayer: MediaPlayer) {
        mediaPlayer.reset ()
        playerInUse - = mediaPlayer
        mediaPlayerPool + = mediaPlayer
    }

    fun playSound (@RawRes rawResId: Int) {
        val sredstvoFileDescriptor = context.resources.openRawResourceFd (rawResId)?: vrnitev
        val mediaPlayer = requestPlayer ()?: vrnitev

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

Zdaj lahko predvaja več zvokov naenkrat in nadzorujemo lahko največje število hkratnih predvajalnikov, da ne bi uporabili preveč pomnilnika ali preveč kodekov. In ker recikliramo primere, ne bo treba zbirati smeti, da bi očistili vse stare primerke, ki so se končali.

Pri tem pristopu je nekaj slabosti:

  • Ko se predvajajo zvoki maxStreams, se vsi dodatni klici na playSound prezrejo, dokler se igralec ne sprosti. To lahko rešite tako, da "ukradete" predvajalnik, ki je že v uporabi za predvajanje novega zvoka.
  • Med klicanjem playSound in dejanskim predvajanjem zvoka lahko občutno zaostanemo. Čeprav se MediaPlayer ponovno uporablja, je v resnici tanek ovoj, ki prek JNI nadzira osnovni C ++ predmet. Domači predvajalnik se uniči vsakič, ko pokličete MediaPlayer.reset (), in ga je treba znova ustvariti, ko je pripravljen MediaPlayer.

Težje je izboljšati zamude pri ohranjanju sposobnosti ponovne uporabe igralcev. Na srečo je za nekatere vrste zvokov in aplikacij, pri katerih je potrebna nizka zamuda, še ena možnost, ki jo bomo preučili naslednjič: SoundPool.