android - Getting Google Cast v3 to work on unsupported devices -
the cast v3 framework has features try make possible run on devices without google play services
required work, ran issues when testing.
- on kindle google api returns service_invalid isuserresolvable() true.
- on devices onactivityresult returning
connectionresult.success
after upgrade,castcontext.getsharedinstance()
can throwruntimeerror
. - as side-effect of 2), xml inflate of items containing
minicontrollerfragment
fail.
some errors found were
java.lang.runtimeexception: unable start activity componentinfo{##########.mainactivity}: android.view.inflateexception: binary xml file line #42: error inflating class fragment caused by: java.lang.runtimeexception: com.google.android.gms.dynamite.dynamitemodule$zzc: remote load failed. no local fallback found. @ com.google.android.gms.internal.zzauj.zzan(unknown source) @ com.google.android.gms.internal.zzauj.zza(unknown source) @ com.google.android.gms.cast.framework.castcontext.<init>(unknown source) @ com.google.android.gms.cast.framework.castcontext.getsharedinstance(unknown source) @ com.google.android.gms.cast.framework.media.uicontroller.uimediacontroller.<init>(unknown source) @ com.google.android.gms.cast.framework.media.widget.minicontrollerfragment.oncreateview(unknown source)
this caused inflation of minicontrollerfragment, on device castcontroller
code wasn't installed. similar question asked so : cast v3 crashing on devices below 5.0. answer provided kamil Ślesiński helped in investigation.
and
java.lang.runtimeexception: failure delivering result resultinfo{who=null, request=123, result=0, data=null} activity #####
when had implemented viewstub, still crashing in pre-release test machines, returning success, didn't have castcontext available. fix this, needed test check if castcontext creatable.
you need singleton / code in application below....
boolean gcastable = false; boolean gcasttested = false; public boolean iscastavailable(activity act, int resultcode ){ if( gcasttested == true ){ return gcastable; } googleapiavailability castapi = googleapiavailability.getinstance(); int castresult = castapi.isgoogleplayservicesavailable(act); switch( castresult ) { case connectionresult.success: gcastable = true; gcasttested = true; return true; /* code needed, user doesn't * * device incompatible "ok" * * message, isn't "user actionable" */ case connectionresult.service_invalid: // result amazon kindle - perhaps check if kindle first?? gcastable = false; gcasttested = true; return false; //////////////////////////////////////////////////////////////// default: if (castapi.isuserresolvableerror(castresult)) { castapi.geterrordialog(act, castresult, resultcode, new dialoginterface.oncancellistener() { @override public void oncancel(dialoginterface dialog) { gcastable = false; gcasttested = false; return; } }).show(); } else { gcasttested = true; gcastable = false; return false; } } return gcastable; } public void setcastok(activity mainactivity, boolean result ) { gcasttested = true; gcastable = result; }
and helper function check if know state of cast.
public boolean iscastavailableknown() { return gcastable; }
however cope devices return success, needed following code in app / singleton.
public boolean oncastresultreceived( activity act, int result ) { boolean wasok = false; if( result == connectionresult.success ){ try { castcontext ctx = castcontext.getsharedinstance(act ); wasok = true; } catch ( runtimeexception e ){ wasok = false; } } if( wasok ) { setcastok(act, true); return true; }else { setcastok(act, false ); return false; } }
the inflation of mini controller disabled using viewstub
, fragment...
fragment mini_controller_fragment.xml
<?xml version="1.0" encoding="utf-8"?> <fragment xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/cast_mini_controller" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignparentbottom="true" android:visibility="gone" app:castshowimagethumbnail="true" class="com.google.android.gms.cast.framework.media.widget.minicontrollerfragment" />
with usage this....
<viewstub android:id="@+id/cast_mini_controller" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignparentbottom="true" android:layout="@layout/mini_controller_fragment" />
activity
an activity's interaction cast
components looks this...
/* called when have found out cast compatible. */ private void oncastavailable() { viewstub minicontrollerstub = (viewstub) findviewbyid(r.id.cast_mini_controller); minicontrollerstub.inflate(); // inflated if cast compatible. mcaststatelistener = new caststatelistener() { @override public void oncaststatechanged(int newstate) { if (newstate != caststate.no_devices_available) { showintroductoryoverlay(); } if (mqueuemenuitem != null) { mqueuemenuitem.setvisible( (mcastsession != null) && mcastsession.isconnected()); } } }; mcastcontext = castcontext.getsharedinstance(this); if (mcastsession == null) { mcastsession = mcastcontext.getsessionmanager() .getcurrentcastsession(); } if (mqueuemenuitem != null) { mqueuemenuitem.setvisible( (mcastsession != null) && mcastsession.isconnected()); } } private void showintroductoryoverlay() { if (moverlay != null) { moverlay.remove(); } if ((mediaroutemenuitem != null) && mediaroutemenuitem.isvisible()) { new handler().post(new runnable() { @override public void run() { moverlay = new introductoryoverlay.builder( mainactivity.this, mediaroutemenuitem) .settitletext(getstring(r.string.introducing_cast)) .setoverlaycolor(r.color.primary) .setsingletime() .setonoverlaydismissedlistener( new introductoryoverlay.onoverlaydismissedlistener() { @override public void onoverlaydismissed() { moverlay = null; } }) .build(); moverlay.show(); } }); } }
oncreate modified below...
protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); mapp = (myapplication)getapplication(); if( mapp.iscastavailable( (activity)this, gps_result )) { oncastavailable(); } ... }
onactivityresult needs cope result google play services upgrade...
protected void onactivityresult(int requestcode, int resultcode, intent data) { if( requestcode == gps_result ) { if(mapp.oncastresultreceived( this, resultcode ) ){ oncastavailable(); }
onresume
protected void onresume() { if( mcastcontext != null && mcaststatelistener != null ) { mcastcontext.addcaststatelistener(mcaststatelistener); mcastcontext.getsessionmanager().addsessionmanagerlistener( msessionmanagerlistener, castsession.class); if (mcastsession == null) { mcastsession = castcontext.getsharedinstance(this).getsessionmanager() .getcurrentcastsession(); } if (mqueuemenuitem != null) { mqueuemenuitem.setvisible( (mcastsession != null) && mcastsession.isconnected()); } } super.onresume(); }
onpause
protected void onpause() { super.onpause(); if( mcastcontext != null && mcaststatelistener != null ) { mcastcontext.removecaststatelistener(mcaststatelistener); mcastcontext.getsessionmanager().removesessionmanagerlistener( msessionmanagerlistener, castsession.class); } }
the session manager listener in class...
private final sessionmanagerlistener<castsession> msessionmanagerlistener = new mysessionmanagerlistener(); private class mysessionmanagerlistener implements sessionmanagerlistener<castsession> { @override public void onsessionended(castsession session, int error) { if (session == mcastsession) { mcastsession = null; } invalidateoptionsmenu(); } @override public void onsessionresumed(castsession session, boolean wassuspended) { mcastsession = session; invalidateoptionsmenu(); } @override public void onsessionstarted(castsession session, string sessionid) { mcastsession = session; invalidateoptionsmenu(); } @override public void onsessionstarting(castsession session) { } @override public void onsessionstartfailed(castsession session, int error) { } @override public void onsessionending(castsession session) { } @override public void onsessionresuming(castsession session, string sessionid) { } @override public void onsessionresumefailed(castsession session, int error) { } @override public void onsessionsuspended(castsession session, int reason) { } }
ui interaction
finally change ui when cast available calling "known" function in application...
int visibility = view.gone; if( mapplication.iscastavailableknown( ) ) { castsession castsession = castcontext.getsharedinstance(mapplication).getsessionmanager() .getcurrentcastsession(); if( castsession != null && castsession.isconnected() ){ visibility = view.visible; } } viewholder.mmenu.setvisibility( visibility);
Comments
Post a Comment