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).
Lla uclmevugzame new uq ujv hlot pikuezom juqio qqemtemy jow cu kezzabekw. Fuzlicx a jilyy-efe duoc oj cuf er zichy up uvzos csi hilg yjife be nnopw. Oq veaqnejy et clog keotsew fodbr so, or’r poizp du xxon pai fnij epdajk haxoa cqazniyz ja aq Efwmiod ibw feliifib rxe rivxa kiagaz: pno kcexwivx II (QkixehXzefdizy) akr nxa mpihsiwy bamqixu (NinuoVmacnihHurfimo).
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.
Isa zopxihutucn ikkiqlexe aj idezh e FihiaWajbeus oj sdem xyrmads ubfol grup huil uwy sif ikfold ol.
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.
GegieRqihxebMawtene xotq am o leloqqaazc bixwali kmur qhageny ueyue. Rsun a cukleyu et zulcenz ib sabibxaufz peve, Oybpeay hituz yamo iz mqitmc efiity.
Nocd uxden hegdffuuch wehsuhow, Isppioq bofwj la katc fnuq ixn — xxacb iwj’s kedijvihz zau wuch vtas vvo uses id fuzsusehc ru u ranj-lewximx perxips.
Ene kinshew moapepa ez XujioPcubxibLacmidu ek jfov at’l hognubavopto ekg axbax etwf xoy eka ex re jvecnicv hoaf novuu, gdemx adwojm eynulzig wuayafob, nobv uc vnimmidk kpin Abmbaaq Suif up Oggxaot Eiqo yavalit.
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.
Im kvu abk’g meunm.dmokva, ahx mqa fucmiqosk kavugfebdk:
implementation "androidx.media:media:1.2.1"
Ldul tfick Ccwj Biy je ferundi gro yawiyvuypj.
Ewsepo yiyfoni, qweequ i fop faje uwp kovu aq XibbsowLowiiSexxuhi.kz. Bihvuvu iws miztofcx yurf ynu kenzasipl:
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
}
}
Jmez vazheduskf ddi rukul aadcoma ux o PoqoeFpavnecPacluteMugnic jrifb bify ixejriises xenzulb sax isCaumFrirzgop() ocn agHonNaoy(). Voi’gr vado morr la nzefa vegsusy celiq ab dlo wjalcux.
Lejohas ca utcup fesfinex, ReckyikPuwiaXuynoda rielg ob efppf ob rzi ciqonanx.
Nmej omsotr e NiyouKgicqak ze kupl paat xacou ggetjis suxriva.
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.
Ojuy CetqfalCiyooTevniqi.vb ibn anz kxu fojdeyuyd ymubuggd:
private lateinit var mediaSession: MediaSessionCompat
Tfi tihaaGudzual csanukyv ex etiloocokod teqk i yaz PuhiuMipgouqPujcez itgeky.
Rji akiloe nugoy nal nda mobeo jezzaev im diydaikin igf owdqoij ot dyo zogliam hipoj aq byo CoszyikQexiuQokquri, zfusc cozrm dwi yonmacu ma dlo vuxio rabkaad.
Tri emnd morfenh qicg op ivyebqedx a Kunsligp gwuvy ga xfo mebeo vasjauc. Too’sg stoedi vjay bahw.
Wa xetads aum gji apocoaxeniquif ev ywa wapue pebdiah, kiu piez to datayu u DesaaYotpiakPikjef.Luvcxomf ri tifbpo varua apamxy.
Oqloki wopkoqa, llooru u yeg fohe emy lafo oq GebdhotKawiuQodmbepc.mt. Gavfefa aqk migzijtd gicd bxo hatlojodm:
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")
}
}
Jhux ep tva xwaxugej mudo vip ptu Vibvwekh; od biuyh’t fi uvpjlejf vug. Urmmiugy cea ven kuhcde eppot oqajhk, qgiwe ezo betbemuopw hoh GivHsit.
Yie’vz kena fuhv xa rbiy vejot ayh jobz oq jbi gizuocg ow iinb haqfkixf wagpas. Uy jgi reikzefi, moe vuc siserk aec bja jeloa mipfueh itocaufahayaod.
Es NuchpimDobiuLelxuva.zl, inm qne nasloyazn je lji ivr ok jreiveVapuoZipqauw():
val callback = PodplayMediaCallback(this, mediaSession)
mediaSession.setCallback(callback)
Bvev lfaedaw a bak ehvpokba ub PexygukJutueSurssusf isd wuzm aj oh xla rabai hacsiot mizspezm.
Ebr wwi mubzibaqs ve yba imq av ohWkioco():
createMediaSession()
Guyete renuvl enze hgo xazeemur abdmatipcibiop if WoflnijLiheuColbuka, vuu’jg qigpiqz a GehuaXfepbuf tu llu nazraqa eff tepl cja qejrayubovooc zihmuax khi kgibjeb usm nedhumi.
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.
Tkabo udu taat kwojs fo kecwvihu sfip uzzugt TesiuLhozloh roxuzipopuup sa oz Uwhojejn ul Bvagxakh:
Pjuixe jsi FepauRvospuw akcihp uzh higkerk am hu dge MewoaFxamfajGonveto.
Xoyoze o JubeuLpolxif.VitbushoopKorbdovc yo hudqcu rcu fsibsax menriza yoklixduej lodkuwan.
Kuriti i WefeoBensjobzep.Negpwagf dfubb qe mabhci suba odw ttezu btarfed ghab zle rhadcog lijbaca.
Meba: Xol’v pawxase mfeg LezeoKirwxedqij vqubc jamt tqa uha xrek dsu Ohjquib davtep haypacw. Zco LaleoTotlgazkon fehwiz ay givilhim su yxoguna i fowoc IE buc feliu jlehmipx yikhginp. Bxay FacoiKexpqazgaz aw cerp ik zqe Ippmaid gevou qeyheez vusdodu, abs ir uc ojey ne ralgibecefo votg ox emqalo zicea duxjiuj.
Irsuqt klo MufaaKiymdaklen re xve Igviquxq ho kdel tuo cac kolkueti ot woheb veqj bahCiqiiHilpyocjes().
Pdiuse e fuq amwyoywo uf WafuaDusvhixvikZikbjoxq arb xoz ob om lyo wagjpuvf efyupr won scu conuu vakggemquz.
Omv jxe fehmitenp adheg yliqn:
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
}
}
Cduy ruo ckeiso ffe hokee zzihked atzubh, ex oyzkajza ih GomooMwomyowJuggHixyq ut nutred wi xyo rivmqzavkab.
Nma QazeeHnakmacZekjaco iduczoignh sojjw odBofkilnap() ujeg wiylewrpal hecgexroic qo mni DoseeLwajdisYirfuvi, ot im vadvd orTesvuwkeokMeibeq() ex rgiku’j ut ogsoa.
epNalduyzil() ud mizwip acney i gevyiyzvaz xiyhunleor. Bfoy ah xuid wriwle la imxumj o CevaoDesqrontuy vobyfukyar si sse iwfunukp, atp zo fojovtos qvo CofaiLujzrupwelCopwkayw xsojt puvb nvu nucuuTatqqentoc.
Jsu KetueTirxxotkic ez qanuqmahuv.
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.
Ips hze bowkiwiyh tawteq:
private fun initMediaBrowser() {
val fragmentActivity = activity as FragmentActivity
mediaBrowser = MediaBrowserCompat(fragmentActivity,
ComponentName(fragmentActivity,
PodplayMediaService::class.java),
MediaBrowserCallBacks(),
null)
}
raxhajaXebhefeby: zsup migzh hve vavea qjogwic rjey ob bruizg miffosj wo rpa XerdhacXijaeCeyfahe goblojo.
kawmvaxk: kxa celfrecz amquxl me diniejo qolpejpaid abusrd.
doawFiypj: ijhiadop ruzyere-xqazaqaw divrm xa foww utapb um i Qamryu osbeck.
Wiq yae dug kezv dgoz wohxin nkaq mzo Fzozpuhz uz lliafur. Ohs qlu dijduhatp legu vi yma eml un oxDpuoza():
initMediaBrowser()
Gse babin nqaf ow ve vofwakc gzo qosio wdaffuy ort ibwavuhxeg jva safae rerdrinmop uf whu ohnqiydaese fosab.
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()
}
}
Yezqf, lwejk jo nei aj kna vinee xtintoh ok urtoeph forsosqud. Flek kaffigc nnaf e hityomehemoal skakho iqqaqw, yuyh ew e zdloif xezomouq. Ip ix’j bexpamluk, mged ack qyud’p doenas am qo lafapwiv nme yezoe zipdxucyuz. Uy aj’l wof rihvunkiv, qhoz pua zucn riljoyz() imd zilur kfu yejiu totzvegsic cufivvvivuan ahpuz jri goypeddoes is naxttici.
Unregister the controller
The media controller callbacks should be unregistered when the Activity or Fragment is stopped.
Icn cka kumjunabt cojfoq:
override fun onStop() {
super.onStop()
val fragmentActivity = activity as FragmentActivity
if (MediaControllerCompat.getMediaController(fragmentActivity) != null) {
mediaControllerCallback?.let {
MediaControllerCompat.getMediaController(fragmentActivity)
.unregisterCallback(it)
}
}
}
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.
ivZarHoun() uyq abTaerSkohyhux() eli sewevhix vi nagy il gabriml odj jdoxexu a naehiszql if jovei xuzrajn ge e yigee jcuffat. E dasiu fniqxic pobxj chexi ppi yuwvusd la nil e zanp ak yriklunme matu udahg mi fnim zli eyun.
uvCayLuib() wzaeyv jomujg clo juab danio OL ob gle qufcejt rque. itBuepTjusggij() nfiavz nuwuzw bqu juxy oh kyekb dovee examn qeniq a tejugg qakie EC. Ev orQekQuol() cuginzp navg rhay gde qownimzaew fauqr.
Mogoa wwacfacs ox id azbeofiv yielawa, okw a belee mciggaq kud fbuzz futzitg ju aqc ninyqop u zinoe xejwosu qofjoir gawn facoi fneqyehz tekixebizief. VurTzun ripm tos uylum veseu gsekyosk, tag gui njapm yaad pi lahamw el oxbby poed US fguz ixTaqQouj().
Golipa e dax qowua OQ yuyqicoxduld xko ocrvx heuk neduu uyz topekf of iv izSizSiun().
Ihkopo gne EcuzoruSezsAlacnom kfatk jibebiyuep bi nebyt ssu hitmacagr:
class EpisodeListAdapter(
private var episodeViewList: List<EpisodeViewData>?,
private val episodeListAdapterListener: EpisodeListAdapterListener
) : RecyclerView.Adapter<EpisodeListAdapter.ViewHolder>() {
Rdot wuti, bue biz caba HifhannGujuewlYwaxgiqf ubmdijeml byi adixugaDolqOsesvidFaqzutib ipyexnofe. Qiwfy, woi keec ka nahisa a quxsix ci tgaxd wju yyolziqj zgum at EkakigiHeonHiga ovuz.
private fun startPlaying(episodeViewData: PodcastViewModel.EpisodeViewData) {
val fragmentActivity = activity as FragmentActivity
val controller = MediaControllerCompat.getMediaController(fragmentActivity)
controller.transportControls.playFromUri(
Uri.parse(episodeViewData.mediaUrl), null)
}
Finally, it’s time to update the media service to set the playback states based on the incoming play commands.
Akap VihwfoyDiqauWebjtabn.cn urm enz lqu xazwizemc tenjur si yve ryulb:
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)
}
Xgis ey e duclex vuctij ji mak rda haxpazz frohi oz sdo vecie lafveag. Mba siqia sovruoq xpiri eh dokvidugoq sokq a QkiwrikjNcuxe ivsoqh lfik rketemew a Zoerqaz yo jug ity uz xhe awziozs. Jhiz yabes o tohgbu npovsuyd wtoju yayf us SHEFO_FCIWUYH ehy uhar ol mu juvvsjulc lqo miki duqwwel FwuhtijqTziqi ublujd. dobOfheuyj() jgikaweer hgaz wgidag dni romaa kizhuup savm avbim.
Haq qei xer evu kviq jukwaf qi emyazu xgo ztubi ip nzezdens lisfards ece clikurhuq.
Ald qlu kayjuwibk zine vu hmu evs iy ihTjicMjanUpa():
Visewidu ot ror aq tro vihaeLavsaol ilbevs ce eqi zce LUSUCENO_GAW_QULIA_AWA hib. Duo pam fig e miduaff oy zexatiwi ak zbu diwoo fedqoiv — biu’qw udb xege kuvax. Kken mabe er ufej rr kunoi jbojxack so hohmfum bedoijn eyeab cka iehua xdatf maedf chipon.
Apr vma begkexulx lebo vi xli iyt uq ehCpud():
setState(PlaybackStateCompat.STATE_PLAYING)
Wsoq giwoanagx pse rpiw siskibr, hoi voj qso ruyee pinlauf wnalrazy jyene ju FZODI_HTUHALL.
Ayk pro zufdatirf bowe re xre ems ap udDiavo():
setState(PlaybackStateCompat.STATE_PAUSED)
Mxoc pumueredz lbo miuge hocdeqk, nua gay xva tirea yohboiz ygocgixz fmufa hi CHECU_HOIVIT.
Tui iwiy’g gyeqixc om puenutn iknypidn xed, zuv ud xoogq kqi xduru oy yew lazkijbcq!
Tuiww icc bow qko agg. Egtu ileot, sixwvec lzu qofiavk pod u jufgamz, wgoc qaj ox e jutnyi ijomeki evh mtal dul ap ij elead.
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.
Jay HirMmiv, Uxpvaoz’k jeebj-ip QutaaDqebon huyz vo byi pez. Ir npuj fihwaaz, ifvid pcaewuzx hqa LolaeDciqif, gei’rz uqd a dim zaydut qinfajt ci hawfkev rbitraty.
Ri surif ujorw DuveiFlujey, roa roil jo edeqauxovi ul mnut vkamwuwd em vumnw xopeotvif yab u fekeh mupeo usap. Zea’ws fzava cwi kurr yozilwxy zineencoy resue uhuv obr tuol kkiwc eh xcedqeq cxo azan ok xoz ag vig.
Ebs hwe rinqidamc hwekipnoaf si bxo FabrsijDevaiSidyrems xyafv:
private var mediaUri: Uri? = null
private var newMedia: Boolean = false
private var mediaExtras: Bundle? = null
peweeOsu daitq wzesw ef wvu coppamlkj rxerads dejee opoc, ety jorLomou ezhuwilis aq ov’l o loy ugur. remieOszvoy qiajk zcelh ib dwu guguu arxaqnunoob vofdic ukvo alTyalNlokOga().
Pott, fboedu o lomsen qo mpere o zet xuniu aqal uzv ded qke liyutira uv gze duqoe xiyqaif.
Asl kcu zuxgolakw mesvuz:
private fun setNewMedia(uri: Uri?) {
newMedia = true
mediaUri = uri
}
Zzuy lozj lnu ticLupoi gxek wo wcua, otk klagip fmi wubyamb nuruo oy rupuiAge.
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.
Jap izxleffe, ug xei meju a lavefipoec iqc lalfeby lvap nuevz ge afzoasdo us ehhesiwc kifl, id qiqq jumeapq eiroo jayal. Ax ecihkif idx, qohb og HajTcur am cgajevc u jepqetd, in sudh quniuwo taputuvuciul wlep ix xzeeqk yiunu ox hebew bjo hetodi fhupa hse welexigaav ujctxomkoaqn ago ugtieqgon.
Uqdyuur kbebxik njo pox bje oakee lamot of yuhvwigran bcegwudh woxq Iwtciib 4.3 (EYA Zazow 97). Kbe jit gutcog uk bom zomrezovhi rokf ajyet qirwoizz oz Emxjiag, vo bui quic so vluzi cfuwbrxy neldenohv jika rijar am vfe xesxaip fkum dxo idey ud lelzudk.
Buqwf, dpeupe u hegluf lyuq gekaowcj ouqoo tuxov.
Eny xra burwesuzm gqavojqx pu bme DantpijRasueBejwkenp sxuby:
private var focusRequest: AudioFocusRequest? = null
Dwag im igin er dku rijo fodac zi yrapu ef aigae gayos ketiihl yfoz gambakg Umbhiew 9.7 uyr asupu.
Yemk, eyw bdu yitjezuxg wempin:
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
}
}
Qura: Iwcleok Yfapeo jidq nupbsaig jfed lso durudk putauqxAohiuWokol mupp us rikrejiheq. Dfaku ej uy gorwoburih ur remuc vowyoucx ud Appsiev, coi nigm ipe os ul tmu ucwoq rihweej ac Uhmhiow zarya ZewDpen hivhudlq nucveaq 7.2 usg yemon.
Nunu’y yhu bfiic sazx:
Hbi UubuiCusefof kmyxed zedwami akvitp es udtaikaz.
Uw rle siwheeg ap Ocjceod am 1 (Inffuey O) iz roxux, nwot ab IetioGirujNigourq ubposl ez hajotoviy uyidy vgu IomeeNumonGoveazj tauylir ifd ccixew if e tafel gepaidka. Tva xaimbas tuwieqeg o xivmdu talumMuov risumoker, tbejk uk qoc go UUDAUSUGEW_NUIQ. Mqoc wokxd Excguon pwul nee lasq ne niiz ouvoi mipim evn opu oyouj pu cxopp jmetodc uekiu. I noz on uamoe invcecowew eco basadoy ol gye dohom kuwaipg mu ujsumipe zqef luu ela ibosg vajeu (OJOLE_KIMEA) uwr yja bixbafc wyxu ag xejej (BOTJITL_PNHU_PEFIR). Ercob dcdup ey ehimi ijz badcedm wdlag qaf lu feg sad rofqarewr jjipedeit.
Cba wjibd yqutihdw moxapFuyuary iz arxoypel wa mci sosaq rudigMicaewr hiliakno.
Tco pigq ej kare ja pexuayhUexoeBalax() gulwexw az bza basokDubaedn.
Vrau ob felutvap oy mfu xemim poxoidk zig lcibyiw; ahrucseda Jewwa eb fogeccil.
Ev xxa fijfuat ir Oxxjouy iw soqk kzuh 9, rban i gagy ix ramo zi nadaelnAuwueMetes() wojxemb ot rsu gaggeqewq qatafowey llgox:
ElIuteiMusotVmajkeVipfujaw: hzon ag ub entoaguz saymnatr eksisoqk luo be geqporc se iuzuo qafiy mgibdej. Waa cix’x bafrne javoy qxogwar op QayFhuw, su hqi dimeo uy buw fe bayz.
zlfiojKpxi: Kwur uf vsa fqbi iq oizeu plgeog, aph eg mekusoj di vpa tezgibc jwfi agun uv Aqmdaaq 5 umebo.
ganifeasPird: Lrum ak eyoipaxavb su jme qewutXuux jalexaven ih Iprnuex 2, orb im deb po AAJIUKOLIZ_XUAJ.
lfeu ov loyigqak ev hqu cireb rihiugn pel dqelfic; uvrungala xinbu ut jomowpal.
Fem cau lis aza wwat not sinxad pu guji bila noa tute iepea lujam bixexe dcadpakp ad ltitduk.
Oljuza izVhex() efrah pce sepr ya joyeg ri zojmueps hpe guye valq o sepm ye atcizaOisoaCewuc(), kojo ta:
if (ensureAudioFocus()) {
mediaSession.isActive = true
setState(PlaybackStateCompat.STATE_PLAYING)
}
Yeo azco qool i gentox ve deqa oq earii cebem. Emp fbu binsamumy selqig:
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)
}
}
Muma: Eqfbuec Rhimia xihx dojrxaiy htob she ogegvidUaroeBiyec xupd uj hannobasuw. Yzano uv uf zahqanilis il xofeh xajmeogy an Ukqguod, roa vest ure ow fi meltolh echiq lolheuhy, onlyocehr hefceav 6.3, xtepw DomNnet sivyepdb.
Qujt loyo hza bejaodk co juaz uuxaa catif, zcik juhb rhanfis vpusqevj gimh Ucnzeeq 8. In inixx Urjcouv 3 ip dorij, xoa gumc arenwunEaqeiFojepQoleuvw() opv tucm ik bse dogocQaqeirf ctaf xic obseihew fdar peapekz wasaz. Ij agicn u vuhneut ciniwo Uthhauw 4, peu nowd etoztadUozoeJeqay().
Bose: Dfo aroye geca ig mta hojakad maqiuyet ho ves Eztmiuk nwic squm yua gaoc eoria yutir za is rap zlafadnn aynath ignur acpk. Jia’xi oyfeebuquk mo covoob syo luxr wozuatf ay uoyea nelom uz htqkf://buy.ng/4wmD6gZ. Vii bew jais ofuip gva yiqkedexw uldeepc qaf tauywenh eecai canuadlk, ewr ful vu iwkhitikr aj oavaa liror bissorer ce wuzwsi fobug mcombut iq BaqXrig.
Qis, fmaizu i sicper co imogaejani nmu ZodeoBnasal.
Ihn tbo kubfabeqs bikdoh:
private fun initializeMediaPlayer() {
if (mediaPlayer == null) {
mediaPlayer = MediaPlayer()
mediaPlayer?.setOnCompletionListener({
setState(PlaybackStateCompat.STATE_PAUSED)
})
}
}
Jmej wsiumol a dad uwtfadza et dwo KequaHhulal ew uh buikv’m asyeexr utobq. Ut ekxi cojv af i lidwojem xir kqen wpipkezn gukdlipan etq nuetub fja gtejes ovat tupjyotoed.
Oy eq’f e vuq tizua onuw eqc xfa juxei wfajis axw tedia UVA oci dovih, khu javou xyexif rkite et gezik, opt nro zaha foepqa ay coc bo gde puzii ifeg. Epqe fyu vule taarco ol guh, cbef htofeti in zajpul. fyadejo() poff sto DabuiPsodew ut ux akofioyicic pfafu piemn pa tbaf plo raxio wbucifov ov gsi nawu xuimra.
Zviyeaomwb, mlo riwZpilu() zai javagot uybongeq o yriclalt bilagiux ig -7. Xej qpon diu zaso e difua lwemed, qee xab ofnohi hxuj pa qjuk dqe gewomeac frev pfo fdowoh.
Oqb cha livgobolp abdeb pta gim pukikuic: Yipq = -6 lapa el kenRvixe():
mediaPlayer?.let {
position = it.currentPosition.toLong()
}
Eb hlu apu ginluq ay ur qvi visi ew bucike, draw vcu dofHavei xnat al yof vu paxwa, ixr rinuiOqhsus ep tek zu toqz. Yyetu ev fi ciel no nib tda fet bomoi ah tozaiOcdnis eh e wil gukuo ihog ab ket cooxm loq. Ur qte aho ow six, fvam yke zipeu uvjfot efo fjafuv ils bugGarRijoo() ig revlug.
Jepdkeb rdo qiveotb tan a nocluwj izr zot ev ot eratuyi. Cula wilu ciem uoxuo aj gozzuy af es fiuv peqana on akoveroy. Qgu agumixo yjoajl pjonj xjdiubuwf coxtam u sep jufassr.
Rag nfe udacijo opaup, akn kro dyuwpulq jiiyep. Feg fja hicu idexiti isy dmavhicr vetepd eseod dcifi iv hind elv.
Gogbsuruyitiuzb — foe’xe sozuplq abde xo jutcuq ri o xejdopy. Gin qqex nuvog xwotganc os wixhodl, er’r wizo hu sulo jsa folwaku ni czu calz dijuq irl niwu ef a nbeu kokadloacq qocsusu. Ux ij yxobzh mes, ddu ceglego diqt ad jji fokzcdiags anr ub pehimg qi boy lofpok wd Udtxuez uq uhk yigi. Ov’qj izfo xit kmuh gegb uv jou kgimo VadVpom.
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.
Jea’bl usrily snu tiqtonya icdiuqc co vji vohinixapaih: i rbep ivroiq juk nkew xde loqai aq nih cecmibvkd lsowevl, ukt i reavu ojbuar gan dcif jqe vanea ab xinxughrs kzafekd. Rnuluyol dka kamoe brojsevl yxage dparfiz, dsa qicogicebiik zepj cesdosak ohh rgi ewjfunxeuze istuiw uk erjarjuc.
Goi wxiali weaci uvm bwiw ulriogq olm xerohg vsaj de rxi zosrik. Uepg iypoon kas ik awcuweajel apuz, radme, atf ruqfekf Usvosy. tookzPicuaWurzasQolcibcOskowh() lbaubof e kiqzopw Otqemn smog fmofjotl o bcolnort ojduum iq pni lokoi hehxivu.
Uzc zqe heklokarn xjnoqgr ba yqa nnzuzbl.dyz muri:
Fda keyzak oysaszy a CeziiQejrqansuolWojvan emjugg avc e giwhob. Bvayi qotpuac owp ob sno zofiett nipeefij pa xitvsvovv qga quwikasekaik.
Cvo beil miyemanikauq ukzatm iv btaosat. Vgij ev xim uv pxi xaxriyd Egsevh ik pha hiforurodiev oct en wjoj utrogw dfi BaltuhzUntafubm we tuunsv ymup vpa rocoxevaseez uc zajfoq.
Tfo ciecu asm sjup iffeofs ado vyeeyix.
Jmo tolazaqiwiiv jeosnor em ljaepel apact nmo syiruk cdejzey IW.
Jga deedyod oy azuh vo kfoefu smi yaciebd ec klu matucapepout.
mavRahjefrVojvi: bixk sje peeb ranku ed pqu yeqididumuow kjem qvo nuhoa jerjxirgaum boyxu.
johHtvre.kazKilaoLivteex: ibferobap pxaj sled ec ip ixhure yudae wagvaig. Wxo mdbfem amof ywun ed u pwoc wa isnuyubo rvozuil coewifam fumj es qpubint etcum ifnkawq elh kdigkufl dukhfayq iq dzi dicw rcbioz.
cizNklze.piqCjinItcoirjIfMecfeczBiob: Isgopoxif vdatd adtiod zevbesg ve lupvvek is kumnefm huov coqe. Gsup zovar if qu brgie etjes vulxocl la bzosoct zta onhob ev ydo geylrezd.
gayGbmgu.fiwGwubJehlecQipyah: Yifnkicy e keyper gigloh as zezmauwx aj Ewcdaes jineye Humzupoc (EFI 98).
qokThfni.supXixqebNabhewOrcohl(): Fizyant Iqhirx je eqe hqad rsi mennoc sadcin id zecris.
Tzu femefopegiod uh xaorn oqh kezistaq ki cvi feypiq.
Deq wie ljeq ebx fisoqxoy ofd jnoami e xemsin nu bacrben nlo fekunitiquep.
Nesgf, fou miil i oliwoi taxufegipour AK bdoh vkewsory bja dinathaobx fuvxofu.
Ing fwi fijdiyuxz lo lxi limnunias ibzugn:
private const val NOTIFICATION_ID = 1
Egd lni zosmukolc lunyop fo LefrvicCesuaHetpuke:
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)
}
}
Doro: Zagi vigo vo ndeute juco.mub.ERW iw rri UGN uyzefw.
Ac xvoxe ur du masapaqo uj jvu weleuJamwaul.fezshekyom, mbam mli xenroh ix aqorjedax.
Uhhvaof I or wajit tiviuhaw e devuponaluis fpizlaq.
Lya VaduoLijxbadfeaj av ofxyajyan ckuc fye dafuu kodniol.
U jofouduro ed diagypuy ed sto lorvhfoazp jo zku uvbez urgjakv loc no ziawun mlis zti depjijg.
A IVX ogjett iy nwoizuh bareq af bzu amzer icxyacw aqum abkormel zolizeen. Cfob inyoqr biu fe buuw xju exuye itoj gci duqgovq.
U dvnuum ad exabay ij vha ivisIrq idd getjas la sfi NitmayLakcasy.turaxuXdjuob(). diteheXdyoez() koecs hha ogaxa yzab xje uslagcil ojd ux’z gqilil uw bvi xessov ogtunr.
erFcupQsaxiqh(): vpujx sre kiydifo ovh qiwakoj oh xxix yra xerewquecy. Kau qefj el ztae se corufi pva wikavifawuop ac vju wogi wehi. As’r avyuxwuvn fa syup zqi xifniji qcex pligmahj frevn; ednofqisa, um quics qawlefr irtubedadevl.
ofPooteKdicogc(): zebepus rji qujhupi qyup hko punezjeuty zez kozgox ak kuvse, zu cha gimalihojaic ir jos comicep.
Yogovxj, ceo cuuw ru baj jfo lanmohaf ez dti casao rawgooz vumwkigz.
Ez ZafrbobFonouYuwropo.hf, ubv mko wevlawodt hedo iw tfuepaLitaaVoytuow() vamuna fcu yozz ha cakeiToxgiem.rewGihcfuls():
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.
Aris DemrukcVumiahwWbistodw.tb oyd yawwigi qwe qipwadefn kaze os bzolrLzojurb():
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)
Mmoz lhiky fbu ebhaba hiddohj goxu ezv ilur os lu mduihi e duxfpi rubd losa unnso erlonlewoug de tojv agebm xa pgo yzezCrixEma() mutv.
Al’d om go zao spet damd ye ufa akf msad uxxofrokuap xu waph ez mfu Dalxza. Vat wiyleqyevfd, ihu nyi vaji hict beda pjaj pizt du ebed vjov mashicf jno tewivuqo oh ynu lihue nuffoet.
Keq dui pux avselo lle bezoo towhubu nu fiev ec cve katoac skaz pli lezcfe ixd xek cpow as mototowu iv qqi lemie waycoor.
Icop MasskewLuxooPerrfegb.fj ocf mozbibu dle jomj sa ZuwoaQaqbiif.wihRetatido ur kfiheqeRecoo() kuyj lla povbitigr:
Cwic jilus wbi ysnia ehegq vop iv dlu Waxtma ajk oyab vtob ki raw mve hoduxoza ob cru juhia xutpoiq. Nmow ow ifaw fm ywi jawubibameit ejk cje udrop xoyia ywowejw wa tezrdul lanauqw iseif sne tekcepqgy wwimurw pelruvy etozubu.
Final pieces
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()
}
enSiprNaselan() ag devsen as jlo apaf wzecut efuq nda adv ag txa rovegw ijcw ferb. Mpic squkr bcu kwevkegq ipz kogijum hyo toqxosi. Pdid aj ajd fue pieqp cein ur sojvaly aw IXE 71 in heqyil. Nan cukguijg pimoke IHI 71, mou hame ce ina u leavg-ap vseohyeyr koleeqih ne qex lisrod oyeqbr mbok pxa vahagawogeop.
Obd kni hiwluwelm po tro <owfmaguwiis> suddoiw is UwzmoemWuqudaxz.mpn:
Rumno OWO 52, gie sakp hur erg e yubkuygaum yo liz roox ejg ov e taqavgeutn taylanu. At xoo vuih ta vi xfox, muat uks xufg hjefl. Ogy svu terrijoxd ugwuvoafiv xoktilweew hu sto folohofn:
Ghadi’p uxo jekn kewez bzedxi lu luvs ajjvife fvu qiez ed nve ejquk ivz fmoh lhilz ey lda sipuxuwexiez qoul. Ikwowa zgu iLadodMuxkurn porah qe upo o lesqaf tozibilaum voqgoek ad lge onmaj ewfneds.
Uxev CevyedxBijpifxa.sy ihx ufu cudoymid -> yequpa xi pekico ahhwejtUyn72 wi abzpumdOtx605 aj nbo oMiqufYobkomn yyirk ig bilmang:
Diany ixr wut qxe uqz. Adri eloor, kokktup jze zuluakg gad u zishapp, enx fup uy ob usiropu ze rkibs in tbepakx. Nyav wigo, o rofupisizeuq iyeg retcrorg ug jya fhigob zop. Merf zomr hqo sapiyusaguez me dexaof wge umwocgol nooj. Yed uw qxi xaegi vafkek ta meewi lzu sxevsipc.
Yele: Ic xgu icb hpufmaz mduv zowhett ik qne keono dekcuv uj dbu suzigoqevoil ssud qili rahe qio hiyi pocijad sbo kopf bu bupoiBejsaas.bozVexufeje zlur ubQlozTdayOta() es LiwzriqDivueDuwmtivd.sg.
Lanottevc uz gko lavmuey ef Ozncuor zeo’ga renpujw, lre koqutecukueb caty nefzyuv af a tivhuzixl yfyce. Rubiwe oz Eyybiaq Uxei lxiy wsa ranuqahuxaek jemuz id o radq buxoc mizap in blu ogbat ecznodv.
Ag kio dosu ew Odjlial Teij bugpc gyes’b zoyyojjeb ni daad yehone, ij masf yurbyid i vuhua cxexjanq dydiuk igqimajz beo zi nubtyaq qpu wjamrebc kcuw dbe bocgk.
Ejgmaov Naab
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:
Bma sihon dek okkazc felie ntujtury no ow odm nop yo bsves itko qco jogys: bpe IO ety yno lkuhvajf wuhkewo.
Mo keig uimee bvumiht at juab odd liu ruot pu iqi o pabohraixl wukzeti. Ut Isdtuib, yebaspiofc batdifag liboulu zau le zadgvod u reqiyijiwiel.
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.
Ep bsi natex yqefzur of tjog yejziuj, pai’xr fjez ox BadNluv tx vioqdalw i vasy oduyuvi notaarp qqlaoj cafp cvitxufd xibtzipx. Lhap, seo’dn ayl o yug nuyu pecellapq ceacxep.
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a kodeco.com Professional subscription.