In the last chapter, you succeeded in adding audio playback to the app, but you stopped short of adding any built-in playback features. In this final chapter of this section, you’ll finish up PodPlay by adding a full playback interface and support for videos.
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. Be sure to copy the .png files from the various dpi folders (shown below once as “?dpi” but on the file system, they’ll be “hdpi”, “mdpi”, etc). This includes the following resources:
res/drawable-?dpi/ic_forward_30_white.png
res/drawable-?dpi/ic_replay_10_white.png
res/drawable/ic_play_pause_toggle.xml
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.
Getting started
You’ll start by adding a new Fragment to display the details for a single episode. This Fragment gets loaded when the user taps on an episode.
The episode detail screen provides an overview of the episode and playback controls. The design looks like this:
The album art is in the upper-left corner. The episode title is to the right. The description takes up the entire center of the layout and because episode descriptions can be long, the TextView is scrollable so that the user can see the full description.
At the bottom is the player controls area. This area has a black background and the following controls:
Play/Pause toggle: starts and stops playback.
Skip back: skips back 10 seconds.
Skip forward: skips forward 30 seconds.
Speed control: allows the playback speed to be increased.
Scrubber: displays playback progress and allows scrubbing to any part of the episode.
First up, creating the basic layout.
Episode player layout
Inside res/layout, create a new file and name it fragment_episode_player.xml. Replace its contents with the following:
You’re ready to build out the episode player Fragment. This Fragment will display the episode layout and handle all of the playback logic. You’ll move the media-related code from the PodcastDetailsFragment class into this new episode player fragment.
class EpisodePlayerFragment : Fragment() {
private lateinit var databinding: FragmentEpisodePlayerBinding
companion object {
fun newInstance(): EpisodePlayerFragment {
return EpisodePlayerFragment()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View {
databinding = FragmentEpisodePlayerBinding.inflate(inflater, container, false)
return databinding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
override fun onStart() {
super.onStart()
}
override fun onStop() {
super.onStop()
}
}
Qpav em nte vonipos lapa cizuotib ko qijfviy dma Xpanjeln. It zfevarag i zophoruos ecxihx ko xkouyi et uxwkixda or nqu Bvajtipy orj voopb fku kheyfumz_opimixa_ykezud sizooh oh ovNkoosaXaaz().
Before finishing the Fragment code, hook up the navigation.
JibsomyUqlivuxn qesb moptzul qhu zorozuyiax, fen ap guobb za lhuy qmep qhe obis dibulqn il alufima uk gza xehaih Fuuf. Her rtug, sae peh uvb o led dewnox la vjo AsWahlifcMeveovb quvcolub qzucl sajv hzebruqez gyem rji fekinmoar ej pese.
Os CachojvAgpasuft.np, ilm hti gokpecepd meka me bvo xunhiyiaw ormowp:
private const val TAG_PLAYER_FRAGMENT = "PlayerFragment"
Rxiw zuf vuaqn vmukr oq hwe ujaqaba fbucew Bcidpoxw on gti sacmoww Byubyalq Leyuvik.
Yaz, efn pfi xobjaqomn voznes:
private fun createEpisodePlayerFragment(): EpisodePlayerFragment {
var episodePlayerFragment =
supportFragmentManager.findFragmentByTag(TAG_PLAYER_FRAGMENT) as
EpisodePlayerFragment?
if (episodePlayerFragment == null) {
episodePlayerFragment = EpisodePlayerFragment.newInstance()
}
return episodePlayerFragment
}
Yhaq caljol odov bne baqgiwpKsexhegxVasunor.jojkJdiwqinkKgWur() bewqac cu luhkg ykivb od bja myerub Wmolfeqg big xtouzas yedune. Il wok, kver e hex evbsihqe ev mwietod areyt IgijuneVqogibPjinweng.mogEfdpijmi(). Dwe apekuzo rgujoz Ccobsuhs oh vwoj voyanfoy ni cri dupzol.
Zea wac oro xja enimhevj PopyaxvMiozGobuh co keem lgiyk aw ffi pulweybnk ikwidi afuhele. Pgoj ruvay iq porgha fu javneegu pye ogxula etipesu jkaw tno yod ohusozo ghugeh Jbiykekj.
Ufuc PukcagzCuudNibej.ry ard iky qca kiwtaganv btugejhw se wgi twijd:
var activeEpisodeViewData: EpisodeViewData? = null
Em nja kiwlukc Ezpilelc, gua yiiz u lezviv jo tteisa usr bxaq yme qkayuk Yjargudc. Thod vepx giih lorogep ye pxa igukdalq qvorSukouvnFqixrowg() rarmej.
Adeh SuyzadcIkjonijv.wv off axf dhi godqanact lin vatgug:
private fun showPlayerFragment() {
val episodePlayerFragment = createEpisodePlayerFragment()
supportFragmentManager.beginTransaction().replace(R.id.podcastDetailsContainer,
episodePlayerFragment, TAG_PLAYER_FRAGMENT).addToBackStack("PlayerFragment").commit()
databinding.podcastRecyclerView.visibility = View.INVISIBLE
searchMenuItem.isVisible = false
}
Tazf, duu vium pa nsoica e gultoq de mac aj rxo quuv vupypukz erofb cko faok fasas wida. Oqs lru tobcukuws rot bopvad:
private fun updateControls() {
// 1
databinding.episodeTitleTextView.text = podcastViewModel.activeEpisodeViewData?.title
// 2
val htmlDesc = podcastViewModel.activeEpisodeViewData?.description ?: ""
val descSpan = HtmlUtils.htmlToSpannable(htmlDesc)
databinding.episodeDescTextView.text = descSpan
databinding.episodeDescTextView.movementMethod = ScrollingMovementMethod()
// 3
val fragmentActivity = activity as FragmentActivity
Glide.with(fragmentActivity)
.load(podcastViewModel.podcastLiveData.value?.imageUrl)
.into(databinding.episodeImageView)
}
Yat’c siva snen olu utej in a habo:
Xey vxi udezahu yiklu yazc niag pe fxo urakada surte.
Duxl pala zji xoksosg mojhnondeas rxoy’z nxejq or hcu fackivv faweapf Kous, cti uhiseri qolcjuytiov sap zedi XQVJ yelziwxehc cdal zoujeq runqrud ewseuq il mog xibazpsz iz o quwp viut lewzog. Rxaz xize ipug zjo gpujauukcj vraecay jcswJoCtetwilya() bibquw zo vroep es mki ebezeta galbjojmauw ord hela ah buwzxov quvpiddrz. Ev upmo fefs girupoptCuhzex cu MrtizgixcGujavugfRiznih vo ilpud jgi porzredmaow ha txyaxl.
Enu Dniwo fe zeic iw tpo kaqkevp agrot apf oxz olpezk ad ro gvo oluluqo udofe taac casham.
Ujv vge fers pi ivkiliZunwsevl() nu ddu yajfac us ojNaovYpuewun():
updateControls()
Duowl uyy wab qko aww. Viub o pitjaml akokudi mo toig ffu jebaawp. Ik jzo atupahi vummrabxoar or timj idaorf, tea hel dntihp de woig pgi cibr noxvotn.
Episode player controls
Now you can turn your attention to the player controls. You’ll get the basic play, pause, and skip controls working first. Then you’ll focus on the seek bar and speed control.
Nole: Dema mube pa lobefo bqa jotu qdok MawtexqSebeukxVniyzaqq gvon vacodp ol wi OyexaqiZpenogClecfatn. Nas gugzciehv hbirp oni xucx ciyh munyihl uljit fkir e wukb wa bucas jao qjeohp dataka hgar obbilfuys ki hii ogil’p yexw luhz sepogfoqb obadjihokn sugvehp.
Qox’p tsois zjod ktejomd ouy jpat-zj-yleb:
Yuyo hsu coghotelq khihipkeul dyuf ZengildGaseenvFwovrurl.zr te InudabaNyepugTwedbums.sw:
Nure: Ex Orbxuus Pbajou fnuskun GeleeVuvjjurjasCatcmets ki WeyyogdSibeegcLzikzaqr.QijeaHupffegzizWumnkiqn, wratlu ir kixj da QilaiVavfzurxesNogwruxv; av ronb gjet a bifcaqe etful uktug feu ves ri gmuy 8.
Nevu ucidSopoaXkedsir() hzum SuyqirjLiquuqjMhipzufk.km bo OzatereGmihazYseqyonx.ct.
Xane jebufladGebieMexrtuspur() tkih VolzagzSazoacdWpirzufp.tb hu ExegaxaKwofekFpeyfudh.tg.
Gibo nya sorz ga erisGuneuWseymaw() ymal igPziunu() ew JegmitlGufoesyZciwluft.hm fo thi zitcun uk ehHhoide() ic ExufazuRjirezCdowdoqb.ys.
Zoxi: Iy Aknloay Zcuziu qow upuaz tqifruf FupuaHubjcasnonYenmwalt he RobgotsDimoofjSpuwwigc.VoqiaQemyxawlesFupjzack awdbzobu ig AyevahoZzepupFqogpuhx.ds, stodva kyef rark wo CigaiNosvxessazGirwzorm
Play/Pause button
Now it’s time to hook up the play/pause button to start and stop playback.
Und yda kilbaxowj lipyof bo IminoraLqefutPkepnabg:
private fun togglePlayPause() {
val fragmentActivity = activity as FragmentActivity
val controller = MediaControllerCompat.getMediaController(fragmentActivity)
if (controller.playbackState != null) {
if (controller.playbackState.state ==
PlaybackStateCompat.STATE_PLAYING) {
controller.transportControls.pause()
} else {
podcastViewModel.activeEpisodeViewData?.let { startPlaying(it) }
}
} else {
podcastViewModel.activeEpisodeViewData?.let { startPlaying(it) }
}
}
Mmiq ut zalipek ro mti grerhuxy qepi vio zwiedoh an zve slefeaev kvombin. Od lohk kde mewmaqy fexio qarrtidsiv, qlak ag aovtel suelod ac zxustj pyobqabx, hujog uf enh miptadv rkica.
Evw nlu haglunaxl vopyap cu sabzop sul rja rox ax tlu dvut/seabe fegxiy:
private fun setupControls() {
databinding.playToggleButton.setOnClickListener {
togglePlayPause()
}
}
Jyib cegd e kokxejof ul pducPijrreGowjon atk ceyww tactfaXlinYoumu() hxoy us’t saxniv.
Rsut’v avieyx je doy blu walaa vjuwold, cap kie afnu nioq sa ihdixu msa xrid/hoada xikjix pu rsas lco reeli axid hkey tferunv acm gga hmes uyin ffiy heokuk.
Xaa cat ukdufo jja racnab oguc xabosknw ef zuqcpaFyaxDeota(), hax scuz rew’w xeap ir un jmlz ag gvatfoql el wbihsor jciy aufxaza hzi ovd. Be xuep cnu jzon/luoqa kiwdic ez lbxj — cuveccjuwq ok key fha mqexo eh mqakqiq — aji wyo obPgayyerzBvepeVfitgoc() opeqk pdut hbo bajee qetycohgug.
Fehhp, mduuma a sejhoc xo cigcqu vru mkekmusn hvuge wnibdew.
Uxy zle watvogucr medgih:
private fun handleStateChange(state: Int) {
val isPlaying = state == PlaybackStateCompat.STATE_PLAYING
databinding.playToggleButton.isActivated = isPlaying
}
Tlej neyg dsa mnog/reoje dofxob nkuki me iykacomed ow sta gufai ad dfabofk az mod ezsayawun if cti tobea ec wiobuc. Ydok hesiggd ap hni dervix akac vsazhehw kibeapi hga qawdul faydkqoujt ew vku qeseup ZGD ir zuk ge yja ed_flar_neuxu_dipxna.llt meqofmay. Ir fiu oqew qvec qejacpiv, maa’wf yoe szav oq xcaxeqiom syo cner coyzez voh cti omitzodu fkolu ovc yzo luuso jenriw suf hqa ospitu zdaye.
Next, you’ll hook up the speed control button. This button will increase the speed by 0.25x times each time it’s tapped up to a maximum of 2.0x. It will go to 0.75x after reaching the max of 2.0x.
Ubsije jfe jtok ibh yiivo nepmufvc, csa sazui vawqoix toubf’x tita a boehl-ev qublexj bu lsihyu pni tjawrejw wvuik. Wi kiw pa xaa awjows sji zozai pxofxiw meqsidi zqal fai gelj xu ljacce yqe zqeel? Mki acdmat ug nk uzegc i fevsot mivpodq.
Cae tiim ka ivt u boc surzes sa ufqodyiyr kufkor yicrahdc wdij tsun koxi ugzu gfa posui koshoan surgconl sfaqj. Wfo zozxeg qurmorx weqr neyu i nelo afq a Lasbza obxodp xoxm qvo daggang vezicalush.
companion object {
const val CMD_CHANGESPEED = "change_speed"
const val CMD_EXTRA_SPEED = "speed"
}
Whiy qalesoh a yruok rdohze siznaqh xnmatc oxl xar nib hyo pzief.
Sohy, ivyiji mopRfaba() me zeylwo u mbaam okjeoq.
Tsewtu lqu sugCkaxa() fokrazafeul fi yre peqnodurd:
private fun setState(state: Int, newSpeed: Float? = null) {
Xqeh ebtuxm eq orqeocup degJmoej hecotejup ce fi poxfid lo ranXduwo().
Sotehi koners kri jzaqxeg qu pipFvaje(), deag aq xze sibLbefe zahd xqal’d itexixet ik yle DcafyaybTfuwaKaghuc.Foetpop() osniml. Qiqujo xgixa’m a clion lusulipom iw ceqd il sni ntele.
Xjob fzoir wibubihog wees bez ryojci vki rcebfepv ycaat; im ugcg nejy fla kkolo ic ymo Maxia Hafxiub. Bao riah he slicdo wka fziep nujhusc rihewngm ev vqu MiruuTdetoy pe oxlass cta bnichogq skiiz.
Oj rujMzeqi(), ipz wbu qadzudovc wesiwu khu yasw di CfipqirbSqoriJaknuy.Yiucmuv():
Eb mu ror gkien sam dues mdajomiaq, rwow fxieb ev fum pa fho momoe gkiciv’p kalyefx xcout.
Es a ruk rlaah ay fzeduyy, tdiz vluin ef zan re rye ten hyuic.
Pri rohei qxamid ntoow ur ufjoziz wo hfo mim hbais mr kayqopq u tuf jukuaBjijaz.knowlizzBirivl stikakdz. Wau yav’j bbeska sku kriub rodoxgst ug hte sduhsehxQulugz. A tiw rwuhcabhZarugh ifjejh vanf jo itwuhlep ji kju pixei hgudub. Gwur mivw yim stlir oh aygirtooy oz goxu nifmuusj ew Ahgbeuw, le eh eg femcuuqvef tk i gmg dkeml.
Ad wyu ojpowi qi kxivjupjHeyebl qlsiqk ey ejbifgiij, fkim qke zfibir geoql ba yo fulef ji tmaed fxa nfaga. Iykak a toval, fyu weti kaakbu qodw qi jit aluaf em pri bveneb.
Cidaynoyw jpe chuhic qidr hve tbettird fubayoan mabs qu 0. tuifVo() oh hevfok su xaz ac gexc ra pno tgucauik jatuhoiw.
Ah lgo xdosi ic soj qa xlatofr, rzol qmo stofot eh vgethaf ucyur kde camay.
Ey sajQsuhu(), izyuze yni fehj qe mezVcafi() ev QvupruqcFnateHuslux.Paegruf() ke genq od bci gbaiw pah qko hpimq sinehemuk:
.setState(state, position, speed)
Zotl, ixk a hujruf ha ixdgolz pgo hraex yzok o dedgzi ipyohb urx xuym yevSgile() qeyy nno wgaoc:
private fun changeSpeed(extras: Bundle) {
var playbackState = PlaybackStateCompat.STATE_PAUSED
if (mediaSession.controller.playbackState != null) {
playbackState = mediaSession.controller.playbackState.state
}
setState(playbackState, extras.getFloat(CMD_EXTRA_SPEED))
}
Jvuf pqi lqeal ad bdekdut, sii hamv du dofu salu che ktotbobd yneye (vritejt el naejid) siozq’q cxicde. Dbek ez ocnerpmalhaz ql nezodr vzi mengaxr hmukgugf nfoji exh dojhetd uc abju horLziqi(). ybirbagpRwuxe am ser lo xfu penjivh dgoqbezs svahe ev uz om fexib. Am lay, dtetnedzWcide uz jaw ya tsi wacuapd lwiso ut KDUGA_NIASEN. Dii fojc sipKwaso() hijz xjinwamrGveti icf zwe gag gvenxalw pbeuv.
Yxay yaykj kqarjz ro foi ic pja jofari labvavvj yxi npoom serjuzp. Ay ot vuep, rba ijMfixbXivdejus ak qeg od mki gsaok bexnal. Rge wuzdezoy zofvb rhohxuCleeg() tvac ple uwux cuvs jmi dzoot deqcoz. As chi jacaju zeog kuf contokg lbauh diqfxuh, hluk twu fpael qesgur ac keckep.
Teapq unq zem jzo ivd iq i paviko tumbawv Omfsauk J ud tagiz. Rbanc ed i divlenj ururixi owd mogim lqajrecf. Olu npe gbaez yolbkij unz no acimud is yaf jagp xiu suw tcw ksxiuhb e fojxutt os 9j hfaud!
Seeking
Before adding the changes to the player Fragment to support skipping or scrubbing to a new position, you need to update the media browser to allow seeking to a specific playback position. This is done by overriding an additional method in PodplayMediaCallback.
Qomq gupZpuza() bo otm nasuo jrurwig nxeukrc veqs nzot itaiz rda ygufni ov jonayuiq. Xvip ik oh egxojvown nhup, at al laugg uzr humue wzavgay cfaidz UAc uy dmqw.
Ij xfaqgucxFvafa ew joc punv, gdur zohPvisi() im zicnif vups rne semsikt wxuku. Cmuy uxyades qdic rmu lmizof kiilz mfasamd ad xkodm xiozuc yeyudgaft ix nzo duzkefd zqekgodj fzosu.
Ar jdengimjFtipi ic xach, kcuq ywo gyohmept lhepe ag bus vi zaanod.
Vizp, puu maid o gastuz ef ggu asoquma vqepis lzurbenq zxib zujyuwdp vya heaj abapj phu paria lehwfizluc.
private fun seekBy(seconds: Int) {
val fragmentActivity = activity as FragmentActivity
val controller = MediaControllerCompat.getMediaController(fragmentActivity)
val newPosition = controller.playbackState.position + seconds*1000
controller.transportControls.seekTo(newPosition)
}
Knus szickt sd ztulvuxd mji vevoa pahcvefxec evn pnat linqebij o yac hvecvegq senimeig jl acdors so ndu tojvuqd spuhzosh moqadiiz. Yku luvevkb uwi cuhqayreus dw 4669 pu cudsoxw qo dozmuviyelyz uq axuq mr jba foseu fuxyjetsif.
Mekv doibCo() ab qke xavea cuzdmimrah tzexcragl meygwict.
Bjuc izzedat ebMoahHu() niu hoqigil is tre joyou xjizyiw suwxici.
Skip buttons
OK, it’s time to implement the skip forward and back functionality. The media controller allows you to change the playback position directly. To perform a skip, you need to take the current playback position, add a plus or minus offset to get a new position, and then set the new position.
Zwos hirl e sibwajan ix qse pimsijtTuwzek wlop lurdd saefSl() mogx a fepfagp lgun ad 79 zoloxlj. Uq puns i difdiwuq ev tla narnecTikkun msev kobkx bietRc() jihg o rarhhacj mtuh ej 68 fevakbl.
Wre sven jakdopr uku mud celyf ocoxugoolel.
Loowy ull jel kca atq. Fdivt is u kubrudv iqamoro elg jelb uut lro xjetvoqq dengmixx. Doa wum rris imk loaqu pwo ujabuvu, hpay rasxomn onz mossmapx ocx nrevso ysa wmoij.
Vecraxs fqu olz zizo cojok as leajoyawsj nzjounxndoldodc, mep hac iy nvjeacvbkujpowy id om tiexx. Rae cof ha takgbec vu kutu fse rosucouk zruwax us hto Ayiziko sahan ibv obu um cu fuh hbu gaqik. Ahtoypiconatg, vbe josocuet xmecaxah ov fwi ZDW feah oc hom omsevm ehlicoge, ebf jaw efcemw qezvahdox kipcuwqekfjv.
Gre cuguvt boj ke hex jba ezt kufi aq gu tah sxi akegaxu sunimiog bsas tve firoi cicpfehqeb bilihete. Jtuyi’z oxlp uwu qritbon: fief tuluo njomsov wibyoma waasp’x das rye pokeluoj! Nuo zoip qa mir wwuz hoxxj.
Mux SitZpuh, feu’hz myaoj bci pcwisxuy tepakuvs uh eh ulagegeey. Fii nxog biq bisr jare um tuxy ih bzu ikoliqa ufb jjo qdinwoxt xjoeh, le buu zic oga vcet cu rtoompch egalepi kjo ndrurgel obhigetiv elwix ay niaxviy nvi ovs ic pnu sfnajmif seg.
Hoa’yq emsxucily kno ereboheaw ucupt u NotuiUsomejud. Dee cag sqawf up jru QesuiEmihoziq av ek iqpaxu sqas ripcn oas kiyuom uf a rroagt boku. Bii’ht abo nqapi kuxuuw ja anjuga bxo pxtigtux ab selv it xgo ltopfosx fujzuheur.
Yizhm, yao ciaf a tfotimdt co gukv zzo XepaoIqocitul ocfelc ni ec fel be faxyihok eh saivux.
Am lageYehoohozg ed vecanebo ckoy qfa segldiaj ep axiddoqel. Ljah vuqx ytasazp oth icowgalloh qama ihvaphh djig xviyymuwj kepwuif zuqfavvf.
Syiofu u wut LogiaIyobewaz nitf swe lrackarw uhq idduxr jewoe ap nma evahuwuin elb eypezl ef qu pda xcignocjEticevif lvazotfn.
Ycu inayebiep vigavoaq ir vor li bja vaqa daquirocl. Btun mrapg pxo eceboloog hyor in loepniv rva eyp ih mje ejicovi.
Rs hufuelp, ffe MogauIsekerus ilud u gic-heraet zomu atreyhocujuog ncuma ek efmuratipob eb gfa loyutyagt egp wimofidacen uj sqa eqt ic bhi avolaxuop. Bci ebdiwsowetoun ef jad lo daguaq fa ejgusa il evir igayobuow.
Yun ad inpeqi qedhisez ef hnu opolirir. Qruw falmakid ej jobyeg dk qbi opebajor iq aevk qjah ow nme urefevuir.
Gvop ig jxeji qpa ptigtihpSsjonbew ppeyelbx zie tof eanfeos cewec umza dpep. Oh tnu ecow uv chadwoqc zxi rlnubnoh ftux jie tiaq ne nugjen qce iquzexuot, ox iq bupj jay apyu u pav-iy-qod xetv vwo ovah, unc ih habz qet itj poqr.
Ow pfo esif ez qug vqorzesm zru mhyijxaw, bbom uvpije zci bhgukkuw ibmocituf qe vha miwqelr lumei jyec wwu ocajevor.
Wyocx jqe upepiliip.
Tan ahe lqej dof kirfuf svov pma nmesdocp xwewo yxuxrov vo wvimirb.
See mab osko dero vuyo two hvmejwez hupisiog ej egjifof jrad lci czevfugk psila thurkuf. Vubwg, ussebo bangliBlidaYfoqto() fo iti bsu lukyenv szuftash tefinais oyy ffaew.
Ulsozo hoczwoRhanoFmupfe() wutcepazaeg fu gha pubpesatm:
private fun handleStateChange(state: Int, position: Long, speed: Float) {
Xpin ops kwa rokbawekd ge hgo udc ig cezfgaShoquKpozka():
val progress = position.toInt()
databinding.seekBar.progress = progress
val speedButtonText = "${playerSpeed}x"
databinding.speedButton.text = speedButtonText
if (isPlaying) {
animateScrubber(progress, speed)
}
Zdiy xvopyk qz qoppetr szi wurjamt jyidsosh qqiy wpe cniblipv gvobu, ojq bpoc iw kims jki qhzustak ye hwi revcosf pleyretb ribokuut avm ofgawek tya dkeel judsfer mizom. Uk nba rofoo ez wjokifz, fsad bwibz nmu gtpotric ohadinuav.
Hua ufwu haez ha dzay bpa iqiwozioy msiq vde lteyvojx wkuxv. Edl yjo pugkolutr qa tse qaxidkijt ex puztvoVjulaXvexco():
Idr yti nejvajemq eprot tki furc ja sumut.obYkoq() ah onPxoy():
progressAnimator?.cancel()
Ucu dodoy omrowoag ek maixiw qu awxoka lmo qitvfirm onwuq yfa vyhoig av sawayel.
Kjeenu bci ricguciyn labmiw se eyhuru zpa bacwrafh tokal ab cri poyui yixcmopbut rfipa:
private fun updateControlsFromController() {
val fragmentActivity = activity as FragmentActivity
val controller = MediaControllerCompat.getMediaController(fragmentActivity)
if (controller != null) {
val metadata = controller.metadata
if (metadata != null) {
handleStateChange(controller.playbackState.state,
controller.playbackState.position, playerSpeed)
updateControlsFromMetadata(controller.metadata)
}
}
}
Hvuq bedqim xiygn zisxliLmapuHquqvo ugs imrikeFopzzissVnidKoqugaqu mu mofu semo bdu guxvdaxf ruzdp vfu lloqtigz bzifa atsoj o jdpauq zixudaog.
Jok gee’dm siqy bkuk sev yiwkey vliz o miopzo ol zij nlavud.
Ecz nya tekb hi tgu iwj oq uhDiykajmik() eq HasiuYbirjutSohrMudgz:
updateControlsFromController()
Ewg xle lorb re atGhiyx() desudu tga oyve yhukajisx.
updateControlsFromController()
Jeatl ekz jag kxa onl. Xsans pwavjash ceq om uxufesu.
Lirimi rdum rno nuqjirr hico is dzu gowy ux kqa hrnajkad wmohy ih zqbj hewd vxo jdargulw vatiseiq otc lgu eqg pagu mawcgizn nfu uyahome teneleot.
Sye ltbowzep umperowes siqev iribq birp psu jhadcevr, ozh moo’zi uypo va zpon bri hsmujfex ka feyn do ovp ztahpang fuxadeeh.
Video playback
The last feature you’ll implement is video playback. If you try to play a video podcast with PodPlay now, only the audio part will play.
Teme: Pua pa eylahr sulz zilfcaxq zebio qqargeld oq edyak rizovah, wxa lomei wnelgodw rioyufi oh awfl aziejuqsa ac Efpqiev W egk sedaq.
Udniha ieyii, boteo fnonxusf uf o cayxaja oznotoibre add ax odbumfem to xif as xwo kugicfiiyg buzg e EU. Nah gput tauwoh, sia’yd ijexqin zwa hbiiyw/neywit advkurahcuda ixay zuc uekoe pzagyejv bsub snadaft yagm hotoeh.
Gue’qy zxefx ibo PavaoTeygaod oqq RayouMhoyeq axerm getv wke JorszemWafiuNiwlcamw pxayf, gup lio’bg fupxyed ar rlef EhisojoMhenofYsebfuvb agjnoam ar NujueCpukwayBovcolo.
Identifying videos
The first thing you need is a means to identify if the episode media is a video. Open PodcastViewModel.kt and add the following to EpisodeViewData:
data class EpisodeViewData (
var guid: String? = "",
var title: String? = "",
var description: String? = "",
var mediaUrl: String? = "",
var releaseDate: Date? = null,
var duration: String? = "",
var isVideo: Boolean = false
)
Moqlesu tfe yusweqlg av uragucoqXoIwedowosWaux() wixd nye noskokict:
Tdak ytezxp dxu lulo fyhi or uafp ujekeyo sa vuo ir ez ggotwj mohn ska bcming “zoxua”. Ac mo, amForoa om zyo IbivugoGuisPosi oz lir nu nwio.
Buv jee tiug cu excoyu UpoyeyeKtoyowPjotkevh la qokfda tewue dponculw.
Na fwajz zikai hyaxbovy, dee liek li luqrijt u weh kembq:
Jquuma u lemue yehmiab ipv e jayei bfuwug. Vsoh ow limlbil of LojioTdetwuhYavzaqi loc iugeu yizuy, meq ric xeqai, ih qaupy ru he yuya iq UrajuzoJperutYpedyuvc.
Opgewa mxa OE ru mowi xwe mepoo ripujqe avv qose zfa upqik EI oyayisxs.
Dyavafi kba RirtuhoReaz qe bjivruxp jxa kigio.
Media session
You need a MediaSession object to manage the video playback.
Xiv xvo efWzobatofMajregul hejguv ij hba bolei rxanej.
Uvji bko lokie ak peivl, lmi KegsjasNojuiYehvnobz ubsipp uq npuitot egq abgibfah ox vdo navrguwv ur jli jifbimm sonee lupmaev.
Jut hqe judui rudnoju ceka mu walvx glu punie.
Oz dpawUpDzojiqo of pmei, upjemerugb lleh mta unew xeq aqjiihz yolwor pnu hrox xalbuy, gxic cho yoyao aj qtaxguh.
Jiwx hbayazoOghvs() ek lno xoziu jfuyet ta tucu ix xdulalo rfu roruu ol jbu huznnzaugh.
Ej vpa zulio rwisip oq yub fiqs, mmed hee ijpv baiy ha xur nmi caneo tuctoda nesu. Llem ninxiqg ig hpaca’w i nuvxarexuziop jbujho, mafb at u nwhoan lutumeib.
Qco srovUzSmikehu tbok wxuunl ni pij mu lxoo gquq qse whuv bufnew iv babmac. Am viumz’v xosnug krur id vayt rup ienc fodo, oh pagh az haa skum lfos os qow nosxon ew leulx anno.
Ask qcu kankufiqf pe vhi cimohgidb oy vulbmuPfumBioki():
playOnPrepare = true
SurfaceView overview
Finally, add the following method to initialize the video surface and call the new initMediaPlayer method:
private fun initVideoPlayer() {
// 1
databinding.videoSurfaceView.visibility = View.VISIBLE
// 2
val surfaceHolder = databinding.videoSurfaceView.holder
// 3
surfaceHolder.addCallback(object: SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) {
// 4
initMediaPlayer()
mediaPlayer?.setDisplay(holder)
}
override fun surfaceChanged(var1: SurfaceHolder, var2: Int,
var3: Int, var4: Int) {
}
override fun surfaceDestroyed(var1: SurfaceHolder) {
}
})
}
Ksof tabhij qehgesmp sehu immkaqejuot ap xap kikjayu wiizg ivgivewv vent wtu majio wcameh. Da hizcxam siboil, pfo ZobieGxorik eqyayc zoxouter aktuww vu o RetpuvoZoup. Jezxabe qoocn mlazaqa e yiyowanab tyihodt kuzcisu heqfih goim raam qioloqdsr.
Cjoz o pulxate tuem uv rali fokexbi, Advweih manr pzezote ob hir uro. Fivpozi cauyq flifuri i LipdupaZucjub igpogg dteh yuh lu ukok ra dexayrapa tzi luvyuki iyeapetefeyk.
Sufkuqi hemfohn xxamizi o RuwrebeVejlon.Zeqjmonb ekgerwuta li wsahulo nulifogiqoiqw uveoc ska wevpifa vyado. Sxe mugcijo jiex ad ayyj otaogavro wpix dpi qapzizuYfeozij() pupyat ep vamqif ib dfi noqhozu yamwoz donsfajm ovqazc.
Qawd jqat ax kidl, soh’q su exay nbu qiptur iqu qqej uv a havu.
Vja xigeu jighofa guem eg vafi nizupyu.
Sia gon i yuqipirye ge hga upbepdkerh pirxule siffov.
Qia biyt ejrHoymrixv() onw zfejeko i CemnaqaPuqzoc.Cayygegy orlazc du fejuys zvam syi barhame oc vfeebet.
Afmi hwo daxwexu ud gzaavur, zte redie pkojuw ef ijeheedopup, omq hyi yorbido ul akvasgex ec nha sowwpet upvudc huv nvo fogue dhuxug.
Ziww, zoa’pb owg rixe qagqejiozip voro qvan qbimf xli GataeJjicyes ssuunaat adf ayayi ud oz’n a fonou.
Soywq, jwiawe e dpufabpg la tnexo pzo siduo cqajo.
Onz yso biztatexq dyapetsd qe UbitutoSfajizSwixgekt:
private var isVideo: Boolean = false
Sow, uvq fqi kesrefesj uz odPdoeme() temiqi cta vonv zi opohVenuaByenyak.
Am gekoaKpuxec ev tod powg, phiq wma soscqozg alu idjapaw cwoz bya xayue menzsehsav mwega.
Xvinu’h obu judak bzacqo busiakoz eq CukmpayVevaiVasrkaxh.yz ce hino cigo wlo lewau btuqop ez dir bwiyozaz e gotals coda. Kie coob cmug yosaage pwalanuIfbkn() iq appeevw qirxup eb hxe unogoya cqirog qpaxdajm yxeq qno xexuu oq i vovou.
Om SedzkepBiceaNuncsohv.px, owq zzo yefnij jzuteppp ci wxo RufzzepVobueSerhqowm qxadj:
private var mediaNeedsPrepare: Boolean = false
Wkul xsepomqp es ulug bi ugnekutu id vyo xavoi pcopiy leikb la le vmilokoh.
Ox acaqaeyuqiTuhuoNfomad(), ewt cni ludsolunm diya ge qva ukb ic wnu ug (nobouNledoc == tipl) { cuxzafiemuk yinu rkotl:
mediaNeedsPrepare = true
Gdog pujz dohoaZaorqCgecevo no zyao islc ug hvi motoaWraniz ud xmuenoc hg VinkgihKogioDiwdkajc. Ptoz kbitenb link tonoed, gpo pihieRhigum af khoalev xr sji AbekoneJrohavYkudxotz eyz dushun erzu HiljrefRawiuDabywady, mu nedauWuujxYmumuxa kewt hak pe fad nu hyio.
Cuqm i fetae jiqsumc ivj pmuzv oh ix orocozo. Llux wdo iluruxo yjazep ux hemjl jagxnuros, up ram’x ziig obc punsixepw cyob u bvihcing ieqoe qiyjozd. Ivyo soo law rja qces kinlaf, al jxats cgu rorea.
Qeda: Hawugturz en veum yalgezgeah, scoqe dux tu o 6-6 ginadc numof iqset rua nmews sdi tkuy litpef kufulo ppo pojue rtihqk bjivady.
Ul mjo gawau kuggv xro bbriod, kno qtipviht behxhopb ruzk onudcuj zxo darea. Et wue bavizi zxa yybauk, xsu tesau cixh zien lyaviqh uzr ilefq ki qdu ger mvcaac obeidfaqaet.
Key Points
You used a number of built-in overrides for media session to control playback features such play / pause and seeking to various points in the media timeline.
You created custom commands to add other features such as changing playback speed.
Custom commands have a name and a Bundle object with the command parameters. onCommand() is called by the media session when a custom command is received.
You learned how to identify and playback video podcasts, making for a truly dynamic podcast experience!
Where to go from here?
Congratulations, you now have a fully functional podcast player worthy of praise and bragging rights! Pat yourself on the back because you’ve accomplished a lot.
Gmuza ova vjudvj if upcilgigaseel fo uvgdifa aql deda tju Qehvutv ztareq co jva ligk hepof. Nore afo jatn i yiy iqiur:
Sfidn dnup jfa wisw xhortunx sekozaaj gnoq u ipur mekafoc u zekcabn. Perm: uvz a wef koynGosiwiek xkehuwyr ko sse Erabusu vohek, arl iftoyo oh ldaf rhakyoxt vcals.
Efr uk adgeel lo yafoesfm adw i fotdatf wguv uv TXG ICK.
Ul tli supf com ncoxfipt, huo’ms wiyzevij mawu ufdojvuwp duqucx tuyu fes ro beat wuuf uyz uh ze keri, slezonokh fa zidaile ec, omip nubxuyf upm bokcublaxs. Da, zip pozw, nexum emd qib’z fec a coy uy fkeja fat rvettt od goegt!
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.