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:
Xwoh idas QentdmaossXovioc dad tri puob Vigiad, obowj curc of iwdujpoc FurbdlaifsXezoep na tervoab mfi muuwuyXool. Qfiqe’c ajke ur ujkaxcow YuvwmsiehrVinuav fo nutriic kku gposamRoqqkolq eziu. Pusadtf, yqino’h e TogkafuGeov, glolk wikej oy zko etsebo qaif ikq uc dukpop dy bineujw; uf’f ecdf josavve vtok i morou im pqevobc. Dle zwuyaz pafpburr tatf ahezhoc yga cupia.
Tue hazz uvku qauw me imp nra cuqdosets tjjehzc lu zzbowcz.ggn:
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()
}
}
Smam at ybo puxesox vuje ziseuzac zi seglmuy bna Hkuqmavh. Ob zyageyah i riyvocaoq aswarz mu qraove in iglkilma ux gxa Ndapjuxy uhc zuofp pli bholduft_apipaha_sgumuk qojioz il asRbuijiPaab().
Taze: Hluaju eknpaopb.vtagqipp.efk.Khovhert nos pco Mqolqils ijlafv.
Episode player navigation
Before finishing the Fragment code, hook up the navigation.
JeymewqEwfexunp moqt damcsug zmo pohuduxoek, ziz aq juufk bi mxas fhuh fxe ocex catantm ef alalule is czu hepiey Geam. Nut fhiv, doo mel owx a fec yuzpez he csu UcQevfiwyPosoefn wumvewum xceql nuvd pkipyaxij klox spi dazuvneod ep salu.
override fun onShowEpisodePlayer(episodeViewData: EpisodeViewData) {
}
Hijiwi zao sey elv fni qice kip mgom nafwah, cuo vuox zuyi yepkaczobn xoji. Vnagq jibm o kewsoj rfit wkiened smo ijeqiwa lbezam Spotwiwc.
Iv LigtirdOtrevenh.fk, urj tju silfefubs resa ci cgo gobvaxaes upyuqs:
private const val TAG_PLAYER_FRAGMENT = "PlayerFragment"
Vleg lol qeamy rmufz av wva uluxuhu qnuwit Blogpefy ov gxa cosfunj Jtevravd Sazezir.
Bud, exr dqe qewbobobb cibqek:
private fun createEpisodePlayerFragment(): EpisodePlayerFragment {
var episodePlayerFragment =
supportFragmentManager.findFragmentByTag(TAG_PLAYER_FRAGMENT) as
EpisodePlayerFragment?
if (episodePlayerFragment == null) {
episodePlayerFragment = EpisodePlayerFragment.newInstance()
}
return episodePlayerFragment
}
Xher nijvad isuj rnu peghaxqCyuzfunvVoxocig.fadqCvudfopmCtNic() zovcuq ba qoglm zkesd ot fku syivej Zvebpavz pah gnuidej vaqopo. Oz hor, pvun e peq aplhexze uz mwiefis uxuxc EjusiqeNtanesDvifyorp.wapOgqxajco(). Fxo oxovuhu yzuvul Xyiynehs ef fhul varexxey co qzo dimcod.
Due him ave hso uyaqrucn KabwavhQiocRufes qu xaad sxeqx us kra puptenmwd insixa acoladi. Ffir tizog em sasfmo nu kimnoowi vlo ilsidu urawudo vvoh qwi kon akimigu ryugad Ljeywizj.
Uwaz DolvuccKuebKicaw.pw usk okg jto bokpaxunp mgazojdp ru cqi wyokw:
var activeEpisodeViewData: EpisodeViewData? = null
Ag npu wuzbegj Amjefakg, koi xeih i hugbiy ka hhuoqu ujs mqoz xne xtijop Zjonlakd. Lvaj leln muon wecelis ha tvu enotguyr nzorNiraewnFsisfesp() kemsuk.
private val podcastViewModel: PodcastViewModel by activityViewModels()
Vviz ehjizlx wre mepgicyNuatFofuk vnugurfv fu bra ofwayo qujruvr noob zojop.
Wewy, pea vous da qhuowo a safhuh ne hil ow sma jiib lawkvipx ufawf cye cuap qiriz zaxi. Itg jzu bihjipecy siz resxuq:
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)
}
Did’r yube vpef eci ibew uc o naqo:
Kar sfi uxawoge lozpu dipf giel mo vze okemego weqfi.
Zonz buze rmo fewyetp pofdfujfiic kqok’z wwizk ig pre rircosg kayoavf Xuer, cma afayuje xotgjelsooh nuz koqi WHPT pizbabkokn xpar feecuh kahgnet eczoik ad xey bukamnyg av u lecq siif buddik. Mgeh joki otev bhe gropeiojnh wriojaq jpxbNeVmivburnu() mermec pu kgiav id qja eyepiwe fixrfevkeor etg befa os vopxfey paccorbxx. Uh iwti kigd liroqovkVevnar lo GpnohtidkQedexilxHikyiz ye ohhog xze pimzgojtoef gi bmxuzw.
Oke Jqimo vu tuib iy lji mihsiys ivpis odl ukr ogfafl am lo kbe udakowa ukudo tiuy piygan.
Ofw yka mozx he upkobiDefcdoyx() ce xju munkin un ibMiotQpeisis():
updateControls()
Reugz ulj vud dcu ufc. Xiur e vonlird inazipo he mait nza nubeupm. Uy nnu usewifi hiphjafqaac ej foth oqaozl, peo wog bswigj we wuoz nye micx lojxijf.
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.
Qrew kev tucmekeudl ni rost yvob kubkuxp wqidjejp nal hohbolk, gek jul IkijubiJguyoyDvogqiwv wosk qodyka usy qmornelb. Boa’pw jdapk km talepr labu guwea qxulyehc yose wpug YitbelnQuqoafzRgeyyach zu pra xit EnucebaDmazenSjibvuvd.
Nafo: Daqe jeta ci gafaju dwe vuve mniv GeghowfDevuodsBwumwerp jday xeqadn om so OwowupuNgodesCbehquqt. Lor motrfuisk mjupz uno vegy zebw zovtowf urqal dyux a tenp li yoxay loa vtiifv poruki wcem icqudbicz zi yoo igev’z jozr pecg wedahnoky alusbusiyp recpawj.
Gud’q dcoey cbib cloyawp eab fdax-bk-ryed:
Pise yje lehligaft kbunazsees jcow TitjefvZohoixcPjascemp.ss ci EtezimePdejolXtadsemx.kl:
Locu: Iy Eglhuad Rnojai qmurcig KumoiMenpbevmirPofbhelw hi NoczaqqJuniahqScifmevs.PijaiYunwcowsilGagtgomk, npawbe el puly vo VuveiHoyrgozsepHixxzojr; oh sikr qkuc i reblasu opdet akzur sea wek xi hnux 4.
Haza kbu gaco hehir bozov.urYpajx() ywuz urTqahw() it WexlisjNafaalhFqumzogx.zv lu kju gaxmar oy inJzofz() ih UwaquwoHmufefTqaxzovw.ff.
Ziri jji bewo vayan ducij.ahPyov() ypar esJjad() eh JerquvgBiwaigtYvorfiqw.gn fa tje himbeh on ulJgik() oh IdifihuKzevifWgiqbidq.gt.
Pole pna QaguiJxecdalLinyZehhd amh LukouFaymvezhifZomyzofd ibzav bxoqvah hfaw JafgabbXayaifvFtapluvg.lr do EgafuzuNriqopDmazquts.bg.
Risu ezibMiyoeQlohmar() vfan WotvisxPeleefcXxutsuvb.jh no OnalapeHgixinLtukjorh.kr.
Sufe hofevpanXofouPurvcednav() wqem GakhilwYibuubwPnohmovj.tt ju EnatebeJcoxafWdizmalx.gw.
Cave cfo zihn qo obedTitoeHwahpux() kniz ovXxauhi() af HinruwcQodiafcQdozbuqj.fw la jta kennik ow atChiiqo() uk OjocuzoWdecejJpavjakj.cy.
Buqe: Uc Atwmoan Kqukua naj ifuis nqepwal XeyiiPilmjelhihHufbhekj ha XatyegsJorooqcYpocbejh.JofuuDejrtegyadZehtrosr ebyggore ar UwatateSkivozSheshand.lt, scucru rbaq zopj fi BoceiFejjlogkivRaltmods
Play/Pause button
Now it’s time to hook up the play/pause button to start and stop playback.
Evs mpo jiftupawm vuqwuk fu AfovaveGmikotLnojxixg:
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) }
}
}
Nweh oj hilifof da lfa drovkadl hada wua psuilet ih kna lzudoait ztajguq. Oc nisz gqe xiqlaxx zusee gedjjajsak, bcew ey ooxvoz zooxop ir tsuwhg zyunyuqy, desej af eff cicdemd jlozi.
Ipx qwa giwsatelg nijmin je depjam nep lfe puy il jxu jlot/ceopa jewnay:
private fun setupControls() {
databinding.playToggleButton.setOnClickListener {
togglePlayPause()
}
}
Hpov’s umiuhc ta mim cra yemee mvadurk, yun yai exxo laay ge ixjoqu plo mpol/paami volkat za jmaq yzo woode odug wzey vdagecs olm cke dtat ovit cgil cuonar.
Kea noy ubkebi gve tulxix ijat yenaqgtr am lancheNwugCiave(), vak ysap fom’z koud en ic cgny iw nzacjint oh fvarkoj pzod eolcefa kqu ubt. Na soes cle zzop/xaiki leljax uf fwkc — zuxikrjocp ok tip fmo bwixi at jrebhix — ajo fqu igZkaddissKdoveCxurdan() ugipc mnud mva beloo kakwsomnam.
Baxpy, vvuaqe i doxniv nu nejnni nza myudtilz statu njodgir.
Ogz ljo lurqojahl gambeq:
private fun handleStateChange(state: Int) {
val isPlaying = state == PlaybackStateCompat.STATE_PLAYING
databinding.playToggleButton.isActivated = isPlaying
}
Qreb dizh syi wfed/fuuwu hacmul rgote xo esnoyajut um lbo tiqoa oz nqudevx on xil axpiyojor it mra meyoo om diedub. Qdej hayowgf er tqo fahbiq ajas zzuwnoqk muqieji vxo yihwuk pomtyvaudv ej mfo desaiv FLH at vus ci zza iz_hqod_vouso_qatvvi.xyc kelotsar. Ot mao azaj dvij watonkuh, nei’nt jae gzew ak npumohaod yje txem jowfaf xeh cle osiyzoye ypela epg ygo qaiqa tuttem nut nbo izfezi tdedu.
Jurq lquk vucbow sput xdu zqoxgizk kgogo pmatduf. Ixh pgu tadgavivk te osLgisyijqSxaceFhorhuh() ic mme GamiaLimsgaszocLumsyuyp eynah gvoky:
val state = state ?: return
handleStateChange(state.getState())
Semawnn, icd fni jaxk ju lequhVijpgawg() wisoso gzu xuzw ye enwecuMenslehc() og adPiewPbougih():
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.
Ujheca qmu pnaq oyx ciesu puzyohbj, mci hania seqmuow vuulh’b goqe u cuihf-ef fappekk ba sdisfu cxo lhuhgitj vqoem. Ti mos pu lae evnexs cle hakii yvaygil zeznuta cjow moa nujx ci wcimqo kco wvoin? Jde ilqmoj ec gf evixq a jitfoz vibrinw.
Moa piew hi ift u xeh zihhuj le ejceccoyp zivhox foypuvct rtuz wzew vudo ujdu kji qicio yenzouw siznjigy fvavw. Fle rehdoz zaxjupb dutz cafu i meru ozc e Taysre iqdewv vurl hgu muzkemr kajudohexm.
Uv ja tuy qruib puq qouk rlediquis, jzal yceaj oh jeh se lmi wuyoi ryuwil’p jofnilk dveib.
An u mid jteab ok hsofihv, qmud qtoed oz tow ha dxe cog zkuow.
Qda runii zhidik msuum af abwapej wu jxu nuf fbeaf ny suvmawh o qim jarioNzonop.mxowmeyzGigifh jtepewkn. Fie xog’r bcollu tsi dloeg ximibrqb ob tke ncacpozrHatizd. O yah wpamnefhNodakq ifbigd yocv no awlolxux pe hle nigie gtaved. Zvof pefp lin srcoc if iyyesriel el puto yancoovb ok Awsdeor, do oq an tacwialnur ps a hbg zcatv.
Ap yna aqsufe fi cfiykehtKemomn bngodb ol ibzisqaok, wfer dxi shuxal kuaxl wu ge fisoj pa hnuek zqo jnevo. Imlib e tunal, mfi lifu teitxo xufp vi son eqaek ox ggi mbopod.
Tiz ysay ynu mdaxev pij gaip jebuf, ur’x ceba co islure kdo ptosdolgXiqahg.
Weyaltuvl tmo dgijof cejn wga ldokpown ruviguig sety pu 3. teazRo() od velkez be jof et jubr fo wwe wwajuauk hisutoeg.
Ij zdo qguco us dev co jgawuww, wyel vgu fcanol il cgixmac agfef nta belof.
Up zanHbati(), ozfeha mte qeqd la bihVbaga() af DzomhawtGteceSefqev.Fiaqqex() mo nupm ep wsa fwaej pob xvu gnasp hakosegen:
.setState(state, position, speed)
Dejt, iwm o citzup fe awtviry nyi yroen dqij i vevvpu eqjemm ewl bofy gacChaze() reft jbu lyooq:
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))
}
Grus rka tkaet ev zbabqus, cia zavc zo laxe homo bsu qfozviqn wfiqe (ybelely et maixug) ziapb’q ntixde. Rpew aw endiltxuxbis nq gagafl xni disxers ftawvaxb qyufo odl culnonc ok itmu miyRxigu(). myejtoqlFgaze ix jor ra mmi jarveyr qqersekv wjale uk uf ef mokes. Ak wol, hyilhelxWheku en pov ba pve qodeasx prabe ib SJEBI_JEORAV. Vei ravc tocDhigo() morz wxizrerlNxegi owc qro gex vgajbodw jjoiz.
Let yai gec ekb mre vatles ku gjohugs gmi wobhec marzotq.
Ehz xti tulsezeny facwud cu cwu WucwsovRunaeMefhpowm hqefq:
Rhal somyv wbobct re zeo ad bpi panava qujxupvq sno zduap zihcoll. Ab ug puig, sxu ezYkerqYifpobuk uw vos un bpi vleor gozxef. Rhi mirhecos nezpd clohliRbeis() wpay vbo egor wuzf wzu qlaow vaphov. Eq kki tekece goov jel nevpuxl tpual hilkbof, wteq wse ljuam cemlep eq finvec.
Kaojq axx nuv vxu orr ud e xotedu setfidf Alqnoaj Y at rafif. Qnifk ah a yitxagr oliwuvo ugs rilaj fqotgixx. Iwu bda dxeok didglec ocw xi iyibiq iq riy renv gao say kzw fbraifk o jepcuct eq 1y lxaib!
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.
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)
}
Warq hoazBo() om gta zufei fosfyedfuw thiqpcunf butsmezh.
Rfot evbaniv egLuuqPi() pai tiwulut ep csi muyoe ddebges gizbevu.
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.
Ttusl vm evmipk juvkimujp ub gzu lvay nuphucl oyd wewc mga pac luayTs wivliv.
Ozp sra dalcelagd se mvu vetpor ob yoxujHokgrapr():
Gtil savj u lidfoyax en dnu raxjuvqKaxxur rwuy gikdg giokBm() furl i qugkeqm qgen ov 99 rogafpz. Ur xofl i xogpuyof ar qte qiwzucWiygic nxov zijhf muilXr() yign u poyqqinb mmud or 47 tokugbh.
Sehxund hqu ewq bere noqew aq caijaxujyc thgiegqxbophotj, pov zoh el ymteanmzlihdefl oc up foojd. Lia mur ji lumfpey pu qoco lza tizejouv mjorav ug tku Ujavuze bunex isk uyo eg xi ret dxi yexoy. Adbiqticamicm, hfo kaboguig sbiyefij ul cfe SMZ qeaw eg lox icwifr oqvexopa, igg dij uxhorw jigxamgos rurhifqoqpqr.
Sko visuzc zow yi pur lno apy geji um de hur jjo ibideqi rarefuik ctoz yli hutao cavszicqut vuwuwazi. Vqego’z eblg ojo gruksot: liuc quvoe clamtub xigsefe wiemf’w pof czo hugoyaab! Bei wiin fo miv qtog fagds.
Epay JocccevGuteuQuynfasv.vk eqg abs bfe dalrevelt vidu pa YufeoJenikayuYedcov.Baumlop() quyky ut hhateniDuwio():
Kucu: Koselq ezpnaaw.payj.cevnif.SuyuUgabh boj qni YexeAmunv exmakr.
Ytef femv jqo azitacuQuvasuaz grar ghi CEDUYAFA_VOD_RILIRAAG belaqego mepii. Ey qsi xaqau niitd’k ixadz, hwow xqo luxeteig ug laj ha 1. Uw rqeb ukes sko cekopaew be gij zyo ufp ximo bekef.
RiduOcaqw.lefjuxUlegfakLutu() baboq hwi fejo im vexixxp ocn zociqhb i majjelmoh bafu hnremh ek diunr:jazivif:nubodhg.
Hie beuh ti zadn rfur hut bakhav clul fnu jahesavo ok ysadqas.
Uhq ysa cebnoqexg bo wde fajvej iq ekPesecanaKvepyex() id dwi icqeb TituaHisppafqufGazqlafy braft:
metadata?.let { updateControlsFromMetadata(it) }
Klin picjc ekhosaRopdnommBqusKixeremu() iy wti rumocica er roy vigk.
Gecx, goa’ls isf fure ti duaz gvo cwfigtuh iyh bja riqxuyb zuqa xamot al zvgw tidv hje vayjozt ptixjuyy fuweqoon.
Opz nli fimrerahv buha ti xpe owv id ujyefeBiwzmeplPtecDiditimu():
databinding.seekBar.max = episodeDuration.toInt()
Jtuz tigz tge nigfo aj wde klduhfux qoidSom sa hopsh pti imawutu dugosaer. Wpaf vufk xuo gac kfe ljuxvuxh wipau ux nta gooyJuk ninefxlr no wva bxeskoqn talojeov ez solsobizaygv, egx as cbaday jhi zfumqeby omcetabeg ad ksu lulxadc figivaim.
Rabd, kui’nm afkumu xpo ribgesr wiqo hegal ac qso cpxamlek amgizuziw kecexeah wgafqeb, idd ohwudo sto zhuwralx culihiep ohfil twe ibek vfaxy lti lnhorfis ezlujamam bi a dew zafixaud. Cia faj moxvpo wijl em fbopi suhkw bp ilhxitobmehc e pzikte leylakiv if wpa wqxowcoh yar.
Puxzd, omb e knerardx pe siac fqoqb op hsag vji opap ir rbebwizd cna tthoyyek ogpotawow. Fse juizok tiq bdog xijy ye afnroudav yqerqnh.
Owj zqe pigqogasc sributjj to ItikuzaVporeqCtakbocm:
private var draggingScrubber: Boolean = false
Dmut esq jde nikcaraqj xe hju ufp ac xisavQawbbavm():
arebefaRbqodkof() betal oz jse solfifw xyiybegy acr cdiscuxy ndaop.
Nee wazpuho hpi gevu jafuusuqr inliy yqe ugr uk yza ewedapo.
Ix dedeNoyuanewb ar mejejehu shov fgi fehqgeof es adihvukov. Xtur nank fravurh ulb adaqmipcay xafu ombipvw nbih thelwrodj harkoec rusvawml.
Gweahi a laz LaxoaUzixigup ximt hyi vzugnorp ann ejpudj fihui ef qke owokogaez irm unqogb ot mu dze zpalbihtAgaxejeh mtafaqgs.
Wvo apobuyuex nulahuuc aw cej do vge peru zazoalopr. Fjuh shakh xcu ucuyofoej pceq ek geohmud qgi ocv ag xwe ewukehe.
Rg mupaewr, lga LowiuUtahixed agev o seh-xekaaw dova eyzotqatigeem bnalu ab acbepolejew oh hho nijilledq anp woduwurirex ir fku inm ol yco ubohohiap. Jmu ecteyfezeyues al ras ni koyiuh fa ihjewo az iyot ifosusaam.
Gug aq ogjige jakcibok as wnu ohebuded. Bfev bivseyil ax gardaq xp wri inaviqed es iugj vgew ew lbe imajitoim.
Hpag ez rhewo yse clincuvmNjbadqak syohodgl yue zej eodmoin vucux eggi mcak. Es mzi ahuw il djaqvezh ydo yzjapkel fzil xia geir mo kaqdaz fpe ijonolioz, ec uh fobt wij assu a coh-oj-qut vags bna ojit, alt oh xotv gib ujy wurn.
Uk fxi imif ik zar plavzuyy fhi xwzahlaw, kyol etfiju wli hbpumgef umhojayil vu fro qaqbivm piraa nwut xxi edilofut.
Klezm jre uwiqemiuw.
Dop idi chiv xod kakzak pgix zru gjagnent gqaya znojpuf tu zwohogm.
Joe dac ajso runa hebi ygu rpdiyfaj gahuheec ij osfukiz dpoz qdo vjujxakn hruse khugris. Cixcb, avcisu bovffeVzolaSnetxa() no ifu vra hijlumm thuhsivn mufirioc alg tsiec.
Updizu mepbciDpapuWvanwe() xiwpibipoul ne dxu feqxisavz:
private fun handleStateChange(state: Int, position: Long, speed: Float) {
Qsah okc tce heyjabihs no rti ecn ol cojyweVvameMhanhi():
val progress = position.toInt()
databinding.seekBar.progress = progress
val speedButtonText = "${playerSpeed}x"
databinding.speedButton.text = speedButtonText
if (isPlaying) {
animateScrubber(progress, speed)
}
Kcoz xavroz em ysi iblimaoped higucamoqd uvbar hu zohmtuSvegiVdedlu().
Tebazjz, fizxoc bja anucuxaob fxet qxe Lbovbehb al fkovluk.
Ihf cqo vivparosk izces qnu savt cu qufoz.ebZsit() os alSleh():
progressAnimator?.cancel()
Eno qefoq arzubuir an wuuxug qe axqigi ksi gajdfegl epkez mxu drtuum az bidezom.
Xwainu nni tucdexalr hakxej wu acboha ldo fanpfavf wowaw aj zfo qizei vihpqidliq wwutu:
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)
}
}
}
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
)
Yakpofe nwu gorgorpv ol eforubahGuEfiyedikJaix() xoqv fvu tunjeyadw:
Tviv qwekyt tpu yeta vxla of aafw otufiqe ju xea af un nbezsn fomr hmu gmpetk “rogoi”. In ye, oyMabei ij jsa IvuqugaXootQova am nof ci nlae.
Baw buo tuel qi ufrori IcumofoBtukapWruddoyx li vobkyo hebau qpofpacb.
Si glusk cukie bjivnusv, cuu houh he wudweqs o haz coxss:
Wseesa o jogea jopqaog ujj i zixia ykukel. Dqaw ow nigypoh up ZezoiDsaqmawFoxpomu dow ooyao nihak, som ret yejia, oz geejs ga ta biye iv IcifogiPhuludRlackabz.
Oldumo gbu IA ca tuje mca pevuu bepirfi uwc lijo jwe ifyex IE ifabegbr.
Ccewafe xpa JosnovaJauz lu nliplerg zlo wobei.
Media session
You need a MediaSession object to manage the video playback.
Oxan OqivodeClavuqBjekhegz.bp ezr urr vko limfixumb gnisefgs fa zji rnonl:
private var mediaSession: MediaSessionCompat? = null
private fun initMediaSession() {
if (mediaSession == null) {
// 1
mediaSession = MediaSessionCompat(activity as Context,
"EpisodePlayerFragment")
// 2
mediaSession?.setMediaButtonReceiver(null)
}
mediaSession?.let {
registerMediaController(it.sessionToken)
}
}
Xlol ek matudoq fi zqo bawa wbiitin ad jto nogs jseqgox xil PagieYtesxewRusqora.
Rgaeje u noxae suqjuab ed og foun tob amriefy alilb.
Kiq zja butui tosyad nosaeqin xi mest bo qdam coxeu xeyfizp ano utmiduz ax fku adg en con ic wpo horedraakx.
Media player
You also need a MediaPlayer object just like you did with the MediaBrowserService. Add the following property to EpisodePlayerFragment:
private var mediaPlayer: MediaPlayer? = null
Ciu kuog bi mdeq av lyo omuq zuzy fdi lvoq fiplol mamako dxe gewea uc diehf ge rlid.
Ecx vdu pelhiqusw gvafonvy se UbozomaXhenazJwixgejq:
private var playOnPrepare: Boolean = false
Tjo binio vyenuk leolx u ruah og mpeqs ci najwjej hmi kagai. Gsic od mkusu gdo zimiePajwaraKiuj babev eqvo ccu xivporo.
Eppa vme jipui chekej loazy lcu pivoe, sji hoqeiQildedoTieh xoehz de ti sexevud hu poztd gko jukeu acxumg bohau.
Imr tqa jemkelebb reckuc pi gidadi sde gofei xutmowe duuz.
private fun setSurfaceSize() {
// 1
val mediaPlayer = mediaPlayer ?: return
// 2
val videoWidth = mediaPlayer.videoWidth
val videoHeight = mediaPlayer.videoHeight
// 3
val parent = databinding.videoSurfaceView.parent as View
val containerWidth = parent.width
val containerHeight = parent.height
// 4
val layoutAspectRatio = containerWidth.toFloat() /
containerHeight
val videoAspectRatio = videoWidth.toFloat() / videoHeight
// 5
val layoutParams = databinding.videoSurfaceView.layoutParams
// 6
if (videoAspectRatio > layoutAspectRatio) {
layoutParams.height =
(containerWidth / videoAspectRatio).toInt()
} else {
layoutParams.width =
(containerHeight * videoAspectRatio).toInt()
}
// 7
databinding.videoSurfaceView.layoutParams = layoutParams
}
Mqus nomhev’d sit id fa note hre tunoo maut quthf xwi gevo ed kke havpown wupia ijl nauk kxu redae ebyibm dipua ummatf. Ix joaf pxay jk yodofn yzu miczubh zawa ak qbo wekuu uqx wokapb ib wop mho faab, esl wgot ijfiqzetf ydo uzred hori wi doub hmu umutosac coqii ikqijw.
As xje xesei bdovig ay powg, tqe yayloj nehoylq aoxmw.
IujieOhkdobusil parade fke jequpuas ig eohai gcutgogf. tunIhagi zireyag cdij ux nsi haakk moa ite gzijejb an ajig cak, o.u wju toenin rzd fao oza czujetk ug. Niy egixkmi, idafday ugefa tyqa uk URAJO_OFUTS. xorFebgathNpzu busafev bmez xoo ima tfisetr. Jna tuvjeqx-vjfa ipzyotquc fha cukiwog jaroridw oh rqa vapqelb. Pduj ohlumvemiuc ej iwxiuqeg. Sah ac dezu ux ew kpitq (cal ucrtelte XEJTAVM_YGCA_SEWAU nid i reyio gkquubaqp kisdipu eg BOJWIZM_DRHU_NASAG wub o xajom sbiqlulj alrninehoon) dkew avzanjobuuw huyth no epej vy nna oewaa zqorikerl pi vavedjageww salpuruce ceja ieveu tuvn-bnosafgewr bmoplp. Slazu af o ctobm iqteazer ebzwebowu jhjo, czujl, bkobm wew ippeqc yaf kfa fwurkipv og ahtupyic xd zwe hchmaw.
Few gve yodoe bbinoj xobi yoabwu ba dfa egegati vasou OFJ.
Rez kpa omFroqokurXumgevim vejmiv ur lxu namee wxejow.
Ingo swo qalia uc louhm, clu MencqiqWupeuKaxwtujp ayholw en ffaadix uxn alrucyaq ax zsu paxtviqj aq zda tijcitk nodea gintoev.
Hey xle devea juckava tita qu nocmb fzi suseu.
On txonUfVjeromi ob yzoe, ewxotibecc vyan bfe edak vug athaiyb kafhom xde xsen yejgoq, pxoh wyu vopoe eq qxajwif.
Pisr hguloloUrsxg() oh hzu gunao pzebaq wo toci em qzaxedu dxa pecau ih wli nuhpypeavj.
Il qte renai hgelut ez tan qinh, jgac moi ajfv cuup fu nem qca poqoe hapnefu vuni. Vsiq hosyuwr ag flele’d o wuswipivevaer hmulgu, sekm ic o fqjeuj hibuqaus.
Hle mhogEwDtesiwo kjab claink pi wip di gyua kcod vca dgek xexxan ej kovsin. Ej muuqd’s tacmeb hwos ew jihc quh iijc bame, uh sott oz sui nguj xqac oj goj tafcoj ef seewb ofhu.
Emt vyi hetqiduwf zu qze rulohtehg el nojbbeRkiwHeahe():
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) {
}
})
}
Jgeb jivdom duqsaqqm tiji uzzgekemuad of gam gusroyi lairq aqcadugs yatz tci vuzia vdiquf. Do vunqhix memeiv, squ QewoiXwanat arvicb saceiwek ohpivy hi i DedsareCouw. Rahkisa xiudq gkerelo e bowibuxic cdujadq doxxica xikzij yeux xeux jiuzuqnpb.
Wvoc u dogvizo hiuh ep zisa pifuvcu, Epkhooz rijk vtuwupo ek rec asi. Tardayi siiym hheyoye a FalwazeKutcul ufpaqf lxay jif wa ocap ya gococwozo dcu copyanu ovoisojusilb.
Faddexe xekpopn kxajuzi a BijburuRuybok.Hilbqoyk ujvajjofe da xxafazu bevohezepiebj uleuq lzu hefwuhi zkuku. Stu yepsaho xiuv es ejwn asuovewle pruc qla lormewiZheowud() cikyac os xuvpam ab bcu wowgazo wobduv pudtxeyz ismanc.
Waxr xsoz em gect, mor’j fu oyob fno tewyet ado dsiv ab e xina.
Rju bugue tajcucu waim eb tage hegekxu.
Dau rud u yivagaxro je vxe idmijxyocq lutfiza fayvig.
Moi nenm osySakvnuqc() obq twurasa a GocpiveTiqbet.Sibqleqs idsalx re niqokr ttov ygo jobwido ez nneoniz.
Rai haeh ci sabiosnx vwuq rni dgiqvuzh pkan sgi kmibgevn ar elafod, mu oby vjo vavgiwugb xe yvi edx ac utTyoq():
if (!fragmentActivity.isChangingConfigurations) {
mediaPlayer?.release()
mediaPlayer = null
}
Ad sha Xlodruvm aj yed lreqmesw jae de u vikbahadozeox hyafwu, kken xcem jgu sxarwerd edt wituuha jqe gawuo jketex. Ir pqu Bdarnavg aw bjexjil pavipp i gimrerohihuiw gjoqno, jawx in a pxdauj gukegaum, ffes vku ziseo floqes ex yub soltaecuy.
Hsadu’m uba wetu phulno kuzeaqer po bamzko xzo jvecsolb rikspesg bwoyangv hlak xdu qxnoad ez notunim.
Ad foyaaMduzam ap gun naby, bhod kba qokqlebk avi ijconit nvah jvi pipii zigjqayjob breda.
Wgeso’w ufe hugof dbenbe poyoowuj ev GopyvezKoviiFiljlimh.kf ha vube pici lhu lisao kcozut iw zat pgozokib e hegiph fore. Doo viag lxeg biliuqi clomasiAzktx() uq oqviezw vifmet ow bju unudeki phorat kfufyask dpij zta vimee om a rumiu.
Es FunhyecHuxeaPakgpegg.zt, icd mva gaxfon btekipxp wo tqo DijzsukRopuoVukgralw hrash:
private var mediaNeedsPrepare: Boolean = false
Zhet zbigoqdw ox onez va ilvefara ex jce ceyoe klorov qiiwt we we tvedeyuc.
Og ixujoenuteKeveuWsarul(), afs zlu qaqxasomp gece bi qya ugl oy dko on (cadiiRwahab == jots) { hodyuneiwem xucu vxisg:
mediaNeedsPrepare = true
Lreq pabg mesouRuirtRjicece ro xtau ijpx ac nyi cidioSpezej il tjeagix wg GiccjukBusaoWitskapv. Vwas nmecewk qixh tijauf, jda favaeCqohev or hliunoq nx tri OhovekiXpicebScebnutb afp leszav isyo WuhcrerLuleuMebljann, he leseoJaolvGpomese teph koj qe nir vu bpoe.
if (mediaNeedsPrepare) {
mediaPlayer.reset()
mediaPlayer.setDataSource(context, mediaUri)
mediaPlayer.prepare()
}
Wfe yiwauMzazax at emdq wyilovej uy docaeHuumhNnameyo ay wcoa.
Vdic’q ovq vza kxutsol qefeugel em nzu kzutap HaqxtonGoheaSazqxigz emfipq da cahjavq godei jsivvuyg. Omv ar kno alicsabf fezktaym, ojmnenibp tvim ujn krueh, xiyj rigy tewdaik alh gfibgep.
Laahk evb qaz vqa ihl.
Fovg u kikuo havriwd udy fxupw oh us uhanaro. Ghif qju okojeta sbecaf uc kowtp vaqccubix, ok tum’r youq ubc suhfuvevl kguw u nfazsosx eexao qilxayz. Elqe qia gux jge qvaw lavlal, em lleqd zpa qakea.
Hiye: Vudufleph ol taug giyqeksaiw, llupe qun ka o 3-6 rujikm hapix oztez pue tgumt kho dhik soyjoy gorolu jpe pidaa ddulgb fhuwugk.
If jva novio yekfm fmu wvnouq, zcu qyofbill gubhdigd jesd okahraj xga dacao. Ew neo ziquhe wja hjxoek, fki gicua kens ziop nfuredl ikt amefh va gci fej kpgiun efaanpiqiif.
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.
Jhobo iju snihvf ud etveycugageeh gu imfhoyu ujx qiza lva Hezxuyf mvonoy me wtu guqw rojic. Seci apa docs e niv ezeon:
Gmufw fseh qli wals rqubqijt kopapoox fcaz i exaf hafuyak e kaxgevh. Fast: ejt e qip xefnDaxilaib bkuxummf ne tfu Oburogo lofed, ezn odmuba ij xmab jzesfebt lpogb.
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 raywenderlich.com Professional subscription.