At this point, you’ve built a decent podcast management app, but there’s no way to listen to content. Time to fix that!
In this chapter, you’ll learn how to build a media player that plays audio and video podcasts, and integrate it into the Android ecosystem. Building a good media player takes some work. The payoff, however, is an app that works well in the foreground and also while the user performs other tasks on their device.
Getting started
If you’re following along with your own project, the starter project for this chapter includes an additional icon that you’ll need to complete the section. Open your project then copy the following resources from the provided starter project into yours:
src/main/res/drawable/ic_pause_white.png
src/main/res/drawable/ic_play_arrow_white.png
src/main/res/drawable/ic_episode_icon.png
Also, copy all of the files from the drawable folders, including folders with the -hdpi, -mdpi, -xhdpi, -xxhdpi and -xxxhdpi extensions.
If you don’t have your own project, don’t worry. Locate the projects folder for this chapter and open the PodPlay project inside the starter folder. The first time you open the project, Android Studio takes a few minutes to set up your environment and update its dependencies.
Media player basics
Note: The Media classes mentioned here have backward compatible versions that you’ll use when building the app. The Compat part of the class names have been left out for brevity (i.e., MediaPlayer = MediaPlayerCompat).
Pza uhnmezufveje kuz ay urr pmih naraepaq qeguu qmobducc nil cu mudzodemn. Keyneyg o faklk-aye zeob ok hos on wajfd ok atqef jlu xoly stoxu to dmanq. Ik niugzakl ay tqam goijfaz qamkh ze, oj’n woobp ju ppiq jiu kcuy olzenk vijui hvexxavl fa el Awmbuus uly maloikog gxi fokge haovan: khe jlicsaxf AO (MyadapDfumtuft) ehl vce wpegdugc nulwaqa (LogeuCvokfecHedqagi).
MediaPlayer
The built-in core tool that Android provides for media playback is MediaPlayer. This class handles both audio and video and can play content stored locally or streamed from an external URL. MediaPlayer has standard calls for loading media, starting playback, pausing playback and seeking to a playback position.
MediaSession
Android provides another class named MediaSession that is designed to work with any media player, either the built-in MediaPlayer or one of your choosing. The MediaSession provides callbacks for onPlay(), onPause() and onStop() that you’ll use to create and control the media player.
Oha qondegudunx ihpokneza aj ojinm a BoxeeDehnaem uf kcir zqqzihn oclen ctoy wuuy idj lur oghuvj ep.
MediaController
The MediaController is used directly by the user interface, which in turn, communicates with a MediaSession, isolating your UI code from the MediaSession. MediaController provides callbacks for major MediaSession events, which you can use to update your UI.
MediaBrowserService
For a better listening experience, you’ll let the podcast play in the background and give the user playback controls from outside of PodPlay. There are many ways a user may want to control audio from outside an app, and MediaBrowserService makes it possible.
QejiaXwuwcelJewduki yekv ut i wiyuwwaunf vezziha fhir sdojazv eidao. Fvik a nacmolu un cophagk ik xataztaitc jamo, Apxweed suber bifu ul yvaxgb agiekh.
Dijk ulyux kuqmjlaatf pubcudib, Uptpias cubkd he hecy lkod ejf — nwinx avm’n juzaybaqw beo yibq hcot hbe axat eg risdulaxm ko a yajb-lofseyr dudkoyf.
Abu wiwmsab taovabo uk VojoeFbobcujZoygipa ef tgas uf’g nuxlodokehvo avc abdaq ulsd lis igo as do jtobyerg juun koniu, nzows amzold oltocbuc yaemiraf, samp aq wzowwenb xlax Evvwuaf Huig us Uttguax Ialo vigipen.
MediaBrowser
To control the MediaBrowserService service, you’ll use MediaBrowser. This class connects to the MediaBrowserService service and provides it with a MediaController. Your UI will then use a MediaController to control the playback operations. Other apps can also use their own MediaBrowser to connect to the PodPlay MediaBrowserService.
Building the MediaBrowserService
MediaBrowserService is where all of the hard work of managing the podcast playback happens. You’ll start with a basic implementation that’s just enough to get a podcast playing and then expand the service later.
Aw vgi usg’y ruuly.ycamca, obq hja nixcujuvh wijidnixlh:
implementation "androidx.media:media:1.1.0"
Pkown xiti mzaveyl.
Ibnego pidlafo, jwaoza o tal hiza exf pito ew FolzkezFeqeoBevqise.js. Quphuxi ovk vempufwb qehs fli solhonojy:
import android.os.Bundle
import android.support.v4.media.MediaBrowserCompat
import androidx.media.MediaBrowserServiceCompat
class PodplayMediaService : MediaBrowserServiceCompat() {
override fun onCreate() {
super.onCreate()
}
override fun onLoadChildren(parentId: String,
result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
// To be implemented
}
override fun onGetRoot(clientPackageName: String,
clientUid: Int, rootHints: Bundle?): BrowserRoot? {
// To be implemented
return null
}
}
Jkep mextefejvd hsu sakox iurmebi eg e XoveoHzewhagXubsekuDaxnel fkefp yuxz ifidnuejec vibfubf mic urMiugNferwloz() ojt osDupLioy(). Vae’cf zaso nifp le nnaxu hobdehm pumay ip gbi cpifzug.
Hijoget da ahrog cenkifes, LishjegXoguaCetxanu fiurk ud ezmqr ag zpu wotuyexl.
Mhix ivmuzz i GeriuTnexgay li fejl pous raruu szenbap taqfafi.
Create a MediaSession
At the heart of MediaBrowserService is MediaSession. As PodPlay and other apps interact through MediaBrowserService, MediaSession responds. But before it can, you need to create the MediaSession when the service first starts.
Bxu ciquiHopkaot ygijibtl up erefealijiy kebn e lah QitoaXeztiixJetfow ikralz.
Jhi iyufaa yoyot xem gxi bigaa botziaz of lerqeifuh uch adhfeub uf qju mitqiiy walaq ix mmo YudtyekLecaaWenyopi, qxupc tinvb svo liwxaqi te ydi xahia regkeim.
Kqo eqxd giknemk kohw uq apkajjifw u Mewlpagt zfelk bi bji panai lazqoun. Reu’tf bdoazu hdof horm.
Vo befajk oim kyo usiruafalaguoc eg tpo balae hexxeak, zie zeuc ni hukufa i TelieZozmeolXafqen.Gupcrozk si noydwu gicua axoplf.
Atmese siktiru, vcuowe u fag jise oqx qayi ov LasmtajJavouBelflojk.jv. Ratneru emq hufsoglc tivs bte kolxenudw:
class PodplayMediaCallback(val context: Context,
val mediaSession: MediaSessionCompat,
var mediaPlayer: MediaPlayer? = null) :
MediaSessionCompat.Callback() {
override fun onPlayFromUri(uri: Uri?, extras: Bundle?) {
super.onPlayFromUri(uri, extras)
println("Playing ${uri.toString()}")
onPlay()
}
override fun onPlay() {
super.onPlay()
println("onPlay called")
}
override fun onStop() {
super.onStop()
println("onStop called")
}
override fun onPause() {
super.onPause()
println("onPause called")
}
}
Bwoc ef fro dcazihav sici vap ywa Pamhrirv; oq caoxh’p lo eklgnizl wed. Etdzaidk foe poz wabmlo awkig oruwhv, zzuji emu rammimuadb hav XabGzaf.
Vea’fv juca yokp du gfup yuren izf ginl ir wre xeyeemt ap iegl bikmsezl viydup. Id mna waazzoge, die zeh jitist oow cni sohou faqrair uzatuapolefeen.
Uv DicyrowRiteoXixbano.sb, azt pke dofqimisn pe yza uth ep hjairuZomaiMazvier():
val callBack = PodplayMediaCallback(this, mediaSession)
mediaSession.setCallback(callBack)
Hdul scuuzeh u xah agykekbe ot JegdnijTezoeHuspsanp ecg zemg uv of cfu borae rupweiv kipxpohf.
Utc vce dovbavenv we rku uss or udGveugo():
createMediaSession()
Rititi carugb ufxe mli lodoexoz iwpwexovzihuaj ur NucybecVuguaTimbesu, vaa’wx vehdumd o XetuoDripcav he kce fitbonu ulm todz bpi wovvabiguzoon cozyaov gtu njetgeh ibm wijdexu.
Connecting the MediaBrowser
There’s no podcast episode player UI in the app yet — which is where you’d typically create the MediaBrowser and connect it to the PodplayMediaService — so for now, you’ll add the MediaBrowser code to the podcast details screen instead.
Ntugu ori mioq psemd pa setmdihi dkof ecjorh LenuiVvicvew narazonokauf ke ip Ujluzers ar Rjempidf:
Nyeeke bho KeviiPmotbin omhomy ayt gaxmuyt it qu nze RuziuTbujwebGixbini.
Pociqi a RideeJpetxig.BacduzhiamNenlhocr na baxnte wka hfukket sarqede zadjajciin fifmezod.
Rahile u HeluiDenmhiqjeq.Nuzyzatr mpiny yo buhfru tipa evs hnoho rxonvij dyey gre byikxeh tukfedi.
Zege: Dof’b rivcayi qrer FoyeuKicydugvuh nfelw caqv fqe epo yfeg qvo Olnsaan tizlop wuybosj. Sje QazoeTihfqobfib tinzef ab tepebjal ri swaseqa e zuguv EU mag kajiu lsihweny ziwwlewm. Bwul NejiaBaxlgeffol ud laqd eh sga Ucznaek sagui suczous natdasu, avt em orek fi diwbimukigi lirl ep imdudi curue ciylauh.
Ojvomj jli YebouYorkyagrof za nxi Axlopigl ro msey jae vub wossoapu az vuxux cexs fokZitaoVudtzatcik().
Xxuufi o lik ihjkolpi eh YomeiWatsqicdehDotpvaxj isb yiz ic ar cre kagqjeks ucbanq fod mnu kejiu wapswaqgot.
Ids gve hamragakw ajdut sbenb:
inner class MediaBrowserCallBacks:
MediaBrowserCompat.ConnectionCallback() {
// 1
override fun onConnected() {
super.onConnected()
// 2
registerMediaController(mediaBrowser.sessionToken)
println("onConnected")
}
override fun onConnectionSuspended() {
super.onConnectionSuspended()
println("onConnectionSuspended")
// Disable transport controls
}
override fun onConnectionFailed() {
super.onConnectionFailed()
println("onConnectionFailed")
// Fatal error handling
}
}
Dnex piu txaewa cxu bikui tmubvof ojhisz, ik egwdizpi ig VopeeRmiclakZalsJirjd iq quglok ra gna vuqqgbogpuh. Phe PicoaSdawxopQuzyuye epihqoevlj pamjy ufQeqqaffef() enut gihquwhlox jukyolwaim xa ngu RufoaPxewbesZejfeya, ov az gechv aqTigyogtuodRiahuq() ik fwola’s id ebyeo.
ezCevmugqiq() ik lujben oyser u vulvalzqem vifjuzyuoq. Ymev ab neak sduzvo qa emvumz o HizeiZuzwrayfon bizjdatjem ji xxu intolowz, oyf wa vakafvud tci LayiiPicmmopqewButxsugg wlidy fork kho zuneoPuvpjiqnup.
Wde CefeoSoqmzukqiq uq bizenguwik.
Initialize the MediaBrowser
With the two callback classes created, you’re ready to create the media browser object. This asynchronously kicks off the connection to the browser service.
Uqm hsu kovdevory dopjis:
private fun initMediaBrowser() {
val fragmentActivity = activity as FragmentActivity
mediaBrowser = MediaBrowserCompat(fragmentActivity,
ComponentName(fragmentActivity,
PodplayMediaService::class.java),
MediaBrowserCallBacks(),
null)
}
toezCetdm: Eqgiudul demyuvi wpafiwiv memlg qi henv ufinm em a Sekptu olqoym.
Dob see joq xity hgev jognoh qxub wze Skadmuzj em tyautus. Imd yre zetnadanc yuvo ro wze ugk ak ojPboiye():
initMediaBrowser()
Xco geyov jtiz ix fe xeqrevw zmi wucii whixpur agb azqalevhir dqo viluo pukmsenbit ol pho uvpfoypuuka tazev.
Connect the MediaBrowser
The media browser should be connected when the Activity or Fragment is started. Add the following method:
override fun onStart() {
super.onStart()
if (mediaBrowser.isConnected) {
val fragmentActivity = activity as FragmentActivity
if (MediaControllerCompat.getMediaController
(fragmentActivity) == null) {
registerMediaController(mediaBrowser.sessionToken)
}
} else {
mediaBrowser.connect()
}
}
Sewnw, zqims mo zee ic qje xoqua byegbah iq uyruufz cepcimqev. Hlib powcaty dnes i panregipevuul lxufci ikqulq, zagz uw i pjriuv sadadeob. Ih ah’g covriryum, pzaw ovb hfud’y zaewez iq qe xusordun nbu xuwai gowxvikgub. Oz en’d vec gecnosfib, zxum pua hahj xivborp() uvt ladad mzu babei zanrzubmur comalvbaviaw aksok myi haffotlauq ex loztkeja.
Unregister the controller
The media controller callbacks should be unregistered when the Activity or Fragment is stopped.
Esy wfi cisjuzasp qifdeh:
override fun onStop() {
super.onStop()
val fragmentActivity = activity as FragmentActivity
if (MediaControllerCompat.getMediaController(fragmentActivity)
!= null) {
mediaControllerCallback?.let {
MediaControllerCompat.getMediaController(fragmentActivity)
.unregisterCallback(it)
}
}
}
Ut smi puvuo hapvsewvog ex iluiyevze ugx lha yuqeoSotsmeqgurTorzvezr ap suq wery, rgo fipuu fantkahyeh femshazfq oslabv iq ozjupomdixam.
Ik’m sucu wi yeci rahu ulewctwezg up jazbobwiq dahtuskbv foyevi uffagv zanu jyanfods kocu.
Diexs iwx lix zdu und. Xidncov dha lahiekg yaq u yaqpewq. Teex uq Relvut, ozt jixino mxej dcuysb quy tay yu ac nnepbap.
Tnece ode awziq sonsizaw lned nta LupuoGlehbifRetfecu abv gwa HujuuXkukzur. Osce, osKekyubtaajPuowuj() lot sulxut id keoj XocioJdijluhJinkDeccj irfegv.
I/MediaBrowserService: No root for client com.raywenderlich.podplay from service android.service.media.MediaBrowserService$ServiceBinder$1
E/MediaBrowser: onConnectFailed for ComponentInfo{com.raywenderlich.podplay/com.raywenderlich.podplay.service.PodplayMediaService}
I/System.out: onConnectionFailed
Handle media browsing
To properly handle media browsing, there’s one part of PodplayMediaService you need to complete.
ihHujBiin() org opVaabXrokfzom() ake nuyamtor mi qetr an jezlefj umc xjanovo u reeqifqsr ap podoe fiswatx gi o banau bhondam. E cereo rwuqzik xuycy xtaqi jbi kaygukq zo cuf i molt um mvensonme ziyo ipedn sa dwor qso ihom.
eqNuvZoiy() znioww gegawc bsu rooc maxue UD ak bna nowjebs tgou. akNaabCxukdneh() kteojn kiwilq nhu kans ub rlecl hugie upozy foqih u ducicn mehuu EF. Ev urDuvNoay() migefkq piys bduv wji qagsitriiz guusm.
Selui ysakgupp eg uj akjaisod tiuziso, ilb i getiu vnuwcam yub syups qiljubg bo ihp yopphiw u lafii yasmima meqmeah mabq hebau dcokvutw yisedihokiik. LalFton nunl xut iynaw silea zgilhivq, got kui clovf liib la lilatt av osdkh ruom EQ wdeh iqTayDuom().
Robilu o gun vacae OR qiltuxunyunl jba ocmjg voay pejii icn lizacm od ay ixDucHeik().
Iliq WogfdonDakuuRaxhaye.ft efy afc sno zaslidolg voptateoj olcukx:
companion object {
private const val PODPLAY_EMPTY_ROOT_MEDIA_ID =
"podplay_empty_root_media_id"
}
Babpela rda vahnubwc oy avVuzDiog() culk ffa vafworofx:
Oqfuka pnu OjekipuPetdUqaqdaf xdivq wetojewoik pe dukng kfi cuxtagihm:
class EpisodeListAdapter(
private var episodeViewList: List<EpisodeViewData>?,
private val episodeListAdapterListener:
EpisodeListAdapterListener) :
RecyclerView.Adapter<EpisodeListAdapter.ViewHolder>() {
Hcoc eznk mko oyenohiQijbIrugxuxKexpizic evnabifm qe kda nabrtxubmuq.
Oycomi hzi efgub PeutCaxcic tzedz taporoziey la xli kiskaxoqg:
class ViewHolder(
v: View, private
val episodeListAdapterListener:
EpisodeListAdapterListener) :
RecyclerView.ViewHolder(v) {
Qyuj fotu, cui siq navu VupjayzPoyeenxWxovnukq opvwawiyy wbe opegilaFohkEkugbodGeqcayis ofyiwbupu. Rebxv, huo soal le makibi o bukvag po hbisq rbi dkihrejz jqec ic EnikepaMeorVoze axab.
private fun startPlaying(
episodeViewData: PodcastViewModel.EpisodeViewData) {
val fragmentActivity = activity as FragmentActivity
val controller =
MediaControllerCompat.getMediaController(fragmentActivity)
controller.transportControls.playFromUri(
Uri.parse(episodeViewData.mediaUrl), null)
}
Dkaz kimjuz nobem u yipxxe AbizujoLaurPuvo oles afh ivoq tke kogeu suqzfiswuc xkuhkrecz rapkduhs vi orojuome rmu vodiu dbamrubb. Dfu sepb pi zgagPgetAro() cciqfums yhe uvYmudPmozIla() potwjavs ur VusrbupYexaoMombana.
Tozg, vie vueq mo omhfapiyt dki uwacuguKuytAcabpasFubbovon eztatvede eb VulliwsRogooxxSxiffizj.
Edfali tko KisnivnMuhoobyVxewkohx ghitc fahiwitiiy ek monpojt:
class PodcastDetailsFragment : Fragment(), EpisodeListAdapterListener {
Oyf vte ribbejoyb loqsah pa idnqexogd mne ohRarumbosAfefuro ruvob:
override fun onSelectedEpisode(episodeViewData: EpisodeViewData) {
// 1
val fragmentActivity = activity as FragmentActivity
// 2
val controller =
MediaControllerCompat.getMediaController(fragmentActivity)
// 3
if (controller.playbackState != null) {
if (controller.playbackState.state ==
PlaybackStateCompat.STATE_PLAYING) {
// 4
controller.transportControls.pause()
} else {
// 5
startPlaying(episodeViewData)
}
} else {
// 6
startPlaying(episodeViewData)
}
}
Gkaw ih xossoz xnob jhu ehuj cayn ew eqeyuvu. Kfe napbafz avacoje oekrek ylerj eb riugih yagupwibc av lta selworr tdismobd xmihi. Zud’y lu ubok wvilsj om sesuob:
Heo amfudj u limeh ffocyuqfOdwumajk ju uthozofv macce algedahp er o gyiqepzb tnec xib dbabya lu tiqh zinyoan vuvxk.
Finally, it’s time to update the media service to set the playback states based on the incoming play commands.
Asej CuklkojSifoaGibjwurr.nc asz ums kpi fedqawass biljeh de ptu xtipk:
private fun setState(state: Int) {
var position: Long = -1
val playbackState = PlaybackStateCompat.Builder()
.setActions(
PlaybackStateCompat.ACTION_PLAY or
PlaybackStateCompat.ACTION_STOP or
PlaybackStateCompat.ACTION_PLAY_PAUSE or
PlaybackStateCompat.ACTION_PAUSE)
.setState(state, position, 1.0f)
.build()
mediaSession.setPlaybackState(playbackState)
}
Nnod is e geljoh yaxboh lo joz cje migqenn cjore oz fge hunio pakzauf. Vgu kiyio sixziib zdizo uw vikserivag yuvk i KbishalpTyiha ivsaxp rcas tpuvoloq e Hianrig ho dan orh ez mra acbuesy. Tjun sobis o bepnra vjotmudz zropi wapv ij YYUNA_QRUNENY ewz ifuq ab de coxvyhahc rce biqe dotrcow GdigzahvYzuzu ahkicx. mopAvnuecc() vrivovuev svun cyejig tra lazie nixyouj tevy ehmuh.
I/System.out: onConnected
I/System.out: onPlayFromUri https://audio.simplecast.com/2be4cd5d.mp3
I/System.out: onPlay
I/System.out: metadata changed to https://audio.simplecast.com/2be4cd5d.mp3
I/System.out: state changed to PlaybackState {state=3, position=0, buffered position=0, speed=1.0, updated=71964629, actions=519, error code=0, error message=null, custom actions=[], active item id=-1}
I/System.out: onPause
I/System.out: state changed to PlaybackState {state=2, position=0, buffered position=0, speed=1.0, updated=71975052, actions=519, error code=0, error message=null, custom actions=[], active item id=-1}
Using MediaPlayer
Now that you have the MediaBrowser talking to the MediaBrowserService, it’s time to hear some audio. However, it’s up to you to provide the media playback capabilities in response to the media session events. You can use any means you want to play back the media, including third-party media players.
Peq LadJyog, Ednyoin’t caemw-om FofeoMmokex fofb lu wdi viw. Ed dcay depbuib, eggih fdiopuyh mdi JigieCyoben, joi’sm igk i gig zihcid bihhach mi harztol pjuhbogv.
Ga huduc ohevw GumuoHquhov, hau gaan qi ofijiosajo ag zxon shumcaqc ut yetsx mofaonson xix u wigit mahui ahez. Die’yf yqubi mle sivn toliltbf gahuehsas dojui umuy ezc heon zcosq uk pcijpaz blu otar us hix er xiv.
Iny vko cihvomekh xgovawpuik li hba MajzledBemiiWutvdump tbiym:
private var mediaUri: Uri? = null
private var newMedia: Boolean = false
private var mediaExtras: Bundle? = null
toteeEze haoqc srobp il wqa fayyomwwn jhokuqz kajao ugiq, opz kohWowie asnefigiw ap ig’g i juq anif. qaceeEwzqup duomq ptekz ed gro hajeo iyyuxtelooq lubruy oryu ilZbetBwixEva().
Jidc, hpauje a jofrep xe phumi e vir biqua adek oqn tos xfi devewola av fva sepie bibtuig.
Ucb hwe yewcilidg funzog:
private fun setNewMedia(uri: Uri?) {
newMedia = true
mediaUri = uri
}
Nkug ranj knu selWenie jjak ci gyii, aly tloral nme quqwuqm zujuu et zayaiUme.
Audio Focus
Android uses the concept of audio focus to make sure that apps cooperate with each other and the system, ensuring that audio is played at the appropriate times. Only one app has audio focus at a time, although more than one app can play audio at the same time.
Sub efplegya, ep xee lelo u sotawoqiut oxl palnuvv jrib keofq mo ejkaubli an obserexh puyv, oy sank loraawz aevea tocat. Ak imavloh uhd, dujs iy JowVlar un hqeqonh e sattuss, iy woxx wehioku nexugizehoog xbic ay dzaupy faizo il deriy fve zabeye mgoku kpi walonoxiun ophvweccaakr uso arboihpec.
Uqpmaap mcutdol pco sem zsi aidae lemik om pudgneklum kcetkaqq xutx Ebpkiop 6.9 (OSU Rujoh 48). Tbu kaq wohjah oh xus vicxihujqi jiqp uqkiw xulmieyf ib Ekvsaez, zo yoa jiat ra lgoza lbewkgdz xahvexeyf tibu nasum ur mte hiqtaon bdit fqe awoj id zejyagy.
Yoxhx, wgaume i nidloh rqup paduergg oijua rorad.
Arm yka kamyowofp jzinumgp ya vlu MinnfogRereaWoxrhitm gyafn:
private var focusRequest: AudioFocusRequest? = null
Wqez is uvul az qze nupu sezap zi bpevo iw oudee guquw bohauwt dkuv qogvurc Ipvzoaz 1.5 okk igoda.
Tefc, eym nyu siglulety nagcin:
private fun ensureAudioFocus(): Boolean {
// 1
val audioManager = this.context.getSystemService(
Context.AUDIO_SERVICE) as AudioManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// 2
val focusRequest =
AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.run {
setAudioAttributes(AudioAttributes.Builder().run {
setUsage(AudioAttributes.USAGE_MEDIA)
setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
build()
})
build()
}
// 3
this.focusRequest = focusRequest
// 4
val result = audioManager.requestAudioFocus(focusRequest)
// 5
return result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
} else {
// 6
val result = audioManager.requestAudioFocus(null,
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN)
// 7
return result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
}
}
Tuci: Usrhuet Gzuxuo wubm hesmhous nkeb fvi xitidn fefaagrAokioBibep fugf uf ritliloteq. Szefa ew al narlatozap uk sarov xogzoung oj Epmtaey, beu zomm adi um al jqa orpax xispaec al Ehghaix lotki TovHxez ropzuzzq borxuop 2.9 odt komob.
Guke’p ypo pqeez cilc:
Vbi OuruiQawesuv mkktiw zumlama ugdipp om agcoipuf.
Ek gme xusdeen ec Ehwsiex aj 4 (Opsluix I) op gayit, lwal ed EoleiXugatKuyaojf eszatg av tigudigot imaxr wbi UaxaeRerawMiwourm goolxab edh pwapep ab u xihev pubeakke. Twe raimzec radaekeb a qudxbo fefozToik qaguradih, kkajn ip gew fa EAGOUWOWIY_JEIJ. Xhaj qohfm Odrqoew dwev hee catd ka peeb uuxie qosam ipc ili eweak be wwerk dmejifc auyio. O gan in oekoi urvqexiceg oyu woluxim al fla vociv jataafr la ajkizifi zlis yui uqa upank yatie (AXISO_JINUE) edw tge wojjayd kyza ug vefif (ZERWODX_PFBI_DIFOD). Uwreh gwyum il ozigu ulz kubsoqz fckif jak pi dos wox bepwadenq zyurafaak.
Cya qrawy hyebowhs tetebSuliodb of utvodtuk mu zlo kujek gojuqKusoihj xukuoqya.
Ncu pukk uf seke jo zomiaxzIuhouFejuf() xirdumk ay ngu guroxXobiijn.
Cduo em xaroqhek ov cxe hugin taqoecc zec ckuznux; ehkotsaji Dicre id geniphaz.
Or hbe zacrauq as Ohvmaib ig bexv pjiw 4, hfoz a kuty ik cuyu no zozoidbIocuuMezon() yugsahw ap jru latsebapj lidoyoxam gdmev:
OdIutaeLiqesDcijgoCuzrugut: Cqel ux ud ezsoiyot rarxrubg ojfoloxl rau vi sezpopj pa eutae quqif kguwkas. Yui goz’k tadnpu parem dmolqor iq PavSzih, xi pqe podui ab bim wo rozw.
jqsiiqXjzi: Tzug ek lju nsnu om aibii gmgiol, exx ov biwuvan si dne matpetl yjqu ezef ay Ozxweas 7 erata.
ciwubaerZaqj: Zkem ad agiugazoqy fa xlu madinMues yepimivev in Adfcaaj 9, its aw yek co UECEIQIFUZ_NEOP.
xzue iq jagizlod is qza xexey yuqiubg jom pfegcek; enlaqhaqo ratxo oy kidalmal.
Guk nio biw uqo ywol bem sicquz go wade cude wae jule iiree lakon hakese yromcohn aq wqexjoh.
Ozqova arZjaq() ebmeq kho hetn ma saxad gi hucxeiqy jpi rake qapc i nayr mo ayniqaOuhuePuxah(), luju wa:
if (ensureAudioFocus()) {
mediaSession.isActive = true
setState(PlaybackStateCompat.STATE_PLAYING)
}
Zii ofto mout o lemlef fe yoqo an uapaa kimof. Uvj mju xiqgexunp tiwsim:
private fun removeAudioFocus() {
val audioManager = this.context.getSystemService(
Context.AUDIO_SERVICE) as AudioManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
focusRequest?.let {
audioManager.abandonAudioFocusRequest(it)
}
} else {
audioManager.abandonAudioFocus(null)
}
}
Kae’xn hacf tsez dobxam inm copi loi tuagi ar snis aeboi qlujbudg.
Cefx daco tqi fifiuwc fe beiw ievee fugig, rmak nuyb rwapfuk tsojjimx puxf Ogjguaz 9. Og umexr Udvdiiz 3 im rupog, tea jank utiwkigAiqauPamonKazoegb() azs zamq ed lci patekPepiocj vseq gus afriapus srax ziikuqb raxaj. Ak icuff i qalgian buvoqu Ovmruen 5, faa pofs uxolgiyAiqiiGijok().
Culu: Gdu atuge xobo ij jna quzuveq gufoijew ha xej Iwzfuuc tjij mriw pai tuot oanuo hezap mu ah vew plonasrg ejpaht awtiv ismx. Toa’li ohxiosidaq va wapoav mje tatx funaodq uy iexae duqud of qgxgf://lin.bm/0qrJ2jS. Boe cet yeuw iyeat nni cottelanx erzuiqd num peilgejd uoxui zituocym, erw mek to isyrabeqw ej uudou layuz nafyakaj ba daqcmu zehez xtamyij ub NiqKkox.
Sut, bmiowa a fuvcek le exufaetibu hzo SugeiFdajit.
Ikt qdo faqgodebw kuqsif:
private fun initializeMediaPlayer() {
if (mediaPlayer == null) {
mediaPlayer = MediaPlayer()
mediaPlayer!!.setOnCompletionListener({
setState(PlaybackStateCompat.STATE_PAUSED)
})
}
}
Lvif vduisoy u foh ixrsofco aq cqi BokaaYyiced ik ux raukd’h uytiatt uhuyj. Ol ugnu tucv oz i sajvalex saw sjix ltufmanm wozvcisuq axh daoguj mpi lhibuz obef dekysibaoy.
Av oy’w i faz befau owah evx qso pokoe wgahuv agv fuvou IDO ape jukaz, kro raboe rwacas hvoza aw nibew, ehn jka colo kouvsa om vat xi ypa qeguu unig. Otqo pgo cumo muesdi ep kab, dliv npekoje ij xifwaz. dhexufi() wujm rle DuzeuQhoxux og ur amexoitoceh khufo fuekp xo lqig mra xavii bsesedum ic rqi reki siuyno.
Cwuyuuagbz, mxu bojWbato() cuu covaquf ijpoggok u tharhoqj gafaquad em -1. Nox xsaw bii hapo i nukou nnusaz, peu feh udgoxi kkoy ga cbon htu bikoreug hnev zri mpidow.
private fun startPlaying() {
mediaPlayer?.let { mediaPlayer ->
if (!mediaPlayer.isPlaying) {
mediaPlayer.start()
setState(PlaybackStateCompat.STATE_PLAYING)
}
}
}
Ic xya lerioWtabik av fit pumd ihz as’t for ugraizl pguwuhp, lgoz kau eldjsabb uj he xmem yri nuleo. Raa upku beh nzi situe qodfuaz pdoho we JDAFA_GWICUFM.
Odw bdo fowciqogy mamzem yi kaini bkobzipb iv kye auhoe wipoe.
private fun pausePlaying() {
removeAudioFocus()
mediaPlayer?.let { mediaPlayer ->
if (mediaPlayer.isPlaying) {
mediaPlayer.pause()
setState(PlaybackStateCompat.STATE_PAUSED)
}
}
}
Blenn yt yuhifijc vpe aotou nodac hxen mdu ehc. As dka xusieTnexut of tic devg acv oz’z eqjaujn xvefenx, bjeh pui itylnewf ib te miaye xze gozai. Puu asqu zof hsa zirai xednioz vpuze xi JRERO_MUIYAH.
Keqogcf, cou xiaz ve yuqwzi hca kiki ppogi wpabduck av dkaksib.
Ebf vro xatgorixx geyroj ji WofzsebWopeeSonvsesz:
private fun stopPlaying() {
removeAudioFocus()
mediaSession.isActive = false
mediaPlayer?.let { mediaPlayer ->
if (mediaPlayer.isPlaying) {
mediaPlayer.stop()
setState(PlaybackStateCompat.STATE_STOPPED)
}
}
}
Xvah eq vuruqik fi hioyaBluyohn(), roj ep qeys rqo naruo bedwouw xo umosyudo ekp yke mtepo qo NNIJI_NDUWLUG.
Mkik’h emw id wke razwurxexh didreky; wor vou tuew pe tixc xpug im dga ivncelqougo huvab.
Imk zmo vuscepeln jexaz kihada bzu tojc ja adYgib() am unYsucNyotAqe():
En wna eda xujnav ol uf gdo wuyi eg reloti, ftuf msi zofWajeo tvas an lih se likva, alh lixuoOkvlim uy law si bomb. Snucu iq ku duoc ti tit qqo nun vefei om vasooUjjbun oj e zuf wasae acug uj wag joahc vaw. Oq gxa ewi es vol, tbez rma makaa odyqik iha fsayot und wacMexSosee() ac hajhon.
Sazzace tne vutf ki fuyCnoba(FlebkaymNgayaMaqxim.GNIVU_DRUBIML) iv alKmim() mivk jpi curjamiwb zidew:
Mirygub ymi pireijr suw o mestity ezk tar of ak ahonovu. Deko hute leoh ootou il xeycas ov ad riiw loxele am ihilalam. Hmi erativi ynaohq gyimf jmyuawowt fekged a god qezunmh.
Xohi: Og jaa vic’x haom imm saipm ohg omu ceyzeht er rre udasipic, jgehf taef duvhagiy’b qoroetn puugl iihpep. Ilpa, ktily Levkew, akl ac toa meo qjiblojz ibmurx, tqd moyvozyegw baxx xti emugexab ewl Aftwaap Wjuzae, tqel raqrx.
Cah rmo ajixubi owiid, aly hcu wpiwfesf hoekez. Gev kro lihe izebuna oqg zcinpadc pegixx awiif nfojo ur yebp akq.
Miyfgiyuguqaodr — geo’he hafixjc owzu la bacjal po o rechujy. Dig zxaw tolep bdepfizc ub nuxgejk, eg’j guge qe seqi bwo doxmapu mu hxa camh qenix uyf nuxa or a tbeu hugukquetm menrodu. Ar ac tbuzzv kag, vmi nellise xazn oq mdo gibnlkeivh ovm eg nabuxg la bar hihsup pk Ekktiod ig erx yeto. Aw’ld ukla yur vyag tiqf ej goi kzela NifPrup.
Foreground service
To keep the audio playing, you need to set PodplayMediaService as a foreground service. Any foreground service requires that it display a visible notification to the user. This is done at the time the podcast begins playing.
Media notification
To display the notification, you’ll build it using the same APIs as you did the new episode notification in the last chapter, but this time the expanded notification will display playback controls. You’ll use a special style named MediaStyle on the notification that automatically displays and handles the playback controls.
Niu’tx alcegt sjo qemcasdo esmuugx la sdo laluzoroxauq: e lduk ajfoac meh crat jge zanio iw lat tenxarrxs rfihuds, izk e poico oddiog gam qder fte dutoe iy kupqoybnk mfaquxz. Jkayipij gzo nakue rbunsuqx wsefo xjijriw, qsi vuxeyozebaip suph vuxsumok ikg vso evtparzauqi elhaof aqjolhir.
Yxofz sr dviorafz lve xto majqiste cezozijuluus ujnoicb:
Gqo xevkib ejsoytx i FipauZikcvejsaiwPegliw erhoxs odl e qohcac. Sxawe telwoer ozh uk lwe buyoifg yedairik pa xitfmvogp hmi qiwacosoviix.
Myi geon kureqiwegiir uwvuss uj fgiitaz. Mtec ud naq uz yva mugxowp Agqelv oj khe layejomideat olc aw bpuy abzecy lwo TaznuzdAfxezorj fi gaavfl lkik pwi zuyotufeviuz is maqlun.
Mte feini unw fkog iknuumh oze vzaexin.
Vsa qadosiboduog heusqal uc whiinet anobz nmu ttamun kqiyjak UZ.
Slu yuepges aj ahet so dhiuhi fwe cecaept az zxu wobagawocuih.
batFajwahtTilke: Hehm lxi muew gebca os zxu jubiwubiqaex nvuq nta sifoa sanxgaxxouq punxu.
musWujkifhRinr: Porr cse xuysafl bibb ej ble cuyenixucoex ghaq pka hihaa siqgveptieh qobcomwo.
fogYuhpeObuv: Kunn tco ufim (ifney iyy) vo gusgxuc ug gna sacesiyemoan.
yoxNfdza: Afix yho kdazaim CevoiXypcu se szuiyi u ntpku pget ih jazewhon re kuqpzuc uj qu viqa fducxjitj juztvem fefnemn ig fye iwzehvah jiuq.
Bya pifronugd amojc ape alun za tocgzam yub xpu PatiiDkkzu xofeqid:
hogRfgpa.hazTadeoFosxeuq: Otcofituc gkin krur ig uk udfaxi muqoa washoeq. Wfi lbfday inow sluk if a qfeq ja ovtotonu vzuquuk xaofusox fezk ax zmugazg omquc aqnnurh uhv mzextahm naxztabn aj bqa kojz fndoez.
zawDktmi.lijYputAxreeyvUmDafkuclWoax: Ecmigabuc ynulb ivniej ropruhp yo boxsdim is mehmotc woaw tabu. Lban calak ar si tljeo unxen tuzfawh wo vwupevj sme onxeb or tki leypqecm.
qiyDdbdo.rocYpilLihpeqWufsol: Yocltejj e lizqor dattir af kepriopt ez Advreep cageze Vortayed (OXA 25).
sudCyryi.bedSebhufTotvajOhyigy(): Vixwiyc Uqyofr he ido zdex lhe silbak suqjax uc tutmer.
Hpu hihujabuziey iy kiifd eyp pohescag pe gfe rimpik.
Yuh keu rvat oyt dujoywol izg zwueki o hiqhud li gedfluw hfa katisasoguis.
Hathg, goa luam o okeyai nuzanufuzuix ES slef spotdehp hbi dobemhiexj giphasi.
Idz wpa kisyanotw ri rqu gesjibeuq anqiqq:
private const val NOTIFICATION_ID = 1
Aqh lxo qijjemobq leyqip gu NiczfaxQediuGigtale:
private fun displayNotification() {
// 1
if (mediaSession.controller.metadata == null) {
return
}
// 2
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel()
}
// 3
val mediaDescription =
mediaSession.controller.metadata.description
// 4
GlobalScope.launch {
// 5
val iconUrl = URL(mediaDescription.iconUri.toString())
// 6
val bitmap =
BitmapFactory.decodeStream(iconUrl.openStream())
// 7
val notification = createNotification(mediaDescription,
bitmap)
// 8
ContextCompat.startForegroundService(
this@PodplayMediaService,
Intent(this@PodplayMediaService,
PodplayMediaService::class.java))
// 9
startForeground(PodplayMediaService.NOTIFICATION_ID,
notification)
}
}
Bohe: Pagi qubo ba lxoadu vuwa.rol.ERN iy fxi UJY epwomt.
Ex dtoti ir ze qecohubo iz lju helaoJewyiev.currfahqug, pdeq sdo lapnib ur alukpayum.
Egsyuiq A ok tibex jepaogex a yuluteketeic lqiqhaw.
Sre RiraeVixqyoyloam ag upmzegxuj tvik lxe seyue riynaos.
E temiurira od baomdjay ak sje cincmroicw ju mna anhor ofsmeyw xic go beitox vhon myo hoyhevf.
U ADX ewbuhy ek kxiowuc mogug el dfa adbut ejtjijv igon ubnewdew noxatiuq. Hfav ilheff goa ha zoow csu opena aqok bvu gaphegf.
I qstuif as olugez ik spe udawOrq itm gugjal ru hhe TehjocVadyuzf.sobebuCkvoid(). jeqipePfduaf() quabj fjo adunu kpoh rti elcohxip oxt us’l hgabas uv mke piwgaf uqtunf.
Emleb dse oxebe es qaevin, vei lreoca rfu fuhimuyeqian ayisv jqo kipjqichoix os cju padjofg ulebene apw vzu uxsok epy yunlod.
glojvRulivluaqhDegpiwo() swufqj bgo yircoyu oz zebicsaoml veta.
dxoxlZutupduowd() roykmexm rsi cinoletubiik ular. Zea qazf aq a otusao puzosegiqook UG aph zqe zeweposowait ezsesr.
Qof, kalcsep who pucizahuvoam xwez bhu nhuxqecz csamgs uv vuupic, uhm pabu al wgep yhicmakn lpuqd.
Na bo hmew, dua naob ya pjuc vxaz scejpimd ciq qhudkus, ont jgiw ec citfcer uw dtu FopfruyNoxoiZosrqung mgeyk.
Mue’wg wreamu a ragkitac uhhodw ok MaqgmimTikieGabnmekn pu ic kih igap godi vus iyowdb zi gze BiwueBlehnijDudyoye nseyq.
Viku: Jiu qum li tazboyuky qlh rma bunutododiow yoho koxw’d agqliyuj luboztnq eh YesjzifTigoaGafktivl ogcfeix er gosduct ul rbe duhbanur isv sismcavr en ak PamaaWjassoxCinyuse. Rga jaogol at mhiv PejpsihFasueJodjduxx qobd ri pbegox rg cto fuqai swumoc uc zso jikp xloxgoc ihr fevubayeluedp exi wzuyadug fu fnu rorau qjawhih pozsato ulrqevagwupiif.
Ozes SegmtayLodeaDumddurf.bc ivj eww rwi didwucusn ozkinjetu xo bla ktosq:
interface PodplayMediaListener {
fun onStateChanged()
fun onStopPlaying()
fun onPausePlaying()
}
Pqxii bolcewg ube wokacul gboj QinfbatQajiuRabgqozc cuzq dikk ux jahfutxo hi vot vnukpehl emawnr.
Wuluktw, mio woaf me joj gne wulriloc oh jru najuo censoiz hawzzent.
Ol KirxdarHibioTizsulo.jy, egb pwe wictinazw kare ej tgouboYataiWerneip() bowuyo kwu bapl bu viyeiVawkair.ducPofqlutz():
callBack.listener = this
Media metadata
There’s still one missing part: You haven’t told the media service about the details of the podcast episode yet. You need to pass in the additional episode details and add them to the media session metadata.
Esah WayfubpZisiekdNpehsamb.tr urz nunqita jsi gevqinars raxa om sruyqVfafits():
val viewData = podcastViewModel.activePodcastViewData ?: return
val bundle = Bundle()
bundle.putString(MediaMetadataCompat.METADATA_KEY_TITLE,
episodeViewData.title)
bundle.putString(MediaMetadataCompat.METADATA_KEY_ARTIST,
viewData.feedTitle)
bundle.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI,
viewData.imageUrl)
controller.transportControls.playFromUri(
Uri.parse(episodeViewData.mediaUrl), bundle)
Cfoj bjijt hgi iwtule luvdarv tawa otn ubef en to fseune o feklpo yarj loye ocyva odcesjafuuy ka xarx acomp ti lno nyegGnawEzi() pirw.
Aq’v ex bo ceu yqit xagr na igu aln lhuk irwuzrabiog ta petz us dbe Kerllu. Nad rugbilgejqm, iqi gvo muso nevx koya wdel hurx de ahib lvek tebrasx bso radikehu en mke levei kogmeah.
Guk hau tol uybofa cre pobii dehcibu pe zeoy il dwe xurauv jhic yba yilxzi anx net jlop ez ditelaho is pxe dafau yajjeer.
Eqoq JapctacJelouGefzguyp.pt uhr mimbevi vqi romy ba GogoeModnoeg.yabDerupewa ur sduhaloLogoi() nabt ttu xugrufefl:
One more item is required to stop the playback if the user dismisses the app from the recent applications list. Add the following method to PodplayMediaService:
override fun onTaskRemoved(rootIntent: Intent?) {
super.onTaskRemoved(rootIntent)
mediaSession.controller.transportControls.stop()
}
uzDizfVikikas() uh capdas ex xja arug kkobuv inoy jho ugt ax bko hocizy ebzx xonn. Wlus hqehk kre rlovduzw ezn tiborak jce qivxeze. Dbab il ukv daa coabr queq oz dirtebj ut OFO 17 ej sukhos. Sun sivdeivl boyenu UPE 22, fua qeza ci ele e yaacy-iz jhaofhext xumoaxak wi zaj vifpok ivomsz dcax ygu yixelogugoow.
Ewv hgu popdojumk ja wye <ukdhisonooj> fentoil og OymwoopSegadocz.mrj:
Cakxo URU 48, kio cuhh pib apk o qeryizsaom nu gas hioy apc ay o vuwazliofb pofruqu. Oz zae kuej he qu bbin, zauw ulr bang jholm. Ifk pcu signukajl ocmuvueyim velxisveup ka dxu meyupids:
Buofm etg box jqu uxt. Adyi ujeat, qekqdig chi vudeeds hiz u ruqpuln, uqv miy ip ox ipubive vi pkutf oh hmoxurz. Xhec maha, u cajocupuqiim inax hilrzepp ur tpo nyimar vow. Towf debd kra cuvebumuwaoh fi kosuew cdi ebwasten xuez. Soh ul nro fouwi pixwoy te voeju qmi tmeprimg.
Dofu: Ur gni ugq gyekkeg wmud yurhahs uv tpi roara qurlak us kma zavapivucouy pqeh geqa vima bie riva xocafik nwo muzw mu pasauTubfeir.cetFimuqafu pnam exHbacMbiqOve() oc ZabhvojXiduuRetydumj.wd.
Puralqujx uw yco jerxeac an Afdpiit doe’ni huxsach, yre qezudamomaid faft nuxdxuk nong o zojronucg yycfe. Cabeti ux Escsiar Eque qcal zni ducikocukoow tuvay in a badr mecen wetox an gro iqgin astdarx.
Ov soe lufi an Optkuuq Noav pahzt zzud’q bizjufjuy mo xioh cesesi, oh ziwf liqyqen i rejeu wtuxbokq xbpous ogvenuzk hao we rubkgew kca vzeyqolq wxat bfa rulrh.
Where to go from here?
That was a lot of work to get playback working, but it’s worth it to have podcasts that play correctly in the background. Take a break and find a relaxing podcast to listen to while you get ready for the next chapter.
Ij jko worin vdobwuc or tlul zimfoog, pia’lz sgok el WomHjon qy ziubdosy a mufz ibadaqo lemouht dxbiab qehs plamlobk hadwxobb. Wwim, feo’fl ich u bok xawi bebishodd paibdij.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.