Dziļā niršana: MediaPlayer labākā prakse

Marcela Laskoski foto vietnē Unsplash

Šķiet, ka MediaPlayer ir maldinoši vienkārši lietojams, taču sarežģītība notiek tieši zem virsmas. Piemēram, var būt vilinoši rakstīt kaut ko līdzīgu:

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

Tas darbojas lieliski gan pirmo, gan, iespējams, otro, trešo vai pat vairāk reizes. Tomēr katrs jaunais MediaPlayer patērē sistēmas resursus, piemēram, atmiņu un kodekus. Tas var pasliktināt jūsu lietotnes un, iespējams, visas ierīces veiktspēju.

Par laimi, MediaPlayer ir iespējams izmantot vienkāršā un drošā veidā, ievērojot dažus vienkāršus noteikumus.

Vienkāršais gadījums

Visvienkāršākais gadījums ir tas, ka mums ir skaņas fails, iespējams, neapstrādāts resurss, kuru mēs vienkārši vēlamies atskaņot. Šajā gadījumā mēs izveidosim vienu atskaņotāju, kurš to atkārtoti izmantos katru reizi, kad būs jāspēlē skaņa. Spēlētājs ir jārada ar kaut ko līdzīgu:

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

Atskaņotājs ir izveidots ar diviem klausītājiem:

  • OnPreparedListener, kas automātiski sāks atskaņošanu pēc atskaņotāja sagatavošanas.
  • OnCompletionListener, kas automātiski attīra resursus, kad atskaņošana ir pabeigta.

Kad atskaņotājs ir izveidots, nākamais solis ir funkcijas izveidošana, kas ņem resursa ID un izmanto šo MediaPlayer tā atskaņošanai:

ignorēt jautru playSound (@RawRes rawResId: Int) {
    val assetFileDescriptor = context.resources.openRawResourceFd (rawResId) ?: atgriezties
    mediaPlayer.run {
        atiestatīt ()
        setDataSource (assetFileDescriptor.fileDescriptor, assetFileDescriptor.startOffset, assetFileDescriptor.declaredLength)
        sagatavotAsync ()
    }
}

Šajā īsajā metodē notiek diezgan daudz:

  • Resursa ID jākonvertē uz AssetFileDescriptor, jo to MediaPlayer izmanto neapstrādātu resursu atskaņošanai. Null pārbaude nodrošina resursa esamību.
  • Zvana atiestatīšana () nodrošina atskaņotāja sākotnējā stāvoklī. Tas darbojas neatkarīgi no spēlētāja stāvokļa.
  • Iestatiet atskaņotāja datu avotu.
  • PrepaAsync sagatavo spēlētāju spēlei un nekavējoties atgriežas, saglabājot UI atsaucību. Tas darbojas, jo pievienotais OnPreparedListener sāk spēlēt pēc avota sagatavošanas.

Ir svarīgi ņemt vērā, ka mūsu atskaņotājam netiek izsaukts zvans () vai tas nav iestatīts uz nulli. Mēs vēlamies to izmantot atkārtoti! Tā vietā mēs saucam reset (), kas atbrīvo atmiņu un kodekus, ko tā izmantoja.

Skaņas atskaņošana ir tikpat vienkārša kā izsaukšana:

playSound (R.raw.cowbell)

Vienkārši!

Vairāk Cowbells

Vienu skaņu vienlaikus atskaņot ir viegli, bet ko darīt, ja vēlaties sākt citu skaņu, kamēr pirmā joprojām skan? Zvanīšana uz PlaySound () vairākas reizes, piemēram, nedarbosies:

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

Šajā gadījumā R.raw.big_cowbell sāk gatavoties, bet otrais zvans atiestata atskaņotāju, pirms kaut kas var notikt, tāpēc tikai jūs dzirdat tikai R.raw.small_cowbell.

Un kas būtu, ja mēs gribētu vienlaikus atskaņot vairākas skaņas? Mums katram būs jāizveido MediaPlayer. Vienkāršākais veids, kā to izdarīt, ir aktīvo spēlētāju saraksts. Varbūt kaut kas līdzīgs šim:

klases MediaPlayers (konteksts: konteksts) {
    privāts val konteksts: Context = context.applicationContext
    private val playersInUse = mvableListOf  ()

    privāta izklaide buildPlayer () = MediaPlayer ().
        setOnPreparedListener {start ()}
        setOnCompletionListener {
            it.release ()
            playersInUse - = tas
        }
    }

    ignorēt jautru playSound (@RawRes rawResId: Int) {
        val assetFileDescriptor = context.resources.openRawResourceFd (rawResId) ?: atgriezties
        val mediaPlayer = buildPlayer ()

        mediaPlayer.run {
            playersUUse + = it
            setDataSource (assetFileDescriptor.fileDescriptor, assetFileDescriptor.startOffset,
                    assetFileDescriptor.declaredLength)
            sagatavotAsync ()
        }
    }
}

Tagad, kad katrai skaņai ir savs atskaņotājs, ir iespējams spēlēt gan R.raw.big_cowbell, gan R.raw.small_cowbell kopā! Lieliski!

… Nu, gandrīz ideāls. Mūsu kodā nav nekā tāda, kas ierobežo vienlaikus atskaņojamo skaņu skaitu, un MediaPlayer joprojām ir jābūt atmiņai un kodekiem, ar kuriem strādāt. Kad viņiem beidzas, MediaPlayer klusi neizdodas, logcat logā tikai atzīmējot “E / MediaPlayer: Error (1, -19)”.

Ievadiet MediaPlayerPool

Mēs vēlamies atbalstīt vairāku skaņu atskaņošanu vienlaikus, bet mēs nevēlamies, lai atmiņā vai kodekos iztrūktu. Labākais veids, kā pārvaldīt šīs lietas, ir spēlētāju fonds un pēc tam izvēlieties to, kuru izmantot, kad vēlamies atskaņot skaņu. Mēs varētu atjaunināt mūsu kodu, lai būtu šāds:

klase MediaPlayerPool (konteksts: konteksts, maxStreams: Int) {
    privāts val konteksts: Context = context.applicationContext

    privāts val mediaPlayerPool =  () mvableListOf (). arī {
        priekš (i 0..maxStreams) tas + = buildPlayer ()
    }
    private val playersInUse = mvableListOf  ()

    privāta izklaide buildPlayer () = MediaPlayer ().
        setOnPreparedListener {start ()}
        setOnCompletionListener {recyclePlayer (it)}
    }

    / **
     * Atgriež [MediaPlayer], ja tāds ir pieejams,
     * citādi nulle.
     * /
    privāts izklaides pieprasījumsPlayer (): MediaPlayer? {
        atgriezties, ja (! mediaPlayerPool.isEmpty ()) {
            mediaPlayerPool.removeAt (0). arī {
                playersUUse + = it
            }
        } else null
    }

    privāts jautrības pārstrādātājsPlayyer (mediaPlayer: MediaPlayer) {
        mediaPlayer.reset ()
        playersInUse - = mediaPlayer
        mediaPlayerPool + = mediaPlayer
    }

    jautra atskaņošanas skaņa (@RawRes rawResId: Int) {
        val assetFileDescriptor = context.resources.openRawResourceFd (rawResId) ?: atgriezties
        val mediaPlayer = requestPlayer ()?: atgriezties

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

Tagad vairākas skaņas var atskaņot vienlaikus, un mēs varam kontrolēt maksimālo vienlaicīgo atskaņotāju skaitu, lai izvairītos no pārāk daudz atmiņas vai pārāk daudz kodeku izmantošanas. Un, tā kā mēs pārstrādājam gadījumus, atkritumu savācējam nevajadzēs darboties, lai sakoptu visas vecās lietas, kuras ir beigušās.

Šai pieejai ir dažas negatīvās puses:

  • Pēc maxStreams skaņu atskaņošanas visi papildu zvani uz playSound tiek ignorēti, līdz spēlētājs tiek atbrīvots. Jūs to varētu novērst, “nozagdams” atskaņotāju, kurš jau tiek izmantots jaunas skaņas atskaņošanai.
  • Starp playSound izsaukšanu un skaņas atskaņošanu var būt ievērojama nobīde. Pat ja MediaPlayer tiek atkārtoti izmantots, tas faktiski ir plāns aptinums, kas, izmantojot JNI, kontrolē pamatā esošo C ++ vietējo objektu. Vietējais atskaņotājs tiek iznīcināts katru reizi, kad zvana uz MediaPlayer.reset (), un tas ir jāatjauno ikreiz, kad tiek sagatavota MediaPlayer.

Grūtāk ir uzlabot latentumu, saglabājot spēju spēlētājus atkārtoti izmantot. Par laimi, dažām skaņu un lietotņu lietām, kurās nepieciešams mazs latentums, ir vēl viena iespēja, kuru mēs nākamreiz izskatīsim: SoundPool.