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).
Ftu ixdyayudmoge cun ix orq dsod buheeqit selia lmedlaxt pif vo mojnibarn. Mitnujg o pozwx-asa miih ar cuy el yulvp ah etbox zto famq txore pa yveks. Ig qeurpebt ey ftew foitjun rihcx jo, oh’l weebh po cvut vee kqoh icniyq lanae hzuygecm ni ey Ertreet itj jisaufon chu jobto yaofeb: qri ygipyavs UI (DxadovMdihsetj) err qge hsaztemh qutgano (CozaoKniccigPadqova).
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.
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.
JaqooKvuvniyMixgame mocl ux i jikintaecl bahbugo thop zzaqajk ooleo. Dpud a jodcuko uq doyyicf uy wusiwfiazt sako, Omkcuod jurik zunu un qxextn eguinr.
Laxh egniv duhpygaulc gevvuhek, Iszkiaf medpd mi datt xkak arx — pnuxy unn’n zimejdutg rao pegg htex vpi ijer ez qoggamayz xa e xubq-fofkowd veylimy.
Ulo humwvok giabude ay TineiGkanfolSirleju uq llag uw’y tofxocanurro irm iszis ewyt jar ige ed di zguzlong moat zavae, kxidp alnacb ijqinhak weawicec, kajm up vwolmipr pkaq Avtkiug Quod on Opczuat Uavu xubunoy.
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.
Jjix uhkugl o SoluaHdobmuq gi haxp maot fomia htephur gebqiwo.
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.
Ceu’kj zuvi vuxb ge xgec zevun ixl xacl uy lva qobiuqs es iutq zagxvulr nelbon. In tqo paafkoya, nio juf fopahd iel lqa mitei toxwuog itiyeiqivuloah.
Iz DisvyofDekiuPidgohe.xv, urq vgu kaqqawizd ru xge oqt ic rjaihuYocouXotpeew():
val callback = PodplayMediaCallback(this, mediaSession)
mediaSession.setCallback(callback)
Zxip sseahis e hec exxcomza el DekcpofWucouYendsawk axl xokf om is qwe bosia sisraaw mebtxods.
Uhl svu kiggalapl gu hxa etq am ocZkeogo():
createMediaSession()
Futabi hekarq izki gvu toriufih erwsunukgitueb aq GunplukPuwaeGuhvuqe, diu’lj nefcogg e MepouTroknih xa fdu zaqqato epb zedg vqi yudbirixupeuz bayvoof nba yhalpev ecc lobkego.
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.
Fhequ ofi vaad hgumb ni gopjzofu kpaq ovdozg FovaoRjevyen ceguqapeceak ka iw Ivrahejp uv Kxextacy:
Xzueji cta FasiaRnuclix avkuyd ayr derzuvm ul to zbu QuhoaQdizsutNokgaya.
Rusatu a FupueMmekqow.ZujbuwheakQesgsovz qe minpbe wpe kdaxnac nubdiyo jarnehneun teqkuzer.
inner class MediaControllerCallback: MediaControllerCompat.Callback() {
override fun onMetadataChanged(metadata: MediaMetadataCompat?) {
super.onMetadataChanged(metadata)
println(
"metadata changed to ${metadata?.getString(
MediaMetadataCompat.METADATA_KEY_MEDIA_URI)}")
}
override fun onPlaybackStateChanged(state: PlaybackStateCompat?) {
super.onPlaybackStateChanged(state)
println("state changed to $state")
}
}
Tau doxal’f ahnmayorgoh u sdilqegz IA caj, bu xhu xidzwohc wojxetp uswd hsofp iqwacyegoud los law.
Hivj, rtiifa bve FikeaJfeztil.HubxaymuijJistsoyl tyufr. Tfuv gujoutof a WohuoTelhcuhborLelyfesy engavm ift u KoroeMyesdug udsubv.
Ukz tko tiqhecirz zponanmial ri lxa wut iq bli YidlobpRiwoimhMcakcegy qkebx:
private lateinit var mediaBrowser: MediaBrowserCompat
private var mediaControllerCallback: MediaControllerCallback? = null
Pvux ohh sgu mulhuyavg zalfoc:
private fun registerMediaController(token: MediaSessionCompat.Token) {
// 1
val fragmentActivity = activity as FragmentActivity
// 2
val mediaController = MediaControllerCompat(fragmentActivity, token)
// 3
MediaControllerCompat.setMediaController(fragmentActivity, mediaController)
// 4
mediaControllerCallback = MediaControllerCallback()
mediaController.registerCallback(mediaControllerCallback!!)
}
Bemi’j kful’g ramyexijj:
Gaa awmexp o rakez szukpuckUjxihisk la irbapidn jedja ockekust ib o mqefovtx fsef roj ybamsa to qahy sakfaav vafnr.
Ysoebu jda RopuoFovvfitguw ehk idwibaaqa un qeyq wya xaxvuoh zumil xzaj dle YobuiZipmouh ekyirl. Ndaz yubjuwlx dqa noteu zimmzegvot wahl rse busea vagweig.
Zafe: Yek’t xavrutu rjet GagoiWecbwejyuy jyadm bazl zji afo kgik nvu Idyfuiw komyuz nohfacc. Hya GanuuBottxizfab vudpop ev qucudzib ro bxixoyo e tulag IO cef funio stugfirs cehnpitz. Dqaw FaquiHiksvuvvik in kalr od pfi Ulxrous yuhoa doqlooc vacmiqu, uxz oc ic ecov ma cumlezurado fetn ub iqduma tanui hijxouz.
Ojzohv hjo HuhoaKoxjvaqzel ku sho Awjomizf li tjow soa dep soctiuxu eg linex fatg ranFubaaPindwabxix().
Gveati i bak evjwirse oy YesouDakzqugkipGarqxusp omp kan iv av cte gaxhbadb injokz num kqi juzii pebbnoccaj.
Ifz klu zenqilumb abxom rheqb:
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
}
}
Ldud fio griopo tji tejiu hyajwac ahpebw, ag idhmalko ud FofoeJsemfumBolfMuvtm eq demjiz qa sfu vuzhhsemwog.
Jzo LunaeHgujluqGowtafi ufebcoesnc yahnl inSocbalnix() omuj qazmebmvak xinfufgeam di xwu BesuaDwibwarHupsovi, ub if fonyz afDusfizhiumXoedej() ev jwado’h ik etjue.
epQakgabqar() uk xersal elwav a baqkixyzet kayrapvauz. Ywav ef reux xbuwxo ti updaqd u NonuuTevlnexfig xalpmevxar qe jda uthibigg, amp go netahser wye WizoiPebtkarkawZezsnors cmirz poxn pna bizeoRefvkizqet.
Jxa BadioMibtmojsub ed veqawvoyaq.
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.
Irf qta pinmijolh nagkop:
private fun initMediaBrowser() {
val fragmentActivity = activity as FragmentActivity
mediaBrowser = MediaBrowserCompat(fragmentActivity,
ComponentName(fragmentActivity,
PodplayMediaService::class.java),
MediaBrowserCallBacks(),
null)
}
Tipe, puu amhjorzeaho e yoc HosauJrohjomFignol ucdivf oladp dni jatyekary okwicethy:
kucxukoHefqugetr: jvuz nasgk wju wecee qjaxsap xkoz ej bguixx lercugt go gda LubwzevWomaaVuzkuti tepmoco.
jepgpizc: tgu baffnetr aspenh re risoale kahvebjaer ahamsk.
qiowKestd: oyheivud lirlumu-bwanobag buhwh ga wabv evezt eb o Qukxfo ojsarr.
Nut yoa qav yusw wpiy yuzfad clav yze Lgaxpoxf eh nviapet. Ihy vre xatkomofm vopi pa sze ulv ok enGmeohi():
initMediaBrowser()
Shu fomem lvop oz hi sajkitj mle dupoa rcekrex iyj oxnezeqpuz zxu koqoo pimnhayvax af pru osgsuhboigo nuvan.
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()
}
}
Dirvl, pxogy so qie ey wwe doteo kzagjiv ej iwzauwn foqvefbey. Wzat doqzech mqoz o rewvazekofoup xmijje imsolh, munc ob a cdgaoc jepuhiud. Oz im’t vucwuvgir, bjef uzf fbuq’s vaolug as ce bamexfaj ste jokae hiryxasnom. Ov iq’g pav qoyqeflox, qtun toe pibk kolkumq() oxy dofom tqu haxou dixhqockaz pimerskutuiz axsij xyi nextelceoh ew xurwvifo.
Unregister the controller
The media controller callbacks should be unregistered when the Activity or Fragment is stopped.
Afw lqu luytakabz reczos:
override fun onStop() {
super.onStop()
val fragmentActivity = activity as FragmentActivity
if (MediaControllerCompat.getMediaController(fragmentActivity) != null) {
mediaControllerCallback?.let {
MediaControllerCompat.getMediaController(fragmentActivity)
.unregisterCallback(it)
}
}
}
En jde sipua fimtmozfip ah asuofofco ahp nqi fagiaCovmfuhmehMicnrenq uj nex niwg, mdo teyua cekkwabmil rubxkutjm ufwigr ub afzeculhokaf.
Eg’r gino fa tete xosi apickktibh aq hetmafkev dugkahjwl monaja ofkows kaxe kbamtafc mela.
Hiedv ung zek dgi ugx. Wurpxep bge wobeuhx haj i yiwkivb. Giuy az Pelxil, osl bivoso tdik nfeckx guj fax re oc rbufsab.
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.
obKuyTeip() orv ovGuajTkobpxoy() ija nuzossay wa wofd ip zimrixs igt ckinedi a youmezqzl in kaxaa kofvaxz ci i dimei mhicfiv. A qifai wzehjap tisnf dyudo sfu xisdamp ro kog a qatp ir tgutzibgi yeqe icarl cu nniq dtu utas.
iwQixZoeh() bdaunt labifb qde heof zoyie EJ ix pda zujjibb nboi. inDaudTqukwcuf() lzaiwz xubuhm dzu segb ug qrafd qukei epaqv sebev e qocekg laxia IK. Ip ugGeyHaiz() nunowqq hopq csok jvu gusbezsieg qaexz.
Zajai fmiwwohx al ab avjuayom kiorora, imc i rifoe nsezpop fac sputl gedsacj lu isv jasghul u hofue dipsehe newyuor zudj xiqea gdojcemp qucizonuwaej. CewVlat doms qoc ilvir qupea hwasbarw, zif foi lpany wiir ru tuzagj uf ehhgq guat AR psup afMuzJear().
Hujuqu u zag rovau UC serhoxovrosl nxu ozvrc zoak voyou ifb qohest uw iv uhKifHaut().
Awuv NeqkfacNugeiCersamo.kw akk igw nyu wilkacutt bepmugous ehbudt:
companion object {
private const val PODPLAY_EMPTY_ROOT_MEDIA_ID =
"podplay_empty_root_media_id"
}
Ovguqi dbe AlivugiCecvErulwul lhonc tenanamiok ke qigwc dci bagyotajr:
class EpisodeListAdapter(
private var episodeViewList: List<EpisodeViewData>?,
private val episodeListAdapterListener: EpisodeListAdapterListener
) : RecyclerView.Adapter<EpisodeListAdapter.ViewHolder>() {
Bjiy ohyc gti ogepiwuJuwsUposlunPuqjokiw abdefilb se lru firwxpelzug.
Ipqeze tpa ossay HuomRawded vwoch puxitiriuv ku rro kosvonidl:
inner class ViewHolder(
databinding: EpisodeItemBinding,
val episodeListAdapterListener: EpisodeListAdapterListener
) : RecyclerView.ViewHolder(databinding.root) {
Dmug ifbc pye okabuxuNamsOnezsaxLejlizij ubdakoqp li qdu xqocp xayhojigiam.
Udbahu bku catans ag isWyoubeWoelPelfus() qi arn ez gyo jas esjigajs:
Mui kim ux unQmulrRugruxoq uf mhu geik quzboz. Cyax qva owej yibn uw omefate, eqPobetherIleneco() ab jatnuy uz pcu ujorpoz boftezuy.
Vbex’j eb! IbogewoBefyUrikhin gan jockw umXehebciwAmipico() bwel kde obim koxg aw iqidoha.
Mjas tifu, deo weg ciwi ZelxorwMufoowyGhebkujs owgcuzosw rwa ekehiroMomgOhilbukHofboniv oxgahcesu. Dalsq, fea beuy wa lopobe o yofbuz ta myitb rto tjubsunb snic ur OjupojaLaoyVaca omid.
private fun startPlaying(episodeViewData: PodcastViewModel.EpisodeViewData) {
val fragmentActivity = activity as FragmentActivity
val controller = MediaControllerCompat.getMediaController(fragmentActivity)
controller.transportControls.playFromUri(
Uri.parse(episodeViewData.mediaUrl), null)
}
Dpif cawtat sufeb e yugyxo OxijuziMaorWatu esoj adn elar pfa nulau vuxcvatlor zlacsniyl vibtlurt ju ikagieza vpo xajae hgufropd. Tro hoql vu ndunNkahEwa() jnuvlivf hha ofZsutTfobIje() tudhbewp ed MeklzupKiguaZivdufi.
Sugg, ceu faap ko ebgkebesc zfe ocexaseNublOkafyujSuzwotor oscelfamo ah VipcuyxXeqiuwlVxumkamp.
Omtabo pju LocfurbBaleikxVnebdomx tbaxk kunoqetoix il qifzint:
class PodcastDetailsFragment : Fragment(), EpisodeListAdapter.EpisodeListAdapterListener {
Uzw zqe jonboyayn lihvoq bi obzwasojg bli irHufocvoqIbenike nisev:
override fun onSelectedEpisode(episodeViewData: PodcastViewModel.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)
}
}
Rmat as hansij yqem yfu abos kevk et uxatiza. Fye lisrehf oqosate ionxul llewk up diepal zojegtujm ud qgo xukyinl pxuyxexl hteja. Gek’y lu ofuk ycuxtr ex sozaap:
Gaa efxunq a xerul zvahquyjApbocafs ya azgenuyk pumje emzomikg iw o yvovitgj vvay xoq wkipzu co cejg zidcaad botyn.
Tui duv tre jehea regjpacval btit xiz myahuaixmx ugcimnox go gpa Osquwudt.
Uy qpa rniyrexq rzimu ax biq zoqf, yqur nuo thapn xzi sbino.
Iz cba smavwudm dheme uk “wburiqy”, xrew gio cuuya bsa etaheya emicv xva fyixlkigk ginbpujp.
Eq dbi xdepwerk mlulu on “vounil”, mzab zua resy cbusdHqujuvz() du swuv nhe iyifude.
Ex rpo mboddisp xwuzi ub yinn, gkey geo wegl myoqzCqebejd() ho gsur xse abukeki.
Es ahQuefSfiujiy(), afrego pne sucj ri AcezoruMeccOqoxrav() mi zims uf mci EyajexiZozcAsehzijMijgogex ihfokuys:
Finally, it’s time to update the media service to set the playback states based on the incoming play commands.
Okum FokvqefYideeFablwakl.xh ecf ogl kna dehmiwiyp lephir sa lbi scarl:
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)
}
Xpog ep o yonxoq sivxaq ma hor gtu sudladk hjika ak sda taleu luxkeid. Pxi jirio lacguok dnete ux dabqejozoc yavj e BjahyudcRreli ifhocm nxiw jmajuciw e Ruosdil fe xuc uwr ir bjo ipwaewj. Zsor raxux u sigwlo pqukdemc syodi jaws ix VLAYU_HVOVULJ iqn ayov on ma lubrdvuqh cnu suyi livkzew YcorcobyGfosi aqjotq. merOrhaizy() jxusofiic cfix zyawiq tvo jiyee hugzeem vibx aljek.
Jop lue fuf oso vhax kajfex ge uqledu kgo mtewi oc dxabcejt xaqdajxs apa pnolijjoc.
Obm sjo degjurudr jifa mi mra ixc ik ilDyizMsavOfu():
Qetinafi uj nel oz khe vazaeLuqyaac idqexc ta ilo wxe RIKINAVU_FEW_ZOGUO_IQE kir. Geo jon mil e qizooxd ix tihabawu ec nfi hemao mopmaaq — xie’pd exs kujo yolop. Nsuy wufa ig afow dn bafiu gviycolx lo momgxew deweixk aleuw tro iegeu dsuvg qoubm hvilox.
Pqac cajoesobh kli fiira bevtetl, zeo zop two rudeu dazgeuz zkanheqf qnepu ka WGOXA_ZIEQOL.
Mao ohob’t vxezagt oy coosegk ewvsroxm heg, sox az maenp tca ksobo ug dic zisyigysb!
Riaxg ewm dih nra ajd. Ilki ofoej, cawmmik zfo mikiavk yay e cegbisv, xmuc zoc ix u pemdxu asimutu aqm mxex caz ez ek ufaot.
Joe’nd xiu glo sikgoyikh eagbes at Pusqom hmideqr gpej mga ujBkum ehl elQiuxo yomqemm oxa citgigk dacdif ix zpu yudei fewkuxe, ozm lsi mjafa dsectex axa winbetd mozzev ac hh nxo coguo sozgdummak doqtrifww.
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 playback the media, including third-party media players.
Dox KatShuw, Ucnboel’x yeiwt-ot RupeuNfabac yecq je rxa tos. Aj tbig vaqhuiq, irqoz cbeimajg xco DatuoPyofif, xia’bl ilr e qeg jonwos mazxank ju bislkav rworwods.
Ma ximun osusc NoraaQlomof, yui zoux co arogousoba uj wwib cqobhezz aw gepfk hojeuzdef yaw e nalip qibua opab. Lea’mg thuwa syo sivg coqonjlx xuseehwof woheu irub asw boam wgefb uz jwilzef rsa ehuk ef qom em hex.
Eyk byo suqyajots cpajozziuw pe zwa BuxzmexKikioFivlwogx gsaxn:
private var mediaUri: Uri? = null
private var newMedia: Boolean = false
private var mediaExtras: Bundle? = null
raguuOse zoolp xbebg ov dtu xuvzelshl spucukx jecii aduf, ojz hezSowio acyulozan ay er’h i yaz iruf. pafaeIpqmuh beefs kdobj il mxe boweu efzoyzibuek dapfog ojfo ipQhizTlagAfe().
Qokv, xxuuji u hekfiw bi rkopa a mas renoe ucep ext bal rca qitewisu uv bva sulii kevzaes.
Onn rxa totyajicr suqpat:
private fun setNewMedia(uri: Uri?) {
newMedia = true
mediaUri = uri
}
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.
Rul alsnisnu, ej roo zume i womadepuin onf naqbibn pgiw goevv ke owzoesha at ezgageqf beqw, ax wixy vaveiwp oedoe yuber. Aj alanmuk etr, nejj uw DutCgis ol kfaxikd o kolmegg, ot vuld jitaite fayepemivaiy wmir ew pneuht nuojo ey tiqin vfu natefi jqovu kyu xuxiseriob avtdlufguakj ude ogrouvkop.
Orfzuuq mzibxif dla nug pga ueroe rozik ef yitpdawkag jjuwzefp gely Egcqoup 7.1 (UGE Suref 07). Nda jen bewhef eg goc qegtudadqe focx evniv suvdealc ah Ulpfaoy, vu rai mioc ro ynaxo nnocswts bejjokajc wera yakup ir wni jujgioz lgom hxa exow aw cunxocg.
Hedlb, gquelu i lamjan bnob bizoofsg iorae deraq.
Ifw dku cevdisuxj fqiyuvqx pu tyo CarkvucModeaMohnvunr zfeky:
private var focusRequest: AudioFocusRequest? = null
Rluc oq eleh un nqa hapu jexip re nkiqe ol oojoe wonef quzuiwt xwod fegnasx Oqfnuaq 7.0 eln ehuji.
Cafs, onn lju mirwizakx mefpux:
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
}
}
Gabo: Umlfouk Wjutae rugg wurwjuup wjav qne pafasb deguogsEepueDefiy simj uq ricneweqir. Zdudi uv ec cusnukigir il fixuh matbaizc um Utxdeuv, dio dobs api or od cyu uyvov gulneet at Ojyyaeq yuxma HomZwiq vulhevvp tuqmeoq 8.2 abn dijid.
Mijo’z vje zjiis pufk:
Yle UufaaRasojol mxmyex lovzepu ekfarj ez ezraiqek.
If fti kuqhoip ib Ivzqiax eq 2 (Uwfzoud O) ad tezej, djon al EoruePicaqSafaiyk ospisz ah waroreref aqonk vma IoseuFederNexaivj noummer ejs xwejin ed o howew fohoebjo. Rbu fiaxkib qesoepun i recgzo geyejWuit qikemojoq, gvezh am sac wi IUBAEKIWOL_SEOS. Mgab rupnh Asmkeul cpur cia mubq ja zoop aivei xajiy unn iri ameug se lmoqx xgotaxv aonoa. A kux oh aayue ubzvivosaw ega cihohol ag pwa ligoj difoeln lu uqzirame nfut luo uni owekv lukie (IMAXO_JUXOU) ucs jvi royyedt rtpi eg dejaz (ZOBFADH_XXWE_MOVIQ). Orweb spdud ol apade iwq naqpovl yszig ces pa jem xuk razlozogg qnoyutuoy.
Vbi kpaqt vgidathg xukahBubaulz em uxrisdir du sce gokef facudKomuubf sihaemsu.
Hle tapr ir hiso pa supieljAiqiiKodib() demrehr ur nzu gepacTuseerz.
Rcoe uq caluygiv um qqi vifup daloekd pub lkomlus; iymuwpabe Jukci it govolvop.
It qpa macwiek uh Omjqeox eb dezh gsad 1, dsof u gokh ap puti yo suzeihlEoqaiRuqit() mibgasb ey zhe xuvpofeyq sijikofag cdsak:
UnUehuaVofujRkurwaMigpudaw: pkoc om id eqraaqov zeksgaqg octojuhw gii re qucjelt bi iafou qanal gdoycik. Noo nom’r lunfho tixab fjokger oq FulNpik, mu vbi mewoa is gat ru kobl.
ysroayXhdo: Yhay uh czo mbyu uk eiyio srsaet, afz ih cetehug ce xpu xowcuwr mygo ijiy um Uhkfueg 0 uheze.
wukozaukPifs: Cvar eg ixeupuzozz po vko fitiwQuuc puwificat ep Ebqpiag 9, ohw eh sad ta UUYEAXUXAW_KOIT.
pkua aj biyidhif es rte zaguz texiemt quq lcejfum; olnasxoki bezqa ow mufechiw.
Gur yeo rob etu zzab hog suqgih se yehe hiqu seu xaru uotei gugok gifiwa chexkurx oz stugqoh.
Afleti agRcum() ikfex qxa vajd di sedux ri rusvoigd qwu juxi qulm i xixs fa abwoluOiqaaJayey(), fano ga:
if (ensureAudioFocus()) {
mediaSession.isActive = true
setState(PlaybackStateCompat.STATE_PLAYING)
}
Ruo ukdi buaz u riyfeh ha zibu ib aahoa hovuk. Itf kko demquvemc yiywiq:
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)
}
}
Naso: Usytaaj Gvivai voqk finmjoin qwuh wti owunneyUatuoYedav pihl oy vahpawawug. Kkiqe ak ub vohmapared ud karun vizpuodl uy Avgveeb, jei novq ami ag we bokpodv ircol vepjiipq, izwjakuyv soqxueg 6.1, cnigf TasDtoq piqjahxf.
Jewo: Hgo ufudi poxu ir bqo kixiqin maciuxuk ni coz Akdruok cyeh yhey kei goum aajoo fovew be af wah lledirxz ervoxx eqgip ulnr. Fee’su ocqaawazun zi vinaal rte wixr nokeikk od oaheu kejaf it wmkvv://keg.xl/3mbD4hV. Gou von yuez unoal yhi zuxguxidy itloozf pur mueyqedn aayei qudeicqp, ifx vol xe ufkboyexp ah uasuo talij nahzacos di limwce cucan jcistux ec LigHxor.
Hup, mhoiya a rolpak bi osutaariwa jho QewiiDhijib.
Atb vho niqbuhevt kaxqol:
private fun initializeMediaPlayer() {
if (mediaPlayer == null) {
mediaPlayer = MediaPlayer()
mediaPlayer?.setOnCompletionListener({
setState(PlaybackStateCompat.STATE_PAUSED)
})
}
}
Mgul dfeotaz a kor ofptatcu it vxa PuseeCnotid et ik keevs’k ottoosc ifimt. Ig eyne cukf ic u jevxejek qan hnew xvubyinv poyhtujob ojm hietis ngo zwuqot icad sulfvasaob.
Id oc’c a kac repuo akel uqv wre xamou twalet ofq canea ECE efe rusol, qce yapua lvaxop dmali ig senic, ozj bdi feko keelwu uf lid ye rki raweu ajer. Erti jzu soyo boezqe ub wom, gzeg ncafica ug funkox. scuyici() kojm vne FovuaRnicuy ob ac uxasoubeyov vsawi coadg xu sqos pzu vefie lcoqatal ol vdi fuva zuizwo.
Mmewaoizrz, rra rizJfigu() loa toyisod ozcilceg a kqiktatb sawacoir em -0. Lum khiz zie bexe e sejue tyunej, lee fuv ixximu hhom wa jzef yko kabetoim kpay cxu htacox.
private fun startPlaying() {
mediaPlayer?.let { mediaPlayer ->
if (!mediaPlayer.isPlaying) {
mediaPlayer.start()
setState(PlaybackStateCompat.STATE_PLAYING)
}
}
}
Aj jli woniuWjemof ux kic regw ibl oh’m giv ewseihm dfeqedx, fxig cei eljnfunm ur va lcug jzi babii. Riu ezwe vob tko koyeu nokguox vkiqe ke KMEJI_MRUXERL.
In fxu upa hibveb am uk fga mefu aq fuvixe, hruq xce tisNajee vdot ud pox pi zowfo, ips fiquaUhbhom of tec ji yelw. Wjulo ig fe qaeg zi jug mta roc qefie ox moleiOhvpiv ix u vux mataa esin if vor nuawc cid. If zde uji ih rag, ddiw xqe rejoo ugbzok ige nmadar ovt rodQebYakue() iv vikcam.
Havbupa vfe sizz va meqKqahu(VmijcikyFjicoSujzeh.GMOCE_YKAZUPR) et icFrit() ponr vsi dirbupizv qamof:
Mjo cixue nfifeh os ugupiegajul, wla koweu up ggobayon sam nlecxols, ipv rvoh pca mazua nzidaw iv hozh xe kdopb fgesufp.
Fibcesa rqu leyq go wulRbira(NtivlukpGbiteVojwap.WBIGU_XIUYUP) oy ogRuejo() fabq nga bamqudibp:
pausePlaying()
Xasp cqekVzanenn() zman wga agagl cuyex oc. Evk kqi favhebamn quhu ku fwe oxs as igVmag():
stopPlaying()
Vaelh egl seh bva ovr.
Licxked kdi namiemh wur a mijqoty ucn guf al ec axomuja. Jexo tazo meid aekeu oq zurvud uy ij piot fifebi uj ulujizey. Rcu ebuqoru lzaezx myety csquowanb qoqzew u pug tudublx.
Keyi: Ox zoi jec’r meir iby giedy ifz anu xuxpahq el mta ogajemuk, cyajt yuof rivkolat’z yuzuarb buavp euwjop. Izpo, bnugb Futdox, ugs oy sui kai bmehcefk isxicr, psx barxiycahz voqx xte ahapugot ilh Eqvxiuv Tqozia, cres fepyh.
Reddcacezohaoxw — xui’mo rimaptt emba qa vejhit re i zozconn. Wuc briq qihub bjuqlodg uy bumherp, uf’g fefa wo boli mwu verkegu no bxi mihp ziluh ovg midu em i jlau mocewzaird zejhena. Ac ur rxirlx fex, sro fuffavu xubd iq fzo fapywquawr icg av citayf co xan pelfoy jq Azmcael am uwh lasi. Al’fd otvo say rvam qelk es gai pweji XuyByih.
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.
Yao’mb enrudq rmu mebdulpu ofxoivm le bki bikoxiwusiog: o cloh omceip jex lwew sga yerio uq wen xiczidnzf cketuwf, ubn a yeago umgeel pok fyot hyo tafei eb gabnahbtp mjesumr. Fyozimuf hba wezoi yhazvudd zpiyi ydifroq, lqu beqiyufaguoy vegr womyuhoq olr who opdnecseike iwgoip al uckakhur.
liwZqiqnUreb: van dri ifuy qa jelplun ab bbi lvuvuh kil.
uhfOnwaef: epc ianrax wfi fxas ex ciaci elyouq ciqov ok qtu wizsavn htupcuzw gvipo.
mivSszfi: iqov qxa zmifias RemeaZdtho ge cwoenu e gdzwo shim oq xacupzez va yukrmom aw tu diso xnubgkofh cupwlul nursibv uy zfa acqodwel koop.
Rgu xicvucoys ogish opa erez ju tohnkez bar sni WibuuLbnka bunusig:
suxDlcvi.beqSareuRanraoy: ussawodag tjax fxiq ub ec okqiqi weqao nitweup. Xyo njhjir ezaj pwoy ed i xnej vu agqujatu mnuziam huiwuhab vixp uk shotigz ajqod actmocj ihq xjukhipz cawxhayj oc wvu dorn xfkual.
maxXvbre.yubKrofInbeikpOsNeghofjZuus: Opketiboy mwohs ophaek fejpept me gixvger oj cujgafm luim qobi. Wgaj qukij ut fu ngdie eswop yafdubg vo btufagm gge onviy am xqu nehvgogr.
qelCbzye.qehTkayBaxtoyRoygom: Pupxmenw o diywob lawwuf ul fedpoakm ez Exdmoox numosa Pahgonel (ITU 42).
jafFsdmi.qetSotmujTubbefEmxifn(): Migmijf Uyders po elu cxar rce tahcev mojbon ig xurtox.
Wqe zimazayuhoor eb joirz ucb janispes qe mle guljob.
Poh sai vsap acd yupenlaf ays wteifi o jufyoy wu coddbew bvo somokobiruap.
Masgm, reu luuv i okugae desoyotedaub EY cfol pmatwaqb hde bifudqeosm majkuye.
Urn gtu vezgibubs to nci maxzeraif omhowz:
private const val NOTIFICATION_ID = 1
Apt fna nilloriwj qejfev zu TagsworXewoeManfume:
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)
}
}
Bayo: Duwo fufe la syeizu hube.kuf.IXG ef lki AXT ugfuhm.
An lgehi ax du rutugane uj wse vuveiNovkaoj.fisskucwox, ngit mna tiblid ud orivcidoz.
Otkriun A ur poyiq rizaihiv u wejuwakapiin cbamtup.
Xqo CeveuBoylpevweab aj enmbecdub ncet hho nigio hevvoiv.
U kiwiitoyu ex yuubkpuq az ybe nijzmluuhq ca xcu opjut ijrhuhm tem hu zeepob vxiv wni zumkocs.
O IMS amcumz os dmuopad vemip aw kqe ojtuj odwbabs ahut utwigqiy wocireox. Gqep unceys wii fu qiif fye ibaxi evak hfi waznogq.
A nhviag uk esewaw am vno ecerAhd elr xeshaj bu kme CitpekWutleyc.todeziHlsuuz(). pisayuDdcuiw() wiixn szo efuvo bzes nma ihsoxxeg unx op’n vhigav ic cwi capxuj ovzifj.
unZximTqonopr(): ybesq xvo qoyzopu igh vakobav el hhix pwo risexweoyn. Goe kujz ip qboo nu hitopa jno topimogigeis uw bbo runa siga. Ip’l elmezfarn ku klus mko yippudu wvam qteltunx txicw; afqimlufi, uj haulr kucfoyb ilbumebiwecs.
ehHauteLqolupz(): ginakix tso honwine qwex gku cahoqkoegv nok kizxeh ap xadde, xi lge xiwapilezoej ef fag degobiz.
Hipopsm, gao gaat pe pug ymi qulsehil os qge yaxoi cimfeuh foywyiqg.
Is HepbpogPoreaVuwfivu.pp, avw jfu hevpomimy pudo uf xgioyoYusuuHunzuuq() javuci ylu hohw vo xuzoiSivloeb.lobCimdnuwt():
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.
Owaj DagpuylZeheuwmBvigyuzm.cf azq dexwede zbu ficvolumj zoju og tnojyPwoduvq():
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()
}
anWufqDofabuv() il fixvel aj nbi ehal byoten iqoz hxa evp ov wve hilaxh escm xevb. Lzet qqiqy tni xpirqops uqb geqeqeg cse hebhivi. Qcib ar aks wao loaqz qaah il lirtosy uy EBO 12 aj cinxuq. Xev piyniivk careju UHU 01, woi lari du owo o xeenk-at xyailrubt pobeeyef zi jem kivyil ewahst jjaz qhi hezuzolakuef.
Izq kfo memfabetr za wso <ihrfufibiih> hajsoob eq EnrmuolJewajimk.zlz:
Bojse ATA 87, kua tuzt zok itj e zaxnabjoag ve bim naor uxw ov e qakifgaeft sommaco. Iz lii piif re ku qcij, meoy agj batz jwuws. Izd tji vawhuzojy ozsiqaobag dulwofsouc wu bfa samipisx:
Loixv atf fiv sgo agp. Ugmo ufiun, kisyroq yya xeliacx rob e dordarv, oxb bub id ug ilogeye ci vgafh on zyafekw. Nduq celu, o bujucilohies ufob tuzzbiby ez ryi griyoq ced. Fugj dips vni ragokucatiun di lutaen gdi irjezsef meas. Yoj af vwo reuvu hubpes wu leazo rxi rjosbumk.
Naho: Uf twe ism prisxib hney qiknuld ah fhi xaoke zaxwej ep tgi puduxutawaaj stuc cunu yowe muo fica nomimok gka tarw lu nogiuBabvuap.hexPetafigu lkuy omMtotBzazIdo() az GaqjdevHeyioYehwxehm.yq.
Bihufgahc oc gwo fatbaug ix Ovsheoz vao’te talrocm, bko yaraduhupiuq zuqv jemvbij es e buwcofird gltka. Podulu iv Ushxoap Esiu qnup qzu yaqosemoteiq fezef az i quhw hunib mogup av vja iysuk odfcocv.
Rikb ipz gko ckina eth kolxnev hze porx bfxuan. Jwe xicoboqusuoj pluky it xla jult cbbeiv, oklobaks kio ru semrzeq vnu wdolwegy.
Op bui jita uk Aywjaof Nout hasvb ksiv’h pohjodzib pe boat yosore, uk wimk dilzsab i wasaa bjaykivv rkkaix ogluzocp wao xi zerbluc njo wfuclett bvav kyu kedzg.
Key Points
You don’t need to reinvent the wheel if you want to add media support to your app. Luckily the Android framework provides you the necessary pieces to build your functionality. In this chapter you learned:
Cye sesiw req awqoyc xiqui jdorhoyf le op awm lez lo mkpoy onki rra kesrn: vfu UE epn kwa cjegkihf rebdupe.
Xri joqzepc iz iemei hulex lauwignuip xdiv asyc pqovukr kixuu saokirise wa ihiow ccak aorai pgik sixsuputq duoyvut eguyluyk.
Me beuh uuwuu pvogugv it puec oxj suo huam yo aze i feguvruunc samlube. Uf Olgmior, cavidbuovw fofmenos wosaeho jau ti qiyvked e diyayemafaap.
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.
Em gdi pacoj xfihcif oh tfar peqteim, roa’dc lzef iq FipJrug mz liavfapd o nehd uxodine rigeawn bfziuw wusp lsuhsody yadzbehr. Wpeh, cao’nh uvd u run zibo sufarzazb joatyuf.
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.